Merge pull request #831 from AnishSarkar22/fix/ui

feat: multiple UI enhancements
This commit is contained in:
Rohan Verma 2026-02-24 00:32:11 -08:00 committed by GitHub
commit 47e6a7f29e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 2176 additions and 2209 deletions

View file

@ -67,7 +67,7 @@ export function DocumentTypeChip({ type, className }: { type: string; className?
const chip = (
<span
className={`inline-flex items-center gap-1.5 rounded-full bg-accent/80 px-2.5 py-1 text-xs font-medium text-accent-foreground/70 shadow-sm max-w-full overflow-hidden ${className ?? ""}`}
className={`inline-flex items-center gap-1.5 rounded-full bg-accent/80 px-2.5 py-1 text-xs font-medium text-accent-foreground shadow-sm max-w-full overflow-hidden ${className ?? ""}`}
>
<span className="flex-shrink-0">{icon}</span>
<span ref={textRef} className="truncate min-w-0">

View file

@ -3,13 +3,13 @@
import { useSetAtom } from "jotai";
import {
CircleAlert,
CircleX,
FilePlus2,
FileType,
ListFilter,
Search,
SlidersHorizontal,
Trash,
Upload,
X,
} from "lucide-react";
import { motion } from "motion/react";
import { useTranslations } from "next-intl";
@ -81,7 +81,7 @@ export function DocumentsFilters({
return (
<motion.div
className="flex flex-col gap-4"
className="flex flex-col gap-4 select-none"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 300, damping: 30, delay: 0.1 }}
@ -96,7 +96,7 @@ export function DocumentsFilters({
size="sm"
className="h-9 gap-2 bg-white text-gray-700 border-white hover:bg-gray-50 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100"
>
<FilePlus2 size={16} />
<Upload size={16} />
<span>Upload documents</span>
</Button>
<Button
@ -126,7 +126,7 @@ export function DocumentsFilters({
<Input
id={`${id}-input`}
ref={inputRef}
className="peer h-9 w-full pl-9 pr-9 text-sm bg-background border-border/60 focus-visible:ring-1 focus-visible:ring-ring/30"
className="peer h-9 w-full pl-9 pr-9 text-sm bg-background border-border/60 focus-visible:ring-1 focus-visible:ring-ring/30 select-none focus:select-text"
value={searchValue}
onChange={(e) => onSearch(e.target.value)}
placeholder="Filter by title"
@ -135,7 +135,7 @@ export function DocumentsFilters({
/>
{Boolean(searchValue) && (
<motion.button
className="absolute inset-y-0 right-0 flex h-full w-9 items-center justify-center rounded-r-md text-muted-foreground/60 hover:text-foreground transition-colors"
className="absolute inset-y-0 right-0 flex h-full w-9 items-center justify-center rounded-r-md text-muted-foreground hover:text-foreground transition-colors"
aria-label="Clear filter"
onClick={() => {
onSearch("");
@ -147,7 +147,7 @@ export function DocumentsFilters({
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
<CircleX size={14} strokeWidth={2} aria-hidden="true" />
<X size={14} strokeWidth={2} aria-hidden="true" />
</motion.button>
)}
</motion.div>

View file

@ -336,7 +336,7 @@ export function DocumentsTableShell({
return (
<motion.div
className="rounded-lg border border-border/40 bg-background overflow-hidden"
className="rounded-lg border border-border/40 bg-background overflow-hidden select-none"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 300, damping: 30, delay: 0.2 }}
@ -453,7 +453,7 @@ export function DocumentsTableShell({
) : error ? (
<div className="flex h-[50vh] w-full items-center justify-center">
<div className="flex flex-col items-center gap-3">
<AlertCircle className="h-8 w-8 text-destructive/60" />
<AlertCircle className="h-8 w-8 text-destructive" />
<p className="text-sm text-destructive">{t("error_loading")}</p>
</div>
</div>
@ -482,7 +482,7 @@ export function DocumentsTableShell({
</div>
) : (
<>
{/* Desktop Table View - Notion Style */}
{/* Desktop Table View */}
<div className="hidden md:flex md:flex-col">
{/* Fixed Header */}
<Table className="table-fixed w-full">
@ -629,7 +629,24 @@ export function DocumentsTableShell({
)}
{columnVisibility.created_by && (
<TableCell className="w-36 py-2.5 text-sm text-foreground truncate border-r border-border/40">
{doc.created_by_name || "—"}
{doc.created_by_name ? (
doc.created_by_email ? (
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-default truncate block">
{doc.created_by_name}
</span>
</TooltipTrigger>
<TooltipContent side="top" align="start">
{doc.created_by_email}
</TooltipContent>
</Tooltip>
) : (
<span className="truncate block">{doc.created_by_name}</span>
)
) : (
<span className="truncate block">{doc.created_by_email || "—"}</span>
)}
</TableCell>
)}
{columnVisibility.created_at && (
@ -765,11 +782,11 @@ export function DocumentsTableShell({
{/* Document Content Viewer - lazy loads content on-demand */}
<Dialog open={!!viewingDoc} onOpenChange={(open) => !open && handleCloseViewer()}>
<DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogContent className="max-w-4xl max-h-[80vh] flex flex-col overflow-hidden pb-0">
<DialogHeader className="flex-shrink-0">
<DialogTitle>{viewingDoc?.title}</DialogTitle>
</DialogHeader>
<div className="mt-4">
<div className="mt-4 overflow-y-auto flex-1 min-h-0 px-6 select-text">
{viewingLoading ? (
<div className="flex items-center justify-center py-12">
<Spinner size="lg" className="text-muted-foreground" />

View file

@ -30,7 +30,7 @@ export function PaginationControls({
return (
<motion.div
className="flex items-center justify-end gap-3 py-3 px-2"
className="flex items-center justify-end gap-3 py-3 px-2 select-none"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 300, damping: 30, delay: 0.3 }}

View file

@ -16,6 +16,7 @@ export type Document = {
search_space_id: number;
created_by_id?: string | null;
created_by_name?: string | null;
created_by_email?: string | null;
status?: DocumentStatus;
};

View file

@ -115,6 +115,7 @@ export default function DocumentsTable() {
title: item.title,
created_by_id: item.created_by_id ?? null,
created_by_name: item.created_by_name ?? null,
created_by_email: item.created_by_email ?? null,
created_at: item.created_at,
status: (
item as {

View file

@ -29,7 +29,6 @@ import {
ChevronRight,
ChevronUp,
CircleAlert,
CircleX,
Clock,
Columns3,
Filter,
@ -741,7 +740,7 @@ function LogsFilters({
inputRef.current?.focus();
}}
>
<CircleX size={16} strokeWidth={2} />
<X size={16} strokeWidth={2} />
</Button>
)}
</motion.div>

View file

@ -75,7 +75,7 @@ export default function MorePagesPage() {
const allCompleted = data?.tasks.every((t) => t.completed) ?? false;
return (
<div className="flex min-h-[calc(100vh-64px)] items-center justify-center px-4 py-8">
<div className="flex min-h-[calc(100vh-64px)] select-none items-center justify-center px-4 py-8">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
@ -174,7 +174,7 @@ export default function MorePagesPage() {
Contact Us
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogContent className="select-none sm:max-w-md">
<DialogHeader>
<DialogTitle>Contact Us</DialogTitle>
<DialogDescription>Schedule a meeting or send us an email.</DialogDescription>

View file

@ -259,7 +259,7 @@ export default function OnboardPage() {
You can add more configurations and customize settings anytime in{" "}
<button
type="button"
onClick={() => router.push(`/dashboard/${searchSpaceId}/settings`)}
onClick={() => router.push(`/dashboard/${searchSpaceId}/settings?section=general`)}
className="text-violet-500 hover:underline"
>
Settings

View file

@ -12,10 +12,11 @@ import {
Menu,
MessageSquare,
Settings,
Shield,
X,
} from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useParams, useRouter } from "next/navigation";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useState } from "react";
import { PublicChatSnapshotsManager } from "@/components/public-chat-snapshots/public-chat-snapshots-manager";
@ -24,6 +25,7 @@ import { ImageModelManager } from "@/components/settings/image-model-manager";
import { LLMRoleManager } from "@/components/settings/llm-role-manager";
import { ModelConfigManager } from "@/components/settings/model-config-manager";
import { PromptConfigManager } from "@/components/settings/prompt-config-manager";
import { RolesManager } from "@/components/settings/roles-manager";
import { Button } from "@/components/ui/button";
import { trackSettingsViewed } from "@/lib/posthog/events";
import { cn } from "@/lib/utils";
@ -72,6 +74,12 @@ const settingsNavItems: SettingsNavItem[] = [
descriptionKey: "nav_public_links_desc",
icon: Globe,
},
{
id: "team-roles",
labelKey: "nav_team_roles",
descriptionKey: "nav_team_roles_desc",
icon: Shield,
},
];
function SettingsSidebar({
@ -240,7 +248,7 @@ function SettingsContent({
{/* Section Header */}
<AnimatePresence mode="wait">
<motion.div
key={activeSection + "-header"}
key={`${activeSection}-header`}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
@ -298,6 +306,7 @@ function SettingsContent({
{activeSection === "public-links" && (
<PublicChatSnapshotsManager searchSpaceId={searchSpaceId} />
)}
{activeSection === "team-roles" && <RolesManager searchSpaceId={searchSpaceId} />}
</motion.div>
</AnimatePresence>
</div>
@ -306,14 +315,27 @@ function SettingsContent({
);
}
const VALID_SECTIONS = new Set(settingsNavItems.map((item) => item.id));
const DEFAULT_SECTION = "general";
export default function SettingsPage() {
const router = useRouter();
const params = useParams();
const searchParams = useSearchParams();
const searchSpaceId = Number(params.search_space_id);
const [activeSection, setActiveSection] = useState("general");
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
// Track settings section view
const sectionParam = searchParams.get("section");
const activeSection =
sectionParam && VALID_SECTIONS.has(sectionParam) ? sectionParam : DEFAULT_SECTION;
const handleSectionChange = useCallback(
(section: string) => {
router.replace(`/dashboard/${searchSpaceId}/settings?section=${section}`, { scroll: false });
},
[router, searchSpaceId]
);
useEffect(() => {
trackSettingsViewed(searchSpaceId, activeSection);
}, [searchSpaceId, activeSection]);
@ -333,7 +355,7 @@ export default function SettingsPage() {
<div className="flex h-full w-full overflow-hidden bg-background md:rounded-xl md:border md:shadow-sm">
<SettingsSidebar
activeSection={activeSection}
onSectionChange={setActiveSection}
onSectionChange={handleSectionChange}
onBackToApp={handleBackToApp}
isOpen={isSidebarOpen}
onClose={() => setIsSidebarOpen(false)}

File diff suppressed because it is too large Load diff