diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx index fd4eb33a..e7e64ae9 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx @@ -11,7 +11,6 @@ import { SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; -import { ContainersSelector } from "@app/components/ContainersSelector"; import { HeadersInput } from "@app/components/HeadersInput"; import { PathMatchDisplay, @@ -19,6 +18,7 @@ import { PathRewriteDisplay, PathRewriteModal } from "@app/components/PathMatchRenameModal"; +import { ResourceTargetAddressItem } from "@app/components/resource-target-address-item"; import { SettingsContainer, SettingsSection, @@ -30,15 +30,6 @@ import { } from "@app/components/Settings"; import { SwitchInput } from "@app/components/SwitchInput"; import { Alert, AlertDescription } from "@app/components/ui/alert"; -import { Badge } from "@app/components/ui/badge"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList -} from "@app/components/ui/command"; import { Form, FormControl, @@ -48,11 +39,6 @@ import { FormLabel, FormMessage } from "@app/components/ui/form"; -import { - Popover, - PopoverContent, - PopoverTrigger -} from "@app/components/ui/popover"; import { Table, TableBody, @@ -73,12 +59,9 @@ import { useResourceContext } from "@app/hooks/useResourceContext"; import { toast } from "@app/hooks/useToast"; import { createApiClient } from "@app/lib/api"; import { formatAxiosError } from "@app/lib/api/formatAxiosError"; -import { cn } from "@app/lib/cn"; import { DockerManager, DockerState } from "@app/lib/docker"; -import { parseHostTarget } from "@app/lib/parseHostTarget"; import { orgQueries, resourceQueries } from "@app/lib/queries"; import { zodResolver } from "@hookform/resolvers/zod"; -import { CaretSortIcon } from "@radix-ui/react-icons"; import { tlsNameSchema } from "@server/lib/schemas"; import { type GetResourceResponse } from "@server/routers/resource"; import type { ListSitesResponse } from "@server/routers/site"; @@ -98,7 +81,6 @@ import { import { AxiosResponse } from "axios"; import { AlertTriangle, - CheckIcon, CircleCheck, CircleX, Info, @@ -107,7 +89,7 @@ import { } from "lucide-react"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; -import { use, useActionState, useEffect, useState } from "react"; +import { use, useActionState, useCallback, useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; @@ -202,7 +184,7 @@ function ProxyResourceTargetsForm({ setDockerStates((prev) => new Map(prev.set(siteId, dockerState))); }; - const refreshContainersForSite = async (siteId: number) => { + const refreshContainersForSite = useCallback(async (siteId: number) => { const dockerManager = new DockerManager(api, siteId); const containers = await dockerManager.fetchContainers(); @@ -214,9 +196,9 @@ function ProxyResourceTargetsForm({ } return newMap; }); - }; + }, [api]); - const getDockerStateForSite = (siteId: number): DockerState => { + const getDockerStateForSite = useCallback((siteId: number): DockerState => { return ( dockerStates.get(siteId) || { isEnabled: false, @@ -224,7 +206,7 @@ function ProxyResourceTargetsForm({ containers: [] } ); - }; + }, [dockerStates]); const [isAdvancedMode, setIsAdvancedMode] = useState(() => { if (typeof window !== "undefined") { @@ -234,8 +216,40 @@ function ProxyResourceTargetsForm({ return false; }); - const getColumns = (): ColumnDef[] => { - const isHttp = resource.http; + const isHttp = resource.http; + + const removeTarget = useCallback((targetId: number) => { + setTargets((prevTargets) => { + const targetToRemove = prevTargets.find((target) => target.targetId === targetId); + if (targetToRemove && !targetToRemove.new) { + setTargetsToRemove((prev) => [...prev, targetId]); + } + return prevTargets.filter((target) => target.targetId !== targetId); + }); + }, []); + + const updateTarget = useCallback((targetId: number, data: Partial) => { + setTargets((prevTargets) => { + const site = sites.find((site) => site.siteId === data.siteId); + return prevTargets.map((target) => + target.targetId === targetId + ? { + ...target, + ...data, + updated: true, + siteType: site ? site.type : target.siteType + } + : target + ); + }); + }, [sites]); + + const openHealthCheckDialog = useCallback((target: LocalTarget) => { + setSelectedTargetForHealthCheck(target); + setHealthCheckDialogOpen(true); + }, []); + + const columns = useMemo((): ColumnDef[] => { const priorityColumn: ColumnDef = { id: "priority", @@ -419,213 +433,15 @@ function ProxyResourceTargetsForm({ accessorKey: "address", header: () => {t("address")}, cell: ({ row }) => { - const selectedSite = sites.find( - (site) => site.siteId === row.original.siteId - ); - - const handleContainerSelectForTarget = ( - hostname: string, - port?: number - ) => { - updateTarget(row.original.targetId, { - ...row.original, - ip: hostname, - ...(port && { port: port }) - }); - }; - return ( -
-
- {selectedSite && - selectedSite.type === "newt" && - (() => { - const dockerState = getDockerStateForSite( - selectedSite.siteId - ); - return ( - - refreshContainersForSite( - selectedSite.siteId - ) - } - /> - ); - })()} - - - - - - - - - - - {t("siteNotFound")} - - - {sites.map((site) => ( - - updateTarget( - row.original - .targetId, - { - siteId: site.siteId - } - ) - } - > - - {site.name} - - ))} - - - - - - - {resource.http && ( - - )} - - {resource.http && ( -
- {"://"} -
- )} - - { - const input = e.target.value.trim(); - const hasProtocol = - /^(https?|h2c):\/\//.test(input); - const hasPort = /:\d+(?:\/|$)/.test(input); - - if (hasProtocol || hasPort) { - const parsed = parseHostTarget(input); - if (parsed) { - updateTarget( - row.original.targetId, - { - ...row.original, - method: hasProtocol - ? parsed.protocol - : row.original.method, - ip: parsed.host, - port: hasPort - ? parsed.port - : row.original.port - } - ); - } else { - updateTarget( - row.original.targetId, - { - ...row.original, - ip: input - } - ); - } - } else { - updateTarget(row.original.targetId, { - ...row.original, - ip: input - }); - } - }} - /> -
- {":"} -
- { - const value = parseInt(e.target.value, 10); - if (!isNaN(value) && value > 0) { - updateTarget(row.original.targetId, { - ...row.original, - port: value - }); - } else { - updateTarget(row.original.targetId, { - ...row.original, - port: 0 - }); - } - }} - /> -
-
+ ); }, size: 400, @@ -765,7 +581,7 @@ function ProxyResourceTargetsForm({ actionsColumn ]; } - }; + }, [isAdvancedMode, isHttp, sites, updateTarget, getDockerStateForSite, refreshContainersForSite, openHealthCheckDialog, removeTarget, t]); function addNewTarget() { const isHttp = resource.http; @@ -806,32 +622,6 @@ function ProxyResourceTargetsForm({ setTargets((prev) => [...prev, newTarget]); } - const removeTarget = (targetId: number) => { - setTargets([ - ...targets.filter((target) => target.targetId !== targetId) - ]); - - if (!targets.find((target) => target.targetId === targetId)?.new) { - setTargetsToRemove([...targetsToRemove, targetId]); - } - }; - - async function updateTarget(targetId: number, data: Partial) { - const site = sites.find((site) => site.siteId === data.siteId); - setTargets( - targets.map((target) => - target.targetId === targetId - ? { - ...target, - ...data, - updated: true, - siteType: site ? site.type : target.siteType - } - : target - ) - ); - } - function updateTargetHealthCheck(targetId: number, config: any) { setTargets( targets.map((target) => @@ -846,14 +636,6 @@ function ProxyResourceTargetsForm({ ); } - const openHealthCheckDialog = (target: LocalTarget) => { - console.log(target); - setSelectedTargetForHealthCheck(target); - setHealthCheckDialogOpen(true); - }; - - const columns = getColumns(); - const table = useReactTable({ data: targets, columns, diff --git a/src/app/[orgId]/settings/resources/proxy/create/page.tsx b/src/app/[orgId]/settings/resources/proxy/create/page.tsx index 1d6212bf..47f24cd9 100644 --- a/src/app/[orgId]/settings/resources/proxy/create/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/create/page.tsx @@ -1,5 +1,14 @@ "use client"; +import CopyTextBox from "@app/components/CopyTextBox"; +import DomainPicker from "@app/components/DomainPicker"; +import HealthCheckDialog from "@app/components/HealthCheckDialog"; +import { + PathMatchDisplay, + PathMatchModal, + PathRewriteDisplay, + PathRewriteModal +} from "@app/components/PathMatchRenameModal"; import { SettingsContainer, SettingsSection, @@ -9,6 +18,10 @@ import { SettingsSectionHeader, SettingsSectionTitle } from "@app/components/Settings"; +import HeaderTitle from "@app/components/SettingsSectionTitle"; +import { StrategySelect } from "@app/components/StrategySelect"; +import { ResourceTargetAddressItem } from "@app/components/resource-target-address-item"; +import { Button } from "@app/components/ui/button"; import { Form, FormControl, @@ -18,22 +31,7 @@ import { FormLabel, FormMessage } from "@app/components/ui/form"; -import HeaderTitle from "@app/components/SettingsSectionTitle"; -import { z } from "zod"; -import { useEffect, useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; import { Input } from "@app/components/ui/input"; -import { Button } from "@app/components/ui/button"; -import { useParams, useRouter } from "next/navigation"; -import { ListSitesResponse } from "@server/routers/site"; -import { formatAxiosError } from "@app/lib/api"; -import { createApiClient } from "@app/lib/api"; -import { useEnvContext } from "@app/hooks/useEnvContext"; -import { toast } from "@app/hooks/useToast"; -import { AxiosResponse } from "axios"; -import { Resource } from "@server/db"; -import { StrategySelect } from "@app/components/StrategySelect"; import { Select, SelectContent, @@ -41,48 +39,7 @@ import { SelectTrigger, SelectValue } from "@app/components/ui/select"; -import { ListDomainsResponse } from "@server/routers/domain"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList -} from "@app/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger -} from "@app/components/ui/popover"; -import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"; -import { cn } from "@app/lib/cn"; -import { - ArrowRight, - CircleCheck, - CircleX, - Info, - MoveRight, - Plus, - Settings, - SquareArrowOutUpRight -} from "lucide-react"; -import CopyTextBox from "@app/components/CopyTextBox"; -import Link from "next/link"; -import { useTranslations } from "next-intl"; -import DomainPicker from "@app/components/DomainPicker"; -import { build } from "@server/build"; -import { ContainersSelector } from "@app/components/ContainersSelector"; -import { - ColumnDef, - getFilteredRowModel, - getSortedRowModel, - getPaginationRowModel, - getCoreRowModel, - useReactTable, - flexRender, - Row -} from "@tanstack/react-table"; +import { Switch } from "@app/components/ui/switch"; import { Table, TableBody, @@ -91,30 +48,49 @@ import { TableHeader, TableRow } from "@app/components/ui/table"; -import { Switch } from "@app/components/ui/switch"; -import { ArrayElement } from "@server/types/ArrayElement"; -import { isTargetValid } from "@server/lib/validators"; -import { ListTargetsResponse } from "@server/routers/target"; -import { DockerManager, DockerState } from "@app/lib/docker"; -import { parseHostTarget } from "@app/lib/parseHostTarget"; -import { toASCII, toUnicode } from "punycode"; -import { DomainRow } from "@app/components/DomainsTable"; -import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@app/components/ui/tooltip"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { toast } from "@app/hooks/useToast"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { DockerManager, DockerState } from "@app/lib/docker"; +import { orgQueries } from "@app/lib/queries"; +import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Resource } from "@server/db"; +import { isTargetValid } from "@server/lib/validators"; +import { ListTargetsResponse } from "@server/routers/target"; +import { ArrayElement } from "@server/types/ArrayElement"; +import { useQuery } from "@tanstack/react-query"; import { - PathMatchDisplay, - PathMatchModal, - PathRewriteDisplay, - PathRewriteModal -} from "@app/components/PathMatchRenameModal"; -import { Badge } from "@app/components/ui/badge"; -import HealthCheckDialog from "@app/components/HealthCheckDialog"; -import { SwitchInput } from "@app/components/SwitchInput"; + ColumnDef, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable +} from "@tanstack/react-table"; +import { AxiosResponse } from "axios"; +import { + CircleCheck, + CircleX, + Info, + Plus, + Settings, + SquareArrowOutUpRight +} from "lucide-react"; +import { useTranslations } from "next-intl"; +import Link from "next/link"; +import { useParams, useRouter } from "next/navigation"; +import { toASCII } from "punycode"; +import { useEffect, useMemo, useState, useCallback } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { z } from "zod"; const baseResourceFormSchema = z.object({ name: z.string().min(1).max(255), @@ -204,10 +180,6 @@ const addTargetSchema = z } ); -type BaseResourceFormValues = z.infer; -type HttpResourceFormValues = z.infer; -type TcpUdpResourceFormValues = z.infer; - type ResourceType = "http" | "raw"; interface ResourceTypeOption { @@ -217,7 +189,7 @@ interface ResourceTypeOption { disabled?: boolean; } -type LocalTarget = Omit< +export type LocalTarget = Omit< ArrayElement & { new?: boolean; updated?: boolean; @@ -233,18 +205,16 @@ export default function Page() { const router = useRouter(); const t = useTranslations(); - const [loadingPage, setLoadingPage] = useState(true); - const [sites, setSites] = useState([]); - const [baseDomains, setBaseDomains] = useState< - { domainId: string; baseDomain: string }[] - >([]); + const { data: sites = [], isLoading: loadingPage } = useQuery( + orgQueries.sites({ orgId: orgId as string }) + ); + const [createLoading, setCreateLoading] = useState(false); const [showSnippets, setShowSnippets] = useState(false); const [niceId, setNiceId] = useState(""); // Target management state const [targets, setTargets] = useState([]); - const [targetsToRemove, setTargetsToRemove] = useState([]); const [dockerStates, setDockerStates] = useState>( new Map() ); @@ -405,102 +375,60 @@ export default function Page() { setDockerStates((prev) => new Map(prev.set(siteId, dockerState))); }; - const refreshContainersForSite = async (siteId: number) => { - const dockerManager = new DockerManager(api, siteId); - const containers = await dockerManager.fetchContainers(); + const refreshContainersForSite = useCallback( + async (siteId: number) => { + const dockerManager = new DockerManager(api, siteId); + const containers = await dockerManager.fetchContainers(); - setDockerStates((prev) => { - const newMap = new Map(prev); - const existingState = newMap.get(siteId); - if (existingState) { - newMap.set(siteId, { ...existingState, containers }); - } - return newMap; + setDockerStates((prev) => { + const newMap = new Map(prev); + const existingState = newMap.get(siteId); + if (existingState) { + newMap.set(siteId, { ...existingState, containers }); + } + return newMap; + }); + }, + [api] + ); + + const getDockerStateForSite = useCallback( + (siteId: number): DockerState => { + return ( + dockerStates.get(siteId) || { + isEnabled: false, + isAvailable: false, + containers: [] + } + ); + }, + [dockerStates] + ); + + const removeTarget = useCallback((targetId: number) => { + setTargets((prevTargets) => { + return prevTargets.filter((target) => target.targetId !== targetId); }); - }; + }, []); - const getDockerStateForSite = (siteId: number): DockerState => { - return ( - dockerStates.get(siteId) || { - isEnabled: false, - isAvailable: false, - containers: [] - } - ); - }; - - async function addTarget(data: z.infer) { - const site = sites.find((site) => site.siteId === data.siteId); - - const isHttp = baseForm.watch("http"); - - const newTarget: LocalTarget = { - ...data, - path: isHttp ? data.path || null : null, - pathMatchType: isHttp ? data.pathMatchType || null : null, - rewritePath: isHttp ? data.rewritePath || null : null, - rewritePathType: isHttp ? data.rewritePathType || null : null, - siteType: site?.type || null, - enabled: true, - targetId: new Date().getTime(), - new: true, - resourceId: 0, // Will be set when resource is created - priority: isHttp ? data.priority || 100 : 100, // Default priority - hcEnabled: false, - hcPath: null, - hcMethod: null, - hcInterval: null, - hcTimeout: null, - hcHeaders: null, - hcScheme: null, - hcHostname: null, - hcPort: null, - hcFollowRedirects: null, - hcHealth: "unknown", - hcStatus: null, - hcMode: null, - hcUnhealthyInterval: null, - hcTlsServerName: null - }; - - setTargets([...targets, newTarget]); - addTargetForm.reset({ - ip: "", - method: baseForm.watch("http") ? "http" : null, - port: "" as any as number, - path: null, - pathMatchType: null, - rewritePath: null, - rewritePathType: null, - priority: isHttp ? 100 : undefined - }); - } - - const removeTarget = (targetId: number) => { - setTargets([ - ...targets.filter((target) => target.targetId !== targetId) - ]); - - if (!targets.find((target) => target.targetId === targetId)?.new) { - setTargetsToRemove([...targetsToRemove, targetId]); - } - }; - - async function updateTarget(targetId: number, data: Partial) { - const site = sites.find((site) => site.siteId === data.siteId); - setTargets( - targets.map((target) => - target.targetId === targetId - ? { - ...target, - ...data, - updated: true, - siteType: site ? site.type : target.siteType - } - : target - ) - ); - } + const updateTarget = useCallback( + (targetId: number, data: Partial) => { + setTargets((prevTargets) => { + const site = sites.find((site) => site.siteId === data.siteId); + return prevTargets.map((target) => + target.targetId === targetId + ? { + ...target, + ...data, + updated: true, + siteType: site ? site.type : target.siteType + } + : target + ); + }); + }, + [sites] + ); async function onSubmit() { setCreateLoading(true); @@ -638,82 +566,18 @@ export default function Page() { } useEffect(() => { - const load = async () => { - setLoadingPage(true); + // Initialize Docker for newt sites + for (const site of sites) { + if (site.type === "newt") { + initializeDockerForSite(site.siteId); + } + } - const fetchSites = async () => { - const res = await api - .get< - AxiosResponse - >(`/org/${orgId}/sites/`) - .catch((e) => { - toast({ - variant: "destructive", - title: t("sitesErrorFetch"), - description: formatAxiosError( - e, - t("sitesErrorFetchDescription") - ) - }); - }); - - if (res?.status === 200) { - setSites(res.data.data.sites); - - // Initialize Docker for newt sites - for (const site of res.data.data.sites) { - if (site.type === "newt") { - initializeDockerForSite(site.siteId); - } - } - - // If there's only one site, set it as the default in the form - if (res.data.data.sites.length) { - addTargetForm.setValue( - "siteId", - res.data.data.sites[0].siteId - ); - } - } - }; - - const fetchDomains = async () => { - const res = await api - .get< - AxiosResponse - >(`/org/${orgId}/domains/`) - .catch((e) => { - toast({ - variant: "destructive", - title: t("domainsErrorFetch"), - description: formatAxiosError( - e, - t("domainsErrorFetchDescription") - ) - }); - }); - - if (res?.status === 200) { - const rawDomains = res.data.data.domains as DomainRow[]; - const domains = rawDomains.map((domain) => ({ - ...domain, - baseDomain: toUnicode(domain.baseDomain) - })); - setBaseDomains(domains); - // if (domains.length) { - // httpForm.setValue("domainId", domains[0].domainId); - // } - } - }; - - await fetchSites(); - await fetchDomains(); - - setLoadingPage(false); - }; - - load(); - }, []); + // If there's at least one site, set it as the default in the form + if (sites.length > 0) { + addTargetForm.setValue("siteId", sites[0].siteId); + } + }, [sites]); function TargetHealthCheck(targetId: number, config: any) { setTargets( @@ -729,16 +593,15 @@ export default function Page() { ); } - const openHealthCheckDialog = (target: LocalTarget) => { + const openHealthCheckDialog = useCallback((target: LocalTarget) => { console.log(target); setSelectedTargetForHealthCheck(target); setHealthCheckDialogOpen(true); - }; + }, []); - const getColumns = (): ColumnDef[] => { - const baseColumns: ColumnDef[] = []; - const isHttp = baseForm.watch("http"); + const isHttp = baseForm.watch("http"); + const columns = useMemo((): ColumnDef[] => { const priorityColumn: ColumnDef = { id: "priority", header: () => ( @@ -875,7 +738,7 @@ export default function Page() { trigger={ - - - - - - - {t("siteNotFound")} - - - {sites.map((site) => ( - - updateTarget( - row.original - .targetId, - { - siteId: site.siteId - } - ) - } - > - - {site.name} - - ))} - - - - - - - {isHttp && ( - - )} - - {isHttp && ( -
- {"://"} -
- )} - - { - const input = e.target.value.trim(); - const hasProtocol = - /^(https?|h2c):\/\//.test(input); - const hasPort = /:\d+(?:\/|$)/.test(input); - - if (hasProtocol || hasPort) { - const parsed = parseHostTarget(input); - if (parsed) { - updateTarget( - row.original.targetId, - { - ...row.original, - method: hasProtocol - ? parsed.protocol - : row.original.method, - ip: parsed.host, - port: hasPort - ? parsed.port - : row.original.port - } - ); - } else { - updateTarget( - row.original.targetId, - { - ...row.original, - ip: input - } - ); - } - } else { - updateTarget(row.original.targetId, { - ...row.original, - ip: input - }); - } - }} - /> -
- {":"} -
- { - const value = parseInt(e.target.value, 10); - if (!isNaN(value) && value > 0) { - updateTarget(row.original.targetId, { - ...row.original, - port: value - }); - } else { - updateTarget(row.original.targetId, { - ...row.original, - port: 0 - }); - } - }} - /> - - - ); - }, + cell: ({ row }) => ( + + ), size: 400, minSize: 350, maxSize: 500 @@ -1186,7 +849,7 @@ export default function Page() { + + + + + + {t("siteNotFound")} + + {sites.map((site) => ( + + updateTarget( + proxyTarget.targetId, + { + siteId: site.siteId + } + ) + } + > + + {site.name} + + ))} + + + + + + + {isHttp && ( + + )} + + {isHttp && ( +
+ {"://"} +
+ )} + + { + const input = e.target.value.trim(); + const hasProtocol = /^(https?|h2c):\/\//.test(input); + const hasPort = /:\d+(?:\/|$)/.test(input); + + if (hasProtocol || hasPort) { + const parsed = parseHostTarget(input); + if (parsed) { + updateTarget(proxyTarget.targetId, { + ...proxyTarget, + method: hasProtocol + ? parsed.protocol + : proxyTarget.method, + ip: parsed.host, + port: hasPort + ? parsed.port + : proxyTarget.port + }); + } else { + updateTarget(proxyTarget.targetId, { + ...proxyTarget, + ip: input + }); + } + } else { + updateTarget(proxyTarget.targetId, { + ...proxyTarget, + ip: input + }); + } + }} + /> +
+ {":"} +
+ { + const value = parseInt(e.target.value, 10); + if (!isNaN(value) && value > 0) { + updateTarget(proxyTarget.targetId, { + ...proxyTarget, + port: value + }); + } else { + updateTarget(proxyTarget.targetId, { + ...proxyTarget, + port: 0 + }); + } + }} + /> + + + ); +} diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index aec536e2..0c48f1f2 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -44,8 +44,8 @@ const Input = React.forwardRef( data-slot="input" className={cn( "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", - "focus-visible:border-ring focus-visible:ring-ring/50", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0", className )} ref={ref} diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 39e79da6..6d065e7a 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -36,7 +36,9 @@ function SelectTrigger({ data-slot="select-trigger" data-size={size} className={cn( - "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 w-full", + "border-input data-placeholder:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer flex items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 w-full", + "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0", + // "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-0", className )} {...props} @@ -60,7 +62,7 @@ function SelectContent({ {children}