Skip to content

Commit

Permalink
feat: Improve sidebar design and scrollareas (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
evadecker authored Oct 21, 2024
1 parent 6f7885b commit 6078c8a
Show file tree
Hide file tree
Showing 16 changed files with 105 additions and 82 deletions.
5 changes: 5 additions & 0 deletions .changeset/curvy-lions-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"namesake": minor
---

Improve sidebar design
5 changes: 5 additions & 0 deletions .changeset/few-oranges-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"namesake": patch
---

Fix user quest sorting
2 changes: 1 addition & 1 deletion convex/userQuests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const getQuestsForCurrentUser = userQuery({
userQuests.map(async (userQuest) => {
const quest = await ctx.db.get(userQuest.questId);
return quest && quest.deletionTime === undefined
? { ...quest, completionTime: userQuest.completionTime }
? { ...quest, ...userQuest }
: null;
}),
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/AppHeader/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const AppHeader = () => {
};

return (
<div className="flex gap-4 bg-gray-app items-center w-screen h-14 px-4 border-b border-gray-dim sticky top-0 z-20">
<div className="flex shrink-0 gap-4 bg-gray-app items-center w-screen h-14 px-4 border-b border-gray-dim sticky top-0 z-20">
<Link href={{ to: "/" }}>
<Logo className="h-[1.25rem]" />
</Link>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Container/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface ContainerProps {

export function Container({ className, children }: ContainerProps) {
return (
<div className={twMerge(className, "w-full flex-1 p-6 mx-auto")}>
<div className={twMerge("w-full flex-1 p-6 mx-auto", className)}>
{children}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/GridList/GridList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const itemStyles = tv({
variants: {
isSelected: {
false: "",
true: "bg-purple-3 dark:bg-purple-dark-3 z-20",
true: "bg-purple-3 dark:bg-purpledark-3 z-20",
},
isDisabled: {
true: "opacity-50 cursor-default forced-colors:text-[GrayText] z-10",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function MenuItem(props: MenuItemProps) {
{isSelected && <RiCheckLine aria-hidden className="w-4 h-4" />}
</span>
)}
<span className="flex items-center cursor-pointer flex-1 gap-2 font-normal truncate group-selected:font-semibold">
<span className="flex items-center flex-1 gap-2 font-normal truncate group-selected:font-semibold">
{children}
</span>
{hasSubmenu && (
Expand Down
12 changes: 10 additions & 2 deletions src/components/PageHeader/PageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { RemixiconComponentType } from "@remixicon/react";
import { twMerge } from "tailwind-merge";

export interface PageHeaderProps {
title: string;
icon?: RemixiconComponentType;
badge?: React.ReactNode;
subtitle?: string;
children?: React.ReactNode;
className?: string;
}

export const PageHeader = ({
Expand All @@ -14,13 +16,19 @@ export const PageHeader = ({
badge,
subtitle,
children,
className,
}: PageHeaderProps) => {
return (
<header className="flex items-center justify-between pb-6 gap-6 text-gray-normal">
<header
className={twMerge(
"h-12 flex items-center justify-between gap-6 text-gray-normal",
className,
)}
>
<div className="flex flex-col gap-1">
<div className="flex gap-2 items-center">
{Icon && <Icon className="text-gray-dim" />}
<h1 className="text-2xl font-semibold">{title}</h1>
<h1 className="text-xl font-medium">{title}</h1>
{badge}
</div>
{subtitle && <p className="text-gray-dim">{subtitle}</p>}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ProgressBar/ProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function ProgressBar({ label, ...props }: ProgressBarProps) {
{...props}
className={composeTailwindRenderProps(
props.className,
"flex flex-col gap-1",
"flex flex-col flex-1 gap-1",
)}
>
{({ percentage, valueText, isIndeterminate }) => (
Expand All @@ -26,7 +26,7 @@ export function ProgressBar({ label, ...props }: ProgressBarProps) {
{valueText}
</span>
</div>
<div className="w-64 h-2 rounded-full bg-gray-4 dark:bg-graydark-4 outline outline-1 -outline-offset-1 outline-transparent relative overflow-hidden">
<div className="w-full h-2 rounded-full bg-gray-4 dark:bg-graydark-4 outline outline-1 -outline-offset-1 outline-transparent relative overflow-hidden">
<div
className={`absolute top-0 h-full rounded-full bg-purple-9 dark:bg-purpledark-9 transition-all forced-colors:bg-[Highlight] ${isIndeterminate ? "left-full animate-in duration-10 [--tw-enter-translate-x:calc(-16rem-1%)] slide-out-to-right-full repeat-infinite ease-out" : "left-0"}`}
style={{ width: `${isIndeterminate ? 40 : percentage}%` }}
Expand Down
2 changes: 1 addition & 1 deletion src/routes/_authenticated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const Route = createFileRoute("/_authenticated")({

function AuthenticatedRoute() {
return (
<main className="flex flex-col flex-1 min-h-screen text-gray-normal">
<main className="flex flex-col h-screen text-gray-normal">
<AppHeader />
<Outlet />
</main>
Expand Down
4 changes: 2 additions & 2 deletions src/routes/_authenticated/admin/quests/$questId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function AdminQuestDetailRoute() {
};

return (
<div>
<>
<PageHeader
icon={quest.icon ? ICONS[quest.icon] : undefined}
title={quest.title}
Expand Down Expand Up @@ -207,6 +207,6 @@ function AdminQuestDetailRoute() {
onSubmit={() => setIsNewFieldModalOpen(false)}
onFieldCreated={handleFieldCreated}
/>
</div>
</>
);
}
4 changes: 2 additions & 2 deletions src/routes/_authenticated/admin/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const Route = createFileRoute("/_authenticated/admin")({

function AdminRoute() {
return (
<div className="flex flex-1 max-w-screen">
<div className="flex flex-1 max-w-screen min-h-0">
<Nav
routes={[
{
Expand All @@ -39,7 +39,7 @@ function AdminRoute() {
},
]}
/>
<Container>
<Container className="overflow-y-auto">
<Outlet />
</Container>
</div>
Expand Down
50 changes: 28 additions & 22 deletions src/routes/_authenticated/quests/$questId.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Badge,
Button,
Container,
Empty,
Menu,
MenuItem,
Expand Down Expand Up @@ -48,11 +49,13 @@ function QuestDetailRoute() {
navigate({ to: "/quests" });
};

// TODO: Improve loading state to prevent flash of empty
if (quest === undefined || userQuest === undefined) return;
if (quest === null || userQuest === null) return "Quest not found";
if (quest === null || userQuest === null)
return <Empty title="Quest not found" icon={RiSignpostLine} />;

return (
<main className="flex-1">
<div className="flex flex-col flex-1">
<PageHeader
icon={ICONS[quest.icon]}
title={quest.title}
Expand All @@ -66,13 +69,14 @@ function QuestDetailRoute() {
)}
</div>
}
className="px-6 border-b border-gray-dim h-16"
>
<MenuTrigger>
<Button
aria-label="Quest settings"
variant="icon"
size="small"
icon={RiMoreFill}
className="-mr-2"
/>
<Menu placement="bottom end">
{!userQuest.completionTime && (
Expand All @@ -92,24 +96,26 @@ function QuestDetailRoute() {
</Menu>
</MenuTrigger>
</PageHeader>
{questSteps && questSteps.length > 0 ? (
<ol className="flex flex-col gap-6">
{questSteps.map((step, i) => {
if (!step) return;
return (
<li key={`${quest.title}-step-${i}`}>
<QuestStep
title={step.title}
description={step.description}
fields={step.fields}
/>
</li>
);
})}
</ol>
) : (
<Empty title="This quest has no steps" icon={RiSignpostLine} />
)}
</main>
<Container className="overflow-y-auto">
{questSteps && questSteps.length > 0 ? (
<ol className="flex flex-col gap-6">
{questSteps.map((step, i) => {
if (!step) return;
return (
<li key={`${quest.title}-step-${i}`}>
<QuestStep
title={step.title}
description={step.description}
fields={step.fields}
/>
</li>
);
})}
</ol>
) : (
<Empty title="This quest has no steps" icon={RiSignpostLine} />
)}
</Container>
</div>
);
}
84 changes: 41 additions & 43 deletions src/routes/_authenticated/quests/route.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import {
Badge,
Button,
Container,
Empty,
Form,
GridList,
GridListItem,
Menu,
MenuItem,
MenuSeparator,
MenuTrigger,
Modal,
ProgressBar,
Tooltip,
Expand All @@ -17,11 +20,8 @@ import { ICONS, type SortQuestsBy } from "@convex/constants";
import {
RiAddLine,
RiCheckLine,
RiCheckboxMultipleBlankLine,
RiCheckboxMultipleFill,
RiMoreFill,
RiSignpostLine,
RiSortNumberAsc,
RiSortNumberDesc,
} from "@remixicon/react";
import { Outlet, createFileRoute } from "@tanstack/react-router";
import {
Expand Down Expand Up @@ -129,11 +129,6 @@ function IndexRoute() {
);
const [isNewQuestModalOpen, setIsNewQuestModalOpen] = useState(false);

const toggleSortByMenu = () => {
if (sortBy === "newest") setSortBy("oldest");
else setSortBy("newest");
};

const toggleShowCompleted = () => {
setShowCompleted(!showCompleted);
};
Expand Down Expand Up @@ -184,52 +179,55 @@ function IndexRoute() {
});

return (
<div className="flex flex-col gap-4">
<div className="flex items-center">
<div className="flex flex-col w-80 border-r border-gray-dim overflow-y-auto">
<div className="flex items-center py-3 px-4 h-16 border-b border-gray-dim">
<ProgressBar
label="Quests complete"
value={completedQuests}
maxValue={totalQuests}
valueLabel={`${completedQuests} of ${totalQuests}`}
className="mr-4"
/>
{Boolean(completedQuests && completedQuests > 0) && (
<TooltipTrigger>
<Button
aria-label="Show completed quests"
onPress={toggleShowCompleted}
icon={
showCompleted
? RiCheckboxMultipleFill
: RiCheckboxMultipleBlankLine
}
variant="icon"
/>
<Tooltip>
{`${showCompleted ? "Hide" : "Show"} completed quests`}
</Tooltip>
</TooltipTrigger>
)}
<TooltipTrigger>
<Button
aria-label="Sort by"
onPress={() => toggleSortByMenu()}
icon={sortBy === "newest" ? RiSortNumberAsc : RiSortNumberDesc}
variant="icon"
/>
<Tooltip>{`Sort by ${sortBy === "newest" ? "oldest" : "newest"}`}</Tooltip>
</TooltipTrigger>
<MenuTrigger>
<Button icon={RiMoreFill} variant="icon" />
<Menu>
<MenuItem
onAction={() => setSortBy("newest")}
isDisabled={sortBy === "newest"}
>
Sort by newest
</MenuItem>
<MenuItem
onAction={() => setSortBy("oldest")}
isDisabled={sortBy === "oldest"}
>
Sort by oldest
</MenuItem>

{typeof completedQuests === "number" && completedQuests > 0 && (
<>
<MenuSeparator />
<MenuItem onAction={toggleShowCompleted}>
{showCompleted
? `Hide ${completedQuests} completed ${completedQuests > 1 ? "quests" : "quest"}`
: `Show ${completedQuests} completed ${completedQuests > 1 ? "quests" : "quest"}`}
</MenuItem>
</>
)}
</Menu>
</MenuTrigger>
<TooltipTrigger>
<Button
aria-label="Add quest"
onPress={() => setIsNewQuestModalOpen(true)}
icon={RiAddLine}
variant="icon"
className="-mr-1"
/>
<Tooltip>Add quest</Tooltip>
</TooltipTrigger>
</div>
<GridList aria-label="My quests">
<GridList aria-label="My quests" className="border-none px-1 py-2">
{filteredQuests.map((quest) => {
if (quest === null) return null;

Expand All @@ -241,7 +239,7 @@ function IndexRoute() {
key={quest._id}
href={{
to: "/quests/$questId",
params: { questId: quest._id },
params: { questId: quest.questId },
}}
>
<div className="flex items-center justify-between gap-2 w-full">
Expand Down Expand Up @@ -282,9 +280,9 @@ function IndexRoute() {
};

return (
<Container className="max-w-screen-lg">
<>
<Authenticated>
<div className="flex gap-6">
<div className="flex flex-1 min-h-0">
<MyQuests />
<Outlet />
</div>
Expand All @@ -297,6 +295,6 @@ function IndexRoute() {
<Unauthenticated>
<h1>Please log in</h1>
</Unauthenticated>
</Container>
</>
);
}
Loading

0 comments on commit 6078c8a

Please sign in to comment.