Skip to content

Commit cc6a764

Browse files
committed
customizable context menu
1 parent 390b346 commit cc6a764

5 files changed

Lines changed: 75 additions & 30 deletions

File tree

src/presets/context-menu/components/Item.tsx

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import styled, { css } from 'styled-components'
33

44
import { useDebounce } from '../hooks'
55
import { CommonStyle } from '../styles'
6-
import { Item } from '../types'
6+
import { Customize, Item } from '../types'
77
import { $width } from '../vars'
88

9-
const ItemStyle = styled(CommonStyle)<{ hasSubitems?: boolean }>`
9+
export const ItemStyle = styled(CommonStyle) <{ hasSubitems?: boolean }>`
1010
${props => props.hasSubitems && css`&:after {
1111
content: '►';
1212
position: absolute;
@@ -16,39 +16,48 @@ const ItemStyle = styled(CommonStyle)<{ hasSubitems?: boolean }>`
1616
}`}
1717
`
1818

19-
const SubitemStyles = styled.div`
19+
export const SubitemStyles = styled.div`
2020
position: absolute;
2121
top: 0;
2222
left: 100%;
2323
width: ${$width}px;
2424
`
2525

26-
export function ItemElement(props: { onClick(): void, delay: number, hide(): void, subitems?: Item[], children: React.ReactNode }) {
26+
type Props = {
27+
data: Item
28+
delay: number
29+
hide(): void
30+
children: React.ReactNode
31+
components?: Pick<Customize, 'item' | 'subitems'>
32+
}
33+
34+
export function ItemElement(props: Props) {
2735
const [visibleSubitems, setVisibleSubitems] = React.useState(false)
2836
const setInvisibile = React.useCallback(() => setVisibleSubitems(false), [setVisibleSubitems])
2937
const [hide, cancelHide] = useDebounce(setInvisibile, props.delay)
38+
const Component = props.components?.item?.(props.data) || ItemStyle
39+
const Subitems = props.components?.subitems?.(props.data) || SubitemStyles
3040

31-
return <ItemStyle
32-
onClick={e => { e.stopPropagation(); props.onClick(); props.hide() }}
33-
hasSubitems={Boolean(props.subitems)}
41+
return <Component
42+
onClick={e => { e.stopPropagation(); props.data.handler(); props.hide() }}
43+
hasSubitems={Boolean(props.data.subitems)}
3444
onPointerDown={e => e.stopPropagation()}
3545
onPointerOver={() => { cancelHide(); setVisibleSubitems(true) }}
3646
onPointerLeave={() => hide && hide()}
3747
data-testid="context-menu-item"
3848
>
3949
{props.children}
40-
{props.subitems && visibleSubitems && (
41-
<SubitemStyles>
42-
{props.subitems.map(item => (
50+
{props.data.subitems && visibleSubitems && (
51+
<Subitems>
52+
{props.data.subitems.map(item => (
4353
<ItemElement
4454
key={item.key}
45-
onClick={item.handler}
55+
data={item}
4656
delay={props.delay}
4757
hide={props.hide}
48-
subitems={item.subitems}
4958
>{item.label}</ItemElement>
5059
))}
51-
</SubitemStyles>
60+
</Subitems>
5261
)}
53-
</ItemStyle>
62+
</Component>
5463
}

src/presets/context-menu/components/Menu.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,57 @@ import styled from 'styled-components'
33

44
import { useDebounce } from '../hooks'
55
import { CommonStyle } from '../styles'
6-
import { Item } from '../types'
6+
import { Customize, Item } from '../types'
77
import { $width } from '../vars'
88
import { ItemElement } from './Item'
99
import { Search } from './Search'
1010

11-
const Styles = styled.div`
11+
export const Styles = styled.div`
1212
padding: 10px;
1313
width: ${$width}px;
1414
margin-top: -20px;
15-
margin-left: -${$width/2}px;
15+
margin-left: -${$width / 2}px;
1616
`
1717

