diff --git a/surfsense_web/app/preview/chat-comments/page.tsx b/surfsense_web/app/preview/chat-comments/page.tsx new file mode 100644 index 000000000..eb6be16ad --- /dev/null +++ b/surfsense_web/app/preview/chat-comments/page.tsx @@ -0,0 +1,127 @@ +"use client"; + +import { useState } from "react"; +import { MemberMentionPicker } from "@/components/chat-comments/member-mention-picker"; +import type { MemberOption } from "@/components/chat-comments/member-mention-picker"; + +const fakeMembersData: MemberOption[] = [ + { + id: "550e8400-e29b-41d4-a716-446655440001", + displayName: "Alice Smith", + email: "alice@example.com", + avatarUrl: null, + }, + { + id: "550e8400-e29b-41d4-a716-446655440002", + displayName: "Bob Johnson", + email: "bob.johnson@example.com", + avatarUrl: null, + }, + { + id: "550e8400-e29b-41d4-a716-446655440003", + displayName: "Charlie Brown", + email: "charlie@example.com", + avatarUrl: null, + }, + { + id: "550e8400-e29b-41d4-a716-446655440004", + displayName: null, + email: "david.wilson@example.com", + avatarUrl: null, + }, + { + id: "550e8400-e29b-41d4-a716-446655440005", + displayName: "Emma Davis", + email: "emma@example.com", + avatarUrl: null, + }, +]; + +export default function ChatCommentsPreviewPage() { + const [highlightedIndex, setHighlightedIndex] = useState(0); + const [selectedMember, setSelectedMember] = useState(null); + + return ( +
+
+
+

Chat Comments UI Preview

+

+ Preview page for chat comments components with fake data +

+
+ +
+
+

After typing @

+

Shows all members

+
+ setSelectedMember(member)} + onHighlightChange={setHighlightedIndex} + /> +
+ {selectedMember && ( +
+ Selected: + + @[{selectedMember.id.slice(0, 8)}...] + + + {" → @"} + {selectedMember.displayName || selectedMember.email} + +
+ )} +
+ +
+

After typing @ali

+

Filtered to matching members

+
+ {}} + onHighlightChange={() => {}} + /> +
+
+ +
+

Loading State

+

While fetching members

+
+ {}} + onHighlightChange={() => {}} + /> +
+
+ +
+

No Results

+

After typing @xyz (no match)

+
+ {}} + onHighlightChange={() => {}} + /> +
+
+
+
+
+ ); +} diff --git a/surfsense_web/components/chat-comments/member-mention-picker/index.ts b/surfsense_web/components/chat-comments/member-mention-picker/index.ts new file mode 100644 index 000000000..45df9b18f --- /dev/null +++ b/surfsense_web/components/chat-comments/member-mention-picker/index.ts @@ -0,0 +1,4 @@ +export { MemberMentionPicker } from "./member-mention-picker"; +export { MemberMentionItem } from "./member-mention-item"; +export type { MemberOption, MemberMentionPickerProps, MemberMentionItemProps } from "./types"; + diff --git a/surfsense_web/components/chat-comments/member-mention-picker/member-mention-item.tsx b/surfsense_web/components/chat-comments/member-mention-picker/member-mention-item.tsx new file mode 100644 index 000000000..c2e85b892 --- /dev/null +++ b/surfsense_web/components/chat-comments/member-mention-picker/member-mention-item.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { cn } from "@/lib/utils"; +import type { MemberMentionItemProps } from "./types"; + +function getInitials(name: string, email: string): string { + if (name) { + return name + .split(" ") + .map((part) => part[0]) + .join("") + .toUpperCase() + .slice(0, 2); + } + return email[0].toUpperCase(); +} + +export function MemberMentionItem({ + member, + isHighlighted, + onSelect, + onMouseEnter, +}: MemberMentionItemProps) { + const displayName = member.displayName || member.email.split("@")[0]; + + return ( + + ); +} + diff --git a/surfsense_web/components/chat-comments/member-mention-picker/member-mention-picker.tsx b/surfsense_web/components/chat-comments/member-mention-picker/member-mention-picker.tsx new file mode 100644 index 000000000..7cc6073eb --- /dev/null +++ b/surfsense_web/components/chat-comments/member-mention-picker/member-mention-picker.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { Loader2 } from "lucide-react"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { MemberMentionItem } from "./member-mention-item"; +import type { MemberMentionPickerProps } from "./types"; + +export function MemberMentionPicker({ + members, + query, + highlightedIndex, + isLoading = false, + onSelect, + onHighlightChange, +}: MemberMentionPickerProps) { + const filteredMembers = query + ? members.filter( + (member) => + member.displayName?.toLowerCase().includes(query.toLowerCase()) || + member.email.toLowerCase().includes(query.toLowerCase()) + ) + : members; + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (filteredMembers.length === 0) { + return ( +
+ {query ? "No members found" : "No members available"} +
+ ); + } + + return ( + +
+ {filteredMembers.map((member, index) => ( + onHighlightChange(index)} + /> + ))} +
+
+ ); +} + diff --git a/surfsense_web/components/chat-comments/member-mention-picker/types.ts b/surfsense_web/components/chat-comments/member-mention-picker/types.ts new file mode 100644 index 000000000..6a653a196 --- /dev/null +++ b/surfsense_web/components/chat-comments/member-mention-picker/types.ts @@ -0,0 +1,23 @@ +export interface MemberOption { + id: string; + displayName: string; + email: string; + avatarUrl?: string | null; +} + +export interface MemberMentionPickerProps { + members: MemberOption[]; + query: string; + highlightedIndex: number; + isLoading?: boolean; + onSelect: (member: MemberOption) => void; + onHighlightChange: (index: number) => void; +} + +export interface MemberMentionItemProps { + member: MemberOption; + isHighlighted: boolean; + onSelect: (member: MemberOption) => void; + onMouseEnter: () => void; +} +