feat(richtext-lexical): add tooltips to toolbar dropdown items (#9218)

Previously, if the dropdown item text is cut off due to length, there
was no way to view the full text.

Now, you can hover:

![CleanShot 2024-11-14 at 18 55
11@2x](https://github.com/user-attachments/assets/b160c172-c78a-4eb5-9fb3-b4ef8aee7eb5)
This commit is contained in:
Alessio Gravili
2024-11-14 19:29:12 -07:00
committed by GitHub
parent e6e0cc2a63
commit 729488028b
6 changed files with 40 additions and 10 deletions

View File

@@ -40,7 +40,7 @@ function ButtonGroupItem({
return ( return (
<ToolbarButton editor={editor} item={item} key={item.key}> <ToolbarButton editor={editor} item={item} key={item.key}>
{<item.ChildComponent />} <item.ChildComponent />
</ToolbarButton> </ToolbarButton>
) )
} }

View File

@@ -1,6 +1,7 @@
'use client' 'use client'
import type { LexicalEditor } from 'lexical' import type { LexicalEditor } from 'lexical'
import { Button } from '@payloadcms/ui'
import React, { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' import React, { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
@@ -19,15 +20,17 @@ export function DropDownItem({
children, children,
editor, editor,
enabled, enabled,
Icon,
item, item,
title, tooltip,
}: { }: {
active?: boolean active?: boolean
children: React.ReactNode children: React.ReactNode
editor: LexicalEditor editor: LexicalEditor
enabled?: boolean enabled?: boolean
Icon: React.ReactNode
item: ToolbarGroupItem item: ToolbarGroupItem
title?: string tooltip?: string
}): React.ReactNode { }): React.ReactNode {
const [className, setClassName] = useState<string>(baseClass) const [className, setClassName] = useState<string>(baseClass)
@@ -61,8 +64,14 @@ export function DropDownItem({
}, [ref, registerItem]) }, [ref, registerItem])
return ( return (
<button <Button
aria-label={tooltip}
buttonStyle="none"
className={className} className={className}
disabled={enabled === false}
icon={Icon}
iconPosition="left"
iconStyle="none"
onClick={() => { onClick={() => {
if (enabled !== false) { if (enabled !== false) {
editor._updateTags = new Set(['toolbar', ...editor._updateTags]) // without setting the tags, our onSelect will not be able to trigger our onChange as focus onChanges are ignored. editor._updateTags = new Set(['toolbar', ...editor._updateTags]) // without setting the tags, our onSelect will not be able to trigger our onChange as focus onChanges are ignored.
@@ -82,11 +91,11 @@ export function DropDownItem({
e.preventDefault() e.preventDefault()
}} }}
ref={ref} ref={ref}
title={title} tooltip={tooltip}
type="button" type="button"
> >
{children} {children}
</button> </Button>
) )
} }

View File

@@ -75,6 +75,7 @@
cursor: pointer; cursor: pointer;
color: var(--theme-elevation-900); color: var(--theme-elevation-900);
transition: background-color 0.15s cubic-bezier(0, 0.2, 0.2, 1); transition: background-color 0.15s cubic-bezier(0, 0.2, 0.2, 1);
position: relative;
.text { .text {
overflow: hidden; overflow: hidden;
@@ -91,6 +92,13 @@
opacity: 0.2; opacity: 0.2;
} }
.btn__icon {
// Override default button icon styles that
// set a background color when focused
background: none !important;
background-color: none !important;
}
padding-left: 6.25px; padding-left: 6.25px;
padding-right: 6.25px; padding-right: 6.25px;
width: 100%; width: 100%;

View File

@@ -49,6 +49,7 @@ const ToolbarItem = ({
} }
let title = item.key let title = item.key
let croppedTitle = item.key
if (item.label) { if (item.label) {
title = title =
typeof item.label === 'function' typeof item.label === 'function'
@@ -57,13 +58,22 @@ const ToolbarItem = ({
} }
// Crop title to max. 25 characters // Crop title to max. 25 characters
if (title.length > 25) { if (title.length > 25) {
title = title.substring(0, 25) + '...' croppedTitle = title.substring(0, 25) + '...'
} else {
croppedTitle = title
} }
return ( return (
<DropDownItem active={active} editor={editor} enabled={enabled} item={item} key={item.key}> <DropDownItem
{item?.ChildComponent && <item.ChildComponent />} active={active}
<span className="text">{title}</span> editor={editor}
enabled={enabled}
Icon={item?.ChildComponent ? <item.ChildComponent /> : undefined}
item={item}
key={item.key}
tooltip={title}
>
<span className="text">{croppedTitle}</span>
</DropDownItem> </DropDownItem>
) )
} }

View File

@@ -63,6 +63,7 @@ export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, Props>((
Link, Link,
newTab, newTab,
onClick, onClick,
onMouseDown,
round, round,
size = 'medium', size = 'medium',
SubMenuPopupContent, SubMenuPopupContent,
@@ -114,6 +115,7 @@ export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, Props>((
className: !SubMenuPopupContent ? [classes, styleClasses].join(' ') : classes, className: !SubMenuPopupContent ? [classes, styleClasses].join(' ') : classes,
disabled, disabled,
onClick: !disabled ? handleClick : undefined, onClick: !disabled ? handleClick : undefined,
onMouseDown: !disabled ? onMouseDown : undefined,
onPointerEnter: tooltip ? () => setShowTooltip(true) : undefined, onPointerEnter: tooltip ? () => setShowTooltip(true) : undefined,
onPointerLeave: tooltip ? () => setShowTooltip(false) : undefined, onPointerLeave: tooltip ? () => setShowTooltip(false) : undefined,
rel: newTab ? 'noopener noreferrer' : undefined, rel: newTab ? 'noopener noreferrer' : undefined,

View File

@@ -20,6 +20,7 @@ export type Props = {
Link?: React.ElementType Link?: React.ElementType
newTab?: boolean newTab?: boolean
onClick?: (event: MouseEvent) => void onClick?: (event: MouseEvent) => void
onMouseDown?: (event: MouseEvent) => void
round?: boolean round?: boolean
secondaryActions?: secondaryAction | secondaryAction[] secondaryActions?: secondaryAction | secondaryAction[]
size?: 'large' | 'medium' | 'small' size?: 'large' | 'medium' | 'small'