Skip to content

Commit

Permalink
add sorting and truncating
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiankaegy committed Feb 19, 2025
1 parent 1049564 commit 8be6443
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 12 deletions.
7 changes: 5 additions & 2 deletions components/content-picker/DraggableChip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Flex, FlexItem } from '@wordpress/components';
import { Flex, FlexItem, __experimentalTruncate as Truncate } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import styled from '@emotion/styled';
import { DragHandle } from '../drag-handle';
Expand All @@ -19,6 +19,7 @@ const Chip = styled.div`
font-size: 13px;
line-height: 1.4;
white-space: nowrap;
max-width: min(300px, 100%);
svg {
fill: currentColor;
Expand All @@ -40,7 +41,9 @@ export const DraggableChip = (props: DraggableChipProps) => {
<ChipWrapper>
<Chip data-testid="draggable-chip">
<Flex justify="center" align="center" gap={4}>
<FlexItem>{title}</FlexItem>
<FlexItem>
<Truncate>{title}</Truncate>
</FlexItem>
<DragHandle />
</Flex>
</Chip>
Expand Down
87 changes: 81 additions & 6 deletions components/content-picker/PickedItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import { CSS } from '@dnd-kit/utilities';
import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
import { decodeEntities } from '@wordpress/html-entities';
import { __ } from '@wordpress/i18n';
import { close } from '@wordpress/icons';
import { Button, __experimentalTreeGridRow as TreeGridRow } from '@wordpress/components';
import { close, chevronUp, chevronDown } from '@wordpress/icons';
import {
Button,
__experimentalTreeGridRow as TreeGridRow,
VisuallyHidden,
__experimentalVStack as VStack,
__experimentalTruncate as Truncate,
} from '@wordpress/components';
import { DragHandle } from '../drag-handle';
import { ContentSearchMode } from '../content-search/types';

Expand All @@ -18,12 +24,15 @@ export type PickedItemType = {
};

const PickedItemContainer = styled.div<{ isDragging?: boolean; isOrderable?: boolean }>`
box-sizing: border-box;
position: relative;
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px;
min-height: 36px;
max-width: 100%;
width: 100%;
color: #1e1e1e;
opacity: ${({ isDragging }) => (isDragging ? 0.5 : 1)};
background: ${({ isDragging }) => (isDragging ? '#f0f0f0' : 'transparent')};
Expand All @@ -37,6 +46,13 @@ const PickedItemContainer = styled.div<{ isDragging?: boolean; isOrderable?: boo
&:hover {
background: #f0f0f0;
.move-up-button,
.move-down-button,
.remove-button {
opacity: 1;
pointer-events: auto;
}
}
.components-button.has-icon {
Expand Down Expand Up @@ -76,6 +92,7 @@ const RemoveButton = styled(Button)<{ isDragging?: boolean }>`
const ItemContent = styled.div`
flex: 1;
min-width: 0;
max-width: calc(100% - 80px); /* Account for the width of buttons */
display: flex;
flex-direction: column;
gap: 2px;
Expand All @@ -88,9 +105,6 @@ const ItemTitle = styled.span`
line-height: 1.4;
font-weight: 500;
color: #1e1e1e;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;

const ItemURL = styled.span`
Expand All @@ -102,6 +116,28 @@ const ItemURL = styled.span`
text-overflow: ellipsis;
`;

const MoveButton = styled(Button)`
&.components-button.has-icon {
min-width: 20px;
padding: 0;
height: 14px;
}
&.components-button.has-icon svg {
width: 18px;
height: 18px;
}
opacity: 0;
pointer-events: none;
transition: opacity 0.1s linear;
&:focus {
opacity: 1;
pointer-events: auto;
}
`;

interface PickedItemProps {
item: PickedItemType;
isOrderable?: boolean;
Expand All @@ -111,6 +147,8 @@ interface PickedItemProps {
isDragging?: boolean;
positionInSet?: number;
setSize?: number;
onMoveUp?: () => void;
onMoveDown?: () => void;
}

/**
Expand All @@ -127,6 +165,8 @@ const PickedItem: React.FC<PickedItemProps> = ({
isDragging = false,
positionInSet = 1,
setSize = 1,
onMoveUp,
onMoveDown,
}) => {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
id,
Expand All @@ -137,6 +177,9 @@ const PickedItem: React.FC<PickedItemProps> = ({
transition,
};

const isFirst = positionInSet === 1;
const isLast = positionInSet === setSize;

return (
<TreeGridRow level={1} positionInSet={positionInSet} setSize={setSize}>
<PickedItemContainer
Expand All @@ -153,11 +196,43 @@ const PickedItem: React.FC<PickedItemProps> = ({
</DragHandleWrapper>
)}
<ItemContent isDragging={isDragging}>
<ItemTitle>{decodeEntities(item.title)}</ItemTitle>
<ItemTitle>
<Truncate>{decodeEntities(item.title)}</Truncate>
</ItemTitle>
{item.url && (
<ItemURL>{filterURLForDisplay(safeDecodeURI(item.url)) || ''}</ItemURL>
)}
</ItemContent>
{isOrderable && !isDragging && (
<VStack spacing={0} className="move-buttons">
<MoveButton
disabled={isFirst}
icon={chevronUp}
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
onMoveUp?.();
}}
className="move-up-button"
>
<VisuallyHidden>
{__('Move item up', '10up-block-components')}
</VisuallyHidden>
</MoveButton>
<MoveButton
disabled={isLast}
icon={chevronDown}
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
onMoveDown?.();
}}
className="move-down-button"
>
<VisuallyHidden>
{__('Move item down', '10up-block-components')}
</VisuallyHidden>
</MoveButton>
</VStack>
)}
{!isDragging && (
<RemoveButton
className="remove-button"
Expand Down
34 changes: 30 additions & 4 deletions components/content-picker/SortableList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useCallback, useState, useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
import { Post, User, store as coreStore } from '@wordpress/core-data';
import styled from '@emotion/styled';
import PickedItem, { PickedItemType } from './PickedItem';
import { DraggableChip } from './DraggableChip';
import { ContentSearchMode } from '../content-search/types';
Expand Down Expand Up @@ -62,6 +63,19 @@ function getEntityKind(mode: ContentSearchMode) {
return type;
}

const StyledTreeGrid = styled(TreeGrid)`
max-width: 100%;
display: block;
& tbody,
& tr,
& td {
display: block;
max-width: 100%;
width: 100%;
}
`;

const SortableList: React.FC<SortableListProps> = ({
posts,
isOrderable = false,
Expand Down Expand Up @@ -176,6 +190,16 @@ const SortableList: React.FC<SortableListProps> = ({
const preparedItem = preparedItems[post.uuid];
if (!preparedItem) return null;

const handleMoveUp = () => {
if (index === 0) return;
setPosts(arrayMove(posts, index, index - 1));
};

const handleMoveDown = () => {
if (index === items.length - 1) return;
setPosts(arrayMove(posts, index, index + 1));
};

return (
<PickedItem
isOrderable={hasMultiplePosts && isOrderable}
Expand All @@ -186,6 +210,8 @@ const SortableList: React.FC<SortableListProps> = ({
id={post.uuid}
positionInSet={index + 1}
setSize={items.length}
onMoveUp={handleMoveUp}
onMoveDown={handleMoveDown}
/>
);
});
Expand All @@ -194,14 +220,14 @@ const SortableList: React.FC<SortableListProps> = ({
// If not orderable or only one item, render simple list
if (!isOrderable || !hasMultiplePosts) {
return (
<TreeGrid
<StyledTreeGrid
className="block-editor-list-view-tree"
aria-label={__('Selected items list')}
onCollapseRow={() => {}}
onExpandRow={() => {}}
>
{renderItems(posts)}
</TreeGrid>
</StyledTreeGrid>
);
}

Expand All @@ -214,7 +240,7 @@ const SortableList: React.FC<SortableListProps> = ({
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<TreeGrid
<StyledTreeGrid
className="block-editor-list-view-tree"
aria-label={__('Selected items list')}
onCollapseRow={() => {}}
Expand All @@ -223,7 +249,7 @@ const SortableList: React.FC<SortableListProps> = ({
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{renderItems(posts)}
</SortableContext>
</TreeGrid>
</StyledTreeGrid>
<DragOverlay dropAnimation={dropAnimation}>
{activeId && activePost ? <DraggableChip title={activePost.title} /> : null}
</DragOverlay>
Expand Down

0 comments on commit 8be6443

Please sign in to comment.