ui enhancements
This commit is contained in:
@@ -16,13 +16,24 @@
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { FolderPlus, Heart, MessageCircle, Repeat2 } from "lucide-react";
|
||||
import {
|
||||
HiOutlineArrowPath,
|
||||
HiOutlineChatBubbleOvalLeft,
|
||||
HiOutlineFolderPlus,
|
||||
HiOutlineHeart,
|
||||
} from "react-icons/hi2";
|
||||
import { toast } from "sonner";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAuthGate } from "@/components/auth/auth-gate";
|
||||
import { useMediaQuery } from "@/components/selection-toolbar/shared";
|
||||
import { CollectionsDrawer } from "@/components/selection-toolbar/collections-drawer";
|
||||
|
||||
// All four icons come from the Heroicons v2 outline family (react-icons
|
||||
// `hi2` namespace), so they share one design language — uniform stroke
|
||||
// weight, matching corner radius, matching optical sizing. No more
|
||||
// Lucide cross-icon visual drift.
|
||||
const ICON_CLASS = "size-[1.375rem]";
|
||||
|
||||
export function PostActionsFooter({ postID }: { postID: string }) {
|
||||
const { requireAuth } = useAuthGate();
|
||||
const isMobile = useMediaQuery("(max-width: 767px)");
|
||||
@@ -44,7 +55,7 @@ export function PostActionsFooter({ postID }: { postID: string }) {
|
||||
toast("Likes are coming soon", { duration: 2000 });
|
||||
})
|
||||
}
|
||||
icon={<Heart className="size-5" />}
|
||||
icon={<HiOutlineHeart className={ICON_CLASS} />}
|
||||
/>
|
||||
<ActionButton
|
||||
label="Comment"
|
||||
@@ -53,7 +64,7 @@ export function PostActionsFooter({ postID }: { postID: string }) {
|
||||
toast("Comments are coming soon", { duration: 2000 });
|
||||
})
|
||||
}
|
||||
icon={<MessageCircle className="size-5" />}
|
||||
icon={<HiOutlineChatBubbleOvalLeft className={ICON_CLASS} />}
|
||||
/>
|
||||
<ActionButton
|
||||
label="Reshare"
|
||||
@@ -62,13 +73,13 @@ export function PostActionsFooter({ postID }: { postID: string }) {
|
||||
toast("Resharing is coming soon", { duration: 2000 });
|
||||
})
|
||||
}
|
||||
icon={<Repeat2 className="size-5" />}
|
||||
icon={<HiOutlineArrowPath className={ICON_CLASS} />}
|
||||
/>
|
||||
</div>
|
||||
<ActionButton
|
||||
label="Save to collection"
|
||||
onClick={() => withAuth(() => setCollectionsOpen(true))}
|
||||
icon={<FolderPlus className="size-5" />}
|
||||
icon={<HiOutlineFolderPlus className={ICON_CLASS} />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
* always visible on touch (mobile).
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ImageIcon, Trash2 } from "lucide-react";
|
||||
@@ -248,22 +248,46 @@ function Caption({
|
||||
expanded: boolean;
|
||||
onToggle: () => void;
|
||||
}) {
|
||||
// Show 3 lines collapsed. The "more" toggle appears only when the text
|
||||
// is long enough to warrant it (a cheap char-length check beats a
|
||||
// measure-and-compare pass for our purposes — captions over ~180 chars
|
||||
// will always overflow 3 lines at the rendered font size).
|
||||
const longEnough = text.length > 180;
|
||||
// We clamp to 3 lines collapsed and expose a "More" toggle only when
|
||||
// the clamp is actually hiding something. A char-length heuristic gets
|
||||
// this wrong: a 200-char caption can fit cleanly in 3 lines on a wide
|
||||
// card (no clipping → "More" would be a false promise). Instead measure
|
||||
// scrollHeight vs clientHeight on the clamped <p>. Once we've observed
|
||||
// overflow we latch it on so "Show less" stays visible after expansion
|
||||
// (where scrollHeight would equal clientHeight and we'd otherwise lose
|
||||
// the way back).
|
||||
const pRef = useRef<HTMLParagraphElement | null>(null);
|
||||
const [overflowed, setOverflowed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (expanded) return;
|
||||
const el = pRef.current;
|
||||
if (!el) return;
|
||||
const measure = () => {
|
||||
// Sub-pixel rounding can leave a 1px gap even when content fits;
|
||||
// require a real difference before flagging overflow.
|
||||
setOverflowed(el.scrollHeight - el.clientHeight > 1);
|
||||
};
|
||||
measure();
|
||||
const ro = new ResizeObserver(measure);
|
||||
ro.observe(el);
|
||||
return () => ro.disconnect();
|
||||
}, [text, expanded]);
|
||||
|
||||
const showToggle = overflowed || expanded;
|
||||
|
||||
return (
|
||||
<div className="text-sm text-foreground leading-relaxed">
|
||||
<p
|
||||
ref={pRef}
|
||||
className={cn(
|
||||
"font-[family-name:var(--font-title)] break-words",
|
||||
!expanded && longEnough && "line-clamp-3",
|
||||
!expanded && "line-clamp-3",
|
||||
)}
|
||||
>
|
||||
“{text}”
|
||||
</p>
|
||||
{longEnough ? (
|
||||
{showToggle ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggle}
|
||||
|
||||
Reference in New Issue
Block a user