edit-slice.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import { useState } from 'react'
  2. import type { FC, ReactNode } from 'react'
  3. import { FloatingFocusManager, type OffsetOptions, autoUpdate, flip, offset, shift, useDismiss, useFloating, useHover, useInteractions, useRole } from '@floating-ui/react'
  4. import { RiDeleteBinLine } from '@remixicon/react'
  5. // @ts-expect-error no types available
  6. import lineClamp from 'line-clamp'
  7. import type { SliceProps } from './type'
  8. import { SliceContainer, SliceContent, SliceDivider, SliceLabel } from './shared'
  9. import classNames from '@/utils/classnames'
  10. import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
  11. type EditSliceProps = SliceProps<{
  12. label: ReactNode
  13. onDelete: () => void
  14. labelClassName?: string
  15. labelInnerClassName?: string
  16. contentClassName?: string
  17. showDivider?: boolean
  18. offsetOptions?: OffsetOptions
  19. }>
  20. export const EditSlice: FC<EditSliceProps> = (props) => {
  21. const {
  22. label,
  23. className,
  24. text,
  25. onDelete,
  26. labelClassName,
  27. labelInnerClassName,
  28. contentClassName,
  29. showDivider = true,
  30. offsetOptions,
  31. ...rest
  32. } = props
  33. const [delBtnShow, setDelBtnShow] = useState(false)
  34. const [isDelBtnHover, setDelBtnHover] = useState(false)
  35. const { refs, floatingStyles, context } = useFloating({
  36. open: delBtnShow,
  37. onOpenChange: setDelBtnShow,
  38. placement: 'right-start',
  39. whileElementsMounted: autoUpdate,
  40. middleware: [
  41. flip(),
  42. shift(),
  43. offset(offsetOptions),
  44. ],
  45. })
  46. const hover = useHover(context, {})
  47. const dismiss = useDismiss(context)
  48. const role = useRole(context)
  49. const { getReferenceProps, getFloatingProps } = useInteractions([hover, dismiss, role])
  50. const isDestructive = delBtnShow && isDelBtnHover
  51. return (
  52. <>
  53. <SliceContainer {...rest}
  54. className={classNames('block mr-0', className)}
  55. ref={(ref) => {
  56. refs.setReference(ref)
  57. if (ref)
  58. lineClamp(ref, 4)
  59. }}
  60. {...getReferenceProps()}
  61. >
  62. <SliceLabel
  63. className={classNames(
  64. isDestructive && '!bg-state-destructive-solid !text-text-primary-on-surface',
  65. labelClassName,
  66. )}
  67. labelInnerClassName={labelInnerClassName}
  68. >
  69. {label}
  70. </SliceLabel>
  71. <SliceContent
  72. className={classNames(
  73. isDestructive && '!bg-state-destructive-hover-alt',
  74. contentClassName,
  75. )}
  76. >
  77. {text}
  78. </SliceContent>
  79. {showDivider && <SliceDivider
  80. className={classNames(
  81. isDestructive && '!bg-state-destructive-hover-alt',
  82. )}
  83. />}
  84. {delBtnShow && <FloatingFocusManager
  85. context={context}
  86. >
  87. <span
  88. ref={refs.setFloating}
  89. style={floatingStyles}
  90. {...getFloatingProps()}
  91. className='p-1 rounded-lg bg-components-actionbar-bg shadow inline-flex items-center justify-center'
  92. onMouseEnter={() => setDelBtnHover(true)}
  93. onMouseLeave={() => setDelBtnHover(false)}
  94. >
  95. <ActionButton
  96. onClick={(e) => {
  97. e.stopPropagation()
  98. onDelete()
  99. setDelBtnShow(false)
  100. }}
  101. state={ActionButtonState.Destructive}
  102. >
  103. <RiDeleteBinLine className='w-4 h-4' />
  104. </ActionButton>
  105. </span>
  106. </FloatingFocusManager>}
  107. </SliceContainer>
  108. </>
  109. )
  110. }