Skip to content

Commit 76fa297

Browse files
authored
Fix issues around cursor on the modal (#7)
1 parent d7a5f1a commit 76fa297

18 files changed

Lines changed: 495 additions & 178 deletions

index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>DevPod Extension Preview</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/pages/preview.tsx"></script>
11+
</body>
12+
</html>

package-lock.json

Lines changed: 289 additions & 37 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
"license": "MIT",
77
"scripts": {
88
"dev": "vite dev",
9+
"preview": "PREVIEW=false vite dev",
910
"build": "tsc && vite build"
1011
},
1112
"dependencies": {
1213
"@types/chrome": "^0.0.268",
13-
"@types/lodash": "^4.17.4",
14-
"@typescript-eslint/eslint-plugin": "^7.11.0",
14+
"@types/lodash": "^4.17.5",
15+
"@typescript-eslint/eslint-plugin": "^7.12.0",
1516
"autoprefixer": "^10.4.19",
1617
"eslint": "^8.57.0",
1718
"eslint-plugin-import": "^2.29.1",
@@ -25,7 +26,7 @@
2526
"react-dom": "^18.3.1",
2627
"react-error-boundary": "^4.0.13",
2728
"swr": "^2.2.5",
28-
"tailwindcss": "^3.4.3",
29+
"tailwindcss": "^3.4.4",
2930
"zod": "^3.23.8"
3031
},
3132
"devDependencies": {
@@ -34,8 +35,8 @@
3435
"@types/webextension-polyfill": "^0.10.7",
3536
"@vitejs/plugin-react": "^4.3.0",
3637
"typescript": "^5.4.5",
37-
"vite": "^5.2.12",
38-
"vite-plugin-web-extension": "^4.1.3",
38+
"vite": "^5.2.13",
39+
"vite-plugin-web-extension": "^4.1.4",
3940
"vite-plugin-zip-pack": "^1.2.3",
4041
"webextension-polyfill": "^0.12.0"
4142
}

src/lib/components/Button.tsx

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,47 @@
11
import { clsx } from "@lib/utils/clsx";
2+
import { WithRequired } from "@lib/utils/typeUtils/WithRequired";
23
import { HTMLMotionProps, motion } from "framer-motion";
34
import { forwardRef } from "react";
45

56
type ButtonStyleProps = {
6-
variant?: "solid" | "outline";
7+
variant?: "solid" | "outline" | "link";
78
color?: "primary";
89
};
910
type ButtonProps = HTMLMotionProps<"button"> & ButtonStyleProps;
10-
type ButtonLinkProps = HTMLMotionProps<"a"> & ButtonStyleProps;
11+
type LinkProps = WithRequired<HTMLMotionProps<"a">, "href"> & ButtonStyleProps;
1112

12-
function colors({ variant = "solid", color = "primary" }: ButtonStyleProps) {
13+
function variantClasses({
14+
variant = "solid",
15+
color = "primary",
16+
}: ButtonStyleProps) {
1317
if (variant === "outline") {
1418
const borderColor = (() => {
1519
switch (color) {
1620
case "primary":
17-
return "border-primary text-text";
21+
return "border-primary text-primary rounded flex flex-row justify-center items-center gap-2 px py-1";
1822
}
1923
})();
2024
return clsx(borderColor, "border-thick");
2125
}
26+
if (variant === "link") {
27+
return clsx("text-primary inline");
28+
}
2229

2330
switch (color) {
2431
case "primary":
25-
return "bg-primary text-primary-contrast";
32+
return "bg-primary text-primary-contrast shadow-sm rounded flex flex-row justify-center items-center gap-2 px py-1";
2633
}
2734
}
2835

2936
function commonProps<Type extends "a" | "button">({
3037
className,
3138
...props
32-
}: Type extends "a" ? ButtonLinkProps : ButtonProps): Type extends "a"
33-
? ButtonLinkProps
34-
: ButtonProps {
39+
}: Type extends "a" ? LinkProps : ButtonProps): (Type extends "a"
40+
? LinkProps
41+
: ButtonProps) & { className: string } {
42+
// @ts-expect-error These types do match, but TypeScript doesn't seem to like it
3543
return {
36-
className: clsx(
37-
"rounded shadow-sm flex flex-row justify-center items-center gap-2 px py-1",
38-
colors(props),
39-
className,
40-
),
44+
className: clsx(variantClasses(props), className, "cursor-pointer"),
4145
...props,
4246
};
4347
}
@@ -53,7 +57,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
5357
);
5458
Button.displayName = "Button";
5559

