Skip to content

Commit a25fde3

Browse files
committed
redesign flags sheet
1 parent 46c22ca commit a25fde3

File tree

3 files changed

+364
-398
lines changed

3 files changed

+364
-398
lines changed

apps/dashboard/app/(main)/websites/[id]/flags/_components/flag-sheet.tsx

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -446,35 +446,32 @@ export function FlagSheet({
446446
/>
447447
</div>
448448

449+
{/* Separator */}
450+
<div className="h-px bg-border" />
451+
449452
{/* Type & Value */}
450453
<div className="space-y-4">
451-
<div className="flex items-center gap-3">
452-
<span className="font-medium text-sm">Type</span>
453-
<div className="flex rounded bg-secondary p-1">
454-
<button
455-
className={cn(
456-
"rounded px-3 py-1.5 font-medium text-sm transition-colors",
457-
isRollout
458-
? "text-muted-foreground hover:text-foreground"
459-
: "bg-background shadow-sm"
460-
)}
461-
onClick={() => form.setValue("flag.type", "boolean")}
462-
type="button"
463-
>
464-
Boolean
465-
</button>
466-
<button
467-
className={cn(
468-
"rounded px-3 py-1.5 font-medium text-sm transition-colors",
469-
isRollout
470-
? "bg-background shadow-sm"
471-
: "text-muted-foreground hover:text-foreground"
472-
)}
473-
onClick={() => form.setValue("flag.type", "rollout")}
474-
type="button"
475-
>
476-
Rollout
477-
</button>
454+
<div className="space-y-2">
455+
<span className="text-muted-foreground text-xs">Type</span>
456+
<div className="flex gap-2">
457+
{(["boolean", "rollout"] as const).map((type) => {
458+
const isSelected = watchedType === type;
459+
return (
460+
<button
461+
className={cn(
462+
"flex-1 rounded border py-2.5 text-center font-medium text-sm capitalize transition-all",
463+
isSelected
464+
? "border-primary bg-primary/5 text-foreground"
465+
: "border-transparent bg-secondary text-muted-foreground hover:text-foreground"
466+
)}
467+
key={type}
468+
onClick={() => form.setValue("flag.type", type)}
469+
type="button"
470+
>
471+
{type}
472+
</button>
473+
);
474+
})}
478475
</div>
479476
</div>
480477

@@ -511,10 +508,10 @@ export function FlagSheet({
511508
{[0, 25, 50, 75, 100].map((preset) => (
512509
<button
513510
className={cn(
514-
"flex-1 rounded py-1 font-medium text-xs transition-colors",
511+
"flex-1 rounded border py-1.5 font-medium text-xs transition-all",
515512
Number(field.value) === preset
516-
? "bg-primary text-primary-foreground"
517-
: "bg-secondary text-muted-foreground hover:text-foreground"
513+
? "border-primary bg-primary text-primary-foreground"
514+
: "border-transparent bg-secondary text-muted-foreground hover:text-foreground"
518515
)}
519516
key={preset}
520517
onClick={() => field.onChange(preset)}
@@ -590,31 +587,34 @@ export function FlagSheet({
590587
const canBeActive = inactiveDeps.length === 0;
591588

592589
return (
593-
<div className="flex items-center justify-between">
594-
<div>
595-
<span className="font-medium text-sm">Status</span>
590+
<div className="space-y-2">
591+
<div className="flex items-center justify-between">
592+
<span className="text-muted-foreground text-xs">
593+
Status
594+
</span>
596595
{!canBeActive && (
597-
<p className="text-amber-600 text-xs">
596+
<span className="text-amber-600 text-xs">
598597
Dependencies must be active first
599-
</p>
598+
</span>
600599
)}
601600
</div>
602-
<div className="flex gap-1">
601+
<div className="flex gap-2">
603602
{(["active", "inactive", "archived"] as const).map(
604603
(status) => {
605604
const isDisabled =
606605
status === "active" && !canBeActive;
606+
const isSelected = field.value === status;
607607
return (
608608
<button
609609
className={cn(
610-
"rounded px-3 py-1.5 font-medium text-xs capitalize transition-colors",
611-
field.value === status
610+
"flex-1 rounded border py-2 font-medium text-sm capitalize transition-all",
611+
isSelected
612612
? status === "active"
613-
? "bg-green-500/15 text-green-600"
613+
? "border-green-500/50 bg-green-500/10 text-green-600"
614614
: status === "inactive"
615-
? "bg-amber-500/15 text-amber-600"
616-
: "bg-muted text-muted-foreground"
617-
: "text-muted-foreground hover:bg-secondary",
615+
? "border-amber-500/50 bg-amber-500/10 text-amber-600"
616+
: "border-border bg-secondary text-foreground"
617+
: "border-transparent bg-secondary text-muted-foreground hover:text-foreground",
618618
isDisabled && "cursor-not-allowed opacity-50"
619619
)}
620620
disabled={isDisabled}
@@ -634,7 +634,7 @@ export function FlagSheet({
634634
/>
635635

636636
{/* Divider */}
637-
<div className="border-t" />
637+
<div className="h-px bg-border" />
638638

639639
{/* Advanced Options */}
640640
<div className="space-y-1">

apps/dashboard/app/(main)/websites/[id]/flags/_components/schedule-manager.tsx

Lines changed: 83 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
PlusIcon,
99
PowerIcon,
1010
TrashIcon,
11+
XIcon,
1112
} from "@phosphor-icons/react";
1213
import { AnimatePresence, motion } from "framer-motion";
1314
import { Button } from "@/components/ui/button";
@@ -38,7 +39,7 @@ function DateTimePicker({
3839
<PopoverTrigger asChild>
3940
<Button
4041
className={cn(
41-
"h-8 w-full justify-start gap-2 text-left font-normal",
42+
"h-9 w-full justify-start gap-2 text-left font-normal",
4243
!value && "text-muted-foreground"
4344
)}
4445
type="button"
@@ -47,7 +48,7 @@ function DateTimePicker({
4748
<CalendarIcon size={14} />
4849
{value
4950
? formatDate(new Date(value), DATE_FORMATS.DATE_TIME_12H)
50-
: "Pick date…"}
51+
: "Select date & time…"}
5152
</Button>
5253
</PopoverTrigger>
5354
<PopoverContent align="start" className="w-auto p-0">
@@ -101,7 +102,7 @@ export function ScheduleManager({ form, flagId }: ScheduleManagerProps) {
101102
form.setValue("schedule.rolloutSteps", [
102103
{
103104
scheduledAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
104-
value: 0,
105+
value: 25,
105106
},
106107
]);
107108
}
@@ -151,49 +152,53 @@ export function ScheduleManager({ form, flagId }: ScheduleManagerProps) {
151152
if (!scheduleEnabled) {
152153
return (
153154
<div className="space-y-2">
154-
<button
155-
className="flex w-full items-center gap-3 rounded py-2 text-left transition-colors hover:bg-accent/50"
156-
onClick={() => enableSchedule("enable")}
157-
type="button"
158-
>
159-
<PowerIcon className="text-green-500" size={16} weight="fill" />
160-
<div>
161-
<p className="font-medium text-sm">Enable on schedule</p>
162-
<p className="text-muted-foreground text-xs">
163-
Automatically enable this flag
164-
</p>
165-
</div>
166-
</button>
155+
<div className="grid gap-2 sm:grid-cols-2">
156+
<button
157+
className="flex items-center gap-3 rounded border border-transparent bg-secondary p-3 text-left transition-all hover:border-green-500/30 hover:bg-green-500/5"
158+
onClick={() => enableSchedule("enable")}
159+
type="button"
160+
>
161+
<div className="flex size-8 items-center justify-center rounded bg-green-500/10">
162+
<PowerIcon className="text-green-500" size={16} weight="fill" />
163+
</div>
164+
<div>
165+
<p className="font-medium text-sm">Enable</p>
166+
<p className="text-muted-foreground text-xs">At a set time</p>
167+
</div>
168+
</button>
167169

168-
<button
169-
className="flex w-full items-center gap-3 rounded py-2 text-left transition-colors hover:bg-accent/50"
170-
onClick={() => enableSchedule("disable")}
171-
type="button"
172-
>
173-
<PowerIcon
174-
className="rotate-180 text-red-500"
175-
size={16}
176-
weight="fill"
177-
/>
178-
<div>
179-
<p className="font-medium text-sm">Disable on schedule</p>
180-
<p className="text-muted-foreground text-xs">
181-
Automatically disable this flag
182-
</p>
183-
</div>
184-
</button>
170+
<button
171+
className="flex items-center gap-3 rounded border border-transparent bg-secondary p-3 text-left transition-all hover:border-red-500/30 hover:bg-red-500/5"
172+
onClick={() => enableSchedule("disable")}
173+
type="button"
174+
>
175+
<div className="flex size-8 items-center justify-center rounded bg-red-500/10">
176+
<PowerIcon
177+
className="rotate-180 text-red-500"
178+
size={16}
179+
weight="fill"
180+
/>
181+
</div>
182+
<div>
183+
<p className="font-medium text-sm">Disable</p>
184+
<p className="text-muted-foreground text-xs">At a set time</p>
185+
</div>
186+
</button>
187+
</div>
185188

186189
{isRolloutFlag && (
187190
<button
188-
className="flex w-full items-center gap-3 rounded py-2 text-left transition-colors hover:bg-accent/50"
191+
className="flex w-full items-center gap-3 rounded border border-transparent bg-secondary p-3 text-left transition-all hover:border-primary/30 hover:bg-primary/5"
189192
onClick={() => enableSchedule("update_rollout")}
190193
type="button"
191194
>
192-
<LightningIcon className="text-primary" size={16} weight="fill" />
195+
<div className="flex size-8 items-center justify-center rounded bg-primary/10">
196+
<LightningIcon className="text-primary" size={16} weight="fill" />
197+
</div>
193198
<div>
194199
<p className="font-medium text-sm">Gradual rollout</p>
195200
<p className="text-muted-foreground text-xs">
196-
Schedule percentage increases
201+
Increase percentage over time
197202
</p>
198203
</div>
199204
</button>
@@ -207,21 +212,35 @@ export function ScheduleManager({ form, flagId }: ScheduleManagerProps) {
207212
<div className="space-y-4">
208213
{/* Header */}
209214
<div className="flex items-center justify-between">
210-
<span className="font-medium text-sm">
211-
{scheduleType === "enable"
212-
? "Enable on schedule"
213-
: scheduleType === "disable"
214-
? "Disable on schedule"
215-
: "Gradual rollout"}
216-
</span>
217-
<Button
215+
<div className="flex items-center gap-2">
216+
{scheduleType === "enable" && (
217+
<PowerIcon className="text-green-500" size={16} weight="fill" />
218+
)}
219+
{scheduleType === "disable" && (
220+
<PowerIcon
221+
className="rotate-180 text-red-500"
222+
size={16}
223+
weight="fill"
224+
/>
225+
)}
226+
{scheduleType === "update_rollout" && (
227+
<LightningIcon className="text-primary" size={16} weight="fill" />
228+
)}
229+
<span className="font-medium text-sm">
230+
{scheduleType === "enable"
231+
? "Enable on schedule"
232+
: scheduleType === "disable"
233+
? "Disable on schedule"
234+
: "Gradual rollout"}
235+
</span>
236+
</div>
237+
<button
238+
className="flex size-6 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground"
218239
onClick={disableSchedule}
219-
size="sm"
220240
type="button"
221-
variant="ghost"
222241
>
223-
Cancel
224-
</Button>
242+
<XIcon size={14} />
243+
</button>
225244
</div>
226245

227246
{/* Simple enable/disable */}
@@ -239,31 +258,26 @@ export function ScheduleManager({ form, flagId }: ScheduleManagerProps) {
239258

240259
{/* Rollout steps */}
241260
{scheduleType === "update_rollout" && (
242-
<div className="space-y-2">
261+
<div className="space-y-3">
243262
<AnimatePresence mode="popLayout">
244263
{rolloutSteps.map((step, index) => (
245264
<motion.div
246265
animate={{ opacity: 1, y: 0 }}
247-
className="flex items-center gap-2"
266+
className="grid grid-cols-[1fr_auto_auto] items-center gap-2"
248267
exit={{ opacity: 0, y: -10 }}
249268
initial={{ opacity: 0, y: 10 }}
250269
key={index}
251270
layout
252271
>
253-
<span className="w-5 shrink-0 text-center text-muted-foreground text-xs">
254-
{index + 1}.
255-
</span>
256-
<div className="flex-1">
257-
<DateTimePicker
258-
onChange={(date) =>
259-
updateRolloutStep(index, "scheduledAt", date)
260-
}
261-
value={step.scheduledAt}
262-
/>
263-
</div>
272+
<DateTimePicker
273+
onChange={(date) =>
274+
updateRolloutStep(index, "scheduledAt", date)
275+
}
276+
value={step.scheduledAt}
277+
/>
264278
<div className="flex items-center gap-1">
265279
<Input
266-
className="h-8 w-16"
280+
className="h-9 w-16 text-center"
267281
max={100}
268282
min={0}
269283
onChange={(e) =>
@@ -272,27 +286,25 @@ export function ScheduleManager({ form, flagId }: ScheduleManagerProps) {
272286
type="number"
273287
value={step.value}
274288
/>
275-
<span className="text-muted-foreground text-xs">%</span>
289+
<span className="text-muted-foreground text-sm">%</span>
276290
</div>
277-
<Button
278-
className="h-8 w-8 shrink-0"
291+
<button
292+
className="flex size-9 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
279293
onClick={() => removeRolloutStep(index)}
280-
size="icon"
281294
type="button"
282-
variant="ghost"
283295
>
284-
<TrashIcon className="text-destructive" size={14} />
285-
</Button>
296+
<TrashIcon size={14} />
297+
</button>
286298
</motion.div>
287299
))}
288300
</AnimatePresence>
289301

290302
<Button
291-
className="w-full"
303+
className="w-full text-muted-foreground"
292304
onClick={addRolloutStep}
293305
size="sm"
294306
type="button"
295-
variant="ghost"
307+
variant="outline"
296308
>
297309
<PlusIcon size={14} />
298310
Add step

0 commit comments

Comments
 (0)