m/cup
1
0
mirror of https://github.com/sergi0g/cup.git synced 2025-11-15 16:43:49 -05:00

Fixed CSS bugs, formatted code

This commit is contained in:
Sergio
2024-10-25 17:09:54 +03:00
parent 6d45409928
commit 8d70d7ae4d
12 changed files with 96 additions and 84 deletions

3
web/.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"plugins": ["prettier-plugin-tailwindcss"]
}

Binary file not shown.

View File

@@ -14,10 +14,10 @@
<div <div
class="flex justify-center items-center min-h-screen bg-{{ theme }}-50 dark:bg-{{ theme }}-950" class="flex justify-center items-center min-h-screen bg-{{ theme }}-50 dark:bg-{{ theme }}-950"
> >
<div class="lg:px-8 sm:px-6 px-4 max-w-[80rem] mx-auto h-full w-full"> <div class="mx-auto h-full w-full max-w-[80rem] px-4 sm:px-6 lg:px-8">
<div class="max-w-[48rem] mx-auto h-full my-8"> <div class="mx-auto my-8 h-full max-w-[48rem]">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<h1 class="text-5xl lg:text-6xl font-bold dark:text-white"> <h1 class="text-5xl font-bold lg:text-6xl dark:text-white">
Cup Cup
</h1> </h1>
<svg <svg
@@ -86,21 +86,23 @@
class="shadow-sm bg-white dark:bg-{{ theme }}-900 rounded-md my-8" class="shadow-sm bg-white dark:bg-{{ theme }}-900 rounded-md my-8"
> >
<dl <dl
class="lg:grid-cols-4 grid-cols-2 gap-1 grid overflow-hidden *:relative" class="grid grid-cols-2 gap-1 overflow-hidden *:relative lg:grid-cols-4"
> >
{% for metric in metrics %} {% for metric in metrics %}
<div class="before:bg-{{ theme }}-200 before:dark:bg-{{ theme }}-800 after:bg-{{ theme }}-200 after:dark:bg-{{ theme }}-800 gi">
<div <div
class="xl:px-8 px-6 py-4 gap-y-2 gap-x-4 justify-between align-baseline flex flex-col h-full" class="before:bg-{{ theme }}-200 before:dark:bg-{{ theme }}-800 after:bg-{{ theme }}-200 after:dark:bg-{{ theme }}-800 gi"
>
<div
class="flex h-full flex-col justify-between gap-x-4 gap-y-2 px-6 py-4 align-baseline xl:px-8"
> >
<dt <dt
class="text-{{ theme }}-500 dark:text-{{ theme }}-400 leading-6 font-medium" class="text-{{ theme }}-500 dark:text-{{ theme }}-400 leading-6 font-medium"
> >
{{ metric.name }} {{ metric.name }}
</dt> </dt>
<div class="flex gap-1 justify-between items-center"> <div class="flex items-center justify-between gap-1">
<dd <dd
class="text-black dark:text-white tracking-tight leading-10 font-medium text-3xl w-full" class="w-full text-3xl font-medium leading-10 tracking-tight text-black dark:text-white"
> >
{{ metric.value }} {{ metric.value }}
</dd> </dd>
@@ -111,7 +113,7 @@
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
class="size-6 text-black dark:text-white shrink-0" class="size-6 shrink-0 text-black dark:text-white"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path <path
@@ -125,7 +127,7 @@
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
class="size-6 text-green-500 shrink-0" class="size-6 shrink-0 text-green-500"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path <path
@@ -139,7 +141,7 @@
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
class="size-6 text-blue-500 shrink-0" class="size-6 shrink-0 text-blue-500"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path <path
@@ -224,7 +226,7 @@
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
class="text-green-500 ml-auto" class="ml-auto text-green-500"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path <path
@@ -238,7 +240,7 @@
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
class="text-blue-500 ml-auto" class="ml-auto text-blue-500"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path <path

View File

@@ -34,6 +34,7 @@
"globals": "^15.9.0", "globals": "^15.9.0",
"postcss": "^8.4.42", "postcss": "^8.4.42",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8",
"tailwindcss": "^3.4.10", "tailwindcss": "^3.4.10",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"typescript-eslint": "^8.0.1", "typescript-eslint": "^8.0.1",

View File

