Skip to content

Commit

Permalink
finish v2
Browse files Browse the repository at this point in the history
  • Loading branch information
marekzelinka committed Dec 7, 2024
1 parent b33ac39 commit cad7fef
Show file tree
Hide file tree
Showing 27 changed files with 1,617 additions and 547 deletions.
16 changes: 16 additions & 0 deletions app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}

.dark {
Expand Down Expand Up @@ -56,6 +64,14 @@
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}

Expand Down
6 changes: 3 additions & 3 deletions app/components/empty-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export function EmptyState({
children?: ReactNode;
}) {
return (
<div className="flex flex-col items-center text-center">
<h3 className="font-semibold">{title}</h3>
<div className="flex flex-col items-center gap-1 text-center">
<h3 className="text-2xl font-bold tracking-tight">{title}</h3>
{description ? (
<p className="mt-2 text-sm text-muted-foreground">{description}</p>
<p className="text-sm text-muted-foreground">{description}</p>
) : null}
{children ? <div className="mt-4">{children}</div> : null}
</div>
Expand Down
33 changes: 20 additions & 13 deletions app/components/error-boundary.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
import { isRouteErrorResponse, useRouteError } from "react-router";
import { isRouteErrorResponse, Link, useRouteError } from "react-router";
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
import { Button } from "./ui/button";

export function GeneralErrorBoundary() {
export function GenericErrorBoundary() {
const error = useRouteError();
const errorMessage = isRouteErrorResponse(error)
? error.data
: getErrorMessage(error);

return (
<div className="flex flex-col items-center gap-1 text-center">
<div className="mx-auto flex size-10 items-center justify-center rounded-full bg-destructive [&_svg]:size-5">
<ExclamationTriangleIcon
className="text-destructive-foreground"
aria-hidden
/>
<div className="mx-auto max-w-lg">
<div className="space-y-6">
<Alert variant="destructive" className="[&_svg]:size-4">
<ExclamationTriangleIcon />
<AlertTitle>Oops! Something went wrong!</AlertTitle>
<AlertDescription>
{errorMessage ??
"We're sorry, but we encountered an unexpected error."}
</AlertDescription>
</Alert>
<div className="space-y-4">
<Button asChild variant="outline" className="w-full">
<Link to="/">Return to Homepage</Link>
</Button>
</div>
</div>
<h3 className="mt-4 text-2xl font-bold tracking-tight">
Oops! An error occurred…
</h3>
<p className="mt-2 text-sm text-muted-foreground">{errorMessage}</p>
</div>
);
}
Expand All @@ -39,5 +46,5 @@ function getErrorMessage(error: unknown) {

console.error("Unable to get error message for error", error);

return "Unknown Error";
return null;
}
17 changes: 0 additions & 17 deletions app/components/loading-overlay..tsx

This file was deleted.

9 changes: 5 additions & 4 deletions app/components/loading-overlay.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import type { PropsWithChildren } from "react";
import type { ReactNode } from "react";
import { useNavigation } from "react-router";
import { useSpinDelay } from "spin-delay";

export function LoadingOverlay({ children }: PropsWithChildren) {
export function LoadingOverlay({ children }: { children?: ReactNode }) {
const navigation = useNavigation();
const isLoading = navigation.state === "loading";
const isSearching = new URLSearchParams(navigation.location?.search).has("q");
const shouldShowOverlay = useSpinDelay(isLoading && !isSearching);

const shouldShow = useSpinDelay(isLoading && !isSearching, { delay: 200 });

return (
<div
className={
shouldShowOverlay ? "opacity-50 transition-opacity" : undefined
shouldShow ? "opacity-25 transition-opacity duration-200" : undefined
}
>
{children}
Expand Down
40 changes: 20 additions & 20 deletions app/components/note-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ export function NoteForm({
},
constraint: getZodConstraint(NoteFormSchema),
lastResult: lastResult,
onValidate: ({ formData }) => {
return parseWithZod(formData, { schema: NoteFormSchema });
},
onValidate: ({ formData }) =>
parseWithZod(formData, { schema: NoteFormSchema }),
onSubmit: (event, context) => {
if (editMode) {
return;
Expand All @@ -63,6 +62,7 @@ export function NoteForm({
event.preventDefault();

const formData = new FormData(event.currentTarget);

const submission = parseWithZod(formData, { schema: NoteFormSchema });
if (submission.status !== "success") {
return;
Expand All @@ -87,11 +87,12 @@ export function NoteForm({
{editMode ? <input type="hidden" name="intent" value="editNote" /> : null}
<fieldset
disabled={isSavingEdits}
className="relative disabled:pointer-events-none disabled:opacity-70"
className="relative disabled:pointer-events-none disabled:opacity-50"
>
<div className="overflow-hidden rounded-lg border bg-background focus-within:ring-1 focus-within:ring-ring">
<div className="relative overflow-hidden rounded-lg border bg-background focus-within:ring-1 focus-within:ring-ring">
<Textarea
ref={textareaRef}
rows={3}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
Expand All @@ -100,27 +101,26 @@ export function NoteForm({
);
}
}}
rows={6}
className="resize-none border-0 p-3 shadow-none focus-visible:ring-0"
className="resize-none scroll-py-3 border-0 p-3 shadow-none focus-visible:ring-0"
placeholder="What would you like to add?"
aria-label="Note"
{...getTextareaProps(fields.text)}
/>
{/* Spacer element to match the height of the toolbar below */}
<div className="h-14" aria-hidden />
</div>
<div className="absolute inset-x-px bottom-0">
<div className="flex items-center px-3 py-3">
<Input
className="max-w-fit"
aria-label="Date"
{...getInputProps(fields.date, { type: "date" })}
/>
<Button type="submit" size="sm" className="ml-auto">
{isSavingEdits ? "Saving…" : "Save"}
</Button>
{/* Spacer element to match the height of the toolbar */}
<div className="py-2" aria-hidden>
<div className="h-9" />
</div>
</div>
<div className="absolute inset-x-0 bottom-0 flex px-3 py-2">
<Input
className="max-w-fit"
aria-label="Date"
{...getInputProps(fields.date, { type: "date" })}
/>
<Button type="submit" className="ml-auto">
{isSavingEdits ? "Saving…" : "Save"}
</Button>
</div>
</fieldset>
<ErrorList
id={fields.text.errorId}
Expand Down
60 changes: 60 additions & 0 deletions app/components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Slot } from "@radix-ui/react-slot";
import { forwardRef, type HTMLAttributes } from "react";
import { cva, cx, type VariantProps } from "~/lib/utils";

export const alertVariants = cva({
base: "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
});

export const Alert = forwardRef<
HTMLDivElement,
HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cx(alertVariants({ variant }), className)}
{...props}
/>
));
Alert.displayName = "Alert";

export const AlertTitle = forwardRef<
HTMLParagraphElement,
HTMLAttributes<HTMLHeadingElement> & {
asChild?: boolean;
}
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "h5";

return (
<Comp
ref={ref}
className={cx("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
);
});
AlertTitle.displayName = "AlertTitle";

export const AlertDescription = forwardRef<
HTMLParagraphElement,
HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cx("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
));
AlertDescription.displayName = "AlertDescription";
120 changes: 120 additions & 0 deletions app/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { Cross2Icon } from "@radix-ui/react-icons";
import {
forwardRef,
type ComponentPropsWithoutRef,
type ElementRef,
type HTMLAttributes,
} from "react";
import { cva, cx, type VariantProps } from "~/lib/utils";

export const Sheet = SheetPrimitive.Root;

export const SheetTrigger = SheetPrimitive.Trigger;

export const SheetClose = SheetPrimitive.Close;

export const SheetPortal = SheetPrimitive.Portal;

export const SheetOverlay = forwardRef<
ElementRef<typeof SheetPrimitive.Overlay>,
ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
ref={ref}
className={cx(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;

export const sheetVariants = cva({
base: "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
});

export const SheetContent = forwardRef<
ElementRef<typeof SheetPrimitive.Content>,
ComponentPropsWithoutRef<typeof SheetPrimitive.Content> &
VariantProps<typeof sheetVariants>
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cx(sheetVariants({ side }), className)}
{...props}
>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary [&_svg]:size-4">
<Cross2Icon aria-hidden />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
{children}
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;

export const SheetHeader = ({
className,
...props
}: HTMLAttributes<HTMLDivElement>) => (
<div
className={cx("flex flex-col gap-y-2 text-center sm:text-left", className)}
{...props}
/>
);
SheetHeader.displayName = "SheetHeader";

export const SheetFooter = ({
className,
...props
}: HTMLAttributes<HTMLDivElement>) => (
<div
className={cx(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2",
className,
)}
{...props}
/>
);
SheetFooter.displayName = "SheetFooter";

export const SheetTitle = forwardRef<
ElementRef<typeof SheetPrimitive.Title>,
ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cx("text-lg font-semibold text-foreground", className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;

export const SheetDescription = forwardRef<
ElementRef<typeof SheetPrimitive.Description>,
ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cx("text-sm text-muted-foreground", className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
Loading

0 comments on commit cad7fef

Please sign in to comment.