mirror of
https://github.com/langgenius/dify.git
synced 2026-09-01 21:55:46 +08:00
135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
import type { DropdownMenuContentProps } from '@langgenius/dify-ui/dropdown-menu'
|
|
import type { SyntheticEvent } from 'react'
|
|
import { cn } from '@langgenius/dify-ui/cn'
|
|
import { useCallback, useState } from 'react'
|
|
|
|
const STEP_BY_STEP_TOUR_HIGHLIGHT_PART_DATA_ATTR = 'data-step-by-step-tour-highlight-part'
|
|
const STEP_BY_STEP_TOUR_MENU_POPUP_NO_MOTION_CLASS_NAME =
|
|
'transition-none data-starting-style:scale-100 data-starting-style:opacity-100 data-ending-style:scale-100 data-ending-style:opacity-100'
|
|
const STEP_BY_STEP_TOUR_MENU_PRESENTATION_CLASS_NAME = 'pointer-events-none cursor-default'
|
|
|
|
type StepByStepTourDropdownMenuContentProps = {
|
|
highlightPart?: string
|
|
disableMotion?: boolean
|
|
interactionMode?: 'interactive' | 'presentation'
|
|
className?: string
|
|
}
|
|
|
|
const blockPresentationMenuEvent = (event: SyntheticEvent) => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
}
|
|
|
|
const stopMenuEventPropagation = (event: SyntheticEvent) => {
|
|
event.stopPropagation()
|
|
}
|
|
|
|
export const getStepByStepTourDropdownMenuContentProps = ({
|
|
highlightPart,
|
|
disableMotion = false,
|
|
interactionMode = 'interactive',
|
|
className,
|
|
}: StepByStepTourDropdownMenuContentProps): Pick<
|
|
DropdownMenuContentProps,
|
|
| 'aria-hidden'
|
|
| 'className'
|
|
| 'onClick'
|
|
| 'onClickCapture'
|
|
| 'onKeyDownCapture'
|
|
| 'onMouseDownCapture'
|
|
| 'onPointerDownCapture'
|
|
> & {
|
|
[STEP_BY_STEP_TOUR_HIGHLIGHT_PART_DATA_ATTR]?: string
|
|
} => {
|
|
const isPresentation = interactionMode === 'presentation'
|
|
|
|
return {
|
|
...(highlightPart
|
|
? { [STEP_BY_STEP_TOUR_HIGHLIGHT_PART_DATA_ATTR]: highlightPart }
|
|
: undefined),
|
|
className: cn(
|
|
className,
|
|
disableMotion && STEP_BY_STEP_TOUR_MENU_POPUP_NO_MOTION_CLASS_NAME,
|
|
isPresentation && STEP_BY_STEP_TOUR_MENU_PRESENTATION_CLASS_NAME,
|
|
),
|
|
...(isPresentation
|
|
? {
|
|
'aria-hidden': true,
|
|
onClickCapture: blockPresentationMenuEvent,
|
|
onMouseDownCapture: blockPresentationMenuEvent,
|
|
onPointerDownCapture: blockPresentationMenuEvent,
|
|
onKeyDownCapture: blockPresentationMenuEvent,
|
|
}
|
|
: { onClick: stopMenuEventPropagation }),
|
|
}
|
|
}
|
|
|
|
type StepByStepTourControlledDropdownOptions = {
|
|
allowTriggerCloseWhileControlled?: boolean
|
|
controlledOpen?: boolean
|
|
}
|
|
|
|
type StepByStepTourControlledDropdownState = {
|
|
controlledOpen?: boolean
|
|
open: boolean
|
|
}
|
|
|
|
const getNextDropdownStateForControlledOpen = (
|
|
state: StepByStepTourControlledDropdownState,
|
|
controlledOpen: boolean | undefined,
|
|
): StepByStepTourControlledDropdownState => {
|
|
if (state.controlledOpen === controlledOpen) return state
|
|
|
|
return {
|
|
controlledOpen,
|
|
open:
|
|
controlledOpen === true
|
|
? true
|
|
: controlledOpen === false || state.controlledOpen === true
|
|
? false
|
|
: state.open,
|
|
}
|
|
}
|
|
|
|
export const useStepByStepTourControlledDropdown = ({
|
|
allowTriggerCloseWhileControlled = true,
|
|
controlledOpen,
|
|
}: StepByStepTourControlledDropdownOptions = {}) => {
|
|
const [state, setState] = useState<StepByStepTourControlledDropdownState>(() => ({
|
|
controlledOpen,
|
|
open: controlledOpen === true,
|
|
}))
|
|
const normalizedState = getNextDropdownStateForControlledOpen(state, controlledOpen)
|
|
if (normalizedState !== state) setState(normalizedState)
|
|
|
|
const onOpenChange = useCallback(
|
|
(nextOpen: boolean) => {
|
|
setState((currentState) => {
|
|
const nextState = getNextDropdownStateForControlledOpen(currentState, controlledOpen)
|
|
if (nextState.controlledOpen && !allowTriggerCloseWhileControlled && !nextOpen)
|
|
return nextState
|
|
|
|
return {
|
|
...nextState,
|
|
open: nextOpen,
|
|
}
|
|
})
|
|
},
|
|
[allowTriggerCloseWhileControlled, controlledOpen],
|
|
)
|
|
|
|
const close = useCallback(() => {
|
|
setState((currentState) => ({
|
|
...currentState,
|
|
open: false,
|
|
}))
|
|
}, [])
|
|
|
|
return {
|
|
close,
|
|
controlled: Boolean(normalizedState.controlledOpen && normalizedState.open),
|
|
open: normalizedState.open,
|
|
onOpenChange,
|
|
}
|
|
}
|