mirror of
https://github.com/ikun0014/lx-music-mobile.git
synced 2025-05-23 22:37:41 +08:00
212 lines
6.6 KiB
TypeScript
212 lines
6.6 KiB
TypeScript
import { useImperativeHandle, forwardRef, useMemo, useRef, useState, type Ref } from 'react'
|
|
import { View, Animated, TouchableHighlight } from 'react-native'
|
|
import { useWindowSize } from '@/utils/hooks'
|
|
|
|
import Modal, { type ModalType } from './Modal'
|
|
|
|
import { createStyle } from '@/utils/tools'
|
|
import { useTheme } from '@/store/theme/hook'
|
|
import Text from './Text'
|
|
import { scaleSizeH, scaleSizeW } from '@/utils/pixelRatio'
|
|
|
|
const menuItemHeight = scaleSizeH(40)
|
|
const menuItemWidth = scaleSizeW(100)
|
|
|
|
export interface Position { w: number, h: number, x: number, y: number, menuWidth?: number, menuHeight?: number }
|
|
export interface MenuSize { width?: number, height?: number }
|
|
export type Menus = Readonly<Array<{ action: string, label: string, disabled?: boolean }>>
|
|
|
|
const styles = createStyle({
|
|
mask: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
opacity: 0,
|
|
backgroundColor: 'black',
|
|
},
|
|
menu: {
|
|
position: 'absolute',
|
|
// borderWidth: StyleSheet.hairlineWidth,
|
|
borderColor: 'lightgray',
|
|
borderRadius: 2,
|
|
backgroundColor: 'white',
|
|
elevation: 3,
|
|
},
|
|
menuItem: {
|
|
paddingLeft: 10,
|
|
paddingRight: 10,
|
|
// height: menuItemHeight,
|
|
// width: menuItemWidth,
|
|
// alignItems: 'center',
|
|
justifyContent: 'center',
|
|
// backgroundColor: '#ccc',
|
|
},
|
|
// menuText: {
|
|
// // textAlign: 'center',
|
|
// fontSize: 14,
|
|
// },
|
|
})
|
|
|
|
interface Props<M extends Menus = Menus> {
|
|
menus: Readonly<M>
|
|
onPress?: (menu: M[number]) => void
|
|
buttonPosition: Position
|
|
menuSize: MenuSize
|
|
onHide: () => void
|
|
width?: number
|
|
height?: number
|
|
fontSize?: number
|
|
center?: boolean
|
|
activeId?: M[number]['action'] | null
|
|
}
|
|
|
|
const Menu = ({
|
|
buttonPosition,
|
|
menuSize,
|
|
menus,
|
|
onPress = () => {},
|
|
onHide,
|
|
activeId,
|
|
fontSize = 15,
|
|
center = false,
|
|
}: Props) => {
|
|
const theme = useTheme()
|
|
const windowSize = useWindowSize()
|
|
// const fadeAnim = useRef(new Animated.Value(0)).current
|
|
// console.log(buttonPosition)
|
|
|
|
const menuItemStyle = useMemo(() => {
|
|
return {
|
|
width: menuSize.width ?? menuItemWidth,
|
|
height: menuSize.height ?? menuItemHeight,
|
|
}
|
|
}, [menuSize])
|
|
|
|
const menuStyle = useMemo(() => {
|
|
let menuHeight = menus.length * menuItemStyle.height
|
|
const topHeight = buttonPosition.y - 20
|
|
const bottomHeight = windowSize.height - buttonPosition.y - buttonPosition.h - 20
|
|
if (menuHeight > topHeight && menuHeight > bottomHeight) menuHeight = Math.max(topHeight, bottomHeight)
|
|
|
|
const menuWidth = menuItemStyle.width
|
|
const bottomSpace = windowSize.height - buttonPosition.y - buttonPosition.h - 20
|
|
const rightSpace = windowSize.width - buttonPosition.x - menuWidth
|
|
const showInBottom = bottomSpace >= menuHeight
|
|
const showInRight = rightSpace >= menuWidth
|
|
const frameStyle: {
|
|
height: number
|
|
width: number
|
|
top: number
|
|
left?: number
|
|
right?: number
|
|
} = {
|
|
height: menuHeight,
|
|
top: showInBottom ? buttonPosition.y + buttonPosition.h : buttonPosition.y - menuHeight,
|
|
width: menuWidth,
|
|
}
|
|
if (showInRight) {
|
|
frameStyle.left = buttonPosition.x
|
|
} else {
|
|
frameStyle.right = windowSize.width - buttonPosition.x - buttonPosition.w
|
|
}
|
|
return frameStyle
|
|
}, [menus.length, menuItemStyle, buttonPosition, windowSize])
|
|
|
|
const menuPress = (menu: Menus[number]) => {
|
|
// if (menu.disabled) return
|
|
onPress(menu)
|
|
onHide()
|
|
}
|
|
|
|
// console.log('render menu')
|
|
// console.log(activeId)
|
|
// console.log(menuStyle)
|
|
// console.log(menuItemStyle)
|
|
return (
|
|
<View style={{ ...styles.menu, ...menuStyle, backgroundColor: theme['c-content-background'] }} onStartShouldSetResponder={() => true}>
|
|
<Animated.ScrollView keyboardShouldPersistTaps={'always'}>
|
|
{
|
|
menus.map((menu, index) => (
|
|
menu.disabled
|
|
? (
|
|
<View
|
|
key={menu.action}
|
|
style={{ ...styles.menuItem, width: menuItemStyle.width, height: menuItemStyle.height, opacity: 0.4 }}
|
|
>
|
|
<Text style={{ textAlign: center ? 'center' : 'left' }} size={fontSize} numberOfLines={1}>{menu.label}</Text>
|
|
</View>
|
|
)
|
|
: menu.action == activeId
|
|
? (
|
|
<View
|
|
key={menu.action}
|
|
style={{ ...styles.menuItem, width: menuItemStyle.width, height: menuItemStyle.height }}
|
|
>
|
|
<Text style={{ textAlign: center ? 'center' : 'left' }} color={theme['c-primary-font-active']} size={fontSize} numberOfLines={1}>{menu.label}</Text>
|
|
</View>
|
|
)
|
|
: (
|
|
<TouchableHighlight
|
|
key={menu.action}
|
|
style={{ ...styles.menuItem, width: menuItemStyle.width, height: menuItemStyle.height }}
|
|
underlayColor={theme['c-primary-background-active']}
|
|
onPress={() => { menuPress(menu) }}
|
|
>
|
|
<Text style={{ textAlign: center ? 'center' : 'left' }} size={fontSize} numberOfLines={1}>{menu.label}</Text>
|
|
</TouchableHighlight>
|
|
)
|
|
|
|
))
|
|
}
|
|
</Animated.ScrollView>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export interface MenuProps<M extends Menus = Menus> {
|
|
menus: M
|
|
onPress: (menu: M[number]) => void
|
|
onHide?: () => void
|
|
width?: number
|
|
height?: number
|
|
fontSize?: number
|
|
center?: boolean
|
|
activeId?: M[number]['action'] | null
|
|
}
|
|
|
|
export interface MenuType {
|
|
show: (position: Position, menuSize?: MenuSize) => void
|
|
hide: () => void
|
|
}
|
|
|
|
const Component = <M extends Menus>({ menus, activeId, onHide, onPress, fontSize, center }: MenuProps<M>, ref: Ref<MenuType>) => {
|
|
// console.log(visible)
|
|
const modalRef = useRef<ModalType>(null)
|
|
const [position, setPosition] = useState<Position>({ w: 0, h: 0, x: 0, y: 0 })
|
|
const [menuSize, setMenuSize] = useState<MenuSize>({ })
|
|
const hide = () => {
|
|
modalRef.current?.setVisible(false)
|
|
}
|
|
useImperativeHandle(ref, () => ({
|
|
show(newPosition, menuSize) {
|
|
setPosition(newPosition)
|
|
if (menuSize) setMenuSize(menuSize)
|
|
modalRef.current?.setVisible(true)
|
|
},
|
|
hide() {
|
|
hide()
|
|
},
|
|
}))
|
|
|
|
return (
|
|
<Modal onHide={onHide} ref={modalRef}>
|
|
<Menu menus={menus} activeId={activeId} buttonPosition={position} menuSize={menuSize} onPress={onPress} onHide={hide} fontSize={fontSize} center={center} />
|
|
</Modal>
|
|
)
|
|
}
|
|
|
|
// export default forwardRef(Component) as ForwardRefFn<MenuType>
|
|
export default forwardRef(Component) as <M extends Menus>(p: MenuProps<M> & { ref?: Ref<MenuType> }) => JSX.Element | null
|