@@ -15,39 +15,39 @@ function App() {
if (!data) return <Loading onLoad={setData} />; if (!data) return <Loading onLoad={setData} />;
return ( return (
<div <div
className={`flex justify-center min-h-screen bg-${theme}-50 dark:bg-${theme}-950`} className={`flex min-h-screen justify-center bg-${theme}-50 dark:bg-${theme}-950`}
> >
<div className="lg:px-8 sm:px-6 px-4 max-w-[80rem] mx-auto h-full w-full"> <div className="mx-auto h-full w-full max-w-[80rem] px-4 sm:px-6 lg:px-8">
<div className="flex flex-col max-w-[48rem] mx-auto h-full my-8"> <div className="mx-auto my-8 flex h-full max-w-[48rem] flex-col">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<h1 className="text-5xl lg:text-6xl font-bold dark:text-white"> <h1 className="text-5xl font-bold lg:text-6xl dark:text-white">
Cup Cup
</h1> </h1>
<Logo /> <Logo />
</div> </div>
<div <div
className={`shadow-sm bg-white dark:bg-${theme}-900 rounded-md my-8`} className={`bg-white shadow-sm dark:bg-${theme}-900 my-8 rounded-md`}
> >
<dl className="lg:grid-cols-4 md:grid-cols-2 gap-1 grid-cols-1 grid overflow-hidden *:relative"> <dl className="grid grid-cols-1 gap-1 overflow-hidden *:relative md:grid-cols-2 lg:grid-cols-4">
{Object.entries(data.metrics).map(([name, value]) => ( {Object.entries(data.metrics).map(([name, value]) => (
<Statistic name={name} value={value} key={name} /> <Statistic name={name} value={value} key={name} />
))} ))}
</dl> </dl>
</div> </div>
<div <div
className={`shadow-sm bg-white dark:bg-${theme}-900 rounded-md my-8`} className={`bg-white shadow-sm dark:bg-${theme}-900 my-8 rounded-md`}
> >
<div <div
className={`flex justify-between items-center px-6 py-4 text-${theme}-500`} className={`flex items-center justify-between px-6 py-4 text-${theme}-500`}
> >
<LastChecked datetime={data.last_updated} /> <LastChecked datetime={data.last_updated} />
<RefreshButton /> <RefreshButton />
</div> </div>
<Search onChange={setSearchQuery} /> <Search onChange={setSearchQuery} />
<ul <ul className={`dark:divide-${theme}-800 divide-y dark:text-white`}>
className={`dark:divide-${theme}-800 divide-y dark:text-white`} {data.images
> .filter((image) => image.reference.includes(searchQuery))
{data.images.filter((image) => image.reference.includes(searchQuery)).map((image) => ( .map((image) => (
<Image data={image} key={image.reference} /> <Image data={image} key={image.reference} />
))} ))}
</ul> </ul>

View File

@@ -62,7 +62,7 @@ export default function Image({ data }: { data: Image }) {
<> <>
<button <button
onClick={handleOpen} onClick={handleOpen}
className={`*:py-4 *:px-6 *:flex *:items-center *:gap-3 w-full`} className={`w-full *:flex *:items-center *:gap-3 *:px-6 *:py-4`}
> >
<li className="break-all"> <li className="break-all">
<IconCube className="size-6 shrink-0" /> <IconCube className="size-6 shrink-0" />
@@ -70,7 +70,7 @@ export default function Image({ data }: { data: Image }) {
{data.result.has_update == false && ( {data.result.has_update == false && (
<WithTooltip <WithTooltip
text="Up to date" text="Up to date"
className="text-green-500 ml-auto size-6 shrink-0" className="ml-auto size-6 shrink-0 text-green-500"
> >
<IconCircleCheckFilled /> <IconCircleCheckFilled />
</WithTooltip> </WithTooltip>
@@ -78,7 +78,7 @@ export default function Image({ data }: { data: Image }) {
{data.result.has_update == true && ( {data.result.has_update == true && (
<WithTooltip <WithTooltip
text="Update available" text="Update available"
className="text-blue-500 ml-auto size-6 shrink-0" className="ml-auto size-6 shrink-0 text-blue-500"
> >
<IconCircleArrowUpFilled /> <IconCircleArrowUpFilled />
</WithTooltip> </WithTooltip>
@@ -86,7 +86,7 @@ export default function Image({ data }: { data: Image }) {
{data.result.has_update == null && ( {data.result.has_update == null && (
<WithTooltip <WithTooltip
text="Unknown" text="Unknown"
className="text-gray-500 ml-auto size-6 shrink-0" className="ml-auto size-6 shrink-0 text-gray-500"
> >
<IconHelpCircleFilled /> <IconHelpCircleFilled />
</WithTooltip> </WithTooltip>
@@ -102,12 +102,12 @@ export default function Image({ data }: { data: Image }) {
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> <div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<DialogPanel <DialogPanel
transition transition
className={`relative transform overflow-hidden rounded-lg bg-white dark:bg-${theme}-900 dark:text-white text-left shadow-xl transition-all data-[closed]:translate-y-4 data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in sm:my-8 sm:w-full sm:max-w-lg md:max-w-xl lg:max-w-2xl data-[closed]:sm:translate-y-0 data-[closed]:sm:scale-95`} className={`relative transform overflow-hidden rounded-lg bg-white dark:bg-${theme}-900 text-left shadow-xl transition-all data-[closed]:translate-y-4 data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in sm:my-8 sm:w-full sm:max-w-lg data-[closed]:sm:translate-y-0 data-[closed]:sm:scale-95 md:max-w-xl lg:max-w-2xl dark:text-white`}
> >
<div <div
className={`py-4 px-6 flex flex-col gap-3 text-${theme}-400 dark:text-${theme}-600`} className={`flex flex-col gap-3 px-6 py-4 text-${theme}-400 dark:text-${theme}-600`}
> >
<div className="flex items-center gap-3 mb-4"> <div className="mb-4 flex items-center gap-3">
<IconCube className="size-6 shrink-0 text-black dark:text-white" /> <IconCube className="size-6 shrink-0 text-black dark:text-white" />
<DialogTitle className="text-black dark:text-white"> <DialogTitle className="text-black dark:text-white">
{url ? ( {url ? (
@@ -116,7 +116,7 @@ export default function Image({ data }: { data: Image }) {
href={url} href={url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-block after:bg-white after:h-[2px] after:bottom-[4px] after:left-0 after:scale-x-0 after:block after:relative after:w-full after:transition-transform after:duration-300 hover:after:scale-x-100" className="inline-block after:relative after:bottom-[1px] after:left-0 after:block after:h-[2px] after:w-full after:scale-x-0 after:bg-black after:dark:bg-white after:transition-transform after:duration-300 hover:after:scale-x-100"
> >
{data.reference} {data.reference}
</a> </a>
@@ -133,30 +133,30 @@ export default function Image({ data }: { data: Image }) {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{data.result.has_update == false && ( {data.result.has_update == false && (
<> <>
<IconCircleCheckFilled className="text-green-500 size-6 shrink-0" /> <IconCircleCheckFilled className="size-6 shrink-0 text-green-500" />
Up to date Up to date
</> </>
)} )}
{data.result.has_update == true && ( {data.result.has_update == true && (
<> <>
<IconCircleArrowUpFilled className="text-blue-500 size-6 shrink-0" /> <IconCircleArrowUpFilled className="size-6 shrink-0 text-blue-500" />
Update available Update available
</> </>
)} )}
{data.result.has_update == null && ( {data.result.has_update == null && (
<> <>
<IconHelpCircleFilled className="text-gray-500 size-6 shrink-0" /> <IconHelpCircleFilled className="size-6 shrink-0 text-gray-500" />
Unknown Unknown
</> </>
)} )}
</div> </div>
<div className="flex items-center gap-3 mb-4"> <div className="mb-4 flex items-center gap-3">
<IconStopwatch className="text-gray-500 size-6 shrink-0" /> <IconStopwatch className="size-6 shrink-0 text-gray-500" />
Checked in {data.time} ms Checked in {data.time} ms
</div> </div>
{data.result.error && ( {data.result.error && (
<div className="bg-yellow-400/10 flex items-center gap-3 overflow-hidden break-all rounded-md px-3 py-2 mb-4"> <div className="mb-4 flex items-center gap-3 overflow-hidden break-all rounded-md bg-yellow-400/10 px-3 py-2">
<IconAlertTriangleFilled className="text-yellow-500 size-5 shrink-0" /> <IconAlertTriangleFilled className="size-5 shrink-0 text-yellow-500" />
{data.result.error} {data.result.error}
</div> </div>
)} )}
@@ -164,7 +164,7 @@ export default function Image({ data }: { data: Image }) {
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
Pull command Pull command
<div <div
className={`bg-${theme}-50 dark:bg-${theme}-950 text-gray-500 flex items-center rounded-md px-3 py-2 font-mono mb-4 group relative`} className={`bg-${theme}-100 dark:bg-${theme}-950 group relative mb-4 flex items-center rounded-md px-3 py-2 font-mono text-gray-500`}
> >
<p className="overflow-scroll"> <p className="overflow-scroll">
docker pull {data.reference} docker pull {data.reference}
@@ -174,9 +174,9 @@ export default function Image({ data }: { data: Image }) {
<IconCheck className="absolute right-3" /> <IconCheck className="absolute right-3" />
) : ( ) : (
<button <button
className="absolute right-3 opacity-0 group-hover:opacity-100 transition-opacity duration-50" className="duration-50 absolute right-3 opacity-0 transition-opacity group-hover:opacity-100"
onClick={handleCopy( onClick={handleCopy(
`docker pull ${data.reference}` `docker pull ${data.reference}`,
)} )}
> >
<IconCopy /> <IconCopy />
@@ -188,7 +188,7 @@ export default function Image({ data }: { data: Image }) {
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
Local digests Local digests
<div <div
className={`bg-${theme}-50 dark:bg-${theme}-950 text-gray-500 rounded-md px-3 py-2 font-mono scrollable`} className={`bg-${theme}-100 dark:bg-${theme}-950 scrollable rounded-md px-3 py-2 font-mono text-gray-500`}
> >
<p className="overflow-x-scroll"> <p className="overflow-x-scroll">
{data.local_digests.join("\n")} {data.local_digests.join("\n")}
@@ -199,7 +199,7 @@ export default function Image({ data }: { data: Image }) {
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
Remote digest Remote digest
<div <div
className={`bg-${theme}-50 dark:bg-${theme}-950 text-gray-500 rounded-md px-3 py-2 font-mono`} className={`bg-${theme}-100 dark:bg-${theme}-950 rounded-md px-3 py-2 font-mono text-gray-500`}
> >
<p className="overflow-x-scroll">{data.remote_digest}</p> <p className="overflow-x-scroll">{data.remote_digest}</p>
</div> </div>

View File

@@ -8,22 +8,25 @@ export default function Loading({ onLoad }: { onLoad: (data: Data) => void }) {
process.env.NODE_ENV === "production" process.env.NODE_ENV === "production"
? "/api/v1/full" ? "/api/v1/full"
: `http://${window.location.hostname}:8000/api/v1/full`, : `http://${window.location.hostname}:8000/api/v1/full`,
).then((response) => response.json().then((data) => {onLoad(data as Data)})); ).then((response) =>
response.json().then((data) => {
onLoad(data as Data);
}),
);
return ( return (
<div <div
className={`flex justify-center min-h-screen bg-${theme}-50 dark:bg-${theme}-950`} className={`flex min-h-screen justify-center bg-${theme}-50 dark:bg-${theme}-950`}
> >
<div className="lg:px-8 sm:px-6 px-4 max-w-[80rem] mx-auto h-full w-full absolute overflow-hidden"> <div className="absolute mx-auto h-full w-full max-w-[80rem] overflow-hidden px-4 sm:px-6 lg:px-8">
<div className="flex flex-col max-w-[48rem] mx-auto h-full my-8"> <div className="mx-auto my-8 flex h-full max-w-[48rem] flex-col">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<h1 className="text-5xl lg:text-6xl font-bold dark:text-white"> <h1 className="text-5xl font-bold lg:text-6xl dark:text-white">
Cup Cup
</h1> </h1>
<Logo /> <Logo />
</div> </div>
<div <div
className={`h-full flex justify-center className={`flex h-full items-center justify-center gap-1 text-${theme}-500 dark:text-${theme}-400`}
items-center gap-1 text-${theme}-500 dark:text-${theme}-400`}
> >
Loading <IconLoader2 className="animate-spin" /> Loading <IconLoader2 className="animate-spin" />
</div> </div>

View File

@@ -25,25 +25,28 @@ export default function Search({
return ( return (
<div className={`w-full px-6 text-${theme}-500`}> <div className={`w-full px-6 text-${theme}-500`}>
<div <div
className={`flex items-center w-full rounded-md border border-${theme}-300 dark:border-${theme}-700 px-2 gap-1 bg-${theme}-200 dark:bg-${theme}-800 flex-nowrap peer`} className={`flex w-full items-center rounded-md border border-${theme}-300 dark:border-${theme}-700 gap-1 px-2 bg-${theme}-200 dark:bg-${theme}-800 peer flex-nowrap`}
> >
<IconSearch className="size-5" /> <IconSearch className="size-5" />
<div className="w-full"> <div className="w-full">
<input <input
className={`w-full h-10 text-sm text-${theme}-600 dark:text-${theme}-400 focus:outline-none peer bg-transparent placeholder:text-${theme}-500`} className={`h-10 w-full text-sm text-${theme}-600 dark:text-${theme}-400 peer bg-transparent focus:outline-none placeholder:text-${theme}-500`}
placeholder="Search" placeholder="Search"
onChange={handleChange} onChange={handleChange}
value={searchQuery} value={searchQuery}
></input> ></input>
</div> </div>
{showClear && ( {showClear && (
<button onClick={handleClear} className={`hover:text-${theme}-600 dark:hover:text-${theme}-400`}> <button
onClick={handleClear}
className={`hover:text-${theme}-600 dark:hover:text-${theme}-400`}
>
<IconX className="size-5" /> <IconX className="size-5" />
</button> </button>
)} )}
</div> </div>
<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" className="relative left-1/2 h-[8px] w-0 -translate-x-1/2 -translate-y-[8px] rounded-md border-b-2 border-b-blue-600 transition-all duration-200 peer-has-[:focus]:w-full"
style={{ clipPath: "inset(calc(100% - 2px) 0 0 0)" }} style={{ clipPath: "inset(calc(100% - 2px) 0 0 0)" }}
></div> ></div>
</div> </div>

View File

@@ -19,27 +19,27 @@ export default function Statistic({
<div <div
className={`before:bg-${theme}-200 before:dark:bg-${theme}-800 after:bg-${theme}-200 after:dark:bg-${theme}-800 gi`} className={`before:bg-${theme}-200 before:dark:bg-${theme}-800 after:bg-${theme}-200 after:dark:bg-${theme}-800 gi`}
> >
<div className="xl:px-8 px-6 py-4 gap-y-2 gap-x-4 justify-between align-baseline flex flex-col h-full"> <div className="flex h-full flex-col justify-between gap-x-4 gap-y-2 px-6 py-4 align-baseline xl:px-8">
<dt <dt
className={`text-${theme}-500 dark:text-${theme}-400 leading-6 font-medium`} className={`text-${theme}-500 dark:text-${theme}-400 font-medium leading-6`}
> >
{name} {name}
</dt> </dt>
<div className="flex gap-1 justify-between items-center"> <div className="flex items-center justify-between gap-1">
<dd className="text-black dark:text-white tracking-tight leading-10 font-medium text-3xl w-full"> <dd className="w-full text-3xl font-medium leading-10 tracking-tight text-black dark:text-white">
{value} {value}
</dd> </dd>
{name == "Monitored images" && ( {name == "Monitored images" && (
<IconEyeFilled className="size-6 text-black dark:text-white shrink-0" /> <IconEyeFilled className="size-6 shrink-0 text-black dark:text-white" />
)} )}
{name == "Up to date" && ( {name == "Up to date" && (
<IconCircleCheckFilled className="size-6 text-green-500 shrink-0" /> <IconCircleCheckFilled className="size-6 shrink-0 text-green-500" />
)} )}
{name == "Update available" && ( {name == "Update available" && (
<IconCircleArrowUpFilled className="size-6 text-blue-500 shrink-0" /> <IconCircleArrowUpFilled className="size-6 shrink-0 text-blue-500" />
)} )}
{name == "Unknown" && ( {name == "Unknown" && (
<IconHelpCircleFilled className="size-6 text-gray-500 shrink-0" /> <IconHelpCircleFilled className="size-6 shrink-0 text-gray-500" />
)} )}
</div> </div>
</div> </div>

View File

@@ -7,20 +7,20 @@ export interface Data {
}; };
images: Image[]; images: Image[];
last_updated: string; last_updated: string;
}; }
export interface Image { export interface Image {
reference: string, reference: string;
parts: { parts: {
registry: string, registry: string;
repository: string, repository: string;
tag: string, tag: string;
}, };
local_digests: string[], local_digests: string[];
remote_digest: string, remote_digest: string;
result: { result: {
has_update: boolean | null, has_update: boolean | null;
error: string | null error: string | null;
}, };
time: number time: number;
} }

View File

@@ -8,7 +8,7 @@ export default {
safelist: [ safelist: [
// Generate minimum extra CSS // Generate minimum extra CSS
{ {
pattern: /bg-(gray|neutral)-(50|500)/, pattern: /bg-(gray|neutral)-(50|100|500)/,
}, },
{ {
pattern: /bg-(gray|neutral)-(900|950)/, pattern: /bg-(gray|neutral)-(900|950)/,
@@ -24,11 +24,11 @@ export default {
}, },
{ {
pattern: /text-(gray|neutral)-600/, pattern: /text-(gray|neutral)-600/,
variants: ["hover"] variants: ["hover"],
}, },
{ {
pattern: /text-(gray|neutral)-400/, pattern: /text-(gray|neutral)-400/,
variants: ["hover", "dark", "dark:hover"] variants: ["hover", "dark", "dark:hover"],
}, },
{ {
pattern: /text-(gray|neutral)-500/, pattern: /text-(gray|neutral)-500/,