feat: remove a link from a Links field (#5313)

Closes #5117

TO FIX in another PR: right now, the "Vertical Dots" LightIconButton
inside the Dropdown menu sometimes needs to be clicked twice to open the
nested dropdown, not sure why 🤔 Maybe an `event.preventDefault()` is
needed somewhere?

<img width="369" alt="image"
src="https://github.com/twentyhq/twenty/assets/3098428/dd0c771a-c18d-4eb2-8ed6-b107f56711e9">

---------

Co-authored-by: Jérémy Magrin <jeremy.magrin@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thaïs
2024-05-22 09:39:21 +02:00
committed by GitHub
parent beaaf33544
commit 48003887ce
11 changed files with 160 additions and 64 deletions

View File

@ -4,8 +4,8 @@ import { Key } from 'ts-key-enum';
import { IconPlus } from 'twenty-ui';
import { useLinksField } from '@/object-record/record-field/meta-types/hooks/useLinksField';
import { LinksFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/LinksFieldMenuItem';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
import { LinkDisplay } from '@/ui/field/display/components/LinkDisplay';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuInput } from '@/ui/layout/dropdown/components/DropdownMenuInput';
@ -14,6 +14,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { toSpliced } from '~/utils/array/toSpliced';
import { isDefined } from '~/utils/isDefined';
const StyledDropdownMenu = styled(DropdownMenu)`
@ -35,7 +36,7 @@ export const LinksFieldInput = ({
const containerRef = useRef<HTMLDivElement>(null);
const links = useMemo(
const links = useMemo<{ url: string; label: string }[]>(
() =>
[
fieldValue.primaryLinkUrl
@ -53,51 +54,47 @@ export const LinksFieldInput = ({
],
);
const handleDropdownClose = () => {
onCancel?.();
};
useListenClickOutside({
refs: [containerRef],
callback: (event) => {
event.stopImmediatePropagation();
const isTargetInput =
event.target instanceof HTMLInputElement &&
event.target.tagName === 'INPUT';
if (!isTargetInput) {
onCancel?.();
}
},
callback: handleDropdownClose,
});
useScopedHotkeys(Key.Escape, handleDropdownClose, hotkeyScope);
const [isInputDisplayed, setIsInputDisplayed] = useState(false);
const [inputValue, setInputValue] = useState('');
useScopedHotkeys(Key.Escape, onCancel ?? (() => {}), hotkeyScope);
const handleSubmit = () => {
const handleAddLink = () => {
if (!inputValue) return;
setIsInputDisplayed(false);
setInputValue('');
if (!links.length) {
onSubmit?.(() =>
persistLinksField({
primaryLinkUrl: inputValue,
primaryLinkLabel: '',
secondaryLinks: [],
}),
);
return;
}
const nextLinks = [...links, { label: '', url: inputValue }];
const [nextPrimaryLink, ...nextSecondaryLinks] = nextLinks;
onSubmit?.(() =>
persistLinksField({
primaryLinkUrl: nextPrimaryLink.url ?? '',
primaryLinkLabel: nextPrimaryLink.label ?? '',
secondaryLinks: nextSecondaryLinks,
}),
);
};
const handleDeleteLink = (index: number) => {
onSubmit?.(() =>
persistLinksField({
...fieldValue,
secondaryLinks: [
...(fieldValue.secondaryLinks ?? []),
{ label: '', url: inputValue },
],
secondaryLinks: toSpliced(
fieldValue.secondaryLinks ?? [],
index - 1,
1,
),
}),
);
};
@ -108,9 +105,13 @@ export const LinksFieldInput = ({
<>
<DropdownMenuItemsContainer>
{links.map(({ label, url }, index) => (
<MenuItem
<LinksFieldMenuItem
key={index}
text={<LinkDisplay value={{ label, url }} />}
dropdownId={`${hotkeyScope}-links-${index}`}
isPrimary={index === 0}
label={label}
onDelete={() => handleDeleteLink(index)}
url={url}
/>
))}
</DropdownMenuItemsContainer>
@ -124,9 +125,9 @@ export const LinksFieldInput = ({
value={inputValue}
hotkeyScope={hotkeyScope}
onChange={(event) => setInputValue(event.target.value)}
onEnter={handleSubmit}
onEnter={handleAddLink}
rightComponent={
<LightIconButton Icon={IconPlus} onClick={handleSubmit} />
<LightIconButton Icon={IconPlus} onClick={handleAddLink} />
}
/>
) : (

View File

@ -0,0 +1,77 @@
import styled from '@emotion/styled';
import {
IconBookmark,
IconComponent,
IconDotsVertical,
IconTrash,
} from 'twenty-ui';
import { LinkDisplay } from '@/ui/field/display/components/LinkDisplay';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
type LinksFieldMenuItemProps = {
dropdownId: string;
isPrimary?: boolean;
label: string;
onDelete: () => void;
url: string;
};
const StyledIconBookmark = styled(IconBookmark)`
color: ${({ theme }) => theme.font.color.light};
height: ${({ theme }) => theme.icon.size.sm}px;
width: ${({ theme }) => theme.icon.size.sm}px;
`;
export const LinksFieldMenuItem = ({
dropdownId,
isPrimary,
label,
onDelete,
url,
}: LinksFieldMenuItemProps) => {
const { isDropdownOpen } = useDropdown(dropdownId);
return (
<MenuItem
text={<LinkDisplay value={{ label, url }} />}
isIconDisplayedOnHoverOnly={!isPrimary && !isDropdownOpen}
iconButtons={[
{
Wrapper: isPrimary
? undefined
: ({ iconButton }) => (
<Dropdown
dropdownId={dropdownId}
dropdownHotkeyScope={{
scope: dropdownId,
}}
dropdownPlacement="right-start"
dropdownStrategy="fixed"
disableBlur
clickableComponent={iconButton}
dropdownComponents={
<DropdownMenuItemsContainer>
<MenuItem
accent="danger"
LeftIcon={IconTrash}
text="Delete"
onClick={onDelete}
/>
</DropdownMenuItemsContainer>
}
/>
),
Icon: isPrimary
? (StyledIconBookmark as IconComponent)
: IconDotsVertical,
accent: 'tertiary',
onClick: isPrimary ? undefined : () => {},
},
]}
/>
);
};