implemented project searching and added safe area margins

This commit is contained in:
corgifist 2025-07-25 17:10:03 +03:00
parent 38973ff7d3
commit 535fd68197
8 changed files with 55 additions and 12 deletions

29
package-lock.json generated
View File

@ -35,6 +35,8 @@
"react-hook-form": "^7.60.0",
"sonner": "^2.0.6",
"tailwind-merge": "^3.3.1",
"tailwindcss-safe-area": "^1.0.0",
"use-debounce": "^10.0.5",
"zod": "^4.0.5"
},
"devDependencies": {
@ -6870,6 +6872,21 @@
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"license": "MIT"
},
"node_modules/tailwindcss-safe-area": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tailwindcss-safe-area/-/tailwindcss-safe-area-1.0.0.tgz",
"integrity": "sha512-cg4eFaTe4dFKZ4H6Vrobxqen6Jki/sSvssmHEYX5rFu1IojwGjTmab/eWzzN2gVrtMSIJoYSMS49p4TjBnWBeA==",
"license": "MIT",
"engines": {
"node": ">=22"
},
"funding": {
"url": "https://github.com/sponsors/mvllow"
},
"peerDependencies": {
"tailwindcss": "^4.0.0"
}
},
"node_modules/tapable": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
@ -7195,6 +7212,18 @@
}
}
},
"node_modules/use-debounce": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.5.tgz",
"integrity": "sha512-Q76E3lnIV+4YT9AHcrHEHYmAd9LKwUAbPXDm7FlqVGDHiSOhX3RDjT8dm0AxbJup6WgOb1YEcKyCr11kBJR5KQ==",
"license": "MIT",
"engines": {
"node": ">= 16.0.0"
},
"peerDependencies": {
"react": "*"
}
},
"node_modules/use-sidecar": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",

View File

@ -36,6 +36,8 @@
"react-hook-form": "^7.60.0",
"sonner": "^2.0.6",
"tailwind-merge": "^3.3.1",
"tailwindcss-safe-area": "^1.0.0",
"use-debounce": "^10.0.5",
"zod": "^4.0.5"
},
"devDependencies": {

View File

@ -1,6 +1,6 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "tailwindcss-safe-area";
@custom-variant dark (&:is(.dark *));
@theme inline {
@ -47,6 +47,7 @@ body {
font-family: var(--font-geist), sans-serif;
background-color: var(--background);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);

View File

@ -1,4 +1,4 @@
import type { Metadata } from "next";
import type { Metadata, Viewport } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { ReactNode } from "react";
import { SidebarProvider } from "@/components/ui/sidebar";
@ -24,6 +24,15 @@ export const metadata: Metadata = {
description: "Desktop power right in your browser",
};
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
viewportFit: 'cover',
themeColor: [
{ media: "(prefers-color-scheme: dark)", color: "#0a0a0a" },
{ media: "(prefers-color-scheme: light)", color: "#ffffff" }
]
};
export default async function RootLayout({
children,
@ -35,7 +44,6 @@ export default async function RootLayout({
<head>
<meta name="apple-mobile-web-app-title" content="ClipFusion" />
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="HandheldFriendly" content="true" />
<Analytics/>
</head>
<body className={`${geist.variable} ${geistMono.variable} antialiased`}>

View File

@ -5,7 +5,7 @@ import { useLiveQuery } from "dexie-react-hooks";
import { db } from "@/lib/db";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { CopyIcon, EditIcon, EllipsisIcon, InfoIcon, ListCheckIcon, PencilIcon, PlusIcon, TrashIcon } from "lucide-react";
import { CheckCheckIcon, CopyIcon, EditIcon, EllipsisIcon, InfoIcon, ListCheckIcon, PencilIcon, PlusIcon, TrashIcon } from "lucide-react";
import { Toggle } from "@/components/ui/toggle";
import Search from "@/components/search";
import { useIsMobile } from "@/hooks/use-mobile";
@ -23,6 +23,7 @@ import { generateUUID } from "@/lib/uuid";
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import { useDebounce } from "use-debounce";
const ProjectInfoFormSchema = z.object({
title: z.string().nonempty("Title cannot be empty"),
@ -118,6 +119,7 @@ const DeleteProjectDialog = ({ project }: { project: Project }) => {
const ProjectDropdown = ({ project }: { project: Project }): ReactNode => {
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
const [deleteAlertOpen, setDeleteAlertOpen] = useState(false);
const isMobile = useIsMobile();
const handleDuplicate = async () => {
const newProject = {
@ -138,7 +140,7 @@ const ProjectDropdown = ({ project }: { project: Project }): ReactNode => {
<EllipsisIcon/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="min-w-48">
<DropdownMenuContent align={isMobile ? "end" : "start"} className="min-w-48">
<div className="flex flex-row items-center justify-between">
<DropdownMenuLabel className="font-semibold">{project.title}</DropdownMenuLabel>
<DropdownMenuItem onClick={() => setRenameDialogOpen(true)}>
@ -221,10 +223,11 @@ const ProjectContainer = ({
export default function Home(): ReactNode {
const isMobile = useIsMobile();
const [search, setSearch] = useState('');
const [debouncedSearch] = useDebounce(search, 300);
const projects = useLiveQuery(() => {
return db.projects.filter((project) => project.title.includes(search)).toArray();
});
const projects = useLiveQuery(() => (
db.projects.filter((project) => project.title.toLowerCase().includes(debouncedSearch.toLowerCase())).toArray()
), [debouncedSearch]);
const newProjectForm = useForm<z.infer<typeof ProjectInfoFormSchema>>({
resolver: zodResolver(ProjectInfoFormSchema),

View File

@ -33,7 +33,7 @@ const items: DashboardItem[] = [
export const Dashboard = (): ReactNode => {
return (
<Sidebar>
<SidebarHeader className="flex justify-center items-center mt-2">
<SidebarHeader className="flex justify-center items-center mt-safe-or-2 ml-safe">
<Link href="/">
<ClipFusionLogo width="30" height="30">
<p className="font-bold text-xl select-none">ClipFusion</p>
@ -84,7 +84,7 @@ export const Dashboard = (): ReactNode => {
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarFooter className="mb-safe">
<SidebarGroup>
<SidebarGroupLabel>
Links

View File

@ -7,7 +7,7 @@ export const Search = (props: ComponentProps<typeof Input>): ReactNode => (
<Input
type="text"
placeholder="Search"
className="peer block w-full rounded-md border py-[9px] pl-10 text-sm"
className="peer block w-full rounded-md border py-[9px] pl-10"
{...props}
/>
<SearchIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2" />

View File

@ -1,5 +1,5 @@
module.exports = {
plugins: [
require("@tailwindcss/line-clamp"),
require("@tailwindcss/line-clamp")
]
};