mosaicmap/components/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx
2025-08-12 21:25:52 +08:00

126 lines
3.3 KiB
TypeScript

"use client"
import * as React from "react"
import { type Editor } from "@tiptap/react"
// --- Hooks ---
import { useTiptapEditor } from "@/hooks/use-tiptap-editor"
// --- Icons ---
import { ChevronDownIcon } from "@/components/tiptap-icons/chevron-down-icon"
// --- Tiptap UI ---
import { ListButton, type ListType } from "@/components/tiptap-ui/list-button"
import { useListDropdownMenu } from "./use-list-dropdown-menu"
// --- UI Primitives ---
import type { ButtonProps } from "@/components/tiptap-ui-primitive/button"
import { Button, ButtonGroup } from "@/components/tiptap-ui-primitive/button"
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from "@/components/tiptap-ui-primitive/dropdown-menu"
import { Card, CardBody } from "@/components/tiptap-ui-primitive/card"
export interface ListDropdownMenuProps extends Omit<ButtonProps, "type"> {
/**
* The Tiptap editor instance.
*/
editor?: Editor
/**
* The list types to display in the dropdown.
*/
types?: ListType[]
/**
* Whether the dropdown should be hidden when no list types are available
* @default false
*/
hideWhenUnavailable?: boolean
/**
* Callback for when the dropdown opens or closes
*/
onOpenChange?: (isOpen: boolean) => void
/**
* Whether to render the dropdown menu in a portal
* @default false
*/
portal?: boolean
}
export function ListDropdownMenu({
editor: providedEditor,
types = ["bulletList", "orderedList", "taskList"],
hideWhenUnavailable = false,
onOpenChange,
portal = false,
...props
}: ListDropdownMenuProps) {
const { editor } = useTiptapEditor(providedEditor)
const [isOpen, setIsOpen] = React.useState(false)
const { filteredLists, canToggle, isActive, isVisible, Icon } =
useListDropdownMenu({
editor,
types,
hideWhenUnavailable,
})
const handleOnOpenChange = React.useCallback(
(open: boolean) => {
setIsOpen(open)
onOpenChange?.(open)
},
[onOpenChange]
)
if (!isVisible || !editor || !editor.isEditable) {
return null
}
return (
<DropdownMenu open={isOpen} onOpenChange={handleOnOpenChange}>
<DropdownMenuTrigger asChild>
<Button
type="button"
data-style="ghost"
data-active-state={isActive ? "on" : "off"}
role="button"
tabIndex={-1}
disabled={!canToggle}
data-disabled={!canToggle}
aria-label="List options"
tooltip="List"
{...props}
>
<Icon className="tiptap-button-icon" />
<ChevronDownIcon className="tiptap-button-dropdown-small" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" portal={portal}>
<Card>
<CardBody>
<ButtonGroup>
{filteredLists.map((option) => (
<DropdownMenuItem key={option.type} asChild>
<ListButton
editor={editor}
type={option.type}
text={option.label}
showTooltip={false}
/>
</DropdownMenuItem>
))}
</ButtonGroup>
</CardBody>
</Card>
</DropdownMenuContent>
</DropdownMenu>
)
}
export default ListDropdownMenu