Skip to content

Commit a734720

Browse files
committed
feat: integrate Radix UI Select component for environment and status filtering in the environment client
1 parent d33a368 commit a734720

6 files changed

Lines changed: 528 additions & 83 deletions

File tree

core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@hookform/resolvers": "^5.2.2",
3333
"@radix-ui/react-accordion": "^1.2.12",
3434
"@radix-ui/react-label": "^2.1.8",
35+
"@radix-ui/react-select": "^2.2.6",
3536
"@radix-ui/react-slot": "^1.2.4",
3637
"@radix-ui/react-toggle": "^1.1.10",
3738
"@radix-ui/react-tooltip": "^1.2.8",

core/src/components/env-client.tsx

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,18 @@ import { Filters } from "@/components/filters";
99
import { FiltersProvider } from "@/components/filters/context";
1010
import { Variables } from "@/components/variables";
1111
import { VariablesProvider } from "@/components/variables/context";
12+
import { Input } from "@/components/ui/input";
13+
import {
14+
Select,
15+
SelectContent,
16+
SelectItem,
17+
SelectTrigger,
18+
SelectValue,
19+
} from "@/components/ui/select";
1220
import type { Variables as VariablesType } from "@/lib/types";
21+
import { Status } from "@/lib/types";
1322
import { useCallback } from "react";
23+
import { Search, RotateCcw, Check, X } from "lucide-react";
1424

