Skip to content
This repository was archived by the owner on Jan 24, 2025. It is now read-only.

Commit cd819ed

Browse files
committed
Merge branch 'DX-1307' of https://github.com/upstash/react-ui into DX-1307
2 parents d99a97a + 7925ab2 commit cd819ed

File tree

17 files changed

+555
-115
lines changed

17 files changed

+555
-115
lines changed

packages/react-databrowser/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"dependencies": {
3131
"@monaco-editor/react": "^4.6.0",
3232
"@radix-ui/react-alert-dialog": "^1.0.5",
33+
"@radix-ui/react-context-menu": "^2.2.2",
3334
"@radix-ui/react-dialog": "^1.0.5",
3435
"@radix-ui/react-dropdown-menu": "^2.1.2",
3536
"@radix-ui/react-icons": "1.3.0",

packages/react-databrowser/src/components/databrowser/components/add-key-modal.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ import { toast } from "@/components/ui/use-toast"
2626
import { TypeTag } from "@/components/databrowser/components/type-tag"
2727
import { useAddKey } from "@/components/databrowser/hooks/use-add-key"
2828

29+
import { useKeys } from "../hooks"
30+
2931
export function AddKeyModal() {
3032
const { setSelectedKey } = useDatabrowserStore()
3133
const [open, setOpen] = useState(false)
34+
const { addArtificalKey } = useKeys()
3235

3336
const { mutateAsync: addKey, isPending } = useAddKey()
3437
const { control, handleSubmit, formState, reset } = useForm<{
@@ -44,8 +47,9 @@ export function AddKeyModal() {
4447
const onSubmit = handleSubmit(async ({ key, type }) => {
4548
try {
4649
await addKey({ key, type })
47-
setOpen(false)
4850
setSelectedKey(key)
51+
addArtificalKey(key, type)
52+
setOpen(false)
4953
} catch (error) {
5054
toast({
5155
description: error instanceof Error ? error.message : "An error occurred",

packages/react-databrowser/src/components/databrowser/components/display/delete-alert-dialog.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { MouseEventHandler, PropsWithChildren } from "react"
1+
import type { MouseEventHandler } from "react"
22

33
import {
44
AlertDialog,
@@ -15,10 +15,17 @@ import {
1515
export function DeleteAlertDialog({
1616
children,
1717
onDeleteConfirm,
18-
}: PropsWithChildren<{ onDeleteConfirm: MouseEventHandler }>) {
18+
open,
19+
onOpenChange,
20+
}: {
21+
children?: React.ReactNode
22+
onDeleteConfirm: MouseEventHandler
23+
open?: boolean
24+
onOpenChange?: (open: boolean) => void
25+
}) {
1926
return (
20-
<AlertDialog>
21-
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
27+
<AlertDialog open={open} onOpenChange={onOpenChange}>
28+
{children && <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>}
2229

2330
<AlertDialogContent>
2431
<AlertDialogHeader>

packages/react-databrowser/src/components/databrowser/components/display/display-list-edit.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ const FormItem = ({ name, label }: { name: string; label: string; isNumber?: boo
126126
name,
127127
form,
128128
isEditorDynamic: true,
129+
showCopyButton: true,
129130
})
130131

131132
return (

packages/react-databrowser/src/components/databrowser/components/display/display-list.tsx

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button"
99

1010
import { useEditListItem } from "../../hooks"
1111
import { useFetchListItems } from "../../hooks/use-fetch-list-items"
12+
import { ItemContextMenu } from "../item-context-menu"
1213
import { InfiniteScroll } from "../sidebar/infinite-scroll"
1314
import { DeleteAlertDialog } from "./delete-alert-dialog"
1415
import { DisplayHeader } from "./display-header"
@@ -71,48 +72,55 @@ export const ListItems = ({
7172

7273
return (
7374
<>
74-
{keys.map(({ key, value }, _i) => (
75-
<tr
76-
key={dataKey + type + key}
77-
onClick={() => {
78-
setSelectedListItem({ key, value })
79-
}}
80-
className="h-10 border-b border-b-zinc-100 hover:bg-zinc-50"
75+
{keys.map(({ key, value }, i) => (
76+
<ItemContextMenu
77+
key={`${dataKey}-${key}-${i}`}
78+
dataKey={dataKey}
79+
type={type}
80+
itemKey={key}
81+
itemValue={value}
8182
>
82-
<td
83-
className={cn(
84-
"cursor-pointer truncate px-3",
85-
type === "list" || type === "stream" ? "w-24" : ""
86-
)}
83+
<tr
84+
onClick={() => {
85+
setSelectedListItem({ key, value })
86+
}}
87+
className="h-10 border-b border-b-zinc-100 hover:bg-zinc-50"
8788
>
88-
{key}
89-
</td>
90-
{value !== undefined && (
91-
<td className={cn("cursor-pointer truncate px-3", type === "zset" ? "w-24" : "")}>
92-
{value}
93-
</td>
94-
)}
95-
{type !== "stream" && (
96-
<td width={20} className="px-3">
97-
<DeleteAlertDialog
98-
onDeleteConfirm={(e) => {
99-
e.stopPropagation()
100-
editItem({
101-
type,
102-
dataKey,
103-
itemKey: key,
104-
// For deletion
105-
newKey: undefined,
106-
})
107-
}}
108-
>
109-
<Button size="icon-sm" variant="secondary" onClick={(e) => e.stopPropagation()}>
110-
<IconTrash className="size-4 text-zinc-500" />
111-
</Button>
112-
</DeleteAlertDialog>
89+
<td
90+
className={cn(
91+
"cursor-pointer truncate px-3",
92+
type === "list" || type === "stream" ? "w-24" : ""
93+
)}
94+
>
95+
{key}
11396
</td>
114-
)}
115-
</tr>
97+
{value !== undefined && (
98+
<td className={cn("cursor-pointer truncate px-3", type === "zset" ? "w-24" : "")}>
99+
{value}
100+
</td>
101+
)}
102+
{type !== "stream" && (
103+
<td width={20} className="px-3">
104+
<DeleteAlertDialog
105+
onDeleteConfirm={(e) => {
106+
e.stopPropagation()
107+
editItem({
108+
type,
109+
dataKey,
110+
itemKey: key,
111+
// For deletion
112+
newKey: undefined,
113+
})
114+
}}
115+
>
116+
<Button size="icon-sm" variant="secondary" onClick={(e) => e.stopPropagation()}>
117+
<IconTrash className="size-4 text-zinc-500" />
118+
</Button>
119+
</DeleteAlertDialog>
120+
</td>
121+
)}
122+
</tr>
123+
</ItemContextMenu>
116124
))}
117125
</>
118126
)

packages/react-databrowser/src/components/databrowser/components/display/index.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable unicorn/no-negated-condition */
12
import { useDatabrowserStore } from "@/store"
23

34
import { useKeyType } from "../../hooks/use-keys"
@@ -8,11 +9,14 @@ export const DataDisplay = () => {
89
const { selectedKey } = useDatabrowserStore()
910
const type = useKeyType(selectedKey)
1011

11-
// TODO: add a empty state
1212
return (
1313
<div className="h-full rounded-xl border p-1">
14-
{!selectedKey || !type ? (
14+
{!selectedKey ? (
1515
<div />
16+
) : !type ? (
17+
<div className="flex h-full items-center justify-center">
18+
<span className="text-gray-500">Loading...</span>
19+
</div>
1620
) : (
1721
<>
1822
{type === "string" || type === "json" ? (

packages/react-databrowser/src/components/databrowser/components/display/input/custom-editor.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ import { useEffect, useRef } from "react"
22
import { Editor, useMonaco } from "@monaco-editor/react"
33

44
import { cn } from "@/lib/utils"
5+
import { CopyButton } from "@/components/databrowser/copy-button"
56

67
export const CustomEditor = ({
78
language,
89
value,
910
onChange,
1011
maxDynamicHeight,
12+
showCopyButton,
1113
}: {
1214
language: string
1315
value: string
1416
onChange: (value: string) => void
1517
maxDynamicHeight?: number
18+
showCopyButton?: boolean
1619
}) => {
1720
const monaco = useMonaco()
1821
const editorRef = useRef()
@@ -28,7 +31,7 @@ export const CustomEditor = ({
2831

2932
return (
3033
<div
31-
className={cn(maxDynamicHeight === undefined && "h-full p-2")}
34+
className={cn("relative", maxDynamicHeight === undefined && "h-full p-2")}
3235
style={{
3336
height: maxDynamicHeight,
3437
}}
@@ -71,6 +74,11 @@ export const CustomEditor = ({
7174
renderLineHighlight: "none",
7275
}}
7376
/>
77+
{showCopyButton && (
78+
<div className="absolute right-0 top-0">
79+
<CopyButton value={value} />
80+
</div>
81+
)}
7482
</div>
7583
)
7684
}

packages/react-databrowser/src/components/databrowser/components/display/input/use-field.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ export const useField = ({
88
name,
99
form,
1010
isEditorDynamic = false,
11+
showCopyButton,
1112
}: {
1213
name: string
1314
form: UseFormReturn<any>
1415
isEditorDynamic?: boolean
16+
showCopyButton?: boolean
1517
}) => {
1618
const { field, fieldState } = useController<Record<string, string>>({
1719
name,
@@ -57,6 +59,7 @@ export const useField = ({
5759
value={field.value}
5860
onChange={field.onChange}
5961
maxDynamicHeight={isEditorDynamic ? 100 : undefined}
62+
showCopyButton={showCopyButton}
6063
/>
6164
</>
6265
),
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { useState, type PropsWithChildren } from "react"
2+
import { type ListDataType } from "@/types"
3+
import { ContextMenuSeparator } from "@radix-ui/react-context-menu"
4+
5+
import {
6+
ContextMenu,
7+
ContextMenuContent,
8+
ContextMenuItem,
9+
ContextMenuTrigger,
10+
} from "@/components/ui/context-menu"
11+
import { toast } from "@/components/ui/use-toast"
12+
13+
import { useEditListItem } from "../hooks"
14+
import { DeleteAlertDialog } from "./display/delete-alert-dialog"
15+
16+
export const ItemContextMenu = ({
17+
children,
18+
dataKey,
19+
itemKey,
20+
itemValue,
21+
type,
22+
}: PropsWithChildren<{
23+
dataKey: string
24+
type: ListDataType
25+
itemKey: string
26+
itemValue?: string
27+
}>) => {
28+
const { mutate: editItem } = useEditListItem()
29+
const [isAlertOpen, setAlertOpen] = useState(false)
30+
31+
return (
32+
<>
33+
<DeleteAlertDialog
34+
open={isAlertOpen}
35+
onOpenChange={setAlertOpen}
36+
onDeleteConfirm={(e) => {
37+
e.stopPropagation()
38+
editItem({
39+
type,
40+
dataKey,
41+
itemKey,
42+
// For deletion
43+
newKey: undefined,
44+
})
45+
setAlertOpen(false)
46+
}}
47+
/>
48+
<ContextMenu>
49+
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger>
50+
<ContextMenuContent>
51+
<ContextMenuItem
52+
onClick={() => {
53+
navigator.clipboard.writeText(itemKey)
54+
toast({
55+
description: "Key copied to clipboard",
56+
})
57+
}}
58+
>
59+
Copy key
60+
</ContextMenuItem>
61+
{itemValue !== undefined && (
62+
<ContextMenuItem
63+
onClick={() => {
64+
navigator.clipboard.writeText(itemValue)
65+
toast({
66+
description: "Value copied to clipboard",
67+
})
68+
}}
69+
>
70+
Copy value
71+
</ContextMenuItem>
72+
)}
73+
<ContextMenuSeparator />
74+
<ContextMenuItem onClick={() => setAlertOpen(true)}>Delete item</ContextMenuItem>
75+
</ContextMenuContent>
76+
</ContextMenu>
77+
</>
78+
)
79+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useState, type PropsWithChildren } from "react"
2+
import { ContextMenuSeparator } from "@radix-ui/react-context-menu"
3+
4+
import {
5+
ContextMenu,
6+
ContextMenuContent,
7+
ContextMenuItem,
8+
ContextMenuTrigger,
9+
} from "@/components/ui/context-menu"
10+
import { toast } from "@/components/ui/use-toast"
11+
12+
import { useDeleteKey } from "../hooks"
13+
import { DeleteAlertDialog } from "./display/delete-alert-dialog"
14+
15+
export const SidebarContextMenu = ({
16+
children,
17+
dataKey,
18+
}: PropsWithChildren<{
19+
dataKey: string
20+
}>) => {
21+
const { mutate: deleteKey } = useDeleteKey()
22+
const [isAlertOpen, setAlertOpen] = useState(false)
23+
24+
return (
25+
<>
26+
<DeleteAlertDialog
27+
open={isAlertOpen}
28+
onOpenChange={setAlertOpen}
29+
onDeleteConfirm={(e) => {
30+
e.stopPropagation()
31+
deleteKey(dataKey)
32+
setAlertOpen(false)
33+
}}
34+
/>
35+
<ContextMenu>
36+
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger>
37+
<ContextMenuContent>
38+
<ContextMenuItem
39+
onClick={() => {
40+
navigator.clipboard.writeText(dataKey)
41+
toast({
42+
description: "Key copied to clipboard",
43+
})
44+
}}
45+
>
46+
Copy key
47+
</ContextMenuItem>
48+
<ContextMenuSeparator />
49+
<ContextMenuItem onClick={() => setAlertOpen(true)}>Delete key</ContextMenuItem>
50+
</ContextMenuContent>
51+
</ContextMenu>
52+
</>
53+
)
54+
}

0 commit comments

Comments
 (0)