Skip to content

Commit 863f920

Browse files
authored
feat: create initial collection prompt + edit name (#7989)
1 parent 64e7fed commit 863f920

File tree

13 files changed

+509
-555
lines changed

13 files changed

+509
-555
lines changed

.changeset/neat-symbols-count.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@graphql-hive/laboratory': patch
3+
'@graphql-hive/render-laboratory': patch
4+
---
5+
6+
Enhanced behavior when no collection exists and the user attempts to save an operation, along with
7+
the ability to edit the collection name.

packages/libraries/laboratory/src/components/laboratory/collections.tsx

Lines changed: 142 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
import { useMemo, useState } from 'react';
1+
import { useEffect, useMemo, useState } from 'react';
22
import {
3+
CheckIcon,
34
FolderIcon,
45
FolderOpenIcon,
56
FolderPlusIcon,
7+
PencilIcon,
68
SearchIcon,
79
TrashIcon,
810
XIcon,
911
} from 'lucide-react';
12+
import {
13+
InputGroup,
14+
InputGroupAddon,
15+
InputGroupButton,
16+
InputGroupInput,
17+
} from '@/components/ui/input-group';
1018
import { TooltipTrigger } from '@radix-ui/react-tooltip';
1119
import type { LaboratoryCollection, LaboratoryCollectionOperation } from '../../lib/collections';
1220
import { cn } from '../../lib/utils';
@@ -44,74 +52,163 @@ export const CollectionItem = (props: { collection: LaboratoryCollection }) => {
4452
addOperation,
4553
setActiveOperation,
4654
deleteCollection,
55+
updateCollection,
4756
deleteOperationFromCollection,
4857
addTab,
4958
setActiveTab,
5059
checkPermissions,
5160
} = useLaboratory();
5261

5362
const [isOpen, setIsOpen] = useState(false);
63+
const [isEditing, setIsEditing] = useState(false);
64+
const [editedName, setEditedName] = useState(props.collection.name);
65+
66+
const hasActiveOperation = useMemo(() => {
67+
return props.collection.operations.some(operation => operation.id === activeOperation?.id);
68+
}, [props.collection.operations, activeOperation]);
69+
70+
useEffect(() => {
71+
if (hasActiveOperation) {
72+
setIsOpen(true);
73+
}
74+
}, [hasActiveOperation]);
5475

5576
return (
5677
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
5778
<CollapsibleTrigger asChild>
58-
<Button
59-
variant="ghost"
60-
className="bg-background group sticky top-0 w-full justify-start px-2"
61-
size="sm"
62-
>
63-
{isOpen ? (
64-
<FolderOpenIcon className="text-muted-foreground size-4" />
65-
) : (
66-
<FolderIcon className="text-muted-foreground size-4" />
67-
)}
68-
{props.collection.name}
69-
{checkPermissions?.('collections:delete') && (
70-
<Tooltip>
71-
<TooltipTrigger asChild>
72-
<AlertDialog>
73-
<AlertDialogTrigger asChild>
79+
{isEditing ? (
80+
<InputGroup className="!bg-accent/50 h-8 border-none">
81+
<InputGroupAddon className="pl-2.5">
82+
{isOpen ? (
83+
<FolderOpenIcon className="text-muted-foreground size-4" />
84+
) : (
85+
<FolderIcon className="text-muted-foreground size-4" />
86+
)}
87+
</InputGroupAddon>
88+
<InputGroupInput
89+
autoFocus
90+
defaultValue={editedName}
91+
className="!pl-1.5 font-medium"
92+
onChange={e => setEditedName(e.target.value)}
93+
onKeyDown={e => {
94+
if (e.key === 'Enter') {
95+
updateCollection(props.collection.id, {
96+
name: editedName,
97+
});
98+
setIsEditing(false);
99+
}
100+
if (e.key === 'Escape') {
101+
setEditedName(props.collection.name);
102+
setIsEditing(false);
103+
}
104+
}}
105+
/>
106+
<InputGroupAddon align="inline-end">
107+
<InputGroupButton
108+
className="p-1!"
109+
onClick={e => {
110+
e.stopPropagation();
111+
112+
updateCollection(props.collection.id, {
113+
name: editedName,
114+
});
115+
116+
setIsEditing(false);
117+
}}
118+
>
119+
<CheckIcon />
120+
</InputGroupButton>
121+
<InputGroupButton
122+
className="p-1!"
123+
onClick={e => {
124+
e.stopPropagation();
125+
126+
setIsEditing(false);
127+
setEditedName(props.collection.name);
128+
}}
129+
>
130+
<XIcon />
131+
</InputGroupButton>
132+
</InputGroupAddon>
133+
</InputGroup>
134+
) : (
135+
<Button
136+
variant="ghost"
137+
className="bg-background !hover:bg-accent/50 group sticky top-0 w-full justify-start px-2"
138+
size="sm"
139+
>
140+
{isOpen ? (
141+
<FolderOpenIcon className="text-muted-foreground size-4" />
142+
) : (
143+
<FolderIcon className="text-muted-foreground size-4" />
144+
)}
145+
{props.collection.name}
146+
<div className="ml-auto flex items-center gap-2">
147+
{checkPermissions?.('collections:update') && (
148+
<Tooltip>
149+
<TooltipTrigger>
74150
<Button
75151
variant="link"
76-
className="text-muted-foreground hover:text-destructive p-1! pr-0! ml-auto opacity-0 transition-opacity group-hover:opacity-100"
152+
className="text-muted-foreground p-1! pr-0! opacity-0 transition-opacity group-hover:opacity-100"
77153
onClick={e => {
78154
e.stopPropagation();
155+
setIsEditing(true);
79156
}}
80157
>
81-
<TrashIcon />
158+
<PencilIcon />
82159
</Button>
83-
</AlertDialogTrigger>
84-
<AlertDialogContent>
85-
<AlertDialogHeader>
86-
<AlertDialogTitle>
87-
Are you sure you want to delete collection?
88-
</AlertDialogTitle>
89-
<AlertDialogDescription>
90-
{props.collection.name} will be permanently deleted. All operations in this
91-
collection will be deleted as well.
92-
</AlertDialogDescription>
93-
</AlertDialogHeader>
94-
<AlertDialogFooter>
95-
<AlertDialogCancel>Cancel</AlertDialogCancel>
96-
<AlertDialogAction asChild>
160+
</TooltipTrigger>
161+
<TooltipContent>Edit collection</TooltipContent>
162+
</Tooltip>
163+
)}
164+
{checkPermissions?.('collections:delete') && (
165+
<Tooltip>
166+
<TooltipTrigger>
167+
<AlertDialog>
168+
<AlertDialogTrigger asChild>
97169
<Button
98-
variant="destructive"
170+
variant="link"
171+
className="text-muted-foreground hover:text-destructive p-1! pr-0! opacity-0 transition-opacity group-hover:opacity-100"
99172
onClick={e => {
100173
e.stopPropagation();
101-
deleteCollection(props.collection.id);
102174
}}
103175
>
104-
Delete
176+
<TrashIcon />
105177
</Button>
106-
</AlertDialogAction>
107-
</AlertDialogFooter>
108-
</AlertDialogContent>
109-
</AlertDialog>
110-
</TooltipTrigger>
111-
<TooltipContent>Delete collection</TooltipContent>
112-
</Tooltip>
113-
)}
114-
</Button>
178+
</AlertDialogTrigger>
179+
<AlertDialogContent>
180+
<AlertDialogHeader>
181+
<AlertDialogTitle>
182+
Are you sure you want to delete collection?
183+
</AlertDialogTitle>
184+
<AlertDialogDescription>
185+
{props.collection.name} will be permanently deleted. All operations in
186+
this collection will be deleted as well.
187+
</AlertDialogDescription>
188+
</AlertDialogHeader>
189+
<AlertDialogFooter>
190+
<AlertDialogCancel>Cancel</AlertDialogCancel>
191+
<AlertDialogAction asChild>
192+
<Button
193+
variant="destructive"
194+
onClick={e => {
195+
e.stopPropagation();
196+
deleteCollection(props.collection.id);
197+
}}
198+
>
199+
Delete
200+
</Button>
201+
</AlertDialogAction>
202+
</AlertDialogFooter>
203+
</AlertDialogContent>
204+
</AlertDialog>
205+
</TooltipTrigger>
206+
<TooltipContent>Delete collection</TooltipContent>
207+
</Tooltip>
208+
)}
209+
</div>
210+
</Button>
211+
)}
115212
</CollapsibleTrigger>
116213
<CollapsibleContent className={cn('border-border ml-4 flex flex-col gap-1 border-l pl-2')}>
117214
{isOpen &&

0 commit comments

Comments
 (0)