Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions packages/extension/__tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@testing-library/jest-dom';
import 'fake-indexeddb/auto';
import type { ReactNode } from 'react';
import { clear } from 'idb-keyval';
import nodeFetch from 'node-fetch';
import { storageWrapper as storage } from '@dailydotdev/shared/src/lib/storageWrapper';
Expand Down Expand Up @@ -119,3 +120,42 @@ Object.defineProperty(global, 'ResizeObserver', {
});

structuredCloneJsonPolyfill();

// Mock dnd-kit for tests
jest.mock('@dnd-kit/core', () => ({
DndContext: ({ children }: { children: ReactNode }) => children,
closestCenter: jest.fn(),
KeyboardSensor: jest.fn(),
PointerSensor: jest.fn(),
useSensor: jest.fn(),
useSensors: jest.fn(() => []),
}));

jest.mock('@dnd-kit/sortable', () => ({
SortableContext: ({ children }: { children: ReactNode }) => children,
arrayMove: jest.fn((arr, from, to) => {
const result = [...arr];
const [removed] = result.splice(from, 1);
result.splice(to, 0, removed);
return result;
}),
sortableKeyboardCoordinates: jest.fn(),
horizontalListSortingStrategy: jest.fn(),
verticalListSortingStrategy: jest.fn(),
useSortable: jest.fn(() => ({
attributes: {},
listeners: {},
setNodeRef: jest.fn(),
transform: null,
transition: null,
isDragging: false,
})),
}));

jest.mock('@dnd-kit/utilities', () => ({
CSS: {
Transform: {
toString: jest.fn(() => ''),
},
},
}));
3 changes: 3 additions & 0 deletions packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"prettier": "@dailydotdev/prettier-config",
"dependencies": {
"@dailydotdev/shared": "workspace:*",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@kickass-coderz/react": "^0.0.4",
"@tanstack/react-query": "^5.80.5",
"@tanstack/react-query-devtools": "^5.80.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@ export default function ShortcutLinks({
shouldUseListFeedLayout,
}: ShortcutLinksProps): ReactElement {
const { openModal } = useLazyModal();
const { showTopSites, toggleShowTopSites } = useSettingsContext();
const { showTopSites, toggleShowTopSites, updateCustomLinks } =
useSettingsContext();
const { logEvent } = useLogContext();
const {
shortcutLinks,
hasCheckedPermission,
isTopSiteActive,
showGetStarted,
hideShortcuts,
isManual,
} = useShortcutLinks();

const { showPermissionsModal } = useShortcuts();
Expand Down Expand Up @@ -88,6 +90,10 @@ export default function ShortcutLinks({
});
};

const onReorder = (reorderedLinks: string[]) => {
updateCustomLinks(reorderedLinks);
};

if (!showTopSites) {
return <></>;
}
Expand All @@ -110,6 +116,8 @@ export default function ShortcutLinks({
showTopSites,
toggleShowTopSites,
hasCheckedPermission,
onReorder,
isManual,
}}
/>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { ReactElement } from 'react';
import React from 'react';
import { PlusIcon } from '@dailydotdev/shared/src/components/icons';
import classNames from 'classnames';
import { MenuIcon, PlusIcon } from '@dailydotdev/shared/src/components/icons';

import { IconSize } from '@dailydotdev/shared/src/components/Icon';
import { combinedClicks } from '@dailydotdev/shared/src/lib/click';

import { apiUrl } from '@dailydotdev/shared/src/lib/config';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

const pixelRatio = globalThis?.window.devicePixelRatio ?? 1;
const iconSize = Math.round(24 * pixelRatio);
Expand Down Expand Up @@ -51,19 +54,47 @@ export function ShortcutLinksItem({
onLinkClick: () => void;
}): ReactElement {
const cleanUrl = url.replace(/http(s)?(:)?(\/\/)?|(\/\/)?(www\.)?/g, '');

const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: url });

const style = {
transform: CSS.Transform.toString(transform),
transition,
};

return (
<a
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
href={url}
rel="noopener noreferrer"
{...combinedClicks(onLinkClick)}
className="group mr-4 flex flex-col items-center"
className={classNames(
'group relative mr-4 flex cursor-grab flex-col items-center active:cursor-grabbing',
isDragging && 'opacity-50',
)}
>
<div className="mb-2 flex size-12 items-center justify-center rounded-full bg-surface-float text-text-secondary">
<div className="relative mb-2 flex size-12 items-center justify-center rounded-full bg-surface-float text-text-secondary">
<img
src={`${apiUrl}/icon?url=${encodeURIComponent(url)}&size=${iconSize}`}
alt={url}
className="size-6"
/>
<div className="rounded shadow-1 absolute -bottom-1 left-1/2 flex -translate-x-1/2 items-center justify-center bg-surface-primary opacity-0 transition-opacity group-hover:opacity-100">
<MenuIcon
size={IconSize.XSmall}
className="rotate-90 text-text-quaternary"
/>
</div>
</div>
<span className="max-w-12 truncate text-text-tertiary typo-caption2">
{cleanUrl}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ import {
DropdownMenuOptions,
DropdownMenuTrigger,
} from '@dailydotdev/shared/src/components/dropdown/DropdownMenu';
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import type { DragEndEvent } from '@dnd-kit/core';
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
horizontalListSortingStrategy,
} from '@dnd-kit/sortable';
import {
ShortcutLinksItem,
ShortcutItemPlaceholder,
Expand All @@ -34,6 +49,8 @@ interface ShortcutLinksListProps {
showTopSites: boolean;
toggleShowTopSites: () => void;
hasCheckedPermission?: boolean;
onReorder?: (links: string[]) => void;
isManual?: boolean;
}

const placeholderLinks = Array.from({ length: 6 }).map((_, index) => index);
Expand All @@ -44,9 +61,32 @@ export function ShortcutLinksList({
toggleShowTopSites,
shortcutLinks,
shouldUseListFeedLayout,
onReorder,
isManual,
}: ShortcutLinksListProps): ReactElement {
const hasShortcuts = shortcutLinks?.length > 0;

const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);

const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;

if (!over || active.id === over.id || !onReorder || !isManual) {
return;
}

const oldIndex = shortcutLinks.indexOf(active.id as string);
const newIndex = shortcutLinks.indexOf(over.id as string);

const reorderedLinks = arrayMove(shortcutLinks, oldIndex, newIndex);
onReorder(reorderedLinks);
};

const options = [
{
icon: <WrappingMenuIcon Icon={EyeIcon} />,
Expand All @@ -60,7 +100,7 @@ export function ShortcutLinksList({
},
];

return (
const content = (
<div
className={classNames(
'hidden tablet:flex',
Expand Down Expand Up @@ -109,4 +149,23 @@ export function ShortcutLinksList({
)}
</div>
);

if (hasShortcuts && isManual && onReorder) {
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={shortcutLinks}
strategy={horizontalListSortingStrategy}
>
{content}
</SortableContext>
</DndContext>
);
}

return content;
}
3 changes: 3 additions & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@
"typescript": "5.6.3"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@growthbook/growthbook": "0.26.0",
"@growthbook/growthbook-react": "^0.17.0",
"@hookform/resolvers": "5.2.2",
Expand Down
Loading