Skip to content

Commit

Permalink
General layout improvements, sub menu is now hybrid, edit and delete …
Browse files Browse the repository at this point in the history
…are easy access and other options moved to the dropdown
  • Loading branch information
jordan-dalby committed Nov 14, 2024
1 parent ba7e2a1 commit e2824d9
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 89 deletions.
10 changes: 5 additions & 5 deletions client/src/components/common/modals/ConfirmationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ export const ConfirmationModal: React.FC<ConfirmationModalProps> = ({

return (
<Modal isOpen={isOpen} onClose={onClose} title={title}>
<div className="p-6">
<p className="text-gray-300 mb-6">{message}</p>
<div className="flex justify-end space-x-4">
<div className="px-0.5 pt-1 pb-3">
<p className="text-gray-300 mb-4">{message}</p>
<div className="flex justify-end gap-2">
<button
onClick={onClose}
className="px-4 py-2 bg-gray-700 text-white rounded-md hover:bg-gray-600 transition-colors"
className="px-3 py-1.5 bg-gray-700 text-white rounded-md hover:bg-gray-600 transition-colors"
>
{cancelLabel}
</button>
<button
onClick={onConfirm}
className={`px-4 py-2 text-white rounded-md transition-colors ${variantClasses[variant]}`}
className={`px-3 py-1.5 text-white rounded-md transition-colors ${variantClasses[variant]}`}
>
{confirmLabel}
</button>
Expand Down
30 changes: 21 additions & 9 deletions client/src/components/snippets/list/SnippetCard.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, { useState } from 'react';
import { Clock, Users, FileCode, ChevronLeft, ChevronRight } from 'lucide-react';
import { dateUtils } from '../../../utils/helpers/dateUtils';
import { SnippetCardMenu } from './SnippetCardMenu';
import SnippetCardMenu from './SnippetCardMenu';
import { ConfirmationModal } from '../../common/modals/ConfirmationModal';
import { Snippet } from '../../../types/snippets';
import CategoryList from '../../categories/CategoryList';
import { PreviewCodeBlock } from '../../editor/PreviewCodeBlock';
import Linkify from 'linkify-react';
import { linkifyOptions } from '../../../constants/linkify';
import { formatDistanceToNow } from 'date-fns';

interface SnippetCardProps {
snippet: Snippet;
Expand Down Expand Up @@ -43,6 +43,16 @@ export const SnippetCard: React.FC<SnippetCardProps> = ({
const [currentFragmentIndex, setCurrentFragmentIndex] = useState(0);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);

const getRelativeUpdateTime = (updatedAt: string): string => {
try {
const updateDate = new Date(updatedAt);
return `Updated ${formatDistanceToNow(updateDate)} ago`;
} catch (error) {
console.error('Error formatting update date:', error);
return 'Unknown update time';
}
};

const handleDeleteConfirm = () => {
onDelete(snippet.id);
setIsDeleteModalOpen(false);
Expand Down Expand Up @@ -91,7 +101,7 @@ export const SnippetCard: React.FC<SnippetCardProps> = ({
<div className="flex items-center gap-3 mt-1 text-sm text-gray-400">
<div className="flex items-center gap-1 text-xs text-gray-500 whitespace-nowrap">
<Clock size={12} />
<span>Updated {dateUtils.formatRelativeTime(snippet.updated_at)}</span>
<span>{getRelativeUpdateTime(snippet.updated_at)}</span>
</div>
{(snippet.share_count || 0) > 0 && (
<div className="flex items-center gap-1 text-xs text-blue-400">
Expand All @@ -102,12 +112,14 @@ export const SnippetCard: React.FC<SnippetCardProps> = ({
</div>
</div>

<SnippetCardMenu
onEdit={() => onEdit(snippet)}
onDelete={() => setIsDeleteModalOpen(true)}
onShare={() => onShare(snippet)}
onOpenInNewTab={handleOpenInNewTab}
/>
<div className="opacity-0 group-hover:opacity-100 transition-opacity">
<SnippetCardMenu
onEdit={() => onEdit(snippet)}
onDelete={() => setIsDeleteModalOpen(true)}
onShare={() => onShare(snippet)}
onOpenInNewTab={handleOpenInNewTab}
/>
</div>
</div>

{!compactView && (
Expand Down
130 changes: 73 additions & 57 deletions client/src/components/snippets/list/SnippetCardMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useRef, useState } from 'react';
import { MoreVertical, Share, Pencil, Trash2, ExternalLink } from 'lucide-react';
import { useOutsideClick } from '../../../hooks/useOutsideClick';
import React, { useState, useRef } from 'react';
import { Share, Pencil, Trash2, ExternalLink, MoreVertical } from 'lucide-react';
import { IconButton } from '../../common/buttons/IconButton';
import { useOutsideClick } from '../../../hooks/useOutsideClick';

interface SnippetCardMenuProps {
onEdit: () => void;
Expand All @@ -10,74 +10,90 @@ interface SnippetCardMenuProps {
onOpenInNewTab: () => void;
}

export const SnippetCardMenu: React.FC<SnippetCardMenuProps> = ({
const SnippetCardMenu: React.FC<SnippetCardMenuProps> = ({
onEdit,
onDelete,
onShare,
onOpenInNewTab
}) => {
const [isOpen, setIsOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);

useOutsideClick(menuRef, () => setIsOpen(false), [buttonRef]);

const handleItemClick = (handler: () => void) => {
setIsOpen(false);
handler();
};

const handleMenuToggle = (e: React.MouseEvent) => {
e.stopPropagation();
setIsOpen(!isOpen);
};
useOutsideClick(dropdownRef, () => setIsDropdownOpen(false), [buttonRef]);

return (
<div className="relative" onClick={(e) => e.stopPropagation()}>
<div className="top-4 right-4 flex items-center gap-1">
{/* Primary Actions - Always Visible */}
<IconButton
icon={<Pencil size={16} />}
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
onEdit();
}}
variant="custom"
size="sm"
className="bg-gray-700 hover:bg-gray-600"
/>
<IconButton
ref={buttonRef}
icon={<MoreVertical size={16} />}
onClick={handleMenuToggle}
icon={<Trash2 size={16} className="hover:text-red-500" />}
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
onDelete();
}}
variant="custom"
size="sm"
className="opacity-0 group-hover:opacity-100 bg-gray-700 hover:bg-gray-600"
className="bg-gray-700 hover:bg-gray-600"
/>

{isOpen && (
<div
ref={menuRef}
className="absolute right-0 top-8 w-48 bg-gray-800 rounded-md shadow-lg border border-gray-700 py-1 z-50"
>
<button
onClick={() => handleItemClick(onOpenInNewTab)}
className="w-full px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 flex items-center gap-2"
>
<ExternalLink size={16} className="text-gray-400" />
Open in new tab
</button>
<button
onClick={() => handleItemClick(onShare)}
className="w-full px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 flex items-center gap-2"
{/* More Actions Dropdown */}
<div className="relative">
<IconButton
ref={buttonRef}
icon={<MoreVertical size={16} />}
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
setIsDropdownOpen(!isDropdownOpen);
}}
variant="custom"
size="sm"
className="bg-gray-700 hover:bg-gray-600"
/>

{isDropdownOpen && (
<div
ref={dropdownRef}
onMouseLeave={() => setIsDropdownOpen(false)}
className="absolute right-0 top-full mt-1 w-48 bg-gray-800 rounded-md shadow-lg
border border-gray-700 py-1 z-50"
>
<Share size={16} className="text-gray-400" />
Share
</button>
<button
onClick={() => handleItemClick(onEdit)}
className="w-full px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 flex items-center gap-2"
>
<Pencil size={16} className="text-gray-400" />
Edit
</button>
<button
onClick={() => handleItemClick(onDelete)}
className="w-full px-4 py-2 text-sm text-red-500 hover:bg-gray-700 flex items-center gap-2"
>
<Trash2 size={16} />
Delete
</button>
</div>
)}
<button
onClick={(e) => {
e.stopPropagation();
onOpenInNewTab();
setIsDropdownOpen(false);
}}
className="w-full px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 flex items-center gap-2"
>
<ExternalLink size={16} className="text-gray-400" />
Open in new tab
</button>
<button
onClick={(e) => {
e.stopPropagation();
onShare();
setIsDropdownOpen(false);
}}
className="w-full px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 flex items-center gap-2"
>
<Share size={16} />
Share snippet
</button>
</div>
)}
</div>
</div>
);
};
};

export default SnippetCardMenu;
18 changes: 0 additions & 18 deletions client/src/utils/helpers/dateUtils.ts

This file was deleted.

0 comments on commit e2824d9

Please sign in to comment.