Skip to content

Commit 6e0d1ad

Browse files
SharzyLclaude
andcommitted
refactor: add TypeScript type checking and fix UI issues
- Add complete TypeScript type checking with tsc --noEmit - Fix all type errors and add proper type definitions for cloudflare:test - Add .js extensions to test imports for node16 module resolution - Improve component accessibility (remove invalid aria-labelledby) - Add success-50/100 colors for encrypted paste indication - Optimize CodeEditor line number rendering with useMemo - Fix CSS transform syntax and color consistency - Add keyboard navigation support for Select and Autocomplete - Add type checking to CI workflow and documentation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f4eed7b commit 6e0d1ad

52 files changed

Lines changed: 3059 additions & 6136 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/pr.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ jobs:
6262
pnpm prettier -c .
6363
pnpm lint
6464
65+
- name: "Type Check"
66+
run: pnpm typecheck
67+
6568
- name: "Run Test with Coverage"
6669
run: pnpm coverage --testTimeout 15000
6770

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,5 @@ Remember to run eslint checks and prettier before commiting your code.
133133
```console
134134
$ pnpm fmt
135135
$ pnpm lint
136+
$ pnpm typecheck
136137
```

frontend/components/CodeEditor.tsx

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
// inspired by https://css-tricks.com/creating-an-editable-textarea-that-supports-syntax-highlighted-code/
2-
3-
import React, { useEffect, useRef, useState } from "react"
4-
import { Autocomplete, AutocompleteItem, Input, Select, SelectItem } from "@heroui/react"
1+
import React, { useEffect, useMemo, useRef, useState } from "react"
2+
import { Autocomplete, AutocompleteItem, Input, Select, SelectItem } from "./ui/index.js"
53

64
import { autoCompleteOverrides, inputOverrides, selectOverrides, tst } from "../utils/overrides.js"
75
import { useHLJS, highlightHTML } from "../utils/HighlightLoader.js"
@@ -41,7 +39,7 @@ function formatTabSetting(s: TabSetting, forHuman: boolean) {
4139
}
4240

