mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-10 06:03:50 -05:00
Added search
This commit is contained in:
6
build.sh
6
build.sh
@@ -1,7 +1,13 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Exit on error
|
||||||
|
set -e
|
||||||
|
|
||||||
# This is kind of like a shim that makes sure the frontend is rebuilt when running a build. For example you can run `./build.sh cargo build --release`
|
# This is kind of like a shim that makes sure the frontend is rebuilt when running a build. For example you can run `./build.sh cargo build --release`
|
||||||
|
|
||||||
|
# Remove old files
|
||||||
|
rm -rf src/static
|
||||||
|
|
||||||
# Frontend
|
# Frontend
|
||||||
cd web/
|
cd web/
|
||||||
|
|
||||||
|
|||||||
BIN
web/bun.lockb
BIN
web/bun.lockb
Binary file not shown.
@@ -7,9 +7,11 @@ import Loading from "./components/Loading";
|
|||||||
import { Data } from "./types";
|
import { Data } from "./types";
|
||||||
import { theme } from "./theme";
|
import { theme } from "./theme";
|
||||||
import RefreshButton from "./components/RefreshButton";
|
import RefreshButton from "./components/RefreshButton";
|
||||||
|
import Search from "./components/Search";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [data, setData] = useState<Data | null>(null);
|
const [data, setData] = useState<Data | null>(null);
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
if (!data) return <Loading onLoad={setData} />;
|
if (!data) return <Loading onLoad={setData} />;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -41,10 +43,11 @@ function App() {
|
|||||||
<LastChecked datetime={data.last_updated} />
|
<LastChecked datetime={data.last_updated} />
|
||||||
<RefreshButton />
|
<RefreshButton />
|
||||||
</div>
|
</div>
|
||||||
|
<Search onChange={setSearchQuery}/>
|
||||||
<ul
|
<ul
|
||||||
className={`*:py-4 *:px-6 *:flex *:items-center *:gap-3 dark:divide-${theme}-800 divide-y dark:text-white`}
|
className={`*:py-4 *:px-6 *:flex *:items-center *:gap-3 dark:divide-${theme}-800 divide-y dark:text-white`}
|
||||||
>
|
>
|
||||||
{Object.entries(data.images).map(([name, status]) => (
|
{Object.entries(data.images).filter(([name]) => name.includes(searchQuery)).map(([name, status]) => (
|
||||||
<Image name={name} status={status} key={name} />
|
<Image name={name} status={status} key={name} />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
51
web/src/components/Search.tsx
Normal file
51
web/src/components/Search.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { ChangeEvent, useState } from "react";
|
||||||
|
import { theme } from "../theme";
|
||||||
|
import { IconSearch, IconX } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
export default function Search({
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}) {
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [showClear, setShowClear] = useState(false);
|
||||||
|
const handleChange = (event: ChangeEvent) => {
|
||||||
|
const value = (event.target as HTMLInputElement).value;
|
||||||
|
setSearchQuery(value);
|
||||||
|
onChange(value);
|
||||||
|
if (value !== "") {
|
||||||
|
setShowClear(true);
|
||||||
|
} else setShowClear(false);
|
||||||
|
};
|
||||||
|
const handleClear = () => {
|
||||||
|
setShowClear(false);
|
||||||
|
setSearchQuery("");
|
||||||
|
onChange("");
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={`w-full px-6 text-${theme}-500`}>
|
||||||
|
<div
|
||||||
|
className={`flex items-center w-full rounded-md border border-${theme}-200 dark:border-${theme}-700 px-2 gap-1 bg-${theme}-800 flex-nowrap peer`}
|
||||||
|
>
|
||||||
|
<IconSearch className="size-5" />
|
||||||
|
<div className="w-full">
|
||||||
|
<input
|
||||||
|
className={`w-full h-10 text-sm text-${theme}-400 focus:outline-none peer bg-transparent placeholder:text-${theme}-500`}
|
||||||
|
placeholder="Search"
|
||||||
|
onChange={handleChange}
|
||||||
|
value={searchQuery}
|
||||||
|
></input>
|
||||||
|
</div>
|
||||||
|
{showClear && (
|
||||||
|
<button onClick={handleClear} className={`hover:text-${theme}-400`}>
|
||||||
|
<IconX className="size-5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="relative -translate-y-[8px] h-[8px] border-b-blue-600 border-b-2 w-0 peer-has-[:focus]:w-full transition-all duration-200 rounded-md left-1/2 -translate-x-1/2"
|
||||||
|
style={{ clipPath: "inset(calc(100% - 2px) 0 0 0)" }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -24,10 +24,11 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /text-(gray|neutral)-400/,
|
pattern: /text-(gray|neutral)-400/,
|
||||||
|
variants: ["hover"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /text-(gray|neutral)-500/,
|
pattern: /text-(gray|neutral)-500/,
|
||||||
variants: ["dark"],
|
variants: ["dark", "placeholder"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /divide-(gray|neutral)-800/,
|
pattern: /divide-(gray|neutral)-800/,
|
||||||
@@ -37,7 +38,7 @@ export default {
|
|||||||
pattern: /border-(gray|neutral)-200/,
|
pattern: /border-(gray|neutral)-200/,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /border-(gray|neutral)-800/,
|
pattern: /border-(gray|neutral)-700/,
|
||||||
variants: ["dark"],
|
variants: ["dark"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user