56-
export const ButtonLink = forwardRef<HTMLAnchorElement, ButtonLinkProps>(
60+
export const ButtonLink = forwardRef<HTMLAnchorElement, LinkProps>(
5761
({ children, ...props }, ref) => {
5862
return (
5963
<motion.a ref={ref} {...commonProps<"a">(props)}>
@@ -63,3 +67,14 @@ export const ButtonLink = forwardRef<HTMLAnchorElement, ButtonLinkProps>(
6367
},
6468
);
6569
ButtonLink.displayName = "ButtonLink";
70+
71+
export const Link = forwardRef<HTMLAnchorElement, Omit<LinkProps, "variant">>(
72+
({ children, ...props }, ref) => {
73+
return (
74+
<motion.a ref={ref} {...commonProps<"a">({ variant: "link", ...props })}>
75+
{children}
76+
</motion.a>
77+
);
78+
},
79+
);
80+
Link.displayName = "Link";

src/lib/components/Modal.tsx

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
11
import { AnimatePresence, motion } from "framer-motion";
22
import { stopPropagation } from "@lib/utils/dom/stopPropagation";
33
import { clsx } from "@lib/utils/clsx";
4+
import { ReactNode, Suspense } from "react";
45

56
const MODAL = "fixed left-0 top-0 bottom-0 right-0 z-20";
67

7-
export function Modal({
8-
isOpen,
9-
onClose,
10-
children,
11-
className,
12-
}: {
8+
type ModalProps = {
139
isOpen: boolean;
1410
onClose?: () => void;
1511
children?: React.ReactNode;
1612
className?: string;
17-
}) {
13+
};
14+
15+
function Modal(props: ModalProps): ReactNode {
16+
// Hide the modal until we can figure out if it was dismissed or not.
17+
return (
18+
<Suspense fallback={null}>
19+
<ModalInside {...props}></ModalInside>
20+
</Suspense>
21+
);
22+
}
23+
24+
function ModalInside({
25+
isOpen,
26+
onClose,
27+
className,
28+
children,
29+
}: ModalProps): ReactNode {
1830
return (
1931
<AnimatePresence>
2032
{isOpen ? (
@@ -26,21 +38,30 @@ export function Modal({
2638
animate={{ opacity: 1 }}
2739
exit={{ opacity: 0 }}
2840
transition={{ duration: 0.2, ease: "easeInOut" }}
29-
className={clsx(MODAL, "bg-black bg-opacity-50 backdrop-blur")}
41+
className={clsx(
42+
MODAL,
43+
"bg-black bg-opacity-50 backdrop-blur pointer-cursor",
44+
)}
3045
/>
3146
<motion.div
3247
initial={{ opacity: 0, y: 200 }}
3348
animate={{ opacity: 1, y: 0 }}
3449
exit={{ opacity: 0, y: 200 }}
3550
transition={{ duration: 0.2, ease: "easeInOut" }}
36-
className={clsx(MODAL, "flex justify-center items-center")}
51+
className={clsx(
52+
MODAL,
53+
"flex justify-center items-center pointer-events-none",
54+
)}
3755
aria-hidden
3856
onClick={onClose}
3957
>
4058
<div
4159
// Stop propagation of the click event to prevent the modal from closing
4260
onClick={stopPropagation}
43-
className={clsx("bg-background p-4 rounded", className)}
61+
className={clsx(
62+
"bg-background p-4 rounded pointer-events-auto",
63+
className,
64+
)}
4465
>
4566
{children}
4667
</div>
@@ -50,3 +71,5 @@ export function Modal({
5071
</AnimatePresence>
5172
);
5273
}
74+
75+
export { Modal };

src/lib/hooks/storage/useModalDismissed.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

src/lib/hooks/useStorage.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,6 @@ export function useStorage<T = unknown>({
6767
};
6868
});
6969

70-
if (!data && !error) {
71-
return {
72-
isLoading: true,
73-
data: undefined,
74-
setData,
75-
error: undefined,
76-
isValidating,
77-
mutate,
78-
};
79-
}
80-
8170
return {
8271
isLoading: false,
8372
data,

src/lib/utils/error.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class EError<T = unknown> extends Error {
2929
return error instanceof Error ? error.message : "An unknown error occurred";
3030
}
3131

32-
/** Convert an error into an object, recursively serializing causes as needed. */
32+
/** Convert an error into a plain object, recursively going down causes as needed. */
3333
public static serialize(error: unknown): unknown {
3434
// Wrapping the recursion in this inner function to hide the recursion parameter
3535
function serializeRecurse(error: unknown, recursion: number): unknown {
@@ -41,39 +41,47 @@ export class EError<T = unknown> extends Error {
4141
error instanceof Error && recursion > 0 && error.cause
4242
? serializeRecurse(error.cause, recursion - 1)
4343
: undefined;
44-
const name = EError.name(error);
45-
const message = EError.message(error);
44+
45+
const base = {
46+
name: EError.name(error),
47+
message: EError.message(error),
48+
version: APP_VERSION,
49+
};
4650

4751
if (error instanceof EError) {
4852
return {
49-
name,
50-
message,
5153
data: error._data,
5254
stack: error.stack,
5355
cause,
56+
...base,
5457
};
5558
}
5659

5760
if (error instanceof Error) {
5861
return {
59-
name,
60-
message,
6162
stack: error.stack,
6263
cause,
64+
...base,
6365
};
6466
}
6567

6668
return {
67-
name,
68-
message,
6969
data: safeStringify(error),
70+
...base,
7071
};
7172
}
7273
return serializeRecurse(error, 10);
7374
}
7475

76+
/** Converts an error into a JSON formatted string. */
77+
public static stringify(error: unknown, space?: string | number): string {
78+
const text = safeStringify(this.serialize(error), space);
79+
return text;
80+
}
81+
82+
/** Converts an error into a base64 encoded JSON string. */
7583
public static encode(error: unknown): string {
76-
return btoa(safeStringify(this.serialize(error)));
84+
return btoa(this.stringify(error));
7785
}
7886
}
7987

src/lib/utils/safeStringify.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
* use this for cases where the serialized data being gibberish is less of a
55
* problem than this function throwing an error.
66
*/
7-
export function safeStringify(value: unknown): string {
7+
export function safeStringify(value: unknown, space?: string | number): string {
88
try {
9-
return JSON.stringify(value);
9+
return JSON.stringify(value, undefined, space);
1010
} catch (error) {
1111
console.debug("safeStringify failed to stringify value", error);
1212
try {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/** Given a type T and key K, makes K required while keeping other keys as-is. */
2+
export type WithRequired<T, K extends keyof T> = Omit<T, K> &
3+
Required<Pick<T, K>>;

0 commit comments

Comments
 (0)