4341
function parseTabSetting(s: string): TabSetting | undefined {
44-
const match = /^(tab|space) ([24])$/.exec(s)
42+
const match = /^(tab|space) ([248])$/.exec(s)
4543
if (match) {
4644
return { char: match[1] as TabSetting["char"], width: parseInt(match[2]) as TabSetting["width"] }
4745
} else {
@@ -59,7 +57,7 @@ const tabSettings: TabSetting[] = [
5957
]
6058

6159
function handleNewLines(str: string): string {
62-
if (str.charAt(-1) === "\n") {
60+
if (str.endsWith("\n")) {
6361
str += " "
6462
}
6563
return str
@@ -140,6 +138,7 @@ export function CodeEditor({
140138
<div className={className} {...rest}>
141139
<div className={"mb-2 gap-2 flex flex-row" + " "}>
142140
<Input
141+
className="flex-1"
143142
classNames={inputOverrides}
144143
type={"text"}
145144
label={"File name"}
@@ -148,27 +147,33 @@ export function CodeEditor({
148147
onValueChange={setFilename}
149148
/>
150149
<Autocomplete
151-
className={"max-w-[10em]"}
150+
className={"max-w-[8em]"}
152151
classNames={autoCompleteOverrides}
153152
label={"Language"}
154153
size={"sm"}
155154
defaultItems={hljs ? hljs.listLanguages().map((lang) => ({ key: lang })) : []}
156155
// we must not use undefined here to avoid conversion from uncontrolled component to controlled component
157156
selectedKey={hljs && lang && hljs.listLanguages().includes(lang) ? lang : ""}
158157
onSelectionChange={(key) => {
159-
setLang((key as string) || undefined) // when key is empty string, convert back to undefined
158+
setLang(key || undefined) // when key is empty string, convert back to undefined
160159
}}
161160
>
162-
{(language) => <AutocompleteItem key={language.key}>{language.key}</AutocompleteItem>}
161+
{(language: { key: string }) => (
162+
<AutocompleteItem key={language.key} value={language.key}>
163+
{language.key}
164+
</AutocompleteItem>
165+
)}
163166
</Autocomplete>
164167
<Select
165168
size={"sm"}
166169
label={"Indent With"}
167-
className={"max-w-[10em] text-foreground"}
170+
className={"w-[6em] text-foreground"}
168171
classNames={selectOverrides}
169172
selectedKeys={[formatTabSetting(tabSetting, false)]}
170173
onSelectionChange={(s) => {
171-
setTabSettings(parseTabSetting(s.currentKey!)!)
174+
const key = Array.from(s)[0]
175+
const parsed = parseTabSetting(key)
176+
if (parsed) setTabSettings(parsed)
172177
}}
173178
>
174179
{tabSettings.map((s) => (
@@ -196,9 +201,13 @@ export function CodeEditor({
196201
}
197202
style={{ height: `${heightPx}px` }}
198203
>
199-
{Array.from({ length: lineCount }, (_, idx) => {
200-
return <span key={idx} />
201-
})}
204+
{useMemo(
205+
() =>
206+
Array.from({ length: lineCount }, (_, idx) => {
207+
return <span key={idx} />
208+
}),
209+
[lineCount],
210+
)}
202211
</span>
203212
</div>
204213
<textarea

frontend/components/CopyWidget.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { ButtonProps } from "@heroui/react"
2-
import { Button } from "@heroui/react"
1+
import type { ButtonProps } from "./ui/index.js"
2+
import { Button } from "./ui/index.js"
33
import { useRef, useState } from "react"
44
import { CopyIcon, CheckIcon } from "./icons.js"
55

@@ -27,7 +27,7 @@ export function CopyWidget({ className, getCopyContent, ...rest }: CopyIconProps
2727
}
2828

2929
return (
30-
<Button isIconOnly aria-label="Copy" className={className} onPress={onCopy} {...rest}>
30+
<Button isIconOnly size="sm" variant="light" aria-label="Copy" className={className} onPress={onCopy} {...rest}>
3131
{hasIssuedCopies ? <CheckIcon className="size-6" /> : <CopyIcon className="size-6" />}
3232
</Button>
3333
)

frontend/components/DarkModeToggle.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { JSX } from "react"
22
import React, { useEffect, useState, useSyncExternalStore } from "react"
3-
import type { ButtonProps } from "@heroui/react"
4-
import { Button, Tooltip } from "@heroui/react"
3+
import type { ButtonProps } from "./ui/index.js"
4+
import { Button, Tooltip } from "./ui/index.js"
55

66
import { ComputerIcon, MoonIcon, SunIcon } from "./icons.js"
77
import { tst } from "../utils/overrides.js"
@@ -79,7 +79,9 @@ export function DarkModeToggle({ modeSelection, setModeSelection, className, ...
7979
return (
8080
<Button
8181
isIconOnly
82-
className={`rounded-full ${tst} bg-background hover:bg-default-100` + " " + className}
82+
size="sm"
83+
variant="light"
84+
className={`${tst}` + " " + className}
8385
aria-label="Toggle dark mode"
8486
style={{ visibility: "hidden" }}
8587
{...rest}
@@ -93,7 +95,9 @@ export function DarkModeToggle({ modeSelection, setModeSelection, className, ...
9395
<Tooltip content={`Toggle dark mode (currently ${currentMode} mode)`}>
9496
<Button
9597
isIconOnly
96-
className={`rounded-full ${tst} bg-background hover:bg-default-100` + " " + className}
98+
size="sm"
99+
variant="light"
100+
className={`${tst}` + " " + className}
97101
aria-label="Toggle dark mode"
98102
onPress={() => {
99103
const newSelected = modeSelections[(modeSelections.indexOf(currentMode) + 1) % modeSelections.length]

frontend/components/ErrorModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { ModalProps } from "@heroui/react"
2-
import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from "@heroui/react"
1+
import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from "./ui/index.js"
2+
import type { ModalProps } from "./ui/index.js"
33
import React, { useState } from "react"
44
import { ErrorWithTitle } from "../utils/errors.js"
55

@@ -9,7 +9,7 @@ export interface ErrorState {
99
isOpen: boolean
1010
}
1111

12-
type ErrorModalProps = Omit<ModalProps, "children">
12+
type ErrorModalProps = Partial<Omit<ModalProps, "children" | "isOpen" | "onClose">>
1313

1414
export function useErrorModal() {
1515
const [errorState, setErrorState] = useState<ErrorState>({ isOpen: false, content: "", title: "" })
@@ -38,7 +38,7 @@ export function useErrorModal() {
3838
setErrorState({ isOpen: false, content: "", title: "" })
3939
}
4040
return (
41-
<Modal isOpen={errorState.isOpen} state={errorState} onClose={onClose} {...rest}>
41+
<Modal isOpen={errorState.isOpen} onClose={onClose} {...rest}>
4242
<ModalContent>
4343
<ModalHeader className="flex flex-col gap-1">{errorState.title}</ModalHeader>
4444
<ModalBody>

frontend/components/PasteInputPanel.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { CardProps } from "@heroui/react"
2-
import { Card, CardBody, Tab, Tabs } from "@heroui/react"
1+
import type { CardProps } from "./ui/index.js"
2+
import { Card, CardBody, Tab, Tabs } from "./ui/index.js"
33
import type { DragEvent } from "react"
44
import { useRef, useState } from "react"
55
import { formatSize } from "../utils/utils.js"
@@ -52,7 +52,7 @@ export function PasteInputPanel({ isPasteLoading, state, onStateChange, ...rest
5252
<Tabs
5353
variant="underlined"
5454
classNames={{
55-
tabList: `gap-2 w-full px-2 py-0 border-divider`,
55+
tabList: `gap-2 w-full py-0 border-divider mb-2 -ml-1`,
5656
cursor: `w-[80%] ${tst}`,
5757
tab: `max-w-fit px-2 h-8 px-2`,
5858
panel: "pb-1",

frontend/components/PasteSettingPanel.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CardProps } from "@heroui/react"
1+
import type { CardProps } from "./ui/index.js"
22
import {
33
Card,
44
CardBody,
@@ -10,7 +10,7 @@ import {
1010
RadioGroup,
1111
Switch,
1212
Tooltip,
13-
} from "@heroui/react"
13+
} from "./ui/index.js"
1414
import { verifyExpiration, verifyManageUrl, verifyName } from "../utils/utils.js"
1515
import React from "react"
1616
import { InfoIcon } from "./icons.js"
@@ -45,10 +45,8 @@ export function PanelSettingsPanel({ setting, onSettingChange, config, ...rest }
4545
<Input
4646
type="text"
4747
label="Expiration"
48-
// to avoid duplicated name, see https://github.com/adobe/react-spectrum/discussions/8037
49-
aria-labelledby=""
5048
classNames={{
51-
base: "basis-80",
49+
base: "basis-40",
5250
...inputOverrides,
5351
}}
5452
defaultValue="7d"
@@ -62,16 +60,18 @@ export function PanelSettingsPanel({ setting, onSettingChange, config, ...rest }
6260
<Input
6361
type="password"
6462
label="Password"
65-
aria-labelledby=""
6663
value={setting.password}
6764
onValueChange={(p) => onSettingChange({ ...setting, password: p })}
68-
classNames={inputOverrides}
65+
classNames={{
66+
base: "flex-1",
67+
...inputOverrides,
68+
}}
6969
placeholder={"Generated randomly"}
7070
description="Used to update/delete your paste"
7171
/>
7272
</div>
7373
<RadioGroup
74-
className="gap-4 mb-3 w-full"
74+
className="mb-3 w-full"
7575
value={setting.uploadKind}
7676
onValueChange={(v) => onSettingChange({ ...setting, uploadKind: v as UploadKind })}
7777
>

frontend/components/UploadedPanel.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react"
22

3-
import type { CardProps } from "@heroui/react"
4-
import { Card, CardBody, CardHeader, CircularProgress, Divider, Input, mergeClasses } from "@heroui/react"
3+
import type { CardProps } from "./ui/index.js"
4+
import { Card, CardBody, CardHeader, CircularProgress, Divider, Input, mergeClasses } from "./ui/index.js"
55

66
import type { PasteResponse } from "../../shared/interfaces.js"
77
import { tst } from "../utils/overrides.js"
@@ -32,9 +32,8 @@ export function UploadedPanel({
3232
encryptionKey,
3333
...rest
3434
}: UploadedPanelProps) {
35-
const copyWidgetClassNames = `bg-transparent ${tst} translate-y-[10%]`
35+
const copyWidgetClassNames = `${tst}`
3636
const inputProps = {
37-
"aria-labelledby": "",
3837
readOnly: true,
3938
className: "mb-2",
4039
}
@@ -49,7 +48,7 @@ export function UploadedPanel({
4948
<CircularProgress
5049
aria-label={"Loading..."}
5150
value={loadingProgress}
52-
className={"absolute top-[50%] left-[50%] translate-[-50%]"}
51+
className={"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"}
5352
/>
5453
</div>
5554
) : (
@@ -59,10 +58,11 @@ export function UploadedPanel({
5958
{...inputProps}
6059
label={"Display URL"}
6160
color={encryptionKey ? "success" : "default"}
61+
className={encryptionKey ? "mb-2 [&_input]:bg-success-50" : "mb-2"}
6262
value={makeDecryptionUrl(pasteResponse.url, encryptionKey)}
6363
endContent={
6464
<CopyWidget
65-
className={copyWidgetClassNames}
65+
className={encryptionKey ? `${copyWidgetClassNames} hover:bg-success-100` : copyWidgetClassNames}
6666
getCopyContent={() => makeDecryptionUrl(pasteResponse.url, encryptionKey)}
6767
/>
6868
}

0 commit comments

Comments
 (0)