diff options
Diffstat (limited to 'apps/web')
3 files changed, 105 insertions, 48 deletions
diff --git a/apps/web/components/dashboard/highlights/HighlightCard.tsx b/apps/web/components/dashboard/highlights/HighlightCard.tsx index 1bba0b47..51421e0f 100644 --- a/apps/web/components/dashboard/highlights/HighlightCard.tsx +++ b/apps/web/components/dashboard/highlights/HighlightCard.tsx @@ -44,16 +44,24 @@ export default function HighlightCard({ }); }; - const Wrapper = ({ children }: { children: React.ReactNode }) => + const Wrapper = ({ + className, + children, + }: { + className?: string; + children: React.ReactNode; + }) => clickable ? ( - <button onClick={onBookmarkClick}>{children}</button> + <button className={className} onClick={onBookmarkClick}> + {children} + </button> ) : ( - <div>{children}</div> + <div className={className}>{children}</div> ); return ( <div className={cn("flex items-center justify-between", className)}> - <Wrapper> + <Wrapper className="flex flex-col gap-2 text-left"> <blockquote cite={highlight.bookmarkId} className={cn( @@ -63,6 +71,11 @@ export default function HighlightCard({ > <p>{highlight.text}</p> </blockquote> + {highlight.note && ( + <span className="text-sm text-muted-foreground"> + {highlight.note} + </span> + )} </Wrapper> {!readOnly && ( <div className="flex gap-2"> diff --git a/apps/web/components/dashboard/preview/BookmarkHtmlHighlighter.tsx b/apps/web/components/dashboard/preview/BookmarkHtmlHighlighter.tsx index e0f20ea2..63098ac0 100644 --- a/apps/web/components/dashboard/preview/BookmarkHtmlHighlighter.tsx +++ b/apps/web/components/dashboard/preview/BookmarkHtmlHighlighter.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import { ActionButton } from "@/components/ui/action-button"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent } from "@/components/ui/popover"; +import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { PopoverAnchor } from "@radix-ui/react-popover"; import { Check, Trash2 } from "lucide-react"; @@ -13,23 +14,38 @@ import { import { HIGHLIGHT_COLOR_MAP } from "./highlights"; -interface ColorPickerMenuProps { +interface HighlightFormProps { position: { x: number; y: number } | null; - onColorSelect: (color: ZHighlightColor) => void; - onDelete?: () => void; selectedHighlight: Highlight | null; onClose: () => void; + onSave: (color: ZHighlightColor, note: string | null) => void; + onDelete?: () => void; isMobile: boolean; } -const ColorPickerMenu: React.FC<ColorPickerMenuProps> = ({ +const HighlightForm: React.FC<HighlightFormProps> = ({ position, - onColorSelect, - onDelete, selectedHighlight, onClose, + onSave, + onDelete, isMobile, }) => { + const [selectedColor, setSelectedColor] = useState<ZHighlightColor>( + selectedHighlight?.color || "yellow", + ); + const [noteText, setNoteText] = useState(selectedHighlight?.note || ""); + + // Update state when selectedHighlight changes + useEffect(() => { + setSelectedColor(selectedHighlight?.color || "yellow"); + setNoteText(selectedHighlight?.note || ""); + }, [selectedHighlight]); + + const handleSave = () => { + onSave(selectedColor, noteText || null); + }; + return ( <Popover open={position !== null} @@ -48,35 +64,59 @@ const ColorPickerMenu: React.FC<ColorPickerMenuProps> = ({ /> <PopoverContent side={isMobile ? "bottom" : "top"} - className="flex w-fit items-center gap-1 p-2" + className="w-80 space-y-3 p-3" > - {SUPPORTED_HIGHLIGHT_COLORS.map((color) => ( - <Button - size="none" - key={color} - onClick={() => onColorSelect(color)} - variant="none" - className={cn( - `size-8 rounded-full hover:border focus-visible:ring-0`, - HIGHLIGHT_COLOR_MAP.bg[color], - )} - > - {selectedHighlight?.color === color && ( - <Check className="size-5 text-gray-600" /> - )} - </Button> - ))} - {selectedHighlight && ( - <ActionButton - loading={false} - size="none" - className="size-8 rounded-full" - onClick={onDelete} - variant="ghost" - > - <Trash2 className="size-5 text-destructive" /> - </ActionButton> - )} + <div> + <label className="mb-2 block text-sm font-medium">Color</label> + <div className="flex items-center gap-1"> + {SUPPORTED_HIGHLIGHT_COLORS.map((color) => ( + <Button + size="none" + key={color} + onClick={() => setSelectedColor(color)} + variant="none" + className={cn( + `size-8 rounded-full hover:border focus-visible:ring-0`, + HIGHLIGHT_COLOR_MAP.bg[color], + )} + > + {selectedColor === color && ( + <Check className="size-5 text-gray-600" /> + )} + </Button> + ))} + </div> + </div> + <div> + <label className="mb-2 block text-sm font-medium">Note</label> + <Textarea + placeholder="Add a note (optional)..." + value={noteText} + onChange={(e) => setNoteText(e.target.value)} + className="min-h-[80px] text-sm" + /> + </div> + <div className="flex items-center justify-between gap-2"> + <div className="flex gap-2"> + <Button onClick={handleSave} size="sm"> + Save + </Button> + <Button onClick={onClose} variant="outline" size="sm"> + Cancel + </Button> + </div> + {selectedHighlight && onDelete && ( + <ActionButton + loading={false} + size="sm" + onClick={onDelete} + variant="ghost" + title="Delete highlight" + > + <Trash2 className="size-4 text-destructive" /> + </ActionButton> + )} + </div> </PopoverContent> </Popover> ); @@ -88,6 +128,7 @@ export interface Highlight { endOffset: number; color: ZHighlightColor; text: string | null; + note?: string | null; } interface HTMLHighlighterProps { @@ -221,18 +262,20 @@ function BookmarkHTMLHighlighter({ setPendingHighlight(createHighlightFromRange(range, "yellow")); }; - const handleColorSelect = (color: ZHighlightColor) => { + const handleSave = (color: ZHighlightColor, note: string | null) => { if (pendingHighlight) { pendingHighlight.color = color; + pendingHighlight.note = note; onHighlight?.(pendingHighlight); } else if (selectedHighlight) { selectedHighlight.color = color; + selectedHighlight.note = note; onUpdateHighlight?.(selectedHighlight); } - closeColorPicker(); + closeForm(); }; - const closeColorPicker = () => { + const closeForm = () => { setMenuPosition(null); setPendingHighlight(null); setSelectedHighlight(null); @@ -242,7 +285,7 @@ function BookmarkHTMLHighlighter({ const handleDelete = () => { if (selectedHighlight && onDeleteHighlight) { onDeleteHighlight(selectedHighlight); - closeColorPicker(); + closeForm(); } }; @@ -355,12 +398,12 @@ function BookmarkHTMLHighlighter({ className={className} style={style} /> - <ColorPickerMenu + <HighlightForm position={menuPosition} - onColorSelect={handleColorSelect} - onDelete={handleDelete} - selectedHighlight={selectedHighlight} - onClose={closeColorPicker} + selectedHighlight={selectedHighlight || pendingHighlight} + onClose={closeForm} + onSave={handleSave} + onDelete={selectedHighlight ? handleDelete : undefined} isMobile={isMobile} /> </div> diff --git a/apps/web/components/dashboard/preview/ReaderView.tsx b/apps/web/components/dashboard/preview/ReaderView.tsx index 1974626a..f2f843ee 100644 --- a/apps/web/components/dashboard/preview/ReaderView.tsx +++ b/apps/web/components/dashboard/preview/ReaderView.tsx @@ -105,6 +105,7 @@ export default function ReaderView({ updateHighlight({ highlightId: h.id, color: h.color, + note: h.note, }) } onHighlight={(h) => @@ -114,7 +115,7 @@ export default function ReaderView({ color: h.color, bookmarkId, text: h.text, - note: null, + note: h.note ?? null, }) } /> |
