feat(search): enhance Search component with shortcuts and item modal
- Added functionality to fetch and display global shortcuts in the Search component. - Introduced ItemModal for displaying detailed information about selected items. - Updated SearchCard components to improve layout and information presentation. - Enhanced user experience with better styling and accessibility features.
This commit is contained in:
Binary file not shown.
@ -1,44 +1,46 @@
|
|||||||
{
|
{
|
||||||
"name": "haystack",
|
"name": "haystack",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Screenshots that organize themselves",
|
"description": "Screenshots that organize themselves",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"lint": "bunx @biomejs/biome lint .",
|
"lint": "bunx @biomejs/biome lint .",
|
||||||
"format": "bunx @biomejs/biome format . --write"
|
"format": "bunx @biomejs/biome format . --write"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "^0.13.9",
|
"@kobalte/core": "^0.13.9",
|
||||||
"@kobalte/tailwindcss": "^0.9.0",
|
"@kobalte/tailwindcss": "^0.9.0",
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"@tabler/icons-solidjs": "^3.30.0",
|
"@tabler/icons-solidjs": "^3.30.0",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-dialog": "~2",
|
"@tauri-apps/plugin-dialog": "~2",
|
||||||
"@tauri-apps/plugin-http": "~2",
|
"@tauri-apps/plugin-http": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"solid-js": "^1.9.3",
|
"solid-js": "^1.9.3",
|
||||||
"solid-motionone": "^1.0.3",
|
"solid-markdown": "^2.0.14",
|
||||||
"tailwind-scrollbar-hide": "^2.0.0",
|
"solid-motionone": "^1.0.3",
|
||||||
"valibot": "^1.0.0-rc.2"
|
"solidjs-markdown": "^0.2.0",
|
||||||
},
|
"tailwind-scrollbar-hide": "^2.0.0",
|
||||||
"devDependencies": {
|
"valibot": "^1.0.0-rc.2"
|
||||||
"@biomejs/biome": "^1.9.4",
|
},
|
||||||
"@tauri-apps/cli": "^2",
|
"devDependencies": {
|
||||||
"autoprefixer": "^10.4.20",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"postcss": "^8.5.3",
|
"@tauri-apps/cli": "^2",
|
||||||
"postcss-cli": "^11.0.0",
|
"autoprefixer": "^10.4.20",
|
||||||
"tailwindcss": "3.4.0",
|
"postcss": "^8.5.3",
|
||||||
"typescript": "~5.6.2",
|
"postcss-cli": "^11.0.0",
|
||||||
"vite": "^6.0.3",
|
"tailwindcss": "3.4.0",
|
||||||
"vite-plugin-solid": "^2.11.0"
|
"typescript": "~5.6.2",
|
||||||
}
|
"vite": "^6.0.3",
|
||||||
|
"vite-plugin-solid": "^2.11.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Button } from "@kobalte/core/button";
|
import { Button } from "@kobalte/core/button";
|
||||||
import { A } from "@solidjs/router";
|
|
||||||
import { IconSearch, IconSettings } from "@tabler/icons-solidjs";
|
import { IconSearch, IconSettings } from "@tabler/icons-solidjs";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import clsx from "clsx";
|
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import {
|
import {
|
||||||
For,
|
For,
|
||||||
@ -12,14 +12,13 @@ import {
|
|||||||
onCleanup,
|
onCleanup,
|
||||||
onMount,
|
onMount,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { ImageViewer } from "./components/ImageViewer";
|
|
||||||
import { SearchCard } from "./components/search-card/SearchCard";
|
import { SearchCard } from "./components/search-card/SearchCard";
|
||||||
import { SearchCardContact } from "./components/search-card/SearchCardContact";
|
|
||||||
import { SearchCardEvent } from "./components/search-card/SearchCardEvent";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { SearchCardLocation } from "./components/search-card/SearchCardLocation";
|
import { ItemModal } from "./components/item-modal/ItemModal";
|
||||||
import { SearchCardNote } from "./components/search-card/SearchCardNote";
|
import type { Shortcut } from "./components/shortcuts/hooks/useShortcutEditor";
|
||||||
import { type UserImage, getUserImages } from "./network";
|
import { type UserImage, getUserImages } from "./network";
|
||||||
import { getCardSize } from "./utils/getCardSize";
|
|
||||||
|
|
||||||
// How wonderfully functional
|
// How wonderfully functional
|
||||||
const getAllValues = (object: object): Array<string> => {
|
const getAllValues = (object: object): Array<string> => {
|
||||||
@ -110,6 +109,22 @@ export const Search = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [shortcut, setShortcut] = createSignal<Shortcut>([]);
|
||||||
|
|
||||||
|
async function getCurrentShortcut() {
|
||||||
|
try {
|
||||||
|
const res: string = await invoke("get_current_shortcut");
|
||||||
|
console.log("DBG: ", res);
|
||||||
|
setShortcut(res?.split("+"));
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to fetch shortcut:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
getCurrentShortcut();
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main class="container pt-2">
|
<main class="container pt-2">
|
||||||
@ -174,9 +189,21 @@ export const Search = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full border-t h-10 bg-white px-4 flex items-center border-neutral-100">
|
<div class="w-full border-t h-10 bg-white px-4 flex items-center border-neutral-100">
|
||||||
footer
|
<p class="text-sm text-neutral-700">
|
||||||
|
Use{" "}
|
||||||
|
{shortcut().length > 0
|
||||||
|
? shortcut().join("+")
|
||||||
|
: "shortcut"}{" "}
|
||||||
|
globally to toggle and reload this window
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
{selectedItem() && (
|
||||||
|
<ItemModal
|
||||||
|
item={selectedItem() as UserImage}
|
||||||
|
onClose={() => setSelectedItem(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,25 @@
|
|||||||
export const ItemModal = () => {
|
import { IconX } from "@tabler/icons-solidjs";
|
||||||
|
import type { UserImage } from "../../network";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
item: UserImage;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ItemModal = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div class="fixed inset-4 bg-white border border-neutral-300">
|
<div class="fixed inset-2 rounded-2xl p-4 bg-white border border-neutral-300">
|
||||||
ItemModal
|
<div class="flex justify-between">
|
||||||
|
<h1 class="text-2xl font-bold">{props.item.data.Name}</h1>
|
||||||
|
<button type="button" onClick={props.onClose}>
|
||||||
|
<IconX size={24} class="text-neutral-500" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2 mb-2">
|
||||||
|
<p class="text-sm text-neutral-500">
|
||||||
|
{JSON.stringify(props.item.data, null, 2)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -12,13 +12,15 @@ export const SearchCardContact = ({ item }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="absolute inset-0 p-3 bg-orange-50">
|
<div class="absolute inset-0 p-3 bg-orange-50">
|
||||||
<div class="grid grid-cols-[auto_20px] gap-1 mb-1">
|
<div class="flex mb-1 items-center gap-1">
|
||||||
<p class="text-sm text-neutral-900 font-bold">{data.Name}</p>
|
<IconUser size={14} class="text-neutral-500" />
|
||||||
<IconUser size={20} class="text-neutral-500 mt-1" />
|
<p class="text-xs text-neutral-500">Contact</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-neutral-500">{data.PhoneNumber}</p>
|
<p class="text-sm text-neutral-900 font-bold mb-1">
|
||||||
<Separator class="my-2" />
|
{data.Name.length > 0 ? data.Name : "Unknown 🐞"}
|
||||||
<p class="text-xs text-neutral-500">{data.Email}</p>
|
</p>
|
||||||
|
<p class="text-xs text-neutral-700">Phone: {data.PhoneNumber}</p>
|
||||||
|
<p class="text-xs text-neutral-700">Mail: {data.Email}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -10,11 +10,14 @@ export const SearchCardEvent = ({ item }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="absolute inset-0 p-3 bg-purple-50">
|
<div class="absolute inset-0 p-3 bg-purple-50">
|
||||||
<div class="grid grid-cols-[auto_20px] gap-1 mb-1">
|
<div class="flex mb-1 items-center gap-1">
|
||||||
<p class="text-sm text-neutral-900 font-bold">{data.Name}</p>
|
<IconCalendar size={14} class="text-neutral-500" />
|
||||||
<IconCalendar size={20} class="text-neutral-500 mt-1" />
|
<p class="text-xs text-neutral-500">Event</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-neutral-500">
|
<p class="text-sm text-neutral-900 font-bold mb-1">
|
||||||
|
{data.Name.length > 0 ? data.Name : "Unknown 🐞"}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-neutral-700">
|
||||||
Organized by {data.Organizer?.Name ?? "unknown"} on{" "}
|
Organized by {data.Organizer?.Name ?? "unknown"} on{" "}
|
||||||
{data.StartDateTime
|
{data.StartDateTime
|
||||||
? new Date(data.StartDateTime).toLocaleDateString("en-US", {
|
? new Date(data.StartDateTime).toLocaleDateString("en-US", {
|
||||||
|
@ -10,11 +10,14 @@ export const SearchCardLocation = ({ item }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="absolute inset-0 p-3 bg-red-50">
|
<div class="absolute inset-0 p-3 bg-red-50">
|
||||||
<div class="grid grid-cols-[auto_20px] gap-1 mb-1">
|
<div class="flex mb-1 items-center gap-1">
|
||||||
<p class="text-sm text-neutral-900 font-bold">{data.Name}</p>
|
<IconMapPin size={14} class="text-neutral-500" />
|
||||||
<IconMapPin size={20} class="text-neutral-500 mt-1" />
|
<p class="text-xs text-neutral-500">Location</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-neutral-500">{data.Address}</p>
|
<p class="text-sm text-neutral-900 font-bold mb-1">
|
||||||
|
{data.Name.length > 0 ? data.Name : "Unknown 🐞"}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-neutral-700">Address: {data.Address}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Separator } from "@kobalte/core/separator";
|
import { Separator } from "@kobalte/core/separator";
|
||||||
|
import SolidjsMarkdown from "solidjs-markdown";
|
||||||
|
|
||||||
import { IconNote } from "@tabler/icons-solidjs";
|
import { IconNote } from "@tabler/icons-solidjs";
|
||||||
import type { UserImage } from "../../network";
|
import type { UserImage } from "../../network";
|
||||||
@ -12,11 +13,16 @@ export const SearchCardNote = ({ item }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="absolute inset-0 p-3 bg-green-50">
|
<div class="absolute inset-0 p-3 bg-green-50">
|
||||||
<div class="grid grid-cols-[auto_20px] gap-1 mb-1">
|
<div class="flex mb-1 items-center gap-1">
|
||||||
<p class="text-sm text-neutral-900 font-bold">{data.Name}</p>
|
<IconNote size={14} class="text-neutral-500" />
|
||||||
<IconNote size={20} class="text-neutral-500 mt-1" />
|
<p class="text-xs text-neutral-500">Note</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-neutral-500">Note</p>
|
<p class="text-sm text-neutral-900 font-bold mb-1">
|
||||||
|
{data.Name.length > 0 ? data.Name : "Unknown 🐞"}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-neutral-700">
|
||||||
|
<SolidjsMarkdown>{data.Content}</SolidjsMarkdown>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user