18-
export function Menu(props: { items: Item[], delay: number, searchBar?: boolean, onHide(): void }) {
18+
type Props = {
19+
items: Item[]
20+
delay: number
21+
searchBar?: boolean
22+
onHide(): void
23+
components?: Customize
24+
}
25+
26+
export function Menu(props: Props) {
1927
const [hide, cancelHide] = useDebounce(props.onHide, props.delay)
2028
const [filter, setFilter] = React.useState('')
2129
const filterRegexp = new RegExp(filter, 'i')
2230
const filteredList = props.items.filter(item => (
2331
item.label.match(filterRegexp)
2432
))
33+
const Component = props.components?.main?.() || Styles
34+
const Common = props.components?.common?.() || CommonStyle
2535

26-
return <Styles
36+
return <Component
2737
onMouseOver={() => cancelHide()}
2838
onMouseLeave={() => hide && hide()}
2939
onWheel={e => e.stopPropagation()}
3040
data-testid="context-menu"
3141
>
3242
{props.searchBar && (
33-
<CommonStyle>
34-
<Search value={filter} onChange={setFilter} />
35-
</CommonStyle>
43+
<Common>
44+
<Search value={filter} onChange={setFilter} component={props.components?.search?.()} />
45+
</Common>
3646
)}
3747
{filteredList.map(item => {
3848
return <ItemElement
3949
key={item.key}
40-
onClick={item.handler}
50+
data={item}
4151
delay={props.delay}
4252
hide={props.onHide}
43-
subitems={item.subitems}
53+
components={props.components}
4454
>
4555
{item.label}
4656
</ItemElement>
4757
})}
48-
</Styles>
58+
</Component>
4959
}

src/presets/context-menu/components/Search.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import * as React from 'react'
22
import styled from 'styled-components'
33

4-
const SearchInput = styled.input`
4+
import { ComponentType } from '../types'
5+
6+
export const SearchInput = styled.input`
57
color: white;
68
padding: 1px 8px;
79
border: 1px solid white;
@@ -13,11 +15,14 @@ const SearchInput = styled.input`
1315
background: transparent;
1416
`
1517

16-
export function Search(props: {value: string, onChange(value: string): void }) {
18+
export function Search(props: { value: string, onChange(value: string): void, component?: ComponentType }) {
19+
const Component = props.component || SearchInput
20+
1721
return (
18-
<SearchInput
22+
<Component
1923
value={props.value}
2024
onInput={e => props.onChange((e.target as HTMLInputElement).value)}
25+
onPointerDown={e => e.stopPropagation()}
2126
data-testid="context-menu-search-input"
2227
/>
2328
)

src/presets/context-menu/index.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,19 @@ import { BaseSchemes } from 'rete'
44

55
import { RenderPreset } from '../types'
66
import { Menu } from './components/Menu'
7-
import { ContextMenuRender } from './types'
7+
import { ContextMenuRender, Customize } from './types'
88

9-
export function setup<Schemes extends BaseSchemes, K extends ContextMenuRender>(props?: { delay?: number }): RenderPreset<Schemes, K> {
9+
export { ItemStyle as Item, SubitemStyles as Subitems } from './components/Item'
10+
export { Styles as Menu } from './components/Menu'
11+
export { SearchInput as Search } from './components/Search'
12+
export { CommonStyle as Common } from './styles'
13+
14+
type Props = {
15+
delay?: number
16+
customize?: Customize
17+
}
18+
19+
export function setup<Schemes extends BaseSchemes, K extends ContextMenuRender>(props?: Props): RenderPreset<Schemes, K> {
1020
const delay = typeof props?.delay === 'undefined' ? 1000 : props.delay
1121

1222
return {
@@ -17,6 +27,7 @@ export function setup<Schemes extends BaseSchemes, K extends ContextMenuRender>(
1727
delay={delay}
1828
searchBar={context.data.searchBar}
1929
onHide={context.data.onHide}
30+
components={props?.customize}
2031
/>
2132
}
2233
}

src/presets/context-menu/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,13 @@ export type Item = {
99

1010
export type ContextMenuRender =
1111
| RenderSignal<'contextmenu', { items: Item[], onHide(): void, searchBar?: boolean }>
12+
13+
export type ComponentType = React.ComponentType<React.HTMLProps<any>>
14+
15+
export type Customize = {
16+
main?: () => ComponentType
17+
item?: (item: Item) => ComponentType
18+
search?: () => ComponentType
19+
common?: () => ComponentType
20+
subitems?: (Item: Item) => ComponentType
21+
}

0 commit comments

Comments
 (0)