1525
type EnvClientProps = {
1626
readonly variables: VariablesType;
@@ -160,7 +170,7 @@ const CodePreview = () => {
160170
</h2>
161171

162172
<div className="sticky top-0 max-h-screen overflow-auto py-4">
163-
<div className="bg-muted/20 overflow-hidden rounded-xl border">
173+
<div className="bg-muted/50 overflow-hidden rounded-xl border">
164174
<CodeEditor
165175
lineClassName="hover:bg-muted rounded-md"
166176
code={envFileContent}
@@ -172,9 +182,11 @@ const CodePreview = () => {
172182
);
173183
};
174184

175-
// Environment selector section component
176-
const EnvironmentSelectorSection = () => {
185+
// Combined environment selector and filters component
186+
const EnvironmentSelectorAndFilters = () => {
177187
const { environment, updateVariables } = useEnvironment();
188+
const { query, setQuery, status, setStatus } = useFilters();
189+
const { variables, issues } = useVariables();
178190

179191
const handleEnvironmentChange = (
180192
newEnvironment: any,
@@ -183,12 +195,89 @@ const EnvironmentSelectorSection = () => {
183195
updateVariables(newVariables);
184196
};
185197

198+
const allCount = Object.keys(variables).length;
199+
const validCount = Object.keys(variables).filter(
200+
(key) => !issues.some((issue) => issue.path?.includes(key))
201+
).length;
202+
const invalidCount = allCount - validCount;
203+
204+
const handleStatusChange = (value: string) => {
205+
switch (value) {
206+
case "all":
207+
setStatus(Status.ALL);
208+
break;
209+
case "valid":
210+
setStatus(Status.VALID);
211+
break;
212+
case "invalid":
213+
setStatus(Status.INVALID);
214+
break;
215+
}
216+
};
217+
218+
const getStatusValue = () => {
219+
switch (status) {
220+
case Status.ALL:
221+
return "all";
222+
case Status.VALID:
223+
return "valid";
224+
case Status.INVALID:
225+
return "invalid";
226+
default:
227+
return "all";
228+
}
229+
};
230+
186231
return (
187-
<div className="rounded-lg border bg-white p-4">
188-
<EnvironmentSelector
189-
currentEnvironment={environment}
190-
onEnvironmentChange={handleEnvironmentChange}
191-
/>
232+
<div className="bg-background rounded-lg border p-4">
233+
<div className="space-y-4">
234+
{/* Filters Row */}
235+
<div className="flex flex-row items-center gap-4">
236+
<EnvironmentSelector
237+
currentEnvironment={environment}
238+
onEnvironmentChange={handleEnvironmentChange}
239+
/>
240+
241+
{/* Status Filter Select */}
242+
<Select value={getStatusValue()} onValueChange={handleStatusChange}>
243+
<SelectTrigger className="w-48">
244+
<SelectValue placeholder="Filter by status" />
245+
</SelectTrigger>
246+
<SelectContent>
247+
<SelectItem value="all">
248+
<div className="flex items-center gap-2">
249+
<RotateCcw className="h-3 w-3" />
250+
All ({allCount})
251+
</div>
252+
</SelectItem>
253+
<SelectItem value="valid">
254+
<div className="flex items-center gap-2">
255+
<Check className="h-3 w-3 text-green-600" />
256+
Valid ({validCount})
257+
</div>
258+
</SelectItem>
259+
<SelectItem value="invalid">
260+
<div className="flex items-center gap-2">
261+
<X className="h-3 w-3 text-red-600" />
262+
Invalid ({invalidCount})
263+
</div>
264+
</SelectItem>
265+
</SelectContent>
266+
</Select>
267+
268+
{/* Search Input */}
269+
<div className="relative flex-1">
270+
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
271+
<Input
272+
type="text"
273+
placeholder="Search environment variables..."
274+
value={query}
275+
onChange={(e) => setQuery(e.target.value)}
276+
className="pl-10"
277+
/>
278+
</div>
279+
</div>
280+
</div>
192281
</div>
193282
);
194283
};
@@ -197,17 +286,16 @@ export const Client = ({ variables }: EnvClientProps) => {
197286
const { query, status } = useFilters();
198287
const { variables: envVariables } = useEnvironment();
199288
const currentVariables = getCurrentVariables(envVariables, variables);
200-
289+
201290
return (
202291
<VariablesProvider
203292
variables={currentVariables}
204293
searchQuery={query}
205294
statusFilter={status}
206295
>
207296
<div className="mx-auto max-w-7xl space-y-6">
208-
<EnvironmentSelectorSection />
209-
<Filters />
210-
297+
<EnvironmentSelectorAndFilters />
298+
211299
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
212300
<Variables />
213301
<CodePreview />

core/src/components/environment-selector.tsx

Lines changed: 40 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import { useState, useTransition } from "react";
44
import { Badge } from "./ui/badge";
5-
import { Button } from "./ui/button";
5+
import {
6+
Select,
7+
SelectContent,
8+
SelectItem,
9+
SelectTrigger,
10+
SelectValue,
11+
} from "./ui/select";
612
import { type Environment, reloadEnvironmentVariables } from "@/lib/actions";
7-
import { cn } from "@/lib/utils";
8-
import { RefreshCw, Settings } from "lucide-react";
913

1014
interface EnvironmentSelectorProps {
1115
currentEnvironment?: Environment;
@@ -37,80 +41,45 @@ export function EnvironmentSelector({
3741
});
3842
};
3943

40-
const handleReload = () => {
41-
startTransition(async () => {
42-
try {
43-
const variables = await reloadEnvironmentVariables(environment);
44-
onEnvironmentChange?.(environment, variables);
45-
} catch (error) {
46-
console.error("Failed to reload environment:", error);
47-
}
48-
});
44+
const handleSelectChange = (value: string) => {
45+
handleEnvironmentChange(value as Environment);
4946
};
5047

5148
return (
5249
<div className="flex items-center gap-3">
53-
<div className="flex items-center gap-2">
54-
<Settings className="text-muted-foreground size-4" />
55-
<span className="text-muted-foreground text-sm font-medium">
56-
Environment:
57-
</span>
58-
</div>
59-
60-
<div className="flex items-center gap-2">
61-
<Button
62-
variant={environment === "development" ? "default" : "outline"}
63-
size="sm"
64-
onClick={() => handleEnvironmentChange("development")}
65-
disabled={isReloading}
66-
className={cn(
67-
"relative",
68-
environment === "development" && "ring-2 ring-blue-500/20"
69-
)}
70-
>
71-
Development
72-
{environment === "development" && (
73-
<Badge
74-
variant="secondary"
75-
className="absolute -top-1 -right-1 size-2 border-none bg-green-500 p-0"
76-
/>
77-
)}
78-
</Button>
79-
80-
<Button
81-
variant={environment === "production" ? "default" : "outline"}
82-
size="sm"
83-
onClick={() => handleEnvironmentChange("production")}
84-
disabled={isReloading}
85-
className={cn(
86-
"relative",
87-
environment === "production" && "ring-2 ring-red-500/20"
88-
)}
89-
>
90-
Production
91-
{environment === "production" && (
92-
<Badge
93-
variant="secondary"
94-
className="absolute -top-1 -right-1 size-2 border-none bg-red-500 p-0"
95-
/>
96-
)}
97-
</Button>
98-
</div>
99-
100-
<Button
101-
variant="outline"
102-
size="sm"
103-
onClick={handleReload}
50+
<Select
51+
value={environment}
52+
onValueChange={handleSelectChange}
10453
disabled={isReloading}
105-
className="gap-2"
10654
>
107-
<RefreshCw className={cn("size-4", isReloading && "animate-spin")} />
108-
Reload
109-
</Button>
110-
111-
<div className="text-muted-foreground text-xs">
112-
{isReloading ? "Loading..." : `Using ${environment} environment`}
113-
</div>
55+
<SelectTrigger className="w-40">
56+
<SelectValue placeholder="Select environment" />
57+
</SelectTrigger>
58+
<SelectContent>
59+
<SelectItem value="development">
60+
<div className="flex items-center gap-2">
61+
Development
62+
{environment === "development" && (
63+
<Badge
64+
variant="secondary"
65+
className="size-2 border-none bg-green-500 p-0"
66+
/>
67+
)}
68+
</div>
69+
</SelectItem>
70+
<SelectItem value="production">
71+
<div className="flex items-center gap-2">
72+
Production
73+
{environment === "production" && (
74+
<Badge
75+
variant="secondary"
76+
className="size-2 border-none bg-red-500 p-0"
77+
/>
78+
)}
79+
</div>
80+
</SelectItem>
81+
</SelectContent>
82+
</Select>
11483
</div>
11584
);
11685
}

0 commit comments

Comments
 (0)