Skip to content

Commit

Permalink
UI overhaul
Browse files Browse the repository at this point in the history
Use flat UI instead of glass. The backdrop blurs didn't stack and the
glass didn't really go with the maze.

Redesign action bar to be mobile-first.

Unify colors using CSS variables.

Closes #2
  • Loading branch information
imericxu committed Jul 15, 2024
1 parent 66e8344 commit 3fef001
Show file tree
Hide file tree
Showing 12 changed files with 519 additions and 491 deletions.
104 changes: 104 additions & 0 deletions src/app/components/ActionBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { MazeEvent, MazeSettings } from "@/lib/maze";
import { MazeController } from "@/lib/MazeController";
import { LucideSlidersHorizontal } from "lucide-react";
import { type ReactElement, useCallback } from "react";
import {
Button,
Dialog,
DialogTrigger,
Modal,
ModalOverlay,
} from "react-aria-components";
import { useImmer } from "use-immer";
import SettingsForm, { FormFields } from "./SettingsForm";

export interface ActionBarProps {
onEvent: (event: MazeEvent, settings: MazeSettings) => void;
solvable: boolean;
}

export default function ActionBar({
onEvent,
solvable,
}: ActionBarProps): ReactElement {
const [fields, setFields] = useImmer<FormFields>({
dimensions: {
rows: "",
cols: "",
cellWallRatio: MazeController.DEFAULTS.dimensions.cellWallRatio,
},
generationAlgorithm: MazeController.DEFAULTS.generationAlgorithm,
doAnimateGenerating: MazeController.DEFAULTS.doAnimateGenerating,
solveAlgorithm: MazeController.DEFAULTS.solveAlgorithm,
doAnimateSolving: MazeController.DEFAULTS.doAnimateSolving,
});

const handleEvent = useCallback(
(action: MazeEvent) => {
const settings: MazeSettings = {
dimensions: {
rows: fields.dimensions.rows
? parseInt(fields.dimensions.rows)
: MazeController.DEFAULTS.dimensions.rows,
cols: fields.dimensions.cols
? parseInt(fields.dimensions.cols)
: MazeController.DEFAULTS.dimensions.cols,
cellWallRatio: fields.dimensions.cellWallRatio,
},
generationAlgorithm: fields.generationAlgorithm,
doAnimateGenerating: fields.doAnimateGenerating,
solveAlgorithm: fields.solveAlgorithm,
doAnimateSolving: fields.doAnimateSolving,
};
console.log("Settings", settings);
onEvent(action, settings);
},
[fields, onEvent],
);

return (
<div className="flex w-full min-w-fit max-w-96 justify-center gap-[clamp(4px,5%,16px)] border border-b-2 border-border bg-surface-overlay px-4 py-2">
{/* Generate Button */}
<Button
onPress={() => handleEvent("generate")}
className="h-8 border border-b-2 border-border bg-secondary px-4 transition-all pressed:border-b pressed:border-t-2"
>
Generate
</Button>

{/* Solve Button */}
<Button
isDisabled={!solvable}
onPress={() => handleEvent("solve")}
className="h-8 border border-b-2 border-border bg-secondary px-4 pressed:border-b pressed:border-t-2 disabled:cursor-not-allowed disabled:border-b disabled:bg-disabled disabled:text-text-disabled"
>
Solve
</Button>

{/* More Settings Button */}
<DialogTrigger>
<Button
aria-label="More Settings"
className="h-8 border border-b-2 border-border bg-primary px-4 pressed:border-b pressed:border-t-2"
>
<LucideSlidersHorizontal strokeWidth={1.5} size={20} />
</Button>

{/* Settings Popover */}
<ModalOverlay className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/30 p-4 backdrop-blur-sm">
<Modal className="relative w-full">
<Dialog className="w-full outline-none">
{({ close }) => (
<SettingsForm
fields={fields}
setFields={setFields}
close={close}
/>
)}
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
</div>
);
}
74 changes: 0 additions & 74 deletions src/app/components/GlassSelect.tsx

This file was deleted.

78 changes: 78 additions & 0 deletions src/app/components/MySelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { LucideCheck, LucideChevronDown } from "lucide-react";
import { type ReactElement } from "react";
import {
Button,
Label,
ListBox,
ListBoxItem,
Popover,
Select,
SelectValue,
} from "react-aria-components";
import { twMerge } from "tailwind-merge";

export interface MySelectProps {
name: string;
defaultSelectedKey: string;
label: string;
ariaLabel?: string;
items: [string, string][];
className?: string;
}

export default function MySelect({
name,
defaultSelectedKey,
label,
ariaLabel,
items,
className,
}: MySelectProps): ReactElement {
return (
<Select
name={name}
defaultSelectedKey={defaultSelectedKey}
className={twMerge("group/select flex w-full flex-col", className)}
>
<Label aria-label={ariaLabel} className="text-sm">
{label}
</Label>

{/* Select Button */}
<Button className="relative h-8 w-full border border-border pe-9 ps-3 text-start transition-colors hover:bg-primary-light">
<SelectValue className="block w-full overflow-hidden overflow-ellipsis text-nowrap" />
{/* Down arrow. Flips over on open */}
<LucideChevronDown
aria-hidden
className="absolute end-3 top-1/2 -translate-y-1/2 stroke-1 transition group-open/select:rotate-180"
/>
</Button>

{/* Dropdown */}
<Popover className="min-w-[var(--trigger-width)]">
<ListBox
items={items}
className="flex flex-col border border-border bg-surface"
>
{(item) => (
<ListBoxItem
key={item[0]}
id={item[0]}
textValue={item[1]}
className="group/item relative flex h-8 cursor-pointer select-none items-center gap-1 pe-3 ps-9 focus:bg-primary-light focus:outline-none"
>
{/* Checkmark */}
<LucideCheck
aria-hidden
className="invisible absolute left-2 top-1/2 h-6 w-6 shrink-0 -translate-y-1/2 stroke-1 group-selected/item:visible"
/>

{/* Label */}
<span>{item[1]}</span>
</ListBoxItem>
)}
</ListBox>
</Popover>
</Select>
);
}
Loading

0 comments on commit 3fef001

Please sign in to comment.