browse for the folder instead of typing it
the project form asked for a path and gave a text field, while fofos-admin — built hours earlier — had a directory picker for exactly the same question. that picker is now shared from the officerdev barrel, so this uses it. browse OR type: the picker fills the field and the FIELD is what saves, so the path stays visible and editable before it is stored. a folder outside home is still reachable by typing, and the server refuses it with a reason rather than the picker silently making it unreachable. picking a folder offers its last two segments as the name when the name is empty — `thing/todo` — because that is what somebody would have typed. left editable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+37
-6
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FolderPlus, FolderTree, Loader2, Trash2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { DirectoryPicker } from 'officerdev';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useProjects, useSelectedProject } from './useKanban';
|
||||
@@ -27,6 +28,7 @@ export const ProjectsPanel = () => {
|
||||
const [name, setName] = useState('');
|
||||
const [path, setPath] = useState('');
|
||||
const [confirming, setConfirming] = useState<number | null>(null);
|
||||
const [picking, setPicking] = useState(false);
|
||||
|
||||
/*
|
||||
Land on something rather than on nothing.
|
||||
@@ -72,14 +74,43 @@ export const ProjectsPanel = () => {
|
||||
|
||||
{adding && (
|
||||
<form onSubmit={submit} className="flex flex-col gap-2 border-b border-border p-3">
|
||||
<Input autoFocus value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" className="text-sm" />
|
||||
<Input
|
||||
value={path}
|
||||
onChange={(e) => setPath(e.target.value)}
|
||||
placeholder="~/projects/thing/todo"
|
||||
className="font-mono text-xs"
|
||||
spellCheck={false}
|
||||
autoFocus
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Name"
|
||||
className="text-sm"
|
||||
/>
|
||||
{/*
|
||||
Browse OR type. The picker fills the field and the field is what saves, so the path stays
|
||||
visible and editable before it is stored — and a folder outside home, which the picker
|
||||
cannot reach, can still be typed and will be refused by the server with a reason.
|
||||
*/}
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={path}
|
||||
onChange={(e) => setPath(e.target.value)}
|
||||
placeholder="~/projects/thing/todo"
|
||||
className="min-w-0 flex-1 font-mono text-xs"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<Button type="button" variant="outline" size="sm" onClick={() => setPicking((open) => !open)}>
|
||||
{picking ? 'Close' : 'Browse'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{picking && (
|
||||
<DirectoryPicker
|
||||
onCancel={() => setPicking(false)}
|
||||
onPick={(absolutePath) => {
|
||||
setPath(absolutePath);
|
||||
setPicking(false);
|
||||
// A folder is usually named after itself — offer that as the name rather than an empty
|
||||
// field, and leave it editable.
|
||||
if (!name.trim()) setName(absolutePath.split('/').filter(Boolean).slice(-2).join('/'));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* Said before they press it, not after it fails: the folder holding the theme directories is
|
||||
not the repository root, and the two are one level apart. */}
|
||||
<p className="text-[11px] text-muted-foreground/70">
|
||||
|
||||
Reference in New Issue
Block a user