Skip to content

Commit bdeeb49

Browse files
committed
feat: Integrate class-variance-authority for type-safe component variants and enhance status display management
1 parent 9c90051 commit bdeeb49

11 files changed

Lines changed: 341 additions & 25 deletions

File tree

package-lock.json

Lines changed: 23 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"@microlink/react-json-view": "^1.26.2",
1515
"@tanstack/query-core": "^5.80.2",
1616
"@types/chrome": "^0.0.326",
17+
"class-variance-authority": "^0.7.1",
18+
"clsx": "^2.1.1",
1719
"lucide-react": "^0.514.0",
1820
"react": "^19.1.0",
1921
"react-dom": "^19.1.0"

src/components/common/Button.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from "react";
2+
import { buttonVariants, type ButtonVariants } from "../../lib/variants";
3+
import { clsx } from "clsx";
4+
5+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, ButtonVariants {
6+
children: React.ReactNode;
7+
isLoading?: boolean;
8+
loadingText?: string;
9+
}
10+
11+
/**
12+
* Reusable Button component using CVA variants
13+
* Provides type-safe variant management with full HTML button props support
14+
*/
15+
export function Button({
16+
children,
17+
variant = "gray",
18+
size = "md",
19+
className,
20+
disabled,
21+
isLoading = false,
22+
loadingText,
23+
...props
24+
}: ButtonProps) {
25+
const isDisabled = disabled || isLoading;
26+
27+
return (
28+
<button
29+
className={clsx(buttonVariants({ variant, size }), className)}
30+
disabled={isDisabled}
31+
{...props}
32+
>
33+
{isLoading ? (loadingText || "Loading...") : children}
34+
</button>
35+
);
36+
}
37+
38+
// Export individual button variants for convenience
39+
export const BlueButton = (props: Omit<ButtonProps, "variant">) => (
40+
<Button variant="blue" {...props} />
41+
);
42+
43+
export const RedButton = (props: Omit<ButtonProps, "variant">) => (
44+
<Button variant="red" {...props} />
45+
);
46+
47+
export const GreenButton = (props: Omit<ButtonProps, "variant">) => (
48+
<Button variant="green" {...props} />
49+
);
50+
51+
export const OrangeButton = (props: Omit<ButtonProps, "variant">) => (
52+
<Button variant="orange" {...props} />
53+
);
54+
55+
export const PinkButton = (props: Omit<ButtonProps, "variant">) => (
56+
<Button variant="pink" {...props} />
57+
);
58+
59+
export const GrayButton = (props: Omit<ButtonProps, "variant">) => (
60+
<Button variant="gray" {...props} />
61+
);

src/components/common/Chip.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from "react";
2+
import { chipVariants, type ChipVariants } from "../../lib/variants";
3+
import { clsx } from "clsx";
4+
5+
interface ChipProps extends React.HTMLAttributes<HTMLSpanElement>, ChipVariants {
6+
children: React.ReactNode;
7+
}
8+
9+
/**
10+
* Reusable Chip component using CVA variants
11+
* Small informational tags and labels with type-safe variant management
12+
*/
13+
export function Chip({
14+
children,
15+
variant = "gray",
16+
size = "md",
17+
className,
18+
...props
19+
}: ChipProps) {
20+
return (
21+
<span
22+
className={clsx(chipVariants({ variant, size }), className)}
23+
{...props}
24+
>
25+
{children}
26+
</span>
27+
);
28+
}
29+
30+
// Export variant-specific chips for convenience
31+
export const DisabledChip = (props: Omit<ChipProps, "variant">) => (
32+
<Chip variant="disabled" {...props} />
33+
);
34+
35+
export const BlueChip = (props: Omit<ChipProps, "variant">) => (
36+
<Chip variant="blue" {...props} />
37+
);
38+
39+
export const GreenChip = (props: Omit<ChipProps, "variant">) => (
40+
<Chip variant="green" {...props} />
41+
);
42+
43+
export const RedChip = (props: Omit<ChipProps, "variant">) => (
44+
<Chip variant="red" {...props} />
45+
);
46+
47+
export const YellowChip = (props: Omit<ChipProps, "variant">) => (
48+
<Chip variant="yellow" {...props} />
49+
);
50+
51+
export const GrayChip = (props: Omit<ChipProps, "variant">) => (
52+
<Chip variant="gray" {...props} />
53+
);

src/components/layout/ToggleGroup.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ViewType } from "../../types/query";
2+
import { toggleOptionVariants } from "../../lib/variants";
23

34
interface ToggleOption {
45
value: ViewType;
@@ -17,7 +18,11 @@ export function ToggleGroup({ currentView, onViewChange, options, className = ""
1718
return (
1819
<div className={`toggle-group-base ${className}`}>
1920
{options.map((option) => (
20-
<button key={option.value} onClick={() => onViewChange(option.value)} className={currentView === option.value ? "toggle-option-active" : "toggle-option-inactive"}>
21+
<button
22+
key={option.value}
23+
onClick={() => onViewChange(option.value)}
24+
className={toggleOptionVariants({ state: currentView === option.value ? "active" : "inactive" })}
25+
>
2126
{option.label} ({option.count})
2227
</button>
2328
))}

src/components/query/QueryActions.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { QueryData } from "../../types/query";
22
import { formatQueryKeyShort } from "../../utils/formatters";
3+
import { buttonVariants } from "../../lib/variants";
34

45
interface QueryActionsProps {
56
selectedQuery: QueryData;
@@ -30,15 +31,15 @@ export function QueryActions({ selectedQuery, onAction, actionLoading, setAction
3031
<div className="p-4 border-b border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-800">
3132
<h4 className="text-base font-semibold mb-3 text-gray-900 dark:text-gray-100">Actions</h4>
3233
<div className="grid grid-cols-3 gap-2">
33-
<button onClick={() => handleAction("REFETCH")} disabled={shouldDisableButtons} className="btn btn-blue btn-animated">
34+
<button onClick={() => handleAction("REFETCH")} disabled={shouldDisableButtons} className={buttonVariants({ variant: "blue" })}>
3435
{actionLoading === "REFETCH" ? "Refreshing..." : "Refresh"}
3536
</button>
3637

37-
<button onClick={() => handleAction("INVALIDATE")} disabled={shouldDisableButtons} className="btn btn-orange btn-animated">
38+
<button onClick={() => handleAction("INVALIDATE")} disabled={shouldDisableButtons} className={buttonVariants({ variant: "orange" })}>
3839
{actionLoading === "INVALIDATE" ? "Invalidating..." : "Invalidate"}
3940
</button>
4041

41-
<button onClick={() => handleAction("RESET")} disabled={shouldDisableButtons} className="btn btn-gray btn-animated">
42+
<button onClick={() => handleAction("RESET")} disabled={shouldDisableButtons} className={buttonVariants({ variant: "gray" })}>
4243
{actionLoading === "RESET" ? "Resetting..." : "Reset"}
4344
</button>
4445

@@ -49,16 +50,23 @@ export function QueryActions({ selectedQuery, onAction, actionLoading, setAction
4950
}
5051
}}
5152
disabled={shouldDisableButtons}
52-
className="btn btn-pink btn-animated"
53+
className={buttonVariants({ variant: "pink" })}
5354
>
5455
{actionLoading === "REMOVE" ? "Removing..." : "Remove"}
5556
</button>
5657

57-
<button onClick={() => handleAction("TRIGGER_LOADING")} className={`btn ${isArtificialLoading ? "btn-gray" : "btn-green"} btn-animated`}>
58+
<button
59+
onClick={() => handleAction("TRIGGER_LOADING")}
60+
className={buttonVariants({ variant: isArtificialLoading ? "gray" : "green" })}
61+
>
5862
{actionLoading === "TRIGGER_LOADING" ? "Triggering..." : isArtificialLoading ? "Cancel Loading" : "Trigger Loading"}
5963
</button>
6064

61-
<button onClick={() => handleAction("TRIGGER_ERROR")} disabled={shouldDisableButtons} className={`btn ${isArtificialError ? "btn-gray" : "btn-red"} btn-animated`}>
65+
<button
66+
onClick={() => handleAction("TRIGGER_ERROR")}
67+
disabled={shouldDisableButtons}
68+
className={buttonVariants({ variant: isArtificialError ? "gray" : "red" })}
69+
>
6270
{actionLoading === "TRIGGER_ERROR" ? "Triggering..." : isArtificialError ? "Cancel Error" : "Trigger Error"}
6371
</button>
6472
</div>

src/components/query/QueryListItem.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from "react";
22
import { StatusBadge } from "../status/StatusBadge";
3+
import { Chip } from "../common/Chip";
34
import { getQueryStatusDisplay } from "../../utils/status";
45
import { formatQueryKeyShort } from "../../utils/formatters";
56
import { useStatusTransition } from "../../hooks/useStatusTransition";
@@ -77,6 +78,13 @@ export function QueryListItem({ query, index, isSelected, onSelect, staggerIndex
7778

7879
{/* Query key */}
7980
<div className="flex-1 font-mono text-xs text-gray-700 dark:text-gray-300 break-all">{formatQueryKeyShort(query.queryKey)}</div>
81+
82+
{/* Disabled chip for pending queries */}
83+
{query.state.status === "pending" && (
84+
<Chip variant="disabled" size="sm">
85+
Disabled
86+
</Chip>
87+
)}
8088
</div>
8189
</div>
8290
);

src/components/status/StatusBadge.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useStatusTransition } from "../../hooks/useStatusTransition";
22
import { IconRenderer } from "../common/IconRenderer";
3+
import { statusBadgeVariants } from "../../lib/variants";
34
import type { StatusDisplay } from "../../types/query";
45

56
interface StatusBadgeProps {
@@ -18,8 +19,8 @@ export function StatusBadge({ status, count, className = "", transitionDuration
1819
return (
1920
<div
2021
className={`
21-
w-6 h-6 flex items-center justify-center text-white text-xs font-bold rounded
22-
status-badge-animated status-transition ${status.bgColor} ${transitionClass} ${className}
22+
flex items-center justify-center text-white text-xs font-bold rounded
23+
${statusBadgeVariants({ status: status.variant })} ${transitionClass} ${className}
2324
`}
2425
onAnimationEnd={handleTransitionEnd}
2526
>

0 commit comments

Comments
 (0)