Files
kandev/apps/web/components/task-create-dialog-repository-options-fields.tsx

166 lines
5.4 KiB
TypeScript

import { useId } from "react";
import type { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
import { Textarea } from "@kandev/ui/textarea";
import { Button } from "@kandev/ui/button";
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@kandev/ui/select";
import type { RepositoryCheckoutCapabilities } from "@/lib/types/repository-checkout-options";
type Props = {
onDemand: boolean;
setOnDemand: (value: boolean) => void;
selectedFolders: boolean;
setSelectedFolders: (value: boolean) => void;
directories: string;
setDirectories: (value: string) => void;
invalid: boolean;
errorLines?: number[];
capabilities?: RepositoryCheckoutCapabilities;
failed?: boolean;
retry: () => void;
};
export function RepositoryOptionsFields(props: Props) {
const { t } = useTranslation();
const id = useId();
const { capabilities } = props;
return (
<div className="space-y-3">
<p className="text-xs text-muted-foreground">{t("task:checkoutOptions.taskOnly")}</p>
<div className="space-y-2">
<CheckoutChoice
id={`${id}-download`}
label={t("task:checkoutOptions.download")}
selected={props.onDemand}
onChange={props.setOnDemand}
defaultLabel={t("task:checkoutOptions.standard")}
selectedLabel={t("task:checkoutOptions.onDemand")}
disabled={!capabilities?.on_demand}
testId="repository-options-download"
/>
<p className="text-xs leading-relaxed text-muted-foreground">
{t("task:checkoutOptions.downloadHelp")}
</p>
</div>
<CheckoutChoice
id={`${id}-folders`}
label={t("task:checkoutOptions.folders")}
selected={props.selectedFolders}
onChange={props.setSelectedFolders}
defaultLabel={t("task:checkoutOptions.allFolders")}
selectedLabel={t("task:checkoutOptions.selectedFolders")}
disabled={!capabilities?.sparse}
testId="repository-options-folders"
/>
{props.selectedFolders && (
<div className="space-y-2">
<label htmlFor={`${id}-directories`} className="text-xs text-muted-foreground">
{t("task:checkoutOptions.directories")}
</label>
<Textarea
id={`${id}-directories`}
value={props.directories}
onChange={(event) => props.setDirectories(event.target.value)}
rows={4}
className="resize-none text-base md:text-xs"
aria-invalid={props.invalid}
aria-describedby={`${id}-help`}
data-testid="repository-options-directories"
/>
<p id={`${id}-help`} className="text-xs text-muted-foreground">
{t("task:checkoutOptions.foldersHelp")}
</p>
{props.invalid && (
<p role="alert" className="text-xs text-destructive">
{t("task:checkoutOptions.invalidDirectories")}
{props.errorLines?.map((line) => (
<span key={line} className="block">
{t("task:checkoutOptions.invalidLine", { line })}
</span>
))}
</p>
)}
</div>
)}
{(!capabilities || capabilities.reason) && (
<p role="status" className="text-xs text-muted-foreground">
{checkoutSupportMessage(props, t)}
</p>
)}
{props.failed && (
<Button
type="button"
variant="outline"
className="h-7 cursor-pointer [@media(pointer:coarse)]:h-11"
onClick={props.retry}
>
{t("task:retry")}
</Button>
)}
</div>
);
}
function CheckoutChoice({
id,
label,
selected,
onChange,
defaultLabel,
selectedLabel,
disabled,
testId,
}: {
id: string;
label: string;
selected: boolean;
onChange: (value: boolean) => void;
defaultLabel: string;
selectedLabel: string;
disabled: boolean;
testId: string;
}) {
return (
<div className="flex items-center justify-between gap-3">
<label htmlFor={id} className="text-xs text-muted-foreground">
{label}
</label>
<Select
value={selected ? "selected" : "default"}
onValueChange={(value) => onChange(value === "selected")}
>
<SelectTrigger
id={id}
data-testid={testId}
className="w-36 border-border/60 bg-muted/30 hover:bg-muted/60"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="default" className="[@media(pointer:coarse)]:min-h-11">
{defaultLabel}
</SelectItem>
<SelectItem
value="selected"
disabled={disabled}
className="[@media(pointer:coarse)]:min-h-11"
>
{selectedLabel}
</SelectItem>
</SelectContent>
</Select>
</div>
);
}
function checkoutSupportMessage(props: Props, t: TFunction) {
if (props.failed) return t("task:checkoutOptions.unavailable");
if (!props.capabilities) return t("task:checkoutOptions.checking");
if (props.capabilities.reason === "credentials_unsupported")
return t("task:checkoutOptions.credentialsUnsupported");
if (props.capabilities.reason === "executor_required")
return t("task:checkoutOptions.executorRequired");
if (props.capabilities.reason === "provider_unsupported")
return t("task:checkoutOptions.providerUnsupported");
return t("task:checkoutOptions.preparationUnsupported");
}