{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "suggestion-node",
  "title": "Suggestion Leaf",
  "description": "A text component for suggestion.",
  "dependencies": [
    "@platejs/suggestion"
  ],
  "registryDependencies": [
    "https://platejs.org/r/suggestion.json",
    "https://platejs.org/r/suggestion-kit.json"
  ],
  "files": [
    {
      "path": "src/registry/ui/suggestion-node.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\n\nimport { cva } from 'class-variance-authority';\nimport { CornerDownLeftIcon } from 'lucide-react';\nimport type {\n  AnyPluginConfig,\n  TElement,\n  TSuggestionData,\n  TSuggestionText,\n  WithRequiredKey,\n} from 'platejs';\nimport { KEYS } from 'platejs';\nimport type {\n  PlateEditor,\n  PlateLeafProps,\n  RenderNodeWrapper,\n} from 'platejs/react';\n\nimport { SuggestionPlugin } from '@platejs/suggestion/react';\nimport { PlateLeaf, useEditorPlugin, usePluginOption } from 'platejs/react';\n\nimport { cn } from '@/lib/utils';\nimport type { SuggestionConfig } from '@/registry/components/editor/plugins/suggestion-kit';\nimport { voidRemoveSuggestionOverlayVariants } from '@/registry/ui/suggestion-node-static';\n\nconst suggestionPlugin = SuggestionPlugin as WithRequiredKey<SuggestionConfig>;\n\nexport const suggestionVariants = cva(\n  cn(\n    'bg-emerald-100 text-emerald-700 no-underline transition-colors duration-200'\n  ),\n  {\n    defaultVariants: {\n      insertActive: false,\n      remove: false,\n      removeActive: false,\n    },\n    variants: {\n      insertActive: {\n        false: '',\n        true: 'bg-emerald-200/80',\n      },\n      remove: {\n        false: '',\n        true: 'bg-red-100 text-red-700',\n      },\n      removeActive: {\n        false: '',\n        true: 'bg-red-200/80 no-underline',\n      },\n    },\n  }\n);\n\nexport function getBlockSuggestionWrapperClassName({\n  elementType,\n  isActive,\n  isHover,\n  isInsert,\n  isRemove,\n}: {\n  elementType?: string;\n  isActive: boolean;\n  isHover: boolean;\n  isInsert: boolean;\n  isRemove: boolean;\n}) {\n  return cn(\n    elementType === KEYS.columnGroup && 'flex size-full rounded',\n    suggestionVariants({\n      insertActive: isInsert && (isActive || isHover),\n      remove: isRemove,\n      removeActive: (isActive || isHover) && isRemove,\n    })\n  );\n}\n\nexport function isVoidRemoveSuggestion(editor: PlateEditor, element: TElement) {\n  return (\n    editor.getApi(SuggestionPlugin).suggestion.suggestionData(element)?.type ===\n    'remove'\n  );\n}\n\nexport function VoidRemoveSuggestionOverlay({\n  editor,\n  element,\n}: {\n  editor: PlateEditor;\n  element: TElement;\n}) {\n  const active =\n    editor.api.isVoid(element) &&\n    !editor.api.isInline(element) &&\n    isVoidRemoveSuggestion(editor, element);\n\n  if (!active) return null;\n\n  return (\n    <div\n      className={voidRemoveSuggestionOverlayVariants({ active })}\n      contentEditable={false}\n      data-slot=\"void-remove-suggestion\"\n    />\n  );\n}\n\nexport function SuggestionLineBreakAnchor({\n  badgeProps,\n  children,\n  className,\n}: {\n  badgeProps?: React.ComponentProps<'span'>;\n  children: React.ReactNode;\n  className?: string;\n}) {\n  const badge = (\n    <span\n      {...badgeProps}\n      className={cn(\n        'inline-flex h-[calc(1lh+2px)] w-[1lh] shrink-0 items-center justify-center leading-none',\n        badgeProps?.className,\n        className\n      )}\n      contentEditable={false}\n    >\n      <CornerDownLeftIcon className=\"relative top-px size-4\" />\n    </span>\n  );\n\n  return (\n    <>\n      {children}\n      {badge}\n    </>\n  );\n}\n\nfunction SuggestionLineBreakElementAnchor({\n  badgeProps,\n  children,\n  className,\n}: {\n  badgeProps?: React.ComponentProps<'span'>;\n  children: React.ReactElement<any>;\n  className?: string;\n}) {\n  if (!React.isValidElement(children)) return children;\n  const badge = (\n    <span\n      {...badgeProps}\n      className={cn(\n        'inline-flex h-[calc(1lh+2px)] w-[1lh] shrink-0 items-center justify-center leading-none',\n        badgeProps?.className,\n        className\n      )}\n      contentEditable={false}\n    >\n      <CornerDownLeftIcon className=\"relative top-px size-4\" />\n    </span>\n  );\n\n  if (children.type === 'ol' || children.type === 'ul') {\n    const childNodes = React.Children.toArray(\n      (children.props as { children?: React.ReactNode }).children\n    );\n    const lastIndex = childNodes.length - 1;\n    const lastChild = childNodes[lastIndex];\n\n    if (!React.isValidElement(lastChild) || lastChild.type !== 'li') {\n      return children;\n    }\n\n    const nextLastChild = React.cloneElement(\n      lastChild as React.ReactElement<any>,\n      {\n        children: (\n          <>\n            {(lastChild.props as { children?: React.ReactNode }).children}\n            {badge}\n          </>\n        ),\n      }\n    );\n\n    return React.cloneElement(children as React.ReactElement<any>, {\n      children: [...childNodes.slice(0, lastIndex), nextLastChild],\n    });\n  }\n\n  if (typeof children.type === 'string') {\n    return (\n      <>\n        {children}\n        {badge}\n      </>\n    );\n  }\n\n  return React.cloneElement(children as React.ReactElement<any>, {\n    lineBreakBadge: badge,\n  });\n}\n\nexport function SuggestionLeaf(props: PlateLeafProps<TSuggestionText>) {\n  const { api, setOption } = useEditorPlugin(suggestionPlugin);\n  const leaf = props.leaf;\n\n  const leafId: string = api.suggestion.nodeId(leaf) ?? '';\n  const activeSuggestionId = usePluginOption(suggestionPlugin, 'activeId');\n  const hoverSuggestionId = usePluginOption(suggestionPlugin, 'hoverId');\n  const dataList = api.suggestion.dataList(leaf);\n\n  const hasRemove = dataList.some((data) => data.type === 'remove');\n  const hasActive = dataList.some((data) => data.id === activeSuggestionId);\n  const hasHover = dataList.some((data) => data.id === hoverSuggestionId);\n\n  const diffOperation = { type: hasRemove ? 'delete' : 'insert' } as const;\n\n  const Component = ({ delete: 'del', insert: 'ins', update: 'span' } as const)[\n    diffOperation.type\n  ];\n\n  return (\n    <PlateLeaf\n      {...props}\n      as={Component}\n      className={cn(\n        suggestionVariants({\n          insertActive: hasActive || hasHover,\n          remove: hasRemove,\n          removeActive: (hasActive || hasHover) && hasRemove,\n        })\n      )}\n      attributes={{\n        ...props.attributes,\n        onMouseEnter: () => setOption('hoverId', leafId),\n        onMouseLeave: () => setOption('hoverId', null),\n      }}\n    >\n      {props.children}\n    </PlateLeaf>\n  );\n}\nexport const SuggestionLineBreak: RenderNodeWrapper<AnyPluginConfig> = ({\n  api,\n  element,\n}) => {\n  if (!api.suggestion.isBlockSuggestion(element)) return;\n\n  const suggestionData = element.suggestion as TSuggestionData;\n\n  return function Component({ children }) {\n    return (\n      <SuggestionLineBreakContent\n        elementType={element.type}\n        suggestionData={suggestionData}\n      >\n        {children}\n      </SuggestionLineBreakContent>\n    );\n  };\n};\n\nexport function SuggestionLineBreakContent({\n  children,\n  elementType,\n  suggestionData,\n}: {\n  children: React.ReactNode;\n  elementType?: string;\n  suggestionData: TSuggestionData;\n}) {\n  const { isLineBreak, type } = suggestionData;\n  const isRemove = type === 'remove';\n  const isInsert = type === 'insert';\n\n  const activeSuggestionId = usePluginOption(suggestionPlugin, 'activeId');\n  const hoverSuggestionId = usePluginOption(suggestionPlugin, 'hoverId');\n\n  const isActive = activeSuggestionId === suggestionData.id;\n  const isHover = hoverSuggestionId === suggestionData.id;\n\n  const { setOption } = useEditorPlugin(suggestionPlugin);\n  const lineBreakBadgeClassName = cn(\n    isInsert &&\n      'bg-transparent! text-emerald-700! transition-colors duration-200',\n    isInsert && (isActive || isHover) && 'bg-transparent! text-emerald-700!',\n    isRemove && 'bg-transparent! text-red-700! transition-colors duration-200',\n    isRemove && (isActive || isHover) && 'bg-transparent! text-red-700!'\n  );\n\n  return (\n    <>\n      {isLineBreak ? (\n        React.isValidElement(children) && typeof children.type !== 'string' ? (\n          <SuggestionLineBreakElementAnchor\n            badgeProps={{\n              onClick: (event) => {\n                event.stopPropagation();\n                setOption('activeId', suggestionData.id);\n              },\n              onMouseDown: (event) => {\n                event.preventDefault();\n              },\n            }}\n            className={lineBreakBadgeClassName}\n          >\n            {children}\n          </SuggestionLineBreakElementAnchor>\n        ) : React.isValidElement(children) &&\n          (children.type === 'ol' || children.type === 'ul') ? (\n          <SuggestionLineBreakElementAnchor\n            badgeProps={{\n              onClick: (event) => {\n                event.stopPropagation();\n                setOption('activeId', suggestionData.id);\n              },\n              onMouseDown: (event) => {\n                event.preventDefault();\n              },\n            }}\n            className={lineBreakBadgeClassName}\n          >\n            {children}\n          </SuggestionLineBreakElementAnchor>\n        ) : (\n          <SuggestionLineBreakAnchor\n            badgeProps={{\n              onClick: (event) => {\n                event.stopPropagation();\n                setOption('activeId', suggestionData.id);\n              },\n              onMouseDown: (event) => {\n                event.preventDefault();\n              },\n            }}\n            className={lineBreakBadgeClassName}\n          >\n            {children}\n          </SuggestionLineBreakAnchor>\n        )\n      ) : (\n        <div\n          className={getBlockSuggestionWrapperClassName({\n            elementType,\n            isActive,\n            isHover,\n            isInsert,\n            isRemove,\n          })}\n          onMouseEnter={() => setOption('hoverId', suggestionData.id)}\n          onMouseLeave={() => setOption('hoverId', null)}\n          data-block-suggestion=\"true\"\n        >\n          {children}\n        </div>\n      )}\n    </>\n  );\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "src/registry/ui/suggestion-node-static.tsx",
      "content": "import * as React from 'react';\n\nimport { cva } from 'class-variance-authority';\nimport type { TElement, TSuggestionText } from 'platejs';\nimport type { SlateLeafProps } from 'platejs/static';\n\nimport { BaseSuggestionPlugin } from '@platejs/suggestion';\nimport { SlateLeaf } from 'platejs/static';\n\nimport { cn } from '@/lib/utils';\n\nexport const voidRemoveSuggestionClass =\n  'relative overflow-hidden before:pointer-events-none before:absolute before:top-1/2 before:left-1/2 before:z-20 before:flex before:size-10 before:-translate-x-1/2 before:-translate-y-1/2 before:items-center before:justify-center before:rounded-full before:bg-red-500/90 before:text-2xl before:font-semibold before:text-white before:shadow-lg before:content-[\"X\"] after:pointer-events-none after:absolute after:inset-0 after:z-10 after:rounded-[inherit] after:border after:border-red-300/80 after:bg-zinc-950/35 after:content-[\"\"]';\n\nexport const voidRemoveSuggestionOverlayVariants = cva(\n  'pointer-events-none absolute inset-0 z-20 overflow-hidden rounded-[inherit]',\n  {\n    defaultVariants: {\n      active: false,\n    },\n    variants: {\n      active: {\n        false: 'hidden',\n        true: 'before:-translate-x-1/2 before:-translate-y-1/2 before:pointer-events-none before:absolute before:top-1/2 before:left-1/2 before:z-20 before:flex before:size-10 before:items-center before:justify-center before:rounded-full before:bg-red-500/90 before:font-semibold before:text-2xl before:text-white before:shadow-lg before:content-[\"X\"] after:pointer-events-none after:absolute after:inset-0 after:z-10 after:rounded-[inherit] after:border after:border-red-300/80 after:bg-zinc-950/35 after:content-[\"\"]',\n      },\n    },\n  }\n);\n\nexport const voidRemoveSuggestionVariants = cva('', {\n  defaultVariants: {\n    active: false,\n  },\n  variants: {\n    active: {\n      false: '',\n      true: voidRemoveSuggestionClass,\n    },\n  },\n});\n\nexport function isStaticVoidRemoveSuggestion(element: TElement) {\n  return (\n    (element as TElement & { suggestion?: { type?: string } }).suggestion\n      ?.type === 'remove'\n  );\n}\n\nexport function VoidRemoveSuggestionOverlayStatic({\n  editor,\n  element,\n}: {\n  editor: any;\n  element: TElement;\n}) {\n  const active =\n    editor.api.isVoid(element) &&\n    !editor.api.isInline(element) &&\n    isStaticVoidRemoveSuggestion(element);\n\n  if (!active) return null;\n\n  return (\n    <div\n      className={voidRemoveSuggestionOverlayVariants({ active })}\n      contentEditable={false}\n      data-slot=\"void-remove-suggestion\"\n    />\n  );\n}\n\nexport function SuggestionLeafStatic(props: SlateLeafProps<TSuggestionText>) {\n  const { editor, leaf } = props;\n\n  const dataList = editor\n    .getApi(BaseSuggestionPlugin)\n    .suggestion.dataList(leaf);\n  const hasRemove = dataList.some((data) => data.type === 'remove');\n  const diffOperation = { type: hasRemove ? 'delete' : 'insert' } as const;\n\n  const Component = ({ delete: 'del', insert: 'ins', update: 'span' } as const)[\n    diffOperation.type\n  ];\n\n  return (\n    <SlateLeaf\n      {...props}\n      as={Component}\n      className={cn(\n        'border-b-2 border-b-brand/[.24] bg-brand/[.08] text-brand/80 no-underline transition-colors duration-200',\n        hasRemove &&\n          'border-b-gray-300 bg-gray-300/25 text-gray-400 line-through'\n      )}\n    >\n      {props.children}\n    </SlateLeaf>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "docs": [
      {
        "route": "/docs/suggestion"
      }
    ],
    "examples": [
      "discussion-demo",
      "discussion-pro"
    ]
  },
  "type": "registry:ui"
}