Skip to content

Commit

Permalink
feat: Update design for links and docs sections (#346)
Browse files Browse the repository at this point in the history
  • Loading branch information
evadecker authored Jan 28, 2025
1 parent 402e2b7 commit 6a2bc7b
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 112 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-parents-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"namesake": minor
---

Refine design for quest documents and references
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/components/common/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const buttonStyles = tv({
primary:
"bg-purple-solid text-white border-purple-10 dark:border-purpledark-10 shadow-sm",
secondary:
"bg-white dark:bg-graydark-3 hover:bg-white dark:hover:bg-graydark-4 text-gray-normal shadow-sm",
"bg-white dark:bg-graydark-3 hover:bg-white dark:hover:bg-graydark-4 hover:border-gray-7 dark:hover:border-graydark-7 text-gray-normal shadow-sm",
destructive: "bg-red-solid",
icon: "bg-transparent hover:bg-graya-3 dark:hover:bg-graydarka-3 text-gray-dim hover:text-gray-normal border-0 flex items-center justify-center rounded-full",
ghost:
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const overlayStyles = tv({
});

const modalStyles = tv({
base: "p-5 w-[400px] max-w-full max-h-full rounded-2xl bg-gray-subtle forced-colors:bg-[Canvas] flex flex-col items-start gap-4 shadow-2xl bg-clip-padding border border-gray-dim",
base: "p-5 w-[400px] max-w-full max-h-full rounded-2xl bg-white dark:bg-graydark-2 forced-colors:bg-[Canvas] flex flex-col items-start gap-4 shadow-2xl bg-clip-padding border border-gray-dim",
variants: {
isEntering: {
true: "animate-in zoom-in-105 ease-out duration-2",
Expand Down
3 changes: 3 additions & 0 deletions src/components/common/TextField/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface TextFieldProps extends AriaTextFieldProps {
suffix?: React.ReactNode;
errorMessage?: string | ((validation: ValidationResult) => string);
size?: FieldSize;
placeholder?: string;
}

export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
Expand All @@ -38,6 +39,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
errorMessage,
type,
size = "medium",
placeholder,
...props
},
ref,
Expand All @@ -63,6 +65,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
type === "tel" && "tabular-nums",
)}
size={size}
placeholder={placeholder}
/>
{suffix}
{type === "password" && (
Expand Down
86 changes: 42 additions & 44 deletions src/components/quests/QuestDocuments/QuestDocuments.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Badge, Button, Tooltip, TooltipTrigger } from "@/components/common";
import { DocumentCard, EditQuestDocumentModal } from "@/components/quests";
import { Empty } from "@/components/common";
import {
DocumentCard,
EditQuestDocumentModal,
QuestSection,
} from "@/components/quests";
import { api } from "@convex/_generated/api";
import type { Doc } from "@convex/_generated/dataModel";
import { useQuery } from "convex/react";
import { Plus } from "lucide-react";
import { FileIcon } from "lucide-react";
import { useState } from "react";
import { Heading } from "react-aria-components";

type QuestDocumentsProps = {
quest: Doc<"quests">;
Expand All @@ -18,48 +21,43 @@ export const QuestDocuments = ({ quest, editable }: QuestDocumentsProps) => {
questId: quest._id,
});

if ((!documents || documents.length === 0) && !editable) return null;
const hasDocuments = documents && documents.length > 0;

if (!hasDocuments && !editable) return null;

return (
<div className="p-4 rounded-lg border border-gray-dim">
<header className="flex gap-1 items-center pb-4">
<Heading className="text-gray-dim text-sm">Documents</Heading>
<Badge size="xs" className="rounded-full">
{documents?.length || 0}
</Badge>
{editable && (
<>
<TooltipTrigger>
<Button
onPress={() => setIsEditing(true)}
variant="ghost"
icon={Plus}
size="small"
aria-label="Add document"
/>
<Tooltip>Add document</Tooltip>
</TooltipTrigger>
<EditQuestDocumentModal
quest={quest}
isOpen={isEditing}
onOpenChange={setIsEditing}
<QuestSection
title="Documents"
action={
editable
? {
children: "Add document",
onPress: () => setIsEditing(true),
}
: undefined
}
>
{editable && (
<EditQuestDocumentModal
quest={quest}
isOpen={isEditing}
onOpenChange={setIsEditing}
/>
)}
{!hasDocuments ? (
<Empty title="No documents" icon={FileIcon} />
) : (
<div className="flex gap-4 overflow-x-auto p-4 -m-4">
{documents?.map((document) => (
<DocumentCard
key={document._id}
title={document.title}
code={document.code}
downloadUrl={document.url ?? undefined}
/>
</>
)}
</header>
<div className="flex gap-4 overflow-x-auto p-4 -m-4">
{documents?.map((document) => (
<DocumentCard
key={document._id}
title={document.title}
code={document.code}
downloadUrl={document.url ?? undefined}
/>
))}
{editable && documents?.length === 0 && (
<Button onPress={() => setIsEditing(true)}>Uplaod document</Button>
)}
</div>
</div>
))}
</div>
)}
</QuestSection>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Button,
Empty,
Link,
Menu,
MenuItem,
Expand All @@ -12,15 +13,21 @@ import { useMutation } from "convex/react";
import { Link as LinkIcon, MoreHorizontal } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { QuestSection } from "../QuestSection/QuestSection";

type QuestUrlProps = {
type QuestReferenceProps = {
quest: Doc<"quests">;
url: string;
editable?: boolean;
onSave: (url: string) => void;
};

const QuestUrl = ({ quest, url, editable, onSave }: QuestUrlProps) => {
const QuestReference = ({
quest,
url,
editable,
onSave,
}: QuestReferenceProps) => {
const [text, setText] = useState(url);
const [isEditing, setIsEditing] = useState(false);
const deleteUrl = useMutation(api.quests.deleteUrl);
Expand All @@ -34,15 +41,21 @@ const QuestUrl = ({ quest, url, editable, onSave }: QuestUrlProps) => {
};

const handleSave = () => {
onSave(text);
onSave(url);
setIsEditing(false);
};

return (
<div key={url} className="flex gap-1 items-center justify-between">
{isEditing ? (
<div className="flex gap-1 items-end">
<TextField value={text} onChange={setText} type="url" label="URL" />
<div className="flex gap-1 items-end w-full">
<TextField
value={text}
onChange={setText}
type="url"
label="URL"
className="flex-1"
/>
<Button type="button" variant="secondary" onPress={handleSave}>
Save
</Button>
Expand Down Expand Up @@ -72,14 +85,17 @@ const QuestUrl = ({ quest, url, editable, onSave }: QuestUrlProps) => {
);
};

type QuestUrlsProps = {
type QuestReferencesProps = {
quest: Doc<"quests">;
editable?: boolean;
};

export const QuestUrls = ({ quest, editable = false }: QuestUrlsProps) => {
export const QuestReferences = ({
quest,
editable = false,
}: QuestReferencesProps) => {
const [isAddingNew, setIsAddingNew] = useState(false);
const [newUrl, setNewUrl] = useState("");
const [newUrl, setNewUrl] = useState("https://");
const updateUrls = useMutation(api.quests.setUrls);

const handleSave = (oldUrl: string, newUrl: string) => {
Expand All @@ -90,59 +106,67 @@ export const QuestUrls = ({ quest, editable = false }: QuestUrlsProps) => {

updateUrls({ questId: quest._id, urls: updatedUrls });
setIsAddingNew(false);
setNewUrl("");
setNewUrl("https://");
} catch (error) {
toast.error("Couldn't update URL");
}
};

if (!editable && quest.urls?.length === 0) return null;
if (!editable && !quest.urls) return null;

return (
<div className="flex flex-col items-start gap-1 mb-4">
{quest.urls?.map((url) => (
<QuestUrl
key={url}
quest={quest}
url={url}
editable={editable}
onSave={(newUrl) => handleSave(url, newUrl)}
/>
))}
{editable &&
(isAddingNew ? (
<div className="flex gap-1 items-end">
<TextField
value={newUrl}
onChange={setNewUrl}
type="url"
label="URL"
className="flex-1"
/>
<Button
type="button"
variant="secondary"
onPress={() => setIsAddingNew(false)}
>
Cancel
</Button>
<Button
type="button"
variant="secondary"
onPress={() => handleSave("", newUrl)}
>
Save
</Button>
</div>
) : (
<QuestSection
title="References"
action={
editable
? {
children: "Add reference",
onPress: () => setIsAddingNew(true),
}
: undefined
}
>
{editable && isAddingNew && (
<div className="flex gap-1 items-end w-full mb-4">
<TextField
value={newUrl}
onChange={setNewUrl}
type="url"
aria-label="URL"
className="flex-1"
autoFocus
/>
<Button
type="button"
variant="secondary"
onPress={() => setIsAddingNew(true)}
onPress={() => setIsAddingNew(false)}
>
Add URL
Cancel
</Button>
))}
</div>
<Button
type="button"
variant="secondary"
onPress={() => handleSave("", newUrl)}
>
Save
</Button>
</div>
)}
{!quest.urls ? (
<Empty title="No references" icon={LinkIcon} />
) : (
<div className="flex flex-col gap-1">
{quest.urls?.map((url) => (
<QuestReference
key={url}
quest={quest}
url={url}
editable={editable}
onSave={(newUrl) => handleSave(url, newUrl)}
/>
))}
</div>
)}
</QuestSection>
);
};
24 changes: 24 additions & 0 deletions src/components/quests/QuestSection/QuestSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Button, type ButtonProps } from "@/components/common";
import { Heading } from "react-aria-components";

type QuestSectionProps = {
title: string;
children: React.ReactNode;
action?: Omit<ButtonProps, "variant" | "size">;
};

export const QuestSection = ({
title,
children,
action,
}: QuestSectionProps) => {
return (
<section>
<div className="flex justify-between items-center border-b border-gray-dim h-9 pb-1 mb-4">
<Heading className="text-lg text-medium">{title}</Heading>
{action && <Button variant="ghost" size="small" {...action} />}
</div>
{children}
</section>
);
};
Loading

0 comments on commit 6a2bc7b

Please sign in to comment.