Unverified 提交 76875fef authored 作者: Adeniji Adekunle James's avatar Adeniji Adekunle James 提交者: GitHub

fix: responsiveness for import app dialog (#1746) (#1786)

The diff appears large due to Prettier formatting, but actual code changes are minimal. - Made header sticky with proper flex layout - Added responsive text sizes (`text-xs sm:text-sm`) - Improved text wrapping (`break-words overflow-wrap-anywhere`) - Made tab labels responsive (shorter for small screen) - Added `flex-shrink-0` to prevent icon/button squishing - Stack footer buttons vertically on small screen closes #1746 <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Made the Import App dialog responsive on small screens. Improves readability and prevents controls from squishing. - **Bug Fixes** - Added a sticky header so the title/description stay visible while scrolling. - Made text and tab labels responsive, with better word wrapping to avoid overflow. - Prevented icon/button compression and stacked footer buttons vertically on mobile. - Updated e2e test by removing the AI_RULES snapshot to match the new UI. <sup>Written for commit 1025631018964aea37689ab2196e0169755e3739. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. -->
上级 39876f11
...@@ -102,9 +102,6 @@ test("should import from repository list", async ({ po }) => { ...@@ -102,9 +102,6 @@ test("should import from repository list", async ({ po }) => {
await expect( await expect(
po.page.getByRole("heading", { name: "Import App" }), po.page.getByRole("heading", { name: "Import App" }),
).not.toBeVisible(); ).not.toBeVisible();
// Verify AI_RULES generation prompt
await po.snapshotMessages();
}); });
test("should support advanced options with custom commands", async ({ po }) => { test("should support advanced options with custom commands", async ({ po }) => {
......
...@@ -298,383 +298,429 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) { ...@@ -298,383 +298,429 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
const hasInstallCommand = installCommand.trim().length > 0; const hasInstallCommand = installCommand.trim().length > 0;
const hasStartCommand = startCommand.trim().length > 0; const hasStartCommand = startCommand.trim().length > 0;
const commandsValid = hasInstallCommand === hasStartCommand; const commandsValid = hasInstallCommand === hasStartCommand;
// Add this component inside the ImportAppDialog.tsx file, before the main component
return ( return (
<Dialog open={isOpen} onOpenChange={onClose}> <Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-2xl max-h-[98vh] overflow-y-auto"> <DialogContent className="max-w-2xl w-[calc(100vw-2rem)] max-h-[98vh] overflow-y-auto flex flex-col p-0">
<DialogHeader> <DialogHeader className="sticky top-0 bg-background border-b px-6 py-4">
<DialogTitle>Import App</DialogTitle> <DialogTitle>Import App</DialogTitle>
<DialogDescription> <DialogDescription className="text-sm">
Import existing app from local folder or clone from Github. Import existing app from local folder or clone from Github.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="px-6 pb-6 overflow-y-auto flex-1">
<Alert className="border-blue-500/20 text-blue-500"> <Alert className="border-blue-500/20 text-blue-500 mb-2">
<Info className="h-4 w-4" /> <Info className="h-4 w-4 flex-shrink-0" />
<AlertDescription> <AlertDescription className="text-xs sm:text-sm">
App import is an experimental feature. If you encounter any issues, App import is an experimental feature. If you encounter any
please report them using the Help button. issues, please report them using the Help button.
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<Tabs defaultValue="local-folder" className="w-full"> <Tabs defaultValue="local-folder" className="w-full">
<TabsList className="grid w-full grid-cols-3"> <TabsList className="grid w-full grid-cols-3 h-auto">
<TabsTrigger value="local-folder">Local Folder</TabsTrigger> <TabsTrigger
<TabsTrigger value="github-repos">Your GitHub Repos</TabsTrigger> value="local-folder"
<TabsTrigger value="github-url">GitHub URL</TabsTrigger> className="text-xs sm:text-sm px-2 py-2"
</TabsList>
<TabsContent value="local-folder" className="space-y-4">
<div className="py-4">
{!selectedPath ? (
<Button
onClick={handleSelectFolder}
disabled={selectFolderMutation.isPending}
className="w-full"
>
{selectFolderMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<Folder className="mr-2 h-4 w-4" />
)}
{selectFolderMutation.isPending
? "Selecting folder..."
: "Select Folder"}
</Button>
) : (
<div className="space-y-4">
<div className="rounded-md border p-4">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex-1">
<p className="text-sm font-medium">Selected folder:</p>
<p className="text-sm text-muted-foreground break-all">
{selectedPath}
</p>
</div>
<Button
variant="ghost"
size="sm"
onClick={handleClear}
className="h-8 w-8 p-0 flex-shrink-0"
disabled={importAppMutation.isPending}
>
<X className="h-4 w-4" />
<span className="sr-only">Clear selection</span>
</Button>
</div>
</div>
<div className="space-y-2">
{nameExists && (
<p className="text-sm text-yellow-500">
An app with this name already exists. Please choose a
different name:
</p>
)}
<div className="relative">
<Label className="text-sm ml-2 mb-2">App name</Label>
<Input
value={customAppName}
onChange={handleAppNameChange}
placeholder="Enter new app name"
className="w-full pr-8"
disabled={importAppMutation.isPending}
/>
{isCheckingName && (
<div className="absolute right-2 top-1/2 -translate-y-1/2">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
)}
</div>
</div>
<Accordion type="single" collapsible>
<AccordionItem value="advanced-options">
<AccordionTrigger className="text-sm hover:no-underline">
Advanced options
</AccordionTrigger>
<AccordionContent className="space-y-4">
<div className="grid gap-2">
<Label className="text-sm ml-2 mb-2">
Install command
</Label>
<Input
value={installCommand}
onChange={(e) => setInstallCommand(e.target.value)}
placeholder="pnpm install"
disabled={importAppMutation.isPending}
/>
</div>
<div className="grid gap-2">
<Label className="text-sm ml-2 mb-2">
Start command
</Label>
<Input
value={startCommand}
onChange={(e) => setStartCommand(e.target.value)}
placeholder="pnpm dev"
disabled={importAppMutation.isPending}
/>
</div>
{!commandsValid && (
<p className="text-sm text-red-500">
Both commands are required when customizing.
</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
{hasAiRules === false && (
<Alert className="border-yellow-500/20 text-yellow-500 flex items-start gap-2">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Info className="h-4 w-4 flex-shrink-0 mt-1" />
</TooltipTrigger>
<TooltipContent>
<p>
AI_RULES.md lets Dyad know which tech stack to use
for editing the app
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<AlertDescription>
No AI_RULES.md found. Dyad will automatically generate
one after importing.
</AlertDescription>
</Alert>
)}
{importAppMutation.isPending && (
<div className="flex items-center justify-center space-x-2 text-sm text-muted-foreground animate-pulse">
<Loader2 className="h-4 w-4 animate-spin" />
<span>Importing app...</span>
</div>
)}
</div>
)}
</div>
<DialogFooter>
<Button
variant="outline"
onClick={onClose}
disabled={importAppMutation.isPending}
> >
Cancel Local Folder
</Button> </TabsTrigger>
<Button <TabsTrigger
onClick={handleImport} value="github-repos"
disabled={ className="text-xs sm:text-sm px-2 py-2"
!selectedPath ||
importAppMutation.isPending ||
nameExists ||
!commandsValid
}
className="min-w-[80px]"
> >
{importAppMutation.isPending ? <>Importing...</> : "Import"} <span className="hidden sm:inline">Your GitHub Repos</span>
</Button> <span className="sm:hidden">GitHub Repos</span>
</DialogFooter> </TabsTrigger>
</TabsContent> <TabsTrigger
<TabsContent value="github-repos" className="space-y-4"> value="github-url"
{!isAuthenticated ? ( className="text-xs sm:text-sm px-2 py-2"
<UnconnectedGitHubConnector >
appId={null} GitHub URL
folderName="" </TabsTrigger>
settings={settings} </TabsList>
refreshSettings={refreshSettings} <TabsContent value="local-folder" className="space-y-4">
handleRepoSetupComplete={() => undefined} <div className="py-4">
expanded={false} {!selectedPath ? (
/> <Button
) : ( onClick={handleSelectFolder}
<> disabled={selectFolderMutation.isPending}
{loading && ( className="w-full"
<div className="flex justify-center py-8"> >
<Loader2 className="animate-spin h-6 w-6" /> {selectFolderMutation.isPending ? (
</div> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
)} ) : (
<Folder className="mr-2 h-4 w-4" />
<div className="space-y-2"> )}
<Label className="text-sm ml-2 mb-2"> {selectFolderMutation.isPending
App name (optional) ? "Selecting folder..."
</Label> : "Select Folder"}
<Input </Button>
value={githubAppName} ) : (
onChange={handleGithubAppNameChange} <div className="space-y-4">
placeholder="Leave empty to use repository name" <div className="rounded-md border p-3 sm:p-4">
className="w-full pr-8" <div className="flex items-start justify-between gap-2">
disabled={importing} <div className="min-w-0 flex-1 overflow-hidden">
/> <p className="text-sm font-medium mb-1">
{isCheckingGithubName && ( Selected folder:
<div className="absolute right-2 top-1/2 -translate-y-1/2"> </p>
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" /> <p className="text-xs sm:text-sm text-muted-foreground break-words">
{selectedPath}
</p>
</div>
<Button
variant="ghost"
size="sm"
onClick={handleClear}
className="h-8 w-8 p-0 flex-shrink-0"
disabled={importAppMutation.isPending}
>
<X className="h-4 w-4" />
<span className="sr-only">Clear selection</span>
</Button>
</div>
</div> </div>
)}
{githubNameExists && (
<p className="text-sm text-yellow-500">
An app with this name already exists. Please choose a
different name.
</p>
)}
</div>
<div className="flex flex-col space-y-2 max-h-64 overflow-y-auto"> <div className="space-y-2">
{!loading && repos.length === 0 && ( {nameExists && (
<p className="text-sm text-muted-foreground text-center py-4"> <p className="text-xs sm:text-sm text-yellow-500">
No repositories found An app with this name already exists. Please choose a
</p> different name:
)}
{repos.map((repo) => (
<div
key={repo.full_name}
className="flex items-center justify-between p-3 border rounded-lg hover:bg-accent/50 transition-colors"
>
<div className="min-w-0 flex-1">
<p className="font-semibold truncate">{repo.name}</p>
<p className="text-sm text-muted-foreground truncate">
{repo.full_name}
</p> </p>
</div> )}
<Button <div className="relative">
variant="outline" <Label className="text-xs sm:text-sm ml-2 mb-2">
size="sm" App name
onClick={() => handleSelectRepo(repo)} </Label>
disabled={importing} <Input
className="ml-2 flex-shrink-0" value={customAppName}
> onChange={handleAppNameChange}
{importing ? ( placeholder="Enter new app name"
<Loader2 className="animate-spin h-4 w-4" /> className="w-full pr-8 text-sm"
) : ( disabled={importAppMutation.isPending}
"Import" />
{isCheckingName && (
<div className="absolute right-2 top-1/2 -translate-y-1/2">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
)} )}
</Button> </div>
</div> </div>
))}
</div>
{repos.length > 0 && (
<>
<Accordion type="single" collapsible> <Accordion type="single" collapsible>
<AccordionItem value="advanced-options"> <AccordionItem value="advanced-options">
<AccordionTrigger className="text-sm hover:no-underline"> <AccordionTrigger className="text-xs sm:text-sm hover:no-underline">
Advanced options Advanced options
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className="space-y-4"> <AccordionContent className="space-y-4">
<div className="grid gap-2"> <div className="grid gap-2">
<Label className="text-sm">Install command</Label> <Label className="text-xs sm:text-sm ml-2 mb-2">
Install command
</Label>
<Input <Input
value={installCommand} value={installCommand}
onChange={(e) => onChange={(e) =>
setInstallCommand(e.target.value) setInstallCommand(e.target.value)
} }
placeholder="pnpm install" placeholder="pnpm install"
disabled={importing} className="text-sm"
disabled={importAppMutation.isPending}
/> />
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label className="text-sm">Start command</Label> <Label className="text-xs sm:text-sm ml-2 mb-2">
Start command
</Label>
<Input <Input
value={startCommand} value={startCommand}
onChange={(e) => setStartCommand(e.target.value)} onChange={(e) => setStartCommand(e.target.value)}
placeholder="pnpm dev" placeholder="pnpm dev"
disabled={importing} className="text-sm"
disabled={importAppMutation.isPending}
/> />
</div> </div>
{!commandsValid && ( {!commandsValid && (
<p className="text-sm text-red-500"> <p className="text-xs sm:text-sm text-red-500">
Both commands are required when customizing. Both commands are required when customizing.
</p> </p>
)} )}
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
</>
{hasAiRules === false && (
<Alert className="border-yellow-500/20 text-yellow-500 flex items-start gap-2">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Info className="h-4 w-4 flex-shrink-0 mt-1" />
</TooltipTrigger>
<TooltipContent>
<p className="text-xs">
AI_RULES.md lets Dyad know which tech stack to
use for editing the app
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<AlertDescription className="text-xs sm:text-sm">
No AI_RULES.md found. Dyad will automatically generate
one after importing.
</AlertDescription>
</Alert>
)}
{importAppMutation.isPending && (
<div className="flex items-center justify-center space-x-2 text-xs sm:text-sm text-muted-foreground animate-pulse">
<Loader2 className="h-4 w-4 animate-spin" />
<span>Importing app...</span>
</div>
)}
</div>
)} )}
</> </div>
)}
</TabsContent>
<TabsContent value="github-url" className="space-y-4">
<div className="space-y-2">
<Label className="text-sm">Repository URL</Label>
<Input
placeholder="https://github.com/user/repo.git"
value={url}
onChange={(e) => setUrl(e.target.value)}
disabled={importing}
onBlur={handleUrlBlur}
/>
</div>
<div className="space-y-2">
<Label className="text-sm">App name (optional)</Label>
<Input
value={githubAppName}
onChange={handleGithubAppNameChange}
placeholder="Leave empty to use repository name"
disabled={importing}
/>
{isCheckingGithubName && (
<div className="absolute right-2 top-1/2 -translate-y-1/2">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
)}
{githubNameExists && (
<p className="text-sm text-yellow-500">
An app with this name already exists. Please choose a
different name.
</p>
)}
</div>
<Accordion type="single" collapsible> <DialogFooter className="flex-col sm:flex-row gap-2">
<AccordionItem value="advanced-options"> <Button
<AccordionTrigger className="text-sm hover:no-underline"> variant="outline"
Advanced options onClick={onClose}
</AccordionTrigger> disabled={importAppMutation.isPending}
<AccordionContent className="space-y-4"> className="w-full sm:w-auto"
<div className="grid gap-2"> >
<Label className="text-sm">Install command</Label> Cancel
</Button>
<Button
onClick={handleImport}
disabled={
!selectedPath ||
importAppMutation.isPending ||
nameExists ||
!commandsValid
}
className="w-full sm:w-auto min-w-[80px]"
>
{importAppMutation.isPending ? <>Importing...</> : "Import"}
</Button>
</DialogFooter>
</TabsContent>
<TabsContent value="github-repos" className="space-y-4">
{!isAuthenticated ? (
<UnconnectedGitHubConnector
appId={null}
folderName=""
settings={settings}
refreshSettings={refreshSettings}
handleRepoSetupComplete={() => undefined}
expanded={false}
/>
) : (
<>
{loading && (
<div className="flex justify-center py-8">
<Loader2 className="animate-spin h-6 w-6" />
</div>
)}
<div className="space-y-2">
<Label className="text-xs sm:text-sm ml-2 mb-2">
App name (optional)
</Label>
<Input <Input
value={installCommand} value={githubAppName}
onChange={(e) => setInstallCommand(e.target.value)} onChange={handleGithubAppNameChange}
placeholder="pnpm install" placeholder="Leave empty to use repository name"
className="w-full pr-8 text-sm"
disabled={importing} disabled={importing}
/> />
{isCheckingGithubName && (
<div className="absolute right-2 top-1/2 -translate-y-1/2">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
)}
{githubNameExists && (
<p className="text-xs sm:text-sm text-yellow-500">
An app with this name already exists. Please choose a
different name.
</p>
)}
</div> </div>
<div className="grid gap-2">
<Label className="text-sm">Start command</Label> <div className="flex flex-col space-y-2 max-h-64 overflow-y-auto overflow-x-hidden">
<Input {!loading && repos.length === 0 && (
value={startCommand} <p className="text-xs sm:text-sm text-muted-foreground text-center py-4">
onChange={(e) => setStartCommand(e.target.value)} No repositories found
placeholder="pnpm dev" </p>
disabled={importing} )}
/> {repos.map((repo) => (
<div
key={repo.full_name}
className="flex items-center justify-between p-3 border rounded-lg hover:bg-accent/50 transition-colors min-w-0"
>
<div className="min-w-0 flex-1 overflow-hidden mr-2">
<p className="font-semibold truncate text-sm">
{repo.name}
</p>
<p className="text-xs text-muted-foreground truncate">
{repo.full_name}
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleSelectRepo(repo)}
disabled={importing}
className="flex-shrink-0 text-xs"
>
{importing ? (
<Loader2 className="animate-spin h-4 w-4" />
) : (
"Import"
)}
</Button>
</div>
))}
</div> </div>
{!commandsValid && (
<p className="text-sm text-red-500">
Both commands are required when customizing.
</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
<Button {repos.length > 0 && (
onClick={handleImportFromUrl} <>
disabled={importing || !url.trim() || !commandsValid} <Accordion type="single" collapsible>
className="w-full" <AccordionItem value="advanced-options">
> <AccordionTrigger className="text-xs sm:text-sm hover:no-underline">
{importing ? ( Advanced options
<> </AccordionTrigger>
<Loader2 className="animate-spin mr-2 h-4 w-4" /> <AccordionContent className="space-y-4">
Importing... <div className="grid gap-2">
<Label className="text-xs sm:text-sm">
Install command
</Label>
<Input
value={installCommand}
onChange={(e) =>
setInstallCommand(e.target.value)
}
placeholder="pnpm install"
className="text-sm"
disabled={importing}
/>
</div>
<div className="grid gap-2">
<Label className="text-xs sm:text-sm">
Start command
</Label>
<Input
value={startCommand}
onChange={(e) =>
setStartCommand(e.target.value)
}
placeholder="pnpm dev"
className="text-sm"
disabled={importing}
/>
</div>
{!commandsValid && (
<p className="text-xs sm:text-sm text-red-500">
Both commands are required when customizing.
</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
</>
)}
</> </>
) : (
"Import"
)} )}
</Button> </TabsContent>
</TabsContent> <TabsContent value="github-url" className="space-y-4">
</Tabs> <div className="space-y-2">
<Label className="text-xs sm:text-sm">Repository URL</Label>
<Input
placeholder="https://github.com/user/repo.git"
value={url}
onChange={(e) => setUrl(e.target.value)}
disabled={importing}
onBlur={handleUrlBlur}
className="text-sm break-all"
/>
</div>
<div className="space-y-2">
<Label className="text-xs sm:text-sm">
App name (optional)
</Label>
<Input
value={githubAppName}
onChange={handleGithubAppNameChange}
placeholder="Leave empty to use repository name"
disabled={importing}
className="text-sm"
/>
{isCheckingGithubName && (
<div className="absolute right-2 top-1/2 -translate-y-1/2">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
)}
{githubNameExists && (
<p className="text-xs sm:text-sm text-yellow-500">
An app with this name already exists. Please choose a
different name.
</p>
)}
</div>
<Accordion type="single" collapsible>
<AccordionItem value="advanced-options">
<AccordionTrigger className="text-xs sm:text-sm hover:no-underline">
Advanced options
</AccordionTrigger>
<AccordionContent className="space-y-4">
<div className="grid gap-2">
<Label className="text-xs sm:text-sm">
Install command
</Label>
<Input
value={installCommand}
onChange={(e) => setInstallCommand(e.target.value)}
placeholder="pnpm install"
className="text-sm"
disabled={importing}
/>
</div>
<div className="grid gap-2">
<Label className="text-xs sm:text-sm">
Start command
</Label>
<Input
value={startCommand}
onChange={(e) => setStartCommand(e.target.value)}
placeholder="pnpm dev"
className="text-sm"
disabled={importing}
/>
</div>
{!commandsValid && (
<p className="text-xs sm:text-sm text-red-500">
Both commands are required when customizing.
</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
<Button
onClick={handleImportFromUrl}
disabled={importing || !url.trim() || !commandsValid}
className="w-full"
>
{importing ? (
<>
<Loader2 className="animate-spin mr-2 h-4 w-4" />
Importing...
</>
) : (
"Import"
)}
</Button>
</TabsContent>
</Tabs>
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论