diff --git a/apps/design-system/components/command-menu.tsx b/apps/design-system/components/command-menu.tsx index fbd26bea2e88d..2fa3cfee17fc4 100644 --- a/apps/design-system/components/command-menu.tsx +++ b/apps/design-system/components/command-menu.tsx @@ -7,12 +7,12 @@ import * as React from 'react' import { Button, CommandDialog, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, DialogProps, DialogTitle, } from 'ui' @@ -77,13 +77,13 @@ export function CommandMenu({ ...props }: DialogProps) { /> Search Design System... - - - No results found. + + + No results found. {docsConfig.sidebarNav.map((group) => ( - + {group.items.map((navItem) => ( - { @@ -94,30 +94,30 @@ export function CommandMenu({ ...props }: DialogProps) { {navItem.title} - + ))} - + ))} - - - runCommand(() => setTheme('light'))}> + + + runCommand(() => setTheme('light'))}> Light - - runCommand(() => setTheme('dark'))}> + + runCommand(() => setTheme('dark'))}> Dark - - runCommand(() => setTheme('classic-dark'))}> + + runCommand(() => setTheme('classic-dark'))}> Classic dark - - runCommand(() => setTheme('system'))}> + + runCommand(() => setTheme('system'))}> System - - - + + + ) diff --git a/apps/design-system/registry/default/example/combobox-demo.tsx b/apps/design-system/registry/default/example/combobox-demo.tsx index 558d83237e4e5..b7f8fabd97817 100644 --- a/apps/design-system/registry/default/example/combobox-demo.tsx +++ b/apps/design-system/registry/default/example/combobox-demo.tsx @@ -4,12 +4,12 @@ import { Check, ChevronsUpDown } from 'lucide-react' import * as React from 'react' import { Button, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -61,13 +61,13 @@ export default function ComboboxDemo() { - - - - No framework found. - + + + + No framework found. + {frameworks.map((framework) => ( - { @@ -82,11 +82,11 @@ export default function ComboboxDemo() { )} /> {framework.label} - + ))} - - - + + + ) diff --git a/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx b/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx index ae866941efaa7..b9885ea531b5d 100644 --- a/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx +++ b/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx @@ -4,12 +4,12 @@ import { Calendar, MoreHorizontal, Tags, Trash, User } from 'lucide-react' import * as React from 'react' import { Button, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, @@ -72,14 +72,14 @@ export default function ComboboxDropdownMenu() { Apply label - - + + - - No label found. - + + No label found. + {labels.map((label) => ( - { @@ -88,12 +88,12 @@ export default function ComboboxDropdownMenu() { }} > {label} - + ))} - - + + - + diff --git a/apps/design-system/registry/default/example/combobox-form.tsx b/apps/design-system/registry/default/example/combobox-form.tsx index b83b07f07faef..b6cd2f507d110 100644 --- a/apps/design-system/registry/default/example/combobox-form.tsx +++ b/apps/design-system/registry/default/example/combobox-form.tsx @@ -6,12 +6,12 @@ import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Form, FormControl, FormDescription, @@ -94,13 +94,13 @@ export default function ComboboxForm() { - - - - No language found. - + + + + No language found. + {languages.map((language) => ( - { @@ -114,11 +114,11 @@ export default function ComboboxForm() { )} /> {language.label} - + ))} - - - + + + diff --git a/apps/design-system/registry/default/example/combobox-popover.tsx b/apps/design-system/registry/default/example/combobox-popover.tsx index 88ac417d507ba..4c2df6b951c20 100644 --- a/apps/design-system/registry/default/example/combobox-popover.tsx +++ b/apps/design-system/registry/default/example/combobox-popover.tsx @@ -12,12 +12,12 @@ import { import * as React from 'react' import { Button, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -84,13 +84,13 @@ export default function ComboboxPopover() { - - - - No results found. - + + + + No results found. + {statuses.map((status) => ( - { @@ -107,11 +107,11 @@ export default function ComboboxPopover() { )} /> {status.label} - + ))} - - - + + + diff --git a/apps/design-system/registry/default/example/combobox-responsive.tsx b/apps/design-system/registry/default/example/combobox-responsive.tsx index d09a1fd62851d..90dcb2f8506df 100644 --- a/apps/design-system/registry/default/example/combobox-responsive.tsx +++ b/apps/design-system/registry/default/example/combobox-responsive.tsx @@ -4,12 +4,12 @@ import { Plus } from 'lucide-react' import * as React from 'react' import { Button, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Drawer, DrawerContent, DrawerTrigger, @@ -97,13 +97,13 @@ function StatusList({ setSelectedStatus: (status: Status | null) => void }) { return ( - - - - No results found. - + + + + No results found. + {statuses.map((status) => ( - { @@ -112,10 +112,10 @@ function StatusList({ }} > {status.label} - + ))} - - - + + + ) } diff --git a/apps/design-system/registry/default/example/command-demo.tsx b/apps/design-system/registry/default/example/command-demo.tsx index 8589373f51dd4..b51049ab5bcf2 100644 --- a/apps/design-system/registry/default/example/command-demo.tsx +++ b/apps/design-system/registry/default/example/command-demo.tsx @@ -1,54 +1,54 @@ import { Calculator, Calendar, CreditCard, Settings, Smile, User } from 'lucide-react' import { - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, - CommandShortcut_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, } from 'ui' export default function CommandDemo() { return ( - - - - No results found. - - + + + + No results found. + + Calendar - - + + Search Emoji - - + + Calculator - - - - - + + + + + Profile - ⌘P - - + ⌘P + + Billing - ⌘B - - + ⌘B + + Settings - ⌘S - - - - + ⌘S + + + + ) } diff --git a/apps/design-system/registry/default/example/command-dialog.tsx b/apps/design-system/registry/default/example/command-dialog.tsx index fc9eab9b0ff60..308c3cc1ac267 100644 --- a/apps/design-system/registry/default/example/command-dialog.tsx +++ b/apps/design-system/registry/default/example/command-dialog.tsx @@ -4,13 +4,13 @@ import { Calculator, Calendar, CreditCard, Settings, Smile, User } from 'lucide- import * as React from 'react' import { CommandDialog, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, - CommandShortcut_Shadcn_, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, } from 'ui' export default function CommandDialogDemo() { @@ -37,42 +37,42 @@ export default function CommandDialogDemo() {

- - - No results found. - - + + + No results found. + + Calendar - - + + Search Emoji - - + + Calculator - - - - - + + + + + Profile - ⌘P - - + ⌘P + + Billing - ⌘B - - + ⌘B + + Settings - ⌘S - - - + ⌘S + + + ) diff --git a/apps/design-system/registry/default/example/commandmenu-badge.tsx b/apps/design-system/registry/default/example/commandmenu-badge.tsx index 309f8cd2afadf..edeb2f3df2f58 100644 --- a/apps/design-system/registry/default/example/commandmenu-badge.tsx +++ b/apps/design-system/registry/default/example/commandmenu-badge.tsx @@ -1,9 +1,9 @@ import { Button } from '@ui/components/shadcn/ui/button' import { BadgeExperimental, - CommandInput, - CommandList, CommandMenu, + CommandMenuInput, + CommandMenuList, CommandProvider, useRegisterCommands, useSetCommandMenuOpen, @@ -34,8 +34,8 @@ export default function CommandMenuDemo() { - - + + ) diff --git a/apps/design-system/registry/default/example/commandmenu-conditional.tsx b/apps/design-system/registry/default/example/commandmenu-conditional.tsx index 4d8159b3573ac..2be767247be66 100644 --- a/apps/design-system/registry/default/example/commandmenu-conditional.tsx +++ b/apps/design-system/registry/default/example/commandmenu-conditional.tsx @@ -1,9 +1,9 @@ import { Button } from '@ui/components/shadcn/ui/button' import { useMemo, useState } from 'react' import { - CommandInput, - CommandList, CommandMenu, + CommandMenuInput, + CommandMenuList, CommandMenuTrigger as CommandMenuTriggerPrimitive, CommandProvider, useRegisterCommands, @@ -60,8 +60,8 @@ export default function CommandMenuDemo() {
}> - - + +
diff --git a/apps/design-system/registry/default/example/commandmenu-demo.tsx b/apps/design-system/registry/default/example/commandmenu-demo.tsx index a1cfec496cd56..e4df0a4385016 100644 --- a/apps/design-system/registry/default/example/commandmenu-demo.tsx +++ b/apps/design-system/registry/default/example/commandmenu-demo.tsx @@ -1,8 +1,8 @@ import { Button } from '@ui/components/shadcn/ui/button' import { - CommandInput, - CommandList, CommandMenu, + CommandMenuInput, + CommandMenuList, CommandMenuTrigger as CommandMenuTriggerPrimitive, CommandProvider, useRegisterCommands, @@ -40,8 +40,8 @@ export default function CommandMenuDemo() { }> - - + + ) diff --git a/apps/design-system/registry/default/example/commandmenu-force.tsx b/apps/design-system/registry/default/example/commandmenu-force.tsx index ac475beb238c8..b7f297d3ba2f7 100644 --- a/apps/design-system/registry/default/example/commandmenu-force.tsx +++ b/apps/design-system/registry/default/example/commandmenu-force.tsx @@ -1,8 +1,8 @@ import { Button } from '@ui/components/shadcn/ui/button' import { - CommandInput, - CommandList, CommandMenu, + CommandMenuInput, + CommandMenuList, CommandMenuTrigger as CommandMenuTriggerPrimitive, CommandProvider, useRegisterCommands, @@ -52,8 +52,8 @@ export default function CommandMenuDemo() { }> - - + + ) diff --git a/apps/design-system/registry/default/example/commandmenu-hidden.tsx b/apps/design-system/registry/default/example/commandmenu-hidden.tsx index a9d60c6902553..3e693b32b60a2 100644 --- a/apps/design-system/registry/default/example/commandmenu-hidden.tsx +++ b/apps/design-system/registry/default/example/commandmenu-hidden.tsx @@ -1,8 +1,8 @@ import { Button } from '@ui/components/shadcn/ui/button' import { - CommandInput, - CommandList, CommandMenu, + CommandMenuInput, + CommandMenuList, CommandMenuTrigger as CommandMenuTriggerPrimitive, CommandProvider, useRegisterCommands, @@ -39,8 +39,8 @@ export default function CommandMenuDemo() { }> - - + + ) diff --git a/apps/design-system/registry/default/example/commandmenu-icon.tsx b/apps/design-system/registry/default/example/commandmenu-icon.tsx index c877f83e45021..7de6da43f0cd4 100644 --- a/apps/design-system/registry/default/example/commandmenu-icon.tsx +++ b/apps/design-system/registry/default/example/commandmenu-icon.tsx @@ -1,9 +1,9 @@ import { Button } from '@ui/components/shadcn/ui/button' import { Github } from 'lucide-react' import { - CommandInput, - CommandList, CommandMenu, + CommandMenuInput, + CommandMenuList, CommandMenuTrigger as CommandMenuTriggerPrimitive, CommandProvider, useRegisterCommands, @@ -35,8 +35,8 @@ export default function CommandMenuDemo() { }> - - + + ) diff --git a/apps/design-system/registry/default/example/commandmenu-subpage-custom.tsx b/apps/design-system/registry/default/example/commandmenu-subpage-custom.tsx index 0cd74c1d7a18f..93f833dbae5df 100644 --- a/apps/design-system/registry/default/example/commandmenu-subpage-custom.tsx +++ b/apps/design-system/registry/default/example/commandmenu-subpage-custom.tsx @@ -1,10 +1,9 @@ import { Button } from '@ui/components/shadcn/ui/button' -import { useMemo } from 'react' import { Breadcrumb, - CommandInput, - CommandList, CommandMenu, + CommandMenuInput, + CommandMenuList, CommandMenuTrigger as CommandMenuTriggerPrimitive, CommandProvider, PageType, @@ -57,8 +56,8 @@ export default function CommandMenuDemo() { }> - - + + ) diff --git a/apps/design-system/registry/default/example/commandmenu-subpage.tsx b/apps/design-system/registry/default/example/commandmenu-subpage.tsx index ef842b4314344..3bc2112c66980 100644 --- a/apps/design-system/registry/default/example/commandmenu-subpage.tsx +++ b/apps/design-system/registry/default/example/commandmenu-subpage.tsx @@ -1,9 +1,9 @@ import { Button } from '@ui/components/shadcn/ui/button' import { useMemo } from 'react' import { - CommandInput, - CommandList, CommandMenu, + CommandMenuInput, + CommandMenuList, CommandMenuTrigger as CommandMenuTriggerPrimitive, CommandProvider, PageType, @@ -65,8 +65,8 @@ export default function CommandMenuDemo() { }> - - + + ) diff --git a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.ComboBox.tsx b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.ComboBox.tsx index fc8d2a50cf566..105cb3e45b6eb 100644 --- a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.ComboBox.tsx +++ b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.ComboBox.tsx @@ -1,21 +1,21 @@ +import { useIntersectionObserver } from '~/hooks/useIntersectionObserver' import { noop } from 'lodash-es' import { Check, ChevronsUpDown } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import { Button_Shadcn_ as Button, cn, - Command_Shadcn_ as Command, - CommandGroup_Shadcn_ as CommandGroup, - CommandInput_Shadcn_ as CommandInput, - CommandItem_Shadcn_ as CommandItem, - CommandList_Shadcn_ as CommandList, - Popover as Popover, - PopoverContent as PopoverContent, - PopoverTrigger as PopoverTrigger, + Command, + CommandGroup as CommandGroup, + CommandInput, + CommandItem as CommandItem, + CommandList as CommandList, + Popover, + PopoverContent, + PopoverTrigger, ScrollArea, } from 'ui' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' -import { useIntersectionObserver } from '~/hooks/useIntersectionObserver' export interface ComboBoxOption { id: string diff --git a/apps/docs/features/command/CommandMenu.tsx b/apps/docs/features/command/CommandMenu.tsx index c22780632348d..03471aada613e 100644 --- a/apps/docs/features/command/CommandMenu.tsx +++ b/apps/docs/features/command/CommandMenu.tsx @@ -1,12 +1,17 @@ -import { CommandHeader, CommandInput, CommandList, CommandMenu } from 'ui-patterns/CommandMenu' +import { + CommandHeader, + CommandMenu, + CommandMenuInput, + CommandMenuList, +} from 'ui-patterns/CommandMenu' import { useChangelogCommand } from 'ui-patterns/CommandMenu/prepackaged/Changelog' import { useDocsAiCommands } from 'ui-patterns/CommandMenu/prepackaged/DocsAi' import { useDocsSearchCommands } from 'ui-patterns/CommandMenu/prepackaged/DocsSearch' import { useSupportCommands } from 'ui-patterns/CommandMenu/prepackaged/Support' import { useThemeSwitcherCommands } from 'ui-patterns/CommandMenu/prepackaged/ThemeSwitcher' -import { useQuickstartCommands } from './Quickstarts' import { useDocsNavCommands } from '../../components/Navigation/Navigation.commands' +import { useQuickstartCommands } from './Quickstarts' const DocsCommandMenu = () => { useDocsSearchCommands({ @@ -23,9 +28,9 @@ const DocsCommandMenu = () => { return ( - + - + ) } diff --git a/apps/docs/features/ui/InfoTooltip.tsx b/apps/docs/features/ui/InfoTooltip.tsx index d8dd67b1da623..d604a0047b128 100644 --- a/apps/docs/features/ui/InfoTooltip.tsx +++ b/apps/docs/features/ui/InfoTooltip.tsx @@ -1,20 +1,20 @@ 'use client' +import { useBreakpoint } from 'common' +import { InfoIcon, XIcon } from 'lucide-react' import React, { - type PropsWithChildren, useCallback, useEffect, useId, useRef, useState, + type PropsWithChildren, } from 'react' -import { InfoIcon, XIcon } from 'lucide-react' import { ErrorBoundary } from 'react-error-boundary' -import { useBreakpoint } from 'common' import { Button, cn, - CommandEmpty_Shadcn_, + CommandEmpty, Sheet, SheetContent, SheetHeader, @@ -105,7 +105,7 @@ const InfoTooltip = ({ 'w-full h-fit min-h-[200px] py-2 px-4' )} > - }> + }>
diff --git a/apps/docs/features/ui/McpConfigPanel.tsx b/apps/docs/features/ui/McpConfigPanel.tsx index e4723715f4a7d..ec2cd3c752f7b 100644 --- a/apps/docs/features/ui/McpConfigPanel.tsx +++ b/apps/docs/features/ui/McpConfigPanel.tsx @@ -1,5 +1,9 @@ 'use client' +import { useDebounce } from '~/hooks/useDebounce' +import { useIntersectionObserver } from '~/hooks/useIntersectionObserver' +import { useProjectsInfiniteQuery } from '~/lib/fetch/projects-infinite' +import { useSendTelemetryEvent } from '~/lib/telemetry' import { useIsLoggedIn, useIsUserLoading } from 'common' import { Check, ChevronDown } from 'lucide-react' import { useTheme } from 'next-themes' @@ -8,11 +12,11 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -25,10 +29,6 @@ import { type McpClient, } from 'ui-patterns/McpUrlBuilder' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' -import { useDebounce } from '~/hooks/useDebounce' -import { useIntersectionObserver } from '~/hooks/useIntersectionObserver' -import { useProjectsInfiniteQuery } from '~/lib/fetch/projects-infinite' -import { useSendTelemetryEvent } from '~/lib/telemetry' type PlatformType = (typeof PLATFORMS)[number]['value'] @@ -135,8 +135,8 @@ function ProjectSelector({ )}
- - + setSearch('')} /> - - + + {isLoading ? (
@@ -160,7 +160,7 @@ function ProjectSelector({ )} 7 ? 'h-[210px]' : ''}> {projects?.map((project) => ( - { @@ -178,16 +178,16 @@ function ProjectSelector({ project.ref === selectedProject?.ref ? 'opacity-100' : 'opacity-0' )} /> - + ))}
{hasNextPage && } )} - - - + + + ) @@ -230,11 +230,11 @@ function PlatformSelector({
- - - + + + {PLATFORMS.map((platform) => ( - { @@ -252,11 +252,11 @@ function PlatformSelector({ platform.value === selectedPlatform ? 'opacity-100' : 'opacity-0' )} /> - + ))} - - - + + + ) diff --git a/apps/learn/components/command-menu.tsx b/apps/learn/components/command-menu.tsx index d764ce7e38f4b..75f055f9a8f2f 100644 --- a/apps/learn/components/command-menu.tsx +++ b/apps/learn/components/command-menu.tsx @@ -4,21 +4,21 @@ import { CircleIcon, LaptopIcon, MoonIcon, SunIcon } from 'lucide-react' import { useTheme } from 'next-themes' import { useRouter } from 'next/navigation' import * as React from 'react' - -import { COMMAND_ITEMS } from '@/config/docs' -import { cn } from '@/lib/utils' import { Button, CommandDialog, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, DialogProps, } from 'ui' +import { COMMAND_ITEMS } from '@/config/docs' +import { cn } from '@/lib/utils' + export function CommandMenu({ ...props }: DialogProps) { const router = useRouter() const [open, setOpen] = React.useState(false) @@ -69,12 +69,12 @@ export function CommandMenu({ ...props }: DialogProps) { - - - No results found. - + + + No results found. + {COMMAND_ITEMS.map((navItem) => ( - runCommand(() => router.push(navItem.href as string))} @@ -83,29 +83,29 @@ export function CommandMenu({ ...props }: DialogProps) {
{navItem.label} - + ))} -
- - - runCommand(() => setTheme('light'))}> + + + + runCommand(() => setTheme('light'))}> Light - - runCommand(() => setTheme('dark'))}> + + runCommand(() => setTheme('dark'))}> Dark - - runCommand(() => setTheme('classic-dark'))}> + + runCommand(() => setTheme('classic-dark'))}> Classic dark - - runCommand(() => setTheme('system'))}> + + runCommand(() => setTheme('system'))}> System - - -
+ + + ) diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/PermissionResourceSelector.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/PermissionResourceSelector.tsx index 8f6416c71a144..f1d2e54c9d2ca 100644 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/PermissionResourceSelector.tsx +++ b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/PermissionResourceSelector.tsx @@ -3,12 +3,12 @@ import { Path, PathValue } from 'react-hook-form' import { Button, Checkbox, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -46,19 +46,19 @@ export const PermissionResourceSelector = - - - - No resources found. + + + + No resources found. - +
{ACCESS_TOKEN_RESOURCES.map((resource) => { const isChecked = permissionRows.some( (row: PermissionRow) => row.resource === resource.resource ) return ( - handleToggleResource(resource)} @@ -77,13 +77,13 @@ export const PermissionResourceSelector =
- + ) })} -
-
-
+ + +
) diff --git a/apps/studio/components/interfaces/Account/Preferences/TimezoneSettings.tsx b/apps/studio/components/interfaces/Account/Preferences/TimezoneSettings.tsx index 2430bf3bdee93..63a318f171bd4 100644 --- a/apps/studio/components/interfaces/Account/Preferences/TimezoneSettings.tsx +++ b/apps/studio/components/interfaces/Account/Preferences/TimezoneSettings.tsx @@ -6,12 +6,12 @@ import { Card, CardContent, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -103,13 +103,13 @@ export const TimezoneSettings = () => { - - - - No timezones found - + + + + No timezones found + - handleSelect('')} @@ -126,12 +126,12 @@ export const TimezoneSettings = () => { isAutoDetected ? 'opacity-100' : 'opacity-0' )} /> - + {TIMEZONES_BY_IANA.map((entry) => { const ianaName = entry.utc[0] const isSelected = !isAutoDetected && storedTimezone === ianaName return ( - { isSelected ? 'opacity-100' : 'opacity-0' )} /> - + ) })} - - - + + + diff --git a/apps/studio/components/interfaces/App/CommandMenu/CommandMenu.tsx b/apps/studio/components/interfaces/App/CommandMenu/CommandMenu.tsx index e35d8e1687f66..41be54b5fc99a 100644 --- a/apps/studio/components/interfaces/App/CommandMenu/CommandMenu.tsx +++ b/apps/studio/components/interfaces/App/CommandMenu/CommandMenu.tsx @@ -1,5 +1,10 @@ import { IS_PLATFORM } from 'common' -import { CommandHeader, CommandInput, CommandList, CommandMenu } from 'ui-patterns/CommandMenu' +import { + CommandHeader, + CommandMenu, + CommandMenuInput, + CommandMenuList, +} from 'ui-patterns/CommandMenu' import { useChangelogCommand } from 'ui-patterns/CommandMenu/prepackaged/Changelog' import { useDocsAiCommands } from 'ui-patterns/CommandMenu/prepackaged/DocsAi' import { useDocsSearchCommands } from 'ui-patterns/CommandMenu/prepackaged/DocsSearch' @@ -25,9 +30,9 @@ export function CommandMenuInnerContent() { return ( <> - + - + ) } diff --git a/apps/studio/components/interfaces/App/CommandMenu/ContextSearchCommands.tsx b/apps/studio/components/interfaces/App/CommandMenu/ContextSearchCommands.tsx index ea270b0a991be..7e8f5718a518e 100644 --- a/apps/studio/components/interfaces/App/CommandMenu/ContextSearchCommands.tsx +++ b/apps/studio/components/interfaces/App/CommandMenu/ContextSearchCommands.tsx @@ -7,7 +7,7 @@ import { useMemo } from 'react' import type { ICommand } from 'ui-patterns/CommandMenu' import { CommandHeader, - CommandInput, + CommandMenuInput, CommandWrapper, PageType, useQuery, @@ -74,7 +74,7 @@ function ContextSearchPage({ return ( - + diff --git a/apps/studio/components/interfaces/App/CommandMenu/ContextSearchResults.shared.tsx b/apps/studio/components/interfaces/App/CommandMenu/ContextSearchResults.shared.tsx index d2ae7fd773bf0..d798f407c50c5 100644 --- a/apps/studio/components/interfaces/App/CommandMenu/ContextSearchResults.shared.tsx +++ b/apps/studio/components/interfaces/App/CommandMenu/ContextSearchResults.shared.tsx @@ -1,10 +1,10 @@ 'use client' -import { cn, CommandList_Shadcn_ } from 'ui' +import { cn, CommandList } from 'ui' import { ShimmeringLoader } from 'ui-patterns' import { TextHighlighter } from 'ui-patterns/CommandMenu' -import { CommandItem } from 'ui-patterns/CommandMenu/internal/Command' -import { CommandGroup } from 'ui-patterns/CommandMenu/internal/CommandGroup' +import { CommandMenuGroup } from 'ui-patterns/CommandMenu/internal/CommandMenuGroup' +import { CommandMenuItem } from 'ui-patterns/CommandMenu/internal/CommandMenuItem' import type { IActionCommand, IRouteCommand } from 'ui-patterns/CommandMenu/internal/types' export interface SearchResult { @@ -86,15 +86,15 @@ export function ResultsList({ }) return ( - - + {commands.map((command) => ( - +
{command.name} {command.value && command.value !== command.name && ( @@ -103,9 +103,9 @@ export function ResultsList({

)}
-
+ ))} -
-
+ +
) } diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx index ca41b17194ab0..01340314da6b2 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx @@ -11,12 +11,12 @@ import { UseFormReturn } from 'react-hook-form' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, FormControl, FormField, FormItem, @@ -182,14 +182,14 @@ export const PolicyDetailsV2 = ({ align="start" sameWidthAsTrigger > - - - event.stopPropagation()}> - No tables found - + + + event.stopPropagation()}> + No tables found + 7 ? 'h-[200px]' : ''}> {(tables ?? []).map((table) => ( - { @@ -205,12 +205,12 @@ export const PolicyDetailsV2 = ({ {field.value === table.name ? : ''} {table.name} - + ))} - - - + + +
diff --git a/apps/studio/components/interfaces/Auth/RLSTester/UserSelector.tsx b/apps/studio/components/interfaces/Auth/RLSTester/UserSelector.tsx index 17daee2762cfe..bd26666b3da96 100644 --- a/apps/studio/components/interfaces/Auth/RLSTester/UserSelector.tsx +++ b/apps/studio/components/interfaces/Auth/RLSTester/UserSelector.tsx @@ -6,12 +6,12 @@ import { toast } from 'sonner' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -82,8 +82,8 @@ export const UserSelector = () => { - - + { Failed to fetch users: {error.message} ) : ( - No user found + No user found )} - + {isPending && (
@@ -107,11 +107,11 @@ export const UserSelector = () => { )} {isSuccess && ( - + 7 ? 'h-full md:h-[210px]' : ''}> {users.map((user) => { return ( - {

{impersonatingUser?.id === user.id && }
- + ) })} - + )} -
-
+ +
diff --git a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AwsRegionSelector.tsx b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AwsRegionSelector.tsx index f6682a1b1874a..6cbbdfab02017 100644 --- a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AwsRegionSelector.tsx +++ b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AwsRegionSelector.tsx @@ -3,12 +3,12 @@ import { useId, useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, FormControl, Popover, PopoverContent, @@ -78,14 +78,14 @@ export const AwsRegionSelector = ({ - - - - No regions found. - + + + + No regions found. + {AWS_IDP_REGIONS.map((option) => ( - { @@ -97,12 +97,12 @@ export const AwsRegionSelector = ({ className={cn('mr-2 h-4 w-4', option === value ? 'opacity-100' : 'opacity-0')} /> {option} - + ))} - - - + + + ) diff --git a/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx index 8316091929190..6c6abcc3d92b7 100644 --- a/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx +++ b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx @@ -20,12 +20,12 @@ import { Button, Checkbox, cn, - Command_Shadcn_ as Command, - CommandEmpty_Shadcn_ as CommandEmpty, - CommandGroup_Shadcn_ as CommandGroup, - CommandInput_Shadcn_ as CommandInput, - CommandItem_Shadcn_ as CommandItem, - CommandList_Shadcn_ as CommandList, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, FormControl, FormField, FormItem, diff --git a/apps/studio/components/interfaces/BranchManagement/BranchSelector.tsx b/apps/studio/components/interfaces/BranchManagement/BranchSelector.tsx index 7fcc00cc2e959..544de78c274f0 100644 --- a/apps/studio/components/interfaces/BranchManagement/BranchSelector.tsx +++ b/apps/studio/components/interfaces/BranchManagement/BranchSelector.tsx @@ -1,12 +1,12 @@ import { Check, GitMerge, Shield } from 'lucide-react' import { useState } from 'react' import { - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -68,14 +68,14 @@ export const BranchSelector = ({ - - - - No available branches found - + + + + No available branches found + {availableBranches.map((branch) => ( - Synced to a Git branch} {branch.review_requested_at && Merge request opened} - + ))} - - - + + + ) diff --git a/apps/studio/components/interfaces/Connect/ConnectDropdown.tsx b/apps/studio/components/interfaces/Connect/ConnectDropdown.tsx index 70a25816e18ba..a2a0b30e9b015 100644 --- a/apps/studio/components/interfaces/Connect/ConnectDropdown.tsx +++ b/apps/studio/components/interfaces/Connect/ConnectDropdown.tsx @@ -3,12 +3,12 @@ import { useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -73,13 +73,13 @@ export const ConnectDropdown = ({ - - - - No results found. - + + + + No results found. + {items.map((item) => ( - { @@ -102,11 +102,11 @@ export const ConnectDropdown = ({ size={15} className={cn('ml-auto ', item.key === state ? 'opacity-100' : 'opacity-0')} /> - + ))} - - - + + + ) diff --git a/apps/studio/components/interfaces/ConnectSheet/FrameworkSelector.tsx b/apps/studio/components/interfaces/ConnectSheet/FrameworkSelector.tsx index 1d372e1bdf22a..1bf01b509d241 100644 --- a/apps/studio/components/interfaces/ConnectSheet/FrameworkSelector.tsx +++ b/apps/studio/components/interfaces/ConnectSheet/FrameworkSelector.tsx @@ -3,12 +3,12 @@ import { useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -64,13 +64,13 @@ export const FrameworkSelector = ({ align="start" onOpenAutoFocus={(e) => e.preventDefault()} > - - - - No results found. - + + + + No results found. + {items.map((item) => ( - handleSelect(item.key)} @@ -82,11 +82,11 @@ export const FrameworkSelector = ({ size={15} className={cn('ml-auto', item.key === value ? 'opacity-100' : 'opacity-0')} /> - + ))} - - - + + + ) diff --git a/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx b/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx index a0a8d24f945bc..028bd48a27c1f 100644 --- a/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx +++ b/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx @@ -3,12 +3,12 @@ import { useId, useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -51,14 +51,14 @@ export const TimezoneSelection = ({ - - - - No timezones found... - + + + + No timezones found... + {timezoneOptions.map((option) => ( - { @@ -78,12 +78,12 @@ export const TimezoneSelection = ({ selectedTimezone.text === option ? 'opacity-100' : 'opacity-0' )} /> - + ))} - - - + + + diff --git a/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx b/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx index 68e89737b6472..270350ebba93f 100644 --- a/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx +++ b/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx @@ -5,12 +5,12 @@ import { toast } from 'sonner' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -195,20 +195,20 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp - - + - 7 && 'max-h-[210px]! overflow-y-auto')} onWheel={(event) => event.stopPropagation()} > - No schemas found - + No schemas found + {(schemas ?? []).map((schema) => ( - { @@ -222,11 +222,11 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp {selectedSchema === schema.name && ( )} - + ))} - - - + + + @@ -264,17 +264,17 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp {/* [Terry] shouldFilter context: https://github.com/pacocoursey/cmdk/issues/267#issuecomment-2252717107 */} - - + - 7 && 'max-h-[210px]! overflow-y-auto')} onWheel={(event) => event.stopPropagation()} > - + {isLoadingEntities ? (
@@ -283,10 +283,10 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp ) : ( 'No tables found' )} - - + + {entityTypes.map((entity) => ( - { @@ -302,11 +302,11 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp {selectedEntity === entity.name && ( )} - + ))} - - - + + + diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationsComboBox.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationsComboBox.tsx index 158a21ab7b4cb..dd4e9fea9a34a 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationsComboBox.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/PublicationsComboBox.tsx @@ -5,13 +5,13 @@ import { Badge, Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, Popover, PopoverContent, PopoverTrigger, @@ -74,8 +74,8 @@ export const PublicationsComboBox = ({ - - +
- - + + {isLoadingPublications ? (
@@ -96,9 +96,9 @@ export const PublicationsComboBox = ({ ) : ( 'No publications found' )} - + - + {publications.length === 0 && (

No publications available @@ -106,7 +106,7 @@ export const PublicationsComboBox = ({ )} 7 ? 'h-[210px]' : ''}> {publications.map((pub) => ( - { @@ -128,25 +128,25 @@ export const PublicationsComboBox = ({ )}

- + ))} - + - + - - +

New publication

-
-
-
-
+ + + +
) diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagement.schema.ts b/apps/studio/components/interfaces/DiskManagement/DiskManagement.schema.ts index 3d9250422d845..ff1743668c971 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagement.schema.ts +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagement.schema.ts @@ -191,22 +191,14 @@ export const CreateDiskStorageSchema = ({ path: ['provisionedIOPS'], }) } else if (provisionedIOPS > maxIopsAllowedForDiskSizeWithGp3) { - if (totalSize >= 8) { - const diskSizeRequiredForIopsWithGp3 = - calculateDiskSizeRequiredForIopsWithGp3(provisionedIOPS) + const diskSizeRequiredForIopsWithGp3 = + calculateDiskSizeRequiredForIopsWithGp3(provisionedIOPS) - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: `Larger Disk size of at least ${formatNumber(diskSizeRequiredForIopsWithGp3)} GB required. Current max is ${formatNumber(maxIopsAllowedForDiskSizeWithGp3)} IOPS.`, - path: ['provisionedIOPS'], - }) - } else { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: `Invalid IOPS value due to invalid disk size`, - path: ['provisionedIOPS'], - }) - } + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Larger Disk size of at least ${formatNumber(diskSizeRequiredForIopsWithGp3)} GB required. Current max is ${formatNumber(maxIopsAllowedForDiskSizeWithGp3)} IOPS.`, + path: ['provisionedIOPS'], + }) } if (throughput !== undefined) { diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagement.test.ts b/apps/studio/components/interfaces/DiskManagement/DiskManagement.test.ts index 2ad56ab5d090a..c7e44d07d1ee4 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagement.test.ts +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagement.test.ts @@ -5,6 +5,7 @@ import { calculateComputeSizeRequiredForIops, calculateDiskSizePrice, calculateIOPSPrice, + calculateMaxIopsAllowedForDiskSizeWithGp3, calculateMaxIopsForComputeSize, calculateThroughputPrice, mapAddOnVariantIdToComputeSize, @@ -66,6 +67,18 @@ describe('DiskManagement utils', () => { }) }) +describe('calculateMaxIopsAllowedForDiskSizeWithGp3', () => { + // Regression: old code returned `3000 * size`, letting a 2 GB disk request 6000 IOPS + // which the platform rejects. The real ceiling is 500 IOPS/GB capped at 16 000. + test('caps a sub-6 GB disk at the 3000 IOPS floor (not 3000 × size)', () => { + expect(calculateMaxIopsAllowedForDiskSizeWithGp3(2)).toBe(3000) + }) + + test('caps large disks at 16 000 IOPS', () => { + expect(calculateMaxIopsAllowedForDiskSizeWithGp3(100)).toBe(16000) + }) +}) + describe('DiskManagement.utils.ts:calculateDiskSizePrice', () => { test('GP3 with 8GB to GP3 with 10GB for pro plan', () => { const result = calculateDiskSizePrice({ diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts b/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts index 2753d1f199090..3e8c6bbb5ab38 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts @@ -282,7 +282,7 @@ export function getAvailableComputeOptions( } export const calculateMaxIopsAllowedForDiskSizeWithGp3 = (totalSize: number) => { - return Math.min(3000 * totalSize, 16000) + return Math.max(3000, Math.min(500 * totalSize, 16000)) } export const calculateDiskSizeRequiredForIopsWithGp3 = (iops: number) => { diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx index 0f98d64dbb9cb..ef82187c5e4c2 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx @@ -22,7 +22,10 @@ import { Admonition } from 'ui-patterns' import { FormFooterChangeBadge } from '../DataWarehouse/FormFooterChangeBadge' import { CreateDiskStorageSchema, DiskStorageSchemaType } from './DiskManagement.schema' import { DiskManagementMessage } from './DiskManagement.types' -import { mapComputeSizeNameToAddonVariantId } from './DiskManagement.utils' +import { + calculateDiskSizeRequiredForIopsWithGp3, + mapComputeSizeNameToAddonVariantId, +} from './DiskManagement.utils' import { DiskMangementRestartRequiredSection } from './DiskManagementRestartRequiredSection' import { DiskManagementReviewAndSubmitDialog } from './DiskManagementReviewAndSubmitDialog/DiskManagementReviewAndSubmitDialog' import { AutoScaleFields } from './fields/AutoScaleFields' @@ -35,6 +38,7 @@ import { DiskCountdownRadial } from './ui/DiskCountdownRadial' import { DISK_LIMITS, DiskType, + PLAN_DETAILS, RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3, } from './ui/DiskManagement.constants' import { NoticeBar } from './ui/NoticeBar' @@ -191,6 +195,18 @@ export function DiskManagementForm() { !isSpendCapEnabled && RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(modifiedComputeSize) + const watchedTotalSize = form.watch('totalSize') ?? 0 + const watchedStorageType = form.watch('storageType') + // Minimum disk size where the platform API will accept an IOPS payload (500 IOPS/GB rule). + const minDiskSizeForCustomIops = calculateDiskSizeRequiredForIopsWithGp3( + DISK_LIMITS[DiskType.GP3].minIops + ) + // Suggested target when prompting a resize, sits above the floor so users + // aren't pinned at the minimum during the 4-hour disk-config cooldown. + const suggestedDiskSizeForCustomIops = PLAN_DETAILS.pro.includedDiskGB.gp3 + const isDiskTooSmallForCustomIops = + watchedStorageType === 'gp3' && watchedTotalSize < minDiskSizeForCustomIops + const isBranch = project?.parent_project_ref !== undefined const disableDiskSizeInput = @@ -524,17 +540,50 @@ export function DiskManagementForm() { ) } /> + { + form.setValue('totalSize', suggestedDiskSizeForCustomIops, { + shouldDirty: true, + shouldValidate: true, + }) + }} + > + Increase to {suggestedDiskSizeForCustomIops} GB + + ) : undefined + } + /> diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/EdgeFunctionSection.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/EdgeFunctionSection.tsx index 774959c90064e..d14d8bd5f7e26 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/EdgeFunctionSection.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/EdgeFunctionSection.tsx @@ -6,12 +6,12 @@ import { UseFormReturn } from 'react-hook-form' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, FormControl, FormField, FormItem, @@ -152,15 +152,15 @@ export const EdgeFunctionSection = ({ form }: HTTPRequestFieldsProps) => { - - - - No edge function found. - + + + + No edge function found. + 7 ? 'h-[210px]' : ''}> {edgeFunctions.map((fn) => { return ( - { @@ -175,13 +175,13 @@ export const EdgeFunctionSection = ({ form }: HTTPRequestFieldsProps) => { )} /> {fn.name} - + ) })} - - - + + + diff --git a/apps/studio/components/interfaces/Integrations/Vercel/OrganizationPicker.tsx b/apps/studio/components/interfaces/Integrations/Vercel/OrganizationPicker.tsx index 6cbda52b97e02..e342673612245 100644 --- a/apps/studio/components/interfaces/Integrations/Vercel/OrganizationPicker.tsx +++ b/apps/studio/components/interfaces/Integrations/Vercel/OrganizationPicker.tsx @@ -4,12 +4,12 @@ import { Badge, Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -85,14 +85,14 @@ const OrganizationPicker = ({ - - - - No results found. - + + + + No results found. + {organizationsData?.map((org) => { return ( - Integration Installed )} - + ) })} - - - + + + diff --git a/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx b/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx index f08401a8858dc..4a790b302e592 100644 --- a/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx +++ b/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx @@ -7,13 +7,13 @@ import { Badge, Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, Popover, PopoverContent, PopoverTrigger, @@ -262,8 +262,8 @@ const ProjectLinker = ({ renderActions={() => { return ( projectCreationEnabled && ( - - + { setOpenProjectsDropdown(false) @@ -281,8 +281,8 @@ const ProjectLinker = ({

Create a new project

-
-
+ + ) ) }} @@ -324,14 +324,14 @@ const ProjectLinker = ({ - - - - No results found. - + + + + No results found. + {foreignProjects.map((project, i) => { return ( - {project.name} - + ) })} {foreignProjects.length === 0 && ( - No results found. + No results found. )} - + {mode === 'GitHub' && ( <> - - - + + openInstallGitHubIntegrationWindow('install')} > Add GitHub Repositories - - + + )} - - + + diff --git a/apps/studio/components/interfaces/Integrations/Wrappers/ColumnType.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/ColumnType.tsx index e258d44edc6c9..634cc26a74f2b 100644 --- a/apps/studio/components/interfaces/Integrations/Wrappers/ColumnType.tsx +++ b/apps/studio/components/interfaces/Integrations/Wrappers/ColumnType.tsx @@ -17,13 +17,13 @@ import { AlertTitle, Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, CriticalIcon, FormControl, FormField, @@ -159,20 +159,20 @@ export const ColumnType = ({ - - + - Type not found. + Type not found. - + - + {POSTGRES_DATA_TYPE_OPTIONS.map((option: PostgresDataTypeOption) => ( - - + ))} - + {enumTypes.length > 0 && ( <> - - + + {enumTypes.map((option) => ( - )} - + ))} - + )} - - + + {showRecommendation && recommendation !== undefined && ( diff --git a/apps/studio/components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx index 12afee2585651..ae230c6fe1091 100644 --- a/apps/studio/components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx +++ b/apps/studio/components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx @@ -12,12 +12,12 @@ import { import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Form, FormControl, FormField, @@ -136,14 +136,14 @@ const WrapperTableEditor = ({ - - - - No targets found - + + + + No targets found + 7 ? 'h-[200px]' : ''}> {(tables ?? []).map((table, i) => ( - { @@ -162,12 +162,12 @@ const WrapperTableEditor = ({ {String(i) === selectedTableIndex && ( )} - + ))} - - - + + + diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerDataForm.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerDataForm.tsx index a91acb83ca6d8..522214d7b79b4 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerDataForm.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerDataForm.tsx @@ -10,12 +10,12 @@ import { UseFormReturn } from 'react-hook-form' import { Button, cn, - Command_Shadcn_ as Command, - CommandEmpty_Shadcn_ as CommandEmpty, - CommandGroup_Shadcn_ as CommandGroup, - CommandInput_Shadcn_ as CommandInput, - CommandItem_Shadcn_ as CommandItem, - CommandList_Shadcn_ as CommandList, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, FormControl, FormField, FormMessage, diff --git a/apps/studio/components/interfaces/Organization/PrivateApps/Apps/CreateAppSheet/CreateAppSheet.tsx b/apps/studio/components/interfaces/Organization/PrivateApps/Apps/CreateAppSheet/CreateAppSheet.tsx index 1128f408b194e..5f603fa83c46a 100644 --- a/apps/studio/components/interfaces/Organization/PrivateApps/Apps/CreateAppSheet/CreateAppSheet.tsx +++ b/apps/studio/components/interfaces/Organization/PrivateApps/Apps/CreateAppSheet/CreateAppSheet.tsx @@ -5,12 +5,12 @@ import { toast } from 'sonner' import { Button, Checkbox, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Input, Popover, PopoverContent, @@ -206,14 +206,14 @@ export function CreateAppSheet({ visible, onClose, onCreated }: CreateAppSheetPr - - - - No permissions found. - + + + + No permissions found. +
{PERMISSIONS.map((perm) => ( - toggle(perm.id)} @@ -230,12 +230,12 @@ export function CreateAppSheet({ visible, onClose, onCreated }: CreateAppSheetPr {perm.label}
- + ))} -
-
-
+ + +
diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx index 4c0c21f4a8c8f..f9221c0ae2ff5 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx @@ -5,7 +5,7 @@ import { Check, ChevronDown } from 'lucide-react' import Link from 'next/link' import { useQueryState } from 'nuqs' import { useMemo, useState } from 'react' -import { Button, cn, CommandGroup_Shadcn_, CommandItem_Shadcn_ } from 'ui' +import { Button, cn, CommandGroup, CommandItem } from 'ui' import { Admonition } from 'ui-patterns' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' @@ -191,8 +191,8 @@ export const Usage = () => { ) }} renderActions={() => ( - - + { setOpenProjectSelector(false) @@ -205,8 +205,8 @@ export const Usage = () => { > All projects {!selectedProjectRef && } - - + + )} /> diff --git a/apps/studio/components/interfaces/ProjectAPIDocs/LanguageSelector.tsx b/apps/studio/components/interfaces/ProjectAPIDocs/LanguageSelector.tsx index ca77123d88555..1f4b2119ec691 100644 --- a/apps/studio/components/interfaces/ProjectAPIDocs/LanguageSelector.tsx +++ b/apps/studio/components/interfaces/ProjectAPIDocs/LanguageSelector.tsx @@ -2,10 +2,10 @@ import { ChevronDown, Terminal } from 'lucide-react' import { useState } from 'react' import { Button, - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandGroup, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -58,26 +58,26 @@ const LanguageSelector = ({ simplifiedVersion = false }: LanguageSelectorProps) - - - - + + + updateLanguage('js')} onClick={() => updateLanguage('js')} >

Javascript

-
- + updateLanguage('bash')} onClick={() => updateLanguage('bash')} >

Bash

-
-
-
-
+ + + +
diff --git a/apps/studio/components/interfaces/ProjectAPIDocs/SecondLevelNav.ResourcePicker.tsx b/apps/studio/components/interfaces/ProjectAPIDocs/SecondLevelNav.ResourcePicker.tsx index a0f04bcfd681c..dd30ddfade3eb 100644 --- a/apps/studio/components/interfaces/ProjectAPIDocs/SecondLevelNav.ResourcePicker.tsx +++ b/apps/studio/components/interfaces/ProjectAPIDocs/SecondLevelNav.ResourcePicker.tsx @@ -1,10 +1,4 @@ -import { - cn, - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, -} from 'ui' +import { cn, Command, CommandGroup, CommandItem, CommandList } from 'ui' import type { ResourcePickerRenderProps } from './SecondLevelNav.Layout' @@ -28,18 +22,18 @@ export const ResourcePickerList = ({ } return ( - - - + + + {items.length === 0 && ( - +

{emptyMessage}

-
+ )} {items.map((item) => { const isActive = item.name === selectedResource return ( - handleSelect(item.name)} >

{item.name}

-
+ ) })} -
-
-
+ + + ) } diff --git a/apps/studio/components/interfaces/ProjectAPIDocs/SecondLevelNav.StoragePicker.tsx b/apps/studio/components/interfaces/ProjectAPIDocs/SecondLevelNav.StoragePicker.tsx index 3433213358d3e..60cdc1e505f92 100644 --- a/apps/studio/components/interfaces/ProjectAPIDocs/SecondLevelNav.StoragePicker.tsx +++ b/apps/studio/components/interfaces/ProjectAPIDocs/SecondLevelNav.StoragePicker.tsx @@ -1,15 +1,7 @@ import { keepPreviousData } from '@tanstack/react-query' import { useDebounce, useIntersectionObserver } from '@uidotdev/usehooks' import { useEffect, useMemo, useRef, useState } from 'react' -import { - cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, -} from 'ui' +import { cn, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from 'ui' import type { ResourcePickerRenderProps } from './SecondLevelNav.Layout' import { usePaginatedBucketsQuery } from '@/data/storage/buckets-query' @@ -101,22 +93,19 @@ export const StorageResourceList = ({ rawQuery.length > 0 ? 'No buckets found for this search' : 'No buckets available' return ( - - + setSearch('')} /> - - - +
+ {isFetching && buckets.length === 0 ? (
Loading buckets...
) : ( @@ -124,7 +113,7 @@ export const StorageResourceList = ({ {buckets.map((bucket) => { const isActive = bucket.name === selectedResource return ( - handleSelect(bucket.name)} >

{bucket.name}

-
+ ) })} {hasNextPage &&
}
)} - +
{isFetchingNextPage && (
Loading more buckets...
)} - - + + ) } diff --git a/apps/studio/components/interfaces/ProjectHome/SnippetDropdown.tsx b/apps/studio/components/interfaces/ProjectHome/SnippetDropdown.tsx index 5fefc1ce49139..fc197b0752628 100644 --- a/apps/studio/components/interfaces/ProjectHome/SnippetDropdown.tsx +++ b/apps/studio/components/interfaces/ProjectHome/SnippetDropdown.tsx @@ -3,11 +3,11 @@ import { useDebounce, useIntersectionObserver } from '@uidotdev/usehooks' import { Plus } from 'lucide-react' import { ReactNode, useEffect, useMemo, useRef, useState } from 'react' import { - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandGroup, + CommandInput, + CommandItem, + CommandList, DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, @@ -91,8 +91,8 @@ export const SnippetDropdown = ({ align={align} className={['w-80 p-0', className].filter(Boolean).join(' ')} > - - + setSearch('')} /> - + {isLoading ? (

Loading...

) : search.length > 0 && snippets.length === 0 ? (

No snippets found

) : ( - + 7 ? 'h-[210px]' : ''}> {snippets.map((snippet) => ( - onSelect({ id: snippet.id, name: snippet.name })} > {snippet.name} - + ))}
{hasNextPage && ( @@ -124,13 +124,13 @@ export const SnippetDropdown = ({
)}
-
+ )}
- - + { setOpen(false) @@ -142,10 +142,10 @@ export const SnippetDropdown = ({

Create snippet

- - -
-
+ + + + ) diff --git a/apps/studio/components/interfaces/QueryInsights/hooks/useQueryInsightsTableColumns.tsx b/apps/studio/components/interfaces/QueryInsights/hooks/useQueryInsightsTableColumns.tsx index 44ab6291afdf6..17708d338ce47 100644 --- a/apps/studio/components/interfaces/QueryInsights/hooks/useQueryInsightsTableColumns.tsx +++ b/apps/studio/components/interfaces/QueryInsights/hooks/useQueryInsightsTableColumns.tsx @@ -8,7 +8,6 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, - DropdownMenuSeparator, DropdownMenuTrigger, Tooltip, TooltipContent, @@ -556,27 +555,25 @@ export function useQueryInsightsTableColumns({ buildPrompt={() => buildQueryInsightFixPrompt(row).prompt} onOpenAssistant={() => handleAiSuggestedFix(row)} copyLabel="Copy Markdown" - extraDropdownItems={ - <> - handleGoToLogs()} className="gap-2"> - - Go to Logs - - {row.issueType === 'slow' && ( - { - setSelectedTriageRow(props.rowIdx) - setSheetView('explain') - }} - className="gap-2" - > - - Explain - - )} - - - } + additionalDropdownItems={[ + { + label: 'Go to Logs', + icon: , + onClick: () => handleGoToLogs(), + }, + ...(row.issueType === 'slow' + ? [ + { + label: 'Explain', + icon: , + onClick: () => { + setSelectedTriageRow(props.rowIdx) + setSheetView('explain') + }, + }, + ] + : []), + ]} /> )} diff --git a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/TableSelector.tsx b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/TableSelector.tsx index b6ed1cb238f45..8d8b07df62e85 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/TableSelector.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/TableSelector.tsx @@ -6,12 +6,12 @@ import { AlertDescription, AlertTitle, Button, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -101,12 +101,9 @@ const TableSelector = ({ - - searchTables(str)} - /> - + + searchTables(str)} /> + {isLoading && (
@@ -128,13 +125,11 @@ const TableSelector = ({ {isSuccess && ( <> - + 7 ? 'h-[210px]' : ''}> - {entities.length === 0 && ( - No tables found - )} + {entities.length === 0 && No tables found} {!searchInput && ( - { @@ -150,10 +145,10 @@ const TableSelector = ({ {selectedSchemaName === '*' && ( )} - + )} {entities?.map((table) => ( - { @@ -169,14 +164,14 @@ const TableSelector = ({ {selectedSchemaName === table.name && ( )} - + ))} - + )} - - + +
diff --git a/apps/studio/components/interfaces/Reports/MetricOptions.tsx b/apps/studio/components/interfaces/Reports/MetricOptions.tsx index 831f2eead538a..d6066f2f98794 100644 --- a/apps/studio/components/interfaces/Reports/MetricOptions.tsx +++ b/apps/studio/components/interfaces/Reports/MetricOptions.tsx @@ -3,11 +3,11 @@ import { useParams } from 'common' import { Home, Plus } from 'lucide-react' import { useState } from 'react' import { - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandGroup, + CommandInput, + CommandItem, + CommandList, DropdownMenuCheckboxItem, DropdownMenuPortal, DropdownMenuSub, @@ -118,14 +118,14 @@ export const MetricOptions = ({ config, handleChartSelection }: MetricOptionsPro - - + - + {isLoading ? (
@@ -136,9 +136,9 @@ export const MetricOptions = ({ config, handleChartSelection }: MetricOptionsPro No snippets found

) : null} - + {snippets?.map((snippet) => ( - {snippet.name} - + ))} - - + +
- - + { editorPanelState.openAsNew() @@ -182,9 +182,9 @@ export const MetricOptions = ({ config, handleChartSelection }: MetricOptionsPro

Create snippet

- - - + + + diff --git a/apps/studio/components/interfaces/Reports/ReportFilterPopover.tsx b/apps/studio/components/interfaces/Reports/ReportFilterPopover.tsx index 1d76a03b68285..da04534450472 100644 --- a/apps/studio/components/interfaces/Reports/ReportFilterPopover.tsx +++ b/apps/studio/components/interfaces/Reports/ReportFilterPopover.tsx @@ -4,12 +4,12 @@ import { KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Input, Popover, PopoverContent, @@ -79,8 +79,8 @@ const FilterableInput = ({ return (
- - + 0 && 'opacity-100 pointer-events-auto' )} > - - + + No matching options found. Press Enter to use "{inputValue}" - - + + {safeOptions.map((option, index) => ( - handleOptionSelect(option)} className="px-3 py-2 text-sm cursor-pointer" > {option} - + ))} - - + +
- + {isOpen &&
setIsOpen(false)} />}
) diff --git a/apps/studio/components/interfaces/Reports/v2/ReportsSelectFilter.tsx b/apps/studio/components/interfaces/Reports/v2/ReportsSelectFilter.tsx index fd06a093715d6..fe867b3c25648 100644 --- a/apps/studio/components/interfaces/Reports/v2/ReportsSelectFilter.tsx +++ b/apps/studio/components/interfaces/Reports/v2/ReportsSelectFilter.tsx @@ -4,15 +4,7 @@ import { Label } from '@ui/components/shadcn/ui/label' import { Popover, PopoverContent, PopoverTrigger } from '@ui/components/shadcn/ui/popover' import { ChevronDown } from 'lucide-react' import { useEffect, useState } from 'react' -import { - Button, - cn, - Command_Shadcn_ as Command, - CommandEmpty_Shadcn_ as CommandEmpty, - CommandInput_Shadcn_ as CommandInput, - CommandItem_Shadcn_, - CommandList_Shadcn_ as CommandList, -} from 'ui' +import { Button, cn, Command, CommandEmpty, CommandInput, CommandItem, CommandList } from 'ui' import { z } from 'zod' export interface ReportSelectOption { @@ -95,7 +87,7 @@ export const ReportsSelectFilter = ({ No options found. {options.map((option) => ( - + - + ))} diff --git a/apps/studio/components/interfaces/SQLEditor/MoveQueryModal.tsx b/apps/studio/components/interfaces/SQLEditor/MoveQueryModal.tsx index c34f80e90796b..981f001b6b7f1 100644 --- a/apps/studio/components/interfaces/SQLEditor/MoveQueryModal.tsx +++ b/apps/studio/components/interfaces/SQLEditor/MoveQueryModal.tsx @@ -7,13 +7,13 @@ import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, Dialog, DialogContent, DialogDescription, @@ -252,13 +252,13 @@ export const MoveQueryModal = ({ visible, snippets = [], onClose }: MoveQueryMod - - - - No folders found - + + + + No folders found + 6 ? 'h-[210px]' : ''}> - {selectedId === 'root' && } - + {folders?.map((folder) => ( - {folder.id === selectedId && } - + ))} - - - - + + + { setOpen(false) @@ -319,10 +319,10 @@ export const MoveQueryModal = ({ visible, snippets = [], onClose }: MoveQueryMod >

New folder

-
-
-
-
+ + + +
diff --git a/apps/studio/components/interfaces/Settings/API/ExposedFunctionSelector.tsx b/apps/studio/components/interfaces/Settings/API/ExposedFunctionSelector.tsx index e7f1cee472e15..3e8041fa35f1c 100644 --- a/apps/studio/components/interfaces/Settings/API/ExposedFunctionSelector.tsx +++ b/apps/studio/components/interfaces/Settings/API/ExposedFunctionSelector.tsx @@ -5,11 +5,11 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -126,15 +126,15 @@ export const ExposedFunctionSelector = ({ align="start" sameWidthAsTrigger > - - + - - + + {isPending ? ( <>
@@ -179,7 +179,7 @@ export const ExposedFunctionSelector = ({ }) return ( -
- + ) })}
@@ -278,9 +278,9 @@ export const ExposedFunctionSelector = ({ )} - - - + + + ) diff --git a/apps/studio/components/interfaces/Settings/API/ExposedSchemaSelector.tsx b/apps/studio/components/interfaces/Settings/API/ExposedSchemaSelector.tsx index 396ef368cfa02..db77567e9ce5c 100644 --- a/apps/studio/components/interfaces/Settings/API/ExposedSchemaSelector.tsx +++ b/apps/studio/components/interfaces/Settings/API/ExposedSchemaSelector.tsx @@ -3,12 +3,12 @@ import { useMemo, useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -57,6 +57,11 @@ export const ExposedSchemaSelector = ({ [allSchemas] ) + const missingExposedSchema = useMemo( + () => selectedSchemas.filter((schema) => !schemas.some((s) => s.name === schema)), + [schemas, selectedSchemas] + ) + const selectedSet = useMemo(() => new Set(selectedSchemas), [selectedSchemas]) const selectedCount = schemas.filter((s) => selectedSet.has(s.name)).length @@ -85,10 +90,10 @@ export const ExposedSchemaSelector = ({ align="start" sameWidthAsTrigger > - - - - + + + + {isPending ? ( <>
@@ -104,17 +109,37 @@ export const ExposedSchemaSelector = ({
) : ( <> - +

No schemas found

-
+ 7 ? 'h-[210px]' : ''}> + {missingExposedSchema.map((schema) => ( + { + onToggleSchema(schema) + }} + > +
+
+ + {schema} +
+ + This schema does not exist + +
+
+ ))} {schemas.map((schema) => { const isExposed = selectedSet.has(schema.name) return ( - } {schema.name}
- + ) })} )} -
-
-
+ +
+
) diff --git a/apps/studio/components/interfaces/Settings/API/ExposedTableSelector.tsx b/apps/studio/components/interfaces/Settings/API/ExposedTableSelector.tsx index e7ecfd3411b7b..21695b9105a14 100644 --- a/apps/studio/components/interfaces/Settings/API/ExposedTableSelector.tsx +++ b/apps/studio/components/interfaces/Settings/API/ExposedTableSelector.tsx @@ -5,11 +5,11 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -120,15 +120,15 @@ export const ExposedTableSelector = ({ align="start" sameWidthAsTrigger > - - + - - + + {isPending ? ( <>
@@ -173,7 +173,7 @@ export const ExposedTableSelector = ({ }) return ( -
- + ) })}
@@ -272,9 +272,9 @@ export const ExposedTableSelector = ({ )} - - - + + + ) diff --git a/apps/studio/components/interfaces/Settings/API/PostgrestConfig.tsx b/apps/studio/components/interfaces/Settings/API/PostgrestConfig.tsx index c1d3fcdaf0e13..c62cad5a5a2d5 100644 --- a/apps/studio/components/interfaces/Settings/API/PostgrestConfig.tsx +++ b/apps/studio/components/interfaces/Settings/API/PostgrestConfig.tsx @@ -260,6 +260,11 @@ export const PostgrestConfig = () => { name: 'functionNamesToRemove', }) + const missingExposedSchema = useMemo( + () => watchedDbSchema.filter((schema) => !allSchemas.some((s) => s.name === schema)), + [allSchemas, watchedDbSchema] + ) + return ( @@ -282,6 +287,9 @@ export const PostgrestConfig = () => { layout="flex-row-reverse" label="Exposed schemas" description="Select schemas to include in the Data API. Schemas must be included before tables can be exposed." + error={ + missingExposedSchema.length > 0 ? 'Some exposed schemas are missing' : null + } > ({ - - - - No repositories found. + + + + No repositories found. {repositories.length > 0 ? ( - + {repositories.map((repo) => ( - ({ {repo.name} - + ))} - + ) : null} - - + { setIsRepoSelectorOpen(false) @@ -222,13 +222,13 @@ export const GitHubRepositoryField = ({ > Add GitHub Repositories - - + + {hasPartialResponseDueToSSO && ( <> - - - + + { setIsRepoSelectorOpen(false) @@ -239,12 +239,12 @@ export const GitHubRepositoryField = ({
Re-authorize GitHub with SSO to show all repositories
-
-
+ + )} -
-
+ +
)} diff --git a/apps/studio/components/interfaces/Settings/Logs/LogColumnRenderers/AuthColumnRenderer.tsx b/apps/studio/components/interfaces/Settings/Logs/LogColumnRenderers/AuthColumnRenderer.tsx index 5e7ac8a052b2d..9e6b01953b7b1 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogColumnRenderers/AuthColumnRenderer.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogColumnRenderers/AuthColumnRenderer.tsx @@ -4,6 +4,7 @@ import { TimestampInfo } from 'ui-patterns/TimestampInfo' import type { LogData } from '../Logs.types' import { RowLayout, SeverityFormatter, TextFormatter } from '../LogsFormatters' import { defaultRenderCell } from './DefaultPreviewColumnRenderer' +import { parseAuthLogEventMessage } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.utils' const columns: Column[] = [ { @@ -23,7 +24,7 @@ const columns: Column[] = [ className="w-full" value={`${props.row.path ? props.row.path + ' | ' : ''}${ // not all log events have metadata.msg - (props.row.msg as string)?.trim() || props.row.event_message + (props.row.msg as string)?.trim() || parseAuthLogEventMessage(props.row.event_message) }`} /> diff --git a/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx b/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx index b7fab29a9ff48..1fa3d8900a7a4 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx @@ -7,12 +7,12 @@ import { Badge, Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, copyToClipboard, DropdownMenu, DropdownMenuContent, @@ -280,13 +280,13 @@ const LogsQueryPanel = ({ - - - - No source found. - + + + + No source found. + {logConstants.schemas.map((schema) => ( - { @@ -301,11 +301,11 @@ const LogsQueryPanel = ({ )} /> {schema.name} - + ))} - - - + + + - - - - + + + onLanguageChange('sql')} onClick={() => onLanguageChange('sql')} >

SQL

-
- + onLanguageChange('javascript')} onClick={() => onLanguageChange('javascript')} >

JavaScript

-
-
-
-
+ + + +
diff --git a/apps/studio/components/interfaces/Support/ProjectAndPlanInfo.tsx b/apps/studio/components/interfaces/Support/ProjectAndPlanInfo.tsx index af6ef46f85790..983c187e708d1 100644 --- a/apps/studio/components/interfaces/Support/ProjectAndPlanInfo.tsx +++ b/apps/studio/components/interfaces/Support/ProjectAndPlanInfo.tsx @@ -6,7 +6,7 @@ import { Check, ChevronsUpDown, ExternalLink } from 'lucide-react' import Link from 'next/link' import type { UseFormReturn } from 'react-hook-form' import { toast } from 'sonner' -import { Button, cn, CommandGroup_Shadcn_, CommandItem_Shadcn_, FormControl, FormField } from 'ui' +import { Button, cn, CommandGroup, CommandItem, FormControl, FormField } from 'ui' import { Admonition } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' @@ -99,8 +99,8 @@ function ProjectSelector({ form, orgSlug, projectRef }: ProjectSelectorProps) { ) }} renderActions={(setOpen) => ( - - + { field.onChange(NO_PROJECT_MARKER) @@ -111,8 +111,8 @@ function ProjectSelector({ form, orgSlug, projectRef }: ProjectSelectorProps) {

No specific project

-
-
+ + )} /> diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx index 887951359dce3..b01df7bc81bf1 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx @@ -17,13 +17,13 @@ import { AlertTitle, Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, CriticalIcon, Input, InputGroup, @@ -209,22 +209,22 @@ const ColumnType = ({ - - + - Type not found. + Type not found. - + - + {POSTGRES_DATA_TYPE_OPTIONS.map((option: PostgresDataTypeOption) => { const isSelected = matchesBuiltin(option.name, value) return ( - {isSelected ? : ''} - + ) })} - + {enumTypes.length > 0 && ( <> - - + + {enumTypes.map((option) => { const isSelected = matchesEnum(option, value) return ( - )} - + ) })} - + )} - - + + diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx index 9bb7473f3b3f7..d203b1232eacd 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx @@ -7,11 +7,11 @@ import { Button, Checkbox, cn, - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandGroup, + CommandItem, + CommandList, + CommandSeparator, Input, Popover, PopoverContent, @@ -167,16 +167,16 @@ export const Column = ({
Involved in {relations.length} foreign key{relations.length > 1 ? 's' : ''}
- - - + + + {relations.map((relation, idx) => { const key = String(relation?.id ?? `${column.id}-relation-${idx}`) const status = getRelationStatus(relation) if (status === 'REMOVE') return null return ( - )} - + ) })} - - - - + + + onEditForeignKey()} onClick={() => onEditForeignKey()} >

Add foreign key relation

-
-
-
-
+ + + + )} diff --git a/apps/studio/components/interfaces/UnifiedLogs/RowSelectionHeader.tsx b/apps/studio/components/interfaces/UnifiedLogs/RowSelectionHeader.tsx new file mode 100644 index 0000000000000..2edf080d685e4 --- /dev/null +++ b/apps/studio/components/interfaces/UnifiedLogs/RowSelectionHeader.tsx @@ -0,0 +1,114 @@ +import { AnimatePresence, motion } from 'framer-motion' +import { Copy, X } from 'lucide-react' +import { toast } from 'sonner' +import { + copyToClipboard, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from 'ui' + +import { type LogData } from '../Settings/Logs/Logs.types' +import { + buildLogsPrompt, + formatLogsAsJson, + formatLogsAsMarkdown, +} from '../Settings/Logs/Logs.utils' +import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' +import { AiAssistantDropdown } from '@/components/ui/AiAssistantDropdown' +import { ButtonTooltip } from '@/components/ui/ButtonTooltip' +import { useDataTable } from '@/components/ui/DataTable/providers/DataTableProvider' +import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state' +import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state' + +// TODO - format Logs as JSON, as markdown, and as prompt + +export const RowSelectionHeader = () => { + const { openSidebar } = useSidebarManagerSnapshot() + const aiSnap = useAiAssistantStateSnapshot() + + const { table } = useDataTable() + const selectedRows = table.getSelectedRowModel().rows.map((x) => x.original) as LogData[] + + const handleOpenAiAssistant = () => { + const prompt = buildLogsPrompt(selectedRows) + openSidebar(SIDEBAR_KEYS.AI_ASSISTANT) + aiSnap.newChat({ initialMessage: prompt }) + } + + const onCopy = (format: 'json' | 'markdown') => { + const text = + format === 'json' ? formatLogsAsJson(selectedRows) : formatLogsAsMarkdown(selectedRows) + copyToClipboard(text, () => { + toast.success( + `Copied ${selectedRows.length} log${selectedRows.length !== 1 ? 's' : ''} as ${format.toUpperCase()}` + ) + }) + } + + return ( +
+ + {selectedRows.length > 0 && ( + +

+ {selectedRows.length} row{selectedRows.length > 1 ? 's' : ''} selected +

+ +
+ + + } + className="w-7" + tooltip={{ content: { side: 'bottom', text: 'Copy selected logs' } }} + /> + + + onCopy('json')} className="gap-2 text-xs"> + + Copy as JSON + + onCopy('markdown')} className="gap-2 text-xs"> + + Copy as Markdown + + + + + buildLogsPrompt(selectedRows)} + onOpenAssistant={handleOpenAiAssistant} + telemetrySource="log_explorer" + /> + + } + className="px-1" + onClick={() => table.resetRowSelection()} + tooltip={{ content: { side: 'bottom', text: 'Clear selection' } }} + /> +
+
+ )} +
+
+ ) +} diff --git a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/ServiceFlowPanelControls.tsx b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/ServiceFlowPanelControls.tsx index 07088400a0313..cd421eee538ab 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/ServiceFlowPanelControls.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/ServiceFlowPanelControls.tsx @@ -25,14 +25,12 @@ export const ServiceFlowPanelControls = ({ dock = 'bottom', setDock, }: ServiceFlowPanelControlsProps) => { - const { table, rowSelection, isLoading } = useDataTable() - - const selectedRowKey = Object.keys(rowSelection)?.[0] + const { table, openRowId, setOpenRowId, isLoading } = useDataTable() const selectedRowData = useMemo(() => { - if (isLoading && !selectedRowKey) return - return table.getCoreRowModel().flatRows.find((row) => row.id === selectedRowKey) - }, [selectedRowKey, isLoading, table]) + if (isLoading && !openRowId) return + return table.getCoreRowModel().flatRows.find((row) => row.id === openRowId) + }, [openRowId, isLoading, table]) const index = table.getCoreRowModel().flatRows.findIndex((row) => row.id === selectedRowData?.id) @@ -49,20 +47,20 @@ export const ServiceFlowPanelControls = ({ ) const onPrev = useCallback(() => { - if (prevId) table.setRowSelection({ [prevId]: true }) - }, [prevId, table]) + if (prevId) setOpenRowId(prevId) + }, [prevId, setOpenRowId]) const onNext = useCallback(() => { - if (nextId) table.setRowSelection({ [nextId]: true }) - }, [nextId, table]) + if (nextId) setOpenRowId(nextId) + }, [nextId, setOpenRowId]) const onClose = useCallback(() => { - table.resetRowSelection() - }, [table]) + setOpenRowId(undefined) + }, [setOpenRowId]) useEffect(() => { const down = (e: KeyboardEvent) => { - if (!selectedRowKey) return + if (!openRowId) return const activeElement = document.activeElement if (activeElement?.closest('[role="menu"]')) return @@ -87,7 +85,7 @@ export const ServiceFlowPanelControls = ({ document.addEventListener('keydown', down) return () => document.removeEventListener('keydown', down) - }, [selectedRowKey, onNext, onPrev]) + }, [openRowId, onNext, onPrev]) return (
diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.fields.tsx b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.fields.tsx index e3512b5ead005..c13ecbbbf5f1f 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.fields.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.fields.tsx @@ -134,6 +134,23 @@ export const filterFields = [ ) }, }, + { + label: 'Event message', + value: 'event_message', + type: 'checkbox', + defaultOpen: false, + options: [], + hasDynamicOptions: false, + hasAsyncSearch: false, + hidden: true, + component: (props: Option) => { + return ( + + {props.value} + + ) + }, + }, ] satisfies DataTableFilterField[] export const sheetFields = [ diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx index ae1abc01f5b11..36d30b0791606 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx @@ -33,6 +33,7 @@ import { DownloadLogsButton } from './components/DownloadLogsButton' import { LogsFilterBar } from './components/LogsFilterBar' import { LogsListPanel } from './components/LogsListPanel' import { TooltipLabel } from './components/TooltipLabel' +import { RowSelectionHeader } from './RowSelectionHeader' import { ServiceFlowPanel } from './ServiceFlowPanel' import { SEARCH_PARAMS_PARSER } from './UnifiedLogs.constants' import { filterFields as defaultFilterFields } from './UnifiedLogs.fields' @@ -85,7 +86,6 @@ export const UnifiedLogs = () => { const { sort, start, size, id, cursor, direction, live, ...filter } = search const defaultColumnSorting = sort ? [sort] : [] const defaultColumnVisibility = { uuid: false } - const defaultRowSelection = search.id ? { [search.id]: true } : {} const defaultColumnFilters = Object.entries(filter) .map(([key, value]) => ({ id: key, value })) .filter(({ value }) => value ?? undefined) @@ -106,7 +106,8 @@ export const UnifiedLogs = () => { const [sorting, setSorting] = useState(defaultColumnSorting) const [columnFilters, setColumnFilters] = useState(defaultColumnFilters) - const [rowSelection, setRowSelection] = useState(defaultRowSelection) + const [rowSelection, setRowSelection] = useState({}) + const [openRowId, setOpenRowId] = useState(search.id ?? undefined) const [dock, setDock] = useLocalStorageQuery<'bottom' | 'right'>( LOCAL_STORAGE_KEYS.UNIFIED_LOGS_DOCK, @@ -222,7 +223,7 @@ export const UnifiedLogs = () => { // Generate dynamic columns based on current data const { columns: dynamicColumns, columnVisibility: dynamicColumnVisibility } = useMemo(() => { - return generateDynamicColumns(flatData) + return generateDynamicColumns({ data: flatData }) }, [flatData]) const table: Table = useReactTable({ @@ -235,7 +236,7 @@ export const UnifiedLogs = () => { rowSelection, columnOrder, }, - enableMultiRowSelection: false, + enableMultiRowSelection: true, columnResizeMode: 'onChange', filterFns: { inDateRange, arrSome }, meta: { getRowClassName }, @@ -253,12 +254,10 @@ export const UnifiedLogs = () => { getFacetedMinMaxValues: getTTableFacetedMinMaxValues(), }) - const selectedRowKey = Object.keys(rowSelection)?.[0] const selectedRow = useMemo(() => { if ((isLoading || isFetching) && !flatData.length) return - - return table.getCoreRowModel().flatRows.find((row) => row.id === selectedRowKey) - }, [isLoading, isFetching, flatData.length, table, selectedRowKey]) + return table.getCoreRowModel().flatRows.find((row) => row.id === openRowId) + }, [isLoading, isFetching, flatData.length, table, openRowId]) // REMINDER: this is currently needed for the cmdk search // [Joshen] This is where facets are getting dynamically loaded @@ -321,24 +320,20 @@ export const UnifiedLogs = () => { useEffect(() => { if (isLoading || isFetching) return - const selectedRowId = Object.keys(rowSelection)?.[0] - if (selectedRowId && !selectedRow) { - // Clear both uuid and logId when no row is selected + if (openRowId && !selectedRow) { + // Clear both uuid and logId when the open row no longer exists in data setSearch({ id: null }) - setRowSelection({}) - } else if (selectedRowId && selectedRow) { - setSearch({ - id: selectedRowId, - }) + setOpenRowId(undefined) + } else if (openRowId && selectedRow) { + setSearch({ id: openRowId }) track('unified_logs_row_clicked', { logType: selectedRow.original.log_type }) - // Don't clear rowSelection here - let it persist to maintain the selection - } else if (!selectedRowId && search.id) { - // Clear the URL parameter when no row is selected + } else if (!openRowId && search.id) { + // Clear the URL parameter when no row is open setSearch({ id: null }) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [rowSelection, selectedRow, isLoading, isFetching]) + }, [openRowId, selectedRow, isLoading, isFetching]) const isMobile = useIsMobile() const [isFilterBarOpen, setIsFilterBarOpen] = useState(!isMobile) @@ -353,6 +348,10 @@ export const UnifiedLogs = () => { } }, [isMobile]) + useEffect(() => { + table.resetRowSelection() + }, [searchParameters, table]) + return ( { columnFilters={columnFilters} sorting={sorting} rowSelection={rowSelection} + openRowId={openRowId} + setOpenRowId={setOpenRowId} columnOrder={columnOrder} columnVisibility={columnVisibility} searchParameters={searchParameters} @@ -427,10 +428,13 @@ export const UnifiedLogs = () => { isFetchingCharts && 'opacity-60 transition-opacity duration-150' )} columnId="timestamp" + filterColumnId="date" chartConfig={filteredChartConfig} />
+ + { - {!!selectedRow && ( + {!!openRowId && !!selectedRow && ( <> diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts index 7cd29dbc9ec69..78c96b0ce832c 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts @@ -115,3 +115,25 @@ export function formatServiceTypeForDisplay(serviceType: string): string { return specialCases[serviceType.toLowerCase()] || serviceType } + +/** + * Parses an auth log event_message that may be a stringified JSON object. + * Auth log entries store metadata as JSON in event_message (e.g. {"msg":"...","level":"info"}). + * Extracts the human-readable msg field, falling back to error, then the raw string. + * The fallback ensures self-hosted versions with different formats still render correctly. + */ +export function parseAuthLogEventMessage(value: string | undefined): string | undefined { + if (!value) return value + try { + const parsed = JSON.parse(value) + if (parsed && typeof parsed === 'object') { + const msg = parsed.msg + const err = parsed.error + if (typeof msg === 'string' && msg.trim()) return msg + if (typeof err === 'string' && err.trim()) return err + } + return value + } catch { + return value + } +} diff --git a/apps/studio/components/interfaces/UnifiedLogs/components/Columns.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/Columns.tsx index 165712c558727..544261a58e432 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/components/Columns.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/components/Columns.tsx @@ -1,11 +1,11 @@ import { ColumnDef } from '@tanstack/react-table' -import { Tooltip, TooltipContent, TooltipTrigger } from 'ui' +import { Checkbox, Tooltip, TooltipContent, TooltipTrigger } from 'ui' import { STATUS_CODE_LABELS } from '../UnifiedLogs.constants' import { ColumnFilterSchema, ColumnSchema } from '../UnifiedLogs.schema' +import { parseAuthLogEventMessage } from '../UnifiedLogs.utils' import { HoverCardTimestamp } from './HoverCardTimestamp' import { LogTypeIcon } from './LogTypeIcon' -import { TextWithTooltip } from './TextWithTooltip' import { DataTableColumnLevelIndicator } from '@/components/ui/DataTable/DataTableColumn/DataTableColumnLevelIndicator' import { DataTableColumnStatusCode } from '@/components/ui/DataTable/DataTableColumn/DataTableColumnStatusCode' @@ -30,7 +30,7 @@ function shouldHideColumn(data: ColumnSchema[], columnKey: keyof ColumnSchema): } // Generate dynamic columns based on data -export function generateDynamicColumns(data: ColumnSchema[]): { +export function generateDynamicColumns({ data }: { data: ColumnSchema[] }): { columns: ColumnDef[] columnVisibility: Record } { @@ -39,6 +39,32 @@ export function generateDynamicColumns(data: ColumnSchema[]): { const hideEventMessage = shouldHideColumn(data, 'event_message') const columns: ColumnDef[] = [ + { + accessorKey: 'select', + header: '', + cell: ({ row }) => { + return ( +
+ row.toggleSelected(!!value)} + onClick={(e) => e.stopPropagation()} + /> +
+ ) + }, + enableHiding: false, + enableResizing: false, + enableSorting: false, + filterFn: (_row, _columnId, _filterValue) => true, + size: 48, + minSize: 48, + maxSize: 48, + meta: { + cellClassName: 'w-[32px]', + headerClassName: 'w-[32px]', + }, + }, // Level column - always visible { accessorKey: 'level', @@ -179,7 +205,7 @@ export function generateDynamicColumns(data: ColumnSchema[]): { header: 'Pathname', cell: ({ row }) => { const value = row.getValue('pathname') ?? '' - return + return value }, enableSorting: false, enableResizing: false, @@ -197,7 +223,9 @@ export function generateDynamicColumns(data: ColumnSchema[]): { header: 'Event message', cell: ({ row }) => { const value = row.getValue('event_message') + const logType = row.original.log_type const logCount = row.original.log_count + const displayMessage = logType === 'auth' ? parseAuthLogEventMessage(value) : value return (
@@ -213,11 +241,7 @@ export function generateDynamicColumns(data: ColumnSchema[]): { )} - {value && ( - - - - )} + {displayMessage && {displayMessage}}
) }, @@ -243,4 +267,6 @@ export function generateDynamicColumns(data: ColumnSchema[]): { } // Static fallback columns -export const UNIFIED_LOGS_COLUMNS: ColumnDef[] = generateDynamicColumns([]).columns +export const UNIFIED_LOGS_COLUMNS: ColumnDef[] = generateDynamicColumns({ + data: [], +}).columns diff --git a/apps/studio/components/interfaces/UnifiedLogs/components/LogsFilterBar.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/LogsFilterBar.tsx index d8d4d68103340..7eb6e43296768 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/components/LogsFilterBar.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/components/LogsFilterBar.tsx @@ -85,6 +85,7 @@ export const LogsFilterBar = () => { return ( (false) - const textRef = useRef(null) - - useEffect(() => { - const checkTruncation = () => { - if (textRef.current) { - const { scrollWidth, clientWidth } = textRef.current - setIsTruncated(scrollWidth > clientWidth) - } - } - - const resizeObserver = new ResizeObserver(() => { - checkTruncation() - }) - - if (textRef.current) { - resizeObserver.observe(textRef.current) - } - - checkTruncation() - - return () => { - resizeObserver.disconnect() - } - }, []) - - return ( - - - -
- {text} -
-
- - {text} - -
-
- ) -} diff --git a/apps/studio/components/interfaces/UserDropdown/TimezoneDropdown.tsx b/apps/studio/components/interfaces/UserDropdown/TimezoneDropdown.tsx index f6e7cf989be95..16fcc29ce996c 100644 --- a/apps/studio/components/interfaces/UserDropdown/TimezoneDropdown.tsx +++ b/apps/studio/components/interfaces/UserDropdown/TimezoneDropdown.tsx @@ -2,12 +2,12 @@ import { CheckIcon } from 'lucide-react' import { useMemo, useState } from 'react' import { cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, @@ -59,13 +59,13 @@ export const TimezoneDropdown = () => { - - - - No timezones found - + + + + No timezones found + - handleSelect('')} @@ -80,12 +80,12 @@ export const TimezoneDropdown = () => { isAutoDetected ? 'opacity-100' : 'opacity-0' )} /> - + {TIMEZONES_BY_IANA.map((entry) => { const ianaName = entry.utc[0] const isSelected = !isAutoDetected && storedTimezone === ianaName return ( - { isSelected ? 'opacity-100' : 'opacity-0' )} /> - + ) })} - - - + + + diff --git a/apps/studio/components/layouts/AppLayout/BranchDropdownCommandContent.tsx b/apps/studio/components/layouts/AppLayout/BranchDropdownCommandContent.tsx index 216164c180f4d..54a044c8e8bdb 100644 --- a/apps/studio/components/layouts/AppLayout/BranchDropdownCommandContent.tsx +++ b/apps/studio/components/layouts/AppLayout/BranchDropdownCommandContent.tsx @@ -3,13 +3,13 @@ import Link from 'next/link' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, ScrollArea, } from 'ui' @@ -46,7 +46,7 @@ export function BranchDropdownCommandContent({ if (embedded) { return ( - +
{isBranchingEnabled && ( - + )} - - {isBranchingEnabled && No branches found} - + + {isBranchingEnabled && No branches found} + {branchList.map((branch) => ( ))} - - -
+ + + ) } return ( - - {isBranchingEnabled && } - - {isBranchingEnabled && No branches found} - + + {isBranchingEnabled && } + + {isBranchingEnabled && No branches found} + {branchList.map((branch) => ( ))} - + - + - - + { track('branch_selector_create_clicked') @@ -148,8 +148,8 @@ export function BranchDropdownCommandContent({

Create branch

-
- + { track('branch_selector_manage_clicked') @@ -163,13 +163,13 @@ export function BranchDropdownCommandContent({

Manage branches

-
-
+ + - + - - + { onClose() @@ -190,9 +190,9 @@ export function BranchDropdownCommandContent({

Join GitHub Discussion

-
-
-
-
+ + + + ) } diff --git a/apps/studio/components/layouts/AppLayout/BranchLink.tsx b/apps/studio/components/layouts/AppLayout/BranchLink.tsx index 32cdce43d41e2..aa65952f733f0 100644 --- a/apps/studio/components/layouts/AppLayout/BranchLink.tsx +++ b/apps/studio/components/layouts/AppLayout/BranchLink.tsx @@ -1,7 +1,7 @@ import { Check, Shield } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' -import { CommandItem_Shadcn_ } from 'ui' +import { CommandItem } from 'ui' import { sanitizeRoute } from './ProjectDropdown.utils' import type { Branch } from '@/data/branches/branches-query' @@ -22,7 +22,7 @@ export function BranchLink({ branch, isSelected, onClose }: BranchLinkProps) { return ( - { @@ -38,7 +38,7 @@ export function BranchLink({ branch, isSelected, onClose }: BranchLinkProps) { {branch.name}

{isSelected && } -
+ ) } diff --git a/apps/studio/components/layouts/AppLayout/OrgCommandItem.tsx b/apps/studio/components/layouts/AppLayout/OrgCommandItem.tsx index cfd09ee9d2703..a4a0e71224830 100644 --- a/apps/studio/components/layouts/AppLayout/OrgCommandItem.tsx +++ b/apps/studio/components/layouts/AppLayout/OrgCommandItem.tsx @@ -1,6 +1,6 @@ import { Check } from 'lucide-react' import Link from 'next/link' -import { cn, CommandItem_Shadcn_ } from 'ui' +import { cn, CommandItem } from 'ui' import PartnerIcon from '@/components/ui/PartnerIcon' import type { Organization } from '@/types' @@ -25,7 +25,7 @@ export function OrgCommandItem({ const href = hasRouteSlug ? routePathname.replace('[slug]', org.slug) : `/org/${org.slug}` return ( - {org.slug === selectedSlug && } - + ) } diff --git a/apps/studio/components/layouts/AppLayout/OrganizationDropdownCommandContent.tsx b/apps/studio/components/layouts/AppLayout/OrganizationDropdownCommandContent.tsx index 78a395fa35ed4..3eb12d3705cb3 100644 --- a/apps/studio/components/layouts/AppLayout/OrganizationDropdownCommandContent.tsx +++ b/apps/studio/components/layouts/AppLayout/OrganizationDropdownCommandContent.tsx @@ -3,13 +3,13 @@ import Link from 'next/link' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, ScrollArea, } from 'ui' @@ -55,7 +55,7 @@ export function OrganizationDropdownCommandContent({ if (embedded) { return ( - +
- - - No organizations found - {orgList} - -
+ + No organizations found + {orgList} + + ) } return ( - - - - No organizations found - + + + + No organizations found + 7 ? 'md:h-[210px]' : ''}> {orgList} - - - - onClose()}> + + + + onClose()}> All Organizations - - + + {organizationCreationEnabled && ( <> - - - onClose()}> + + + onClose()}>

New organization

-
-
+ + )} -
-
+ + ) } diff --git a/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx b/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx index 14acd11c4512e..671c6947e46cb 100644 --- a/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx +++ b/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx @@ -4,7 +4,7 @@ import Link from 'next/link' import { useRouter } from 'next/router' import type { ComponentProps } from 'react' import { useState } from 'react' -import { Button, CommandGroup_Shadcn_, CommandItem_Shadcn_ } from 'ui' +import { Button, CommandGroup, CommandItem } from 'ui' import { ShimmeringLoader } from 'ui-patterns' import { AppLayoutDropdownTriggerButton } from './AppLayoutDropdown' @@ -55,8 +55,8 @@ function ProjectDropdownNewProjectActions({ } return ( - - + { onClose() @@ -68,8 +68,8 @@ function ProjectDropdownNewProjectActions({

New project

-
-
+ + ) } diff --git a/apps/studio/components/layouts/Navigation/LayoutHeader/HeaderUpgradeButton.tsx b/apps/studio/components/layouts/Navigation/LayoutHeader/HeaderUpgradeButton.tsx deleted file mode 100644 index e4de5957c25f2..0000000000000 --- a/apps/studio/components/layouts/Navigation/LayoutHeader/HeaderUpgradeButton.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { UpgradePlanButton } from '@/components/ui/UpgradePlanButton' -import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' -import { useTrackExperimentExposure } from '@/hooks/misc/useTrackExperimentExposure' -import { usePHFlag } from '@/hooks/ui/useFlag' -import { useTrack } from '@/lib/telemetry/track' - -const EXPERIMENT_FLAG_KEY = 'headerUpgradeCta' -const EXPERIMENT_EXPOSURE_NAME = 'header_upgrade_cta' -type HeaderUpgradeCtaVariant = 'control' | 'test' - -interface HeaderUpgradeButtonProps { - className?: string -} - -export const HeaderUpgradeButton = ({ className }: HeaderUpgradeButtonProps) => { - const track = useTrack() - const { data: organization } = useSelectedOrganizationQuery() - const flagValue = usePHFlag(EXPERIMENT_FLAG_KEY) - - const isFreePlan = organization?.plan?.id === 'free' - const isInExperiment = flagValue === 'control' || flagValue === 'test' - const showButton = flagValue === 'test' - - const variant = isFreePlan && isInExperiment ? (flagValue as string) : undefined - useTrackExperimentExposure(EXPERIMENT_EXPOSURE_NAME, variant) - - if (!isFreePlan) return null - if (!showButton) return null - - const handleClick = () => { - track('header_upgrade_cta_clicked') - } - - return ( - - ) -} diff --git a/apps/studio/components/layouts/Navigation/LayoutHeader/LayoutHeader.tsx b/apps/studio/components/layouts/Navigation/LayoutHeader/LayoutHeader.tsx index 1d05bc775c407..516c8f72e640d 100644 --- a/apps/studio/components/layouts/Navigation/LayoutHeader/LayoutHeader.tsx +++ b/apps/studio/components/layouts/Navigation/LayoutHeader/LayoutHeader.tsx @@ -10,7 +10,6 @@ import { CommandMenuTriggerInput } from 'ui-patterns' import { BreadcrumbsView } from './BreadcrumbsView' import { FeedbackDropdown } from './FeedbackDropdown/FeedbackDropdown' -import { HeaderUpgradeButton } from './HeaderUpgradeButton' import { HomeIcon } from './HomeIcon' import { LocalVersionPopover } from './LocalVersionPopover' import { MergeRequestButton } from './MergeRequestButton' @@ -253,7 +252,6 @@ export const LayoutHeader = ({ )} - ) : ( diff --git a/apps/studio/components/layouts/Navigation/NavigationBar/MobileNavigationBar.tsx b/apps/studio/components/layouts/Navigation/NavigationBar/MobileNavigationBar.tsx index 18c4d1e2ea686..a3b25512ba424 100644 --- a/apps/studio/components/layouts/Navigation/NavigationBar/MobileNavigationBar.tsx +++ b/apps/studio/components/layouts/Navigation/NavigationBar/MobileNavigationBar.tsx @@ -5,7 +5,6 @@ import { useState } from 'react' import { Button, cn } from 'ui' import { MobileSheetNav } from 'ui-patterns' -import { HeaderUpgradeButton } from '../LayoutHeader/HeaderUpgradeButton' import { HomeIcon } from '../LayoutHeader/HomeIcon' import { useMobileSheet } from './MobileSheetContext' import { OrgSelector } from './OrgSelector' @@ -71,7 +70,6 @@ const MobileNavigationBar = ({ )}
- {IS_PLATFORM && } {IS_PLATFORM ? : } {!hideMobileMenu && ( - - - - No chats found. - + + + + No chats found. + 4 ? 'h-40' : ''}> {/* @ts-ignore */} {chats.map(([id, chat]) => ( - handleSelectChat(id)} @@ -185,13 +185,13 @@ export const AIAssistantChatSelector = ({ disabled = false }: AIAssistantChatSel )}
)} - + ))} - - - - + + + { snap.newChat() @@ -205,10 +205,10 @@ export const AIAssistantChatSelector = ({ disabled = false }: AIAssistantChatSel > Start a new chat - - - - + + + + ) diff --git a/apps/studio/components/ui/AIAssistantPanel/ModelSelector.tsx b/apps/studio/components/ui/AIAssistantPanel/ModelSelector.tsx index f9c4740b9a02c..8e7817dad49ab 100644 --- a/apps/studio/components/ui/AIAssistantPanel/ModelSelector.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/ModelSelector.tsx @@ -4,10 +4,10 @@ import { useState } from 'react' import { Badge, Button, - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandGroup, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -64,11 +64,11 @@ export const ModelSelector = ({ selectedModel, onSelectModel }: ModelSelectorPro - - - + + + {ASSISTANT_MODELS.map((m) => ( - )} - + ))} - - - + + + ) diff --git a/apps/studio/components/ui/AiAssistantDropdown.tsx b/apps/studio/components/ui/AiAssistantDropdown.tsx index e3652414c00d2..da2c95264fd2f 100644 --- a/apps/studio/components/ui/AiAssistantDropdown.tsx +++ b/apps/studio/components/ui/AiAssistantDropdown.tsx @@ -1,6 +1,7 @@ import { AiAssistantSource } from 'common/telemetry-constants' import { Chatgpt, Claude } from 'icons' import { Check, ChevronDown, Copy } from 'lucide-react' +import Link from 'next/link' import { ComponentProps, ReactNode, useEffect, useState } from 'react' import { AiIconAnimation, @@ -41,7 +42,8 @@ const EXTERNAL_AI_TOOLS = [ export interface AiAssistantDropdownItem { label: string icon?: ReactNode - onClick: () => void + href?: string + onClick?: () => void } export interface AiAssistantDropdownProps { @@ -59,7 +61,6 @@ export interface AiAssistantDropdownProps { tooltip?: string copyLabel?: string showExternalAI?: boolean - extraDropdownItems?: ReactNode additionalDropdownItems?: AiAssistantDropdownItem[] } @@ -78,7 +79,6 @@ export function AiAssistantDropdown({ tooltip, copyLabel = 'Copy prompt', showExternalAI = false, - extraDropdownItems, additionalDropdownItems, }: AiAssistantDropdownProps) { const track = useTrack() @@ -150,11 +150,11 @@ export function AiAssistantDropdown({ /> - {extraDropdownItems} {showCopied ? : } {showCopied ? 'Copied!' : copyLabel} + {showExternalAI && ( <> @@ -170,13 +170,23 @@ export function AiAssistantDropdown({ ))} )} + {additionalDropdownItems && additionalDropdownItems.length > 0 && ( <> {additionalDropdownItems.map((item, i) => ( - {item.icon} - {item.label} + {item.href ? ( + + {item.icon} + {item.label} + + ) : ( + <> + {item.icon} + {item.label} + + )} ))} diff --git a/apps/studio/components/ui/Charts/ChartHighlightActions.tsx b/apps/studio/components/ui/Charts/ChartHighlightActions.tsx index 2babb10d0f65b..ddc0460555e09 100644 --- a/apps/studio/components/ui/Charts/ChartHighlightActions.tsx +++ b/apps/studio/components/ui/Charts/ChartHighlightActions.tsx @@ -90,7 +90,17 @@ export const ChartHighlightActions = ({ top: chartHighlight?.popoverPosition?.y + 'px' || 0, }} /> - + clearHighlight?.()} + onInteractOutside={(e) => { + const target = e.target as Element | null + // If the user clicked on a chart, handleMouseDown will manage the new selection. + // Calling clearHighlight here would race with it and clobber the new state. + if (target?.closest('.recharts-wrapper')) return + clearHighlight?.() + }} + > {formatChartDate(selectedRangeStart!, 'MMM D, H:mm')} diff --git a/apps/studio/components/ui/DataTable/DataTable.types.ts b/apps/studio/components/ui/DataTable/DataTable.types.ts index 7354550ed97d3..cb500d6aedb38 100644 --- a/apps/studio/components/ui/DataTable/DataTable.types.ts +++ b/apps/studio/components/ui/DataTable/DataTable.types.ts @@ -54,6 +54,8 @@ export type Base = { commandDisabled?: boolean hasDynamicOptions?: boolean hasAsyncSearch?: boolean + /** Defines if the filter should be present in the filter nav */ + hidden?: boolean } export type DataTableCheckboxFilterField = Base & Checkbox diff --git a/apps/studio/components/ui/DataTable/DataTable.utils.ts b/apps/studio/components/ui/DataTable/DataTable.utils.ts index deec18fab9730..d206a302dd8df 100644 --- a/apps/studio/components/ui/DataTable/DataTable.utils.ts +++ b/apps/studio/components/ui/DataTable/DataTable.utils.ts @@ -63,8 +63,8 @@ export function getLevelColor( case 'success': return { text: 'text-muted', - bg: 'bg-muted', - border: 'border-muted', + bg: 'bg-muted group-data-[state=selected]/row:bg-foreground-lighter', + border: 'border-muted group-data-[state=selected]/row:border-foreground-lighter', } case 'warning': return { @@ -86,43 +86,3 @@ export function getLevelColor( } } } - -export function getStatusColor(value?: number | string): Record<'text' | 'bg' | 'border', string> { - switch (value) { - case '1': - case 'info': - return { - text: 'text-blue-500', - bg: '', - border: 'border-blue-200 dark:border-blue-800', - } - case '2': - case 'success': - return { - text: 'text-foreground', - bg: '', - border: 'border-green-200 dark:border-green-800', - } - case '4': - case 'warning': - case 'redirect': - return { - text: 'text-warning', - bg: 'bg-warning-300 dark:bg-warning-200', - border: 'border border-warning-400/50 dark:border-warning-400/50', - } - case '5': - case 'error': - return { - text: 'text-destructive', - bg: 'bg-destructive-300 dark:bg-destructive-300/50', - border: 'border border-destructive-400/50 dark:border-destructive-400/50', - } - default: - return { - text: 'text-foreground', - bg: '', - border: '', - } - } -} diff --git a/apps/studio/components/ui/DataTable/DataTableColumn/DataTableColumnLevelIndicator.tsx b/apps/studio/components/ui/DataTable/DataTableColumn/DataTableColumnLevelIndicator.tsx index dde560e82d98c..a4b8ed3ebcfbe 100644 --- a/apps/studio/components/ui/DataTable/DataTableColumn/DataTableColumnLevelIndicator.tsx +++ b/apps/studio/components/ui/DataTable/DataTableColumn/DataTableColumnLevelIndicator.tsx @@ -16,7 +16,7 @@ export const DataTableColumnLevelIndicator = ({
{ - const colors = getStatusColor(level) + const colorClassName = getStatusColor(level) + + function getStatusColor(value?: number | string): string { + switch (value) { + case '1': + case 'info': + return 'text-blue-500' + case '2': + case 'success': + return 'text-foreground' + case '4': + case 'warning': + case 'redirect': + return 'text-warning' + case '5': + case 'error': + return 'text-destructive' + default: + return 'text-foreground' + } + } + if (!value) { return } return (
-
+
{value}
diff --git a/apps/studio/components/ui/DataTable/DataTableFilters/DataTableFilterControls.tsx b/apps/studio/components/ui/DataTable/DataTableFilters/DataTableFilterControls.tsx index ee813b21790fd..e13a8906643c1 100644 --- a/apps/studio/components/ui/DataTable/DataTableFilters/DataTableFilterControls.tsx +++ b/apps/studio/components/ui/DataTable/DataTableFilters/DataTableFilterControls.tsx @@ -30,51 +30,53 @@ export function DataTableFilterControls({ ?.filter(({ defaultOpen }) => defaultOpen) ?.map(({ value }) => value as string)} > - {filterFields?.map((field) => { - const value = field.value as string - return ( - -
- -
-

{field.label}

-
-
- -
- - {/* REMINDER: avoid the focus state to be cut due to overflow-hidden */} - {/* REMINDER: need to move within here because of accordion height animation */} -
- {(() => { - switch (field.type) { - case 'checkbox': { - // [Joshen] Loader here so that CheckboxAsync can retrieve the data - // immediately to be set in its react query state - if (field.hasDynamicOptions && isLoadingCounts) { - return - } else if (field.hasAsyncSearch) { - return - } else { - return + {filterFields + ?.filter((field) => !field.hidden) + .map((field) => { + const value = field.value as string + return ( + +
+ +
+

{field.label}

+
+
+ +
+ + {/* REMINDER: avoid the focus state to be cut due to overflow-hidden */} + {/* REMINDER: need to move within here because of accordion height animation */} +
+ {(() => { + switch (field.type) { + case 'checkbox': { + // [Joshen] Loader here so that CheckboxAsync can retrieve the data + // immediately to be set in its react query state + if (field.hasDynamicOptions && isLoadingCounts) { + return + } else if (field.hasAsyncSearch) { + return + } else { + return + } + } + case 'slider': { + return + } + case 'input': { + return + } + case 'timerange': { + return } } - case 'slider': { - return - } - case 'input': { - return - } - case 'timerange': { - return - } - } - })()} -
-
-
- ) - })} + })()} +
+
+
+ ) + })} ) } diff --git a/apps/studio/components/ui/DataTable/DataTableInfinite.tsx b/apps/studio/components/ui/DataTable/DataTableInfinite.tsx index e190f9d6deef7..2656050405fe0 100644 --- a/apps/studio/components/ui/DataTable/DataTableInfinite.tsx +++ b/apps/studio/components/ui/DataTable/DataTableInfinite.tsx @@ -45,8 +45,8 @@ export function DataTableInfinite({ setColumnVisibility, searchParamsParser, }: DataTableInfiniteProps) { - const { table, error, isError, isLoading, isFetching } = useDataTable() const tableRef = useRef(null) + const { table, error, isError, isLoading, isFetching, openRowId, setOpenRowId } = useDataTable() const headerGroups = table.getHeaderGroups() const headers = headerGroups[0].headers @@ -128,7 +128,8 @@ export function DataTableInfinite({ row={row} table={table} searchParamsParser={searchParamsParser} - selected={row.getIsSelected()} + selected={row.id === openRowId} + onSelect={() => setOpenRowId(row.id === openRowId ? undefined : row.id)} /> )) ) : isLoading ? ( @@ -229,14 +230,16 @@ function DataTableRow({ table, selected, searchParamsParser, + onSelect, }: { row: Row table: TTable selected?: boolean searchParamsParser: any + onSelect: () => void }) { useQueryState('live', searchParamsParser.live) - const rowClassName = (table.options.meta as any)?.getRowClassName?.(row) + const rowClassName = cn('group/row', (table.options.meta as any)?.getRowClassName?.(row)) const cells = row.getVisibleCells() return ( @@ -244,11 +247,11 @@ function DataTableRow({ id={row.id} tabIndex={0} data-state={selected && 'selected'} - onClick={() => row.toggleSelected()} + onClick={onSelect} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault() - row.toggleSelected() + onSelect() } }} className={cn(rowClassName)} diff --git a/apps/studio/components/ui/DataTable/DataTableSheetRowAction.tsx b/apps/studio/components/ui/DataTable/DataTableSheetRowAction.tsx index e26101a1553aa..7c3f965528556 100644 --- a/apps/studio/components/ui/DataTable/DataTableSheetRowAction.tsx +++ b/apps/studio/components/ui/DataTable/DataTableSheetRowAction.tsx @@ -47,8 +47,12 @@ export function DataTableSheetRowAction) { const { copy, isCopied } = useCopyToClipboard() + const field = !!fieldValue ? filterFields.find((f) => f.value === fieldValue) : undefined - const column = !!fieldValue ? table.getColumn(fieldValue.toString()) : undefined + const column = + !!fieldValue && !!field + ? table.getAllColumns().find((c) => c.id === fieldValue.toString()) + : undefined function renderOptions() { if (!field) return null diff --git a/apps/studio/components/ui/DataTable/DataTableViewOptions.tsx b/apps/studio/components/ui/DataTable/DataTableViewOptions.tsx index c61a2b80365c9..dc92700e354e7 100644 --- a/apps/studio/components/ui/DataTable/DataTableViewOptions.tsx +++ b/apps/studio/components/ui/DataTable/DataTableViewOptions.tsx @@ -2,12 +2,12 @@ import { GripVertical, Settings2 } from 'lucide-react' import { useId, useMemo, useState } from 'react' import { Checkbox, - Command_Shadcn_ as Command, - CommandEmpty_Shadcn_ as CommandEmpty, - CommandGroup_Shadcn_ as CommandGroup, - CommandInput_Shadcn_ as CommandInput, - CommandItem_Shadcn_ as CommandItem, - CommandList_Shadcn_ as CommandList, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, diff --git a/apps/studio/components/ui/DataTable/FilterSideBar.tsx b/apps/studio/components/ui/DataTable/FilterSideBar.tsx index 6b07599d94fd2..dbd7dff3eb812 100644 --- a/apps/studio/components/ui/DataTable/FilterSideBar.tsx +++ b/apps/studio/components/ui/DataTable/FilterSideBar.tsx @@ -52,8 +52,9 @@ export function FilterSideBar({ return ( { diff --git a/apps/studio/components/ui/DataTable/Table.tsx b/apps/studio/components/ui/DataTable/Table.tsx index 7b5c3d8798c0e..9f81520bbfe76 100644 --- a/apps/studio/components/ui/DataTable/Table.tsx +++ b/apps/studio/components/ui/DataTable/Table.tsx @@ -79,7 +79,7 @@ export const TableHead = forwardRef< className={cn( 'text-xs! font-normal! text-foreground-lighter font-mono', 'relative select-none truncate [&>.cursor-col-resize]:last:opacity-0', - 'text-muted-foreground h-8 px-2 text-left align-middle [&:has([role=checkbox])]:pr-0 *:[[role=checkbox]]:translate-y-[2px]', + 'text-muted-foreground h-9 px-2 text-left align-middle [&:has([role=checkbox])]:pr-0 *:[[role=checkbox]]:translate-y-[2px]', className )} {...props} diff --git a/apps/studio/components/ui/DataTable/TimelineChart.tsx b/apps/studio/components/ui/DataTable/TimelineChart.tsx index 236941804e8ed..64524e9cc1d56 100644 --- a/apps/studio/components/ui/DataTable/TimelineChart.tsx +++ b/apps/studio/components/ui/DataTable/TimelineChart.tsx @@ -1,10 +1,16 @@ import { format } from 'date-fns' -import { useMemo, useState } from 'react' +import { SearchIcon } from 'lucide-react' +import { useTheme } from 'next-themes' +import { useMemo } from 'react' import { Bar, BarChart, CartesianGrid, ReferenceArea, XAxis } from 'recharts' -import type { CategoricalChartFunc } from 'recharts/types/chart/generateCategoricalChart' import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, cn } from 'ui' import { useDataTable } from './providers/DataTableProvider' +import { + ChartHighlightAction, + ChartHighlightActions, +} from '@/components/ui/Charts/ChartHighlightActions' +import { useChartHighlight } from '@/components/ui/Charts/useChartHighlight' export type BaseChartSchema = { timestamp: number; [key: string]: number } export const description = 'A stacked bar chart' @@ -16,6 +22,11 @@ interface TimelineChartProps { * TBD: if using keyof TData to be closer to the data table props */ columnId: string + /** + * Optional override for the column id used when applying the time range filter. + * Defaults to `columnId` if not provided. + */ + filterColumnId?: string /** * Same data as of the InfiniteQueryMeta. */ @@ -27,12 +38,18 @@ export function TimelineChart({ data, className, columnId, + filterColumnId, chartConfig, }: TimelineChartProps) { + const resolvedFilterColumnId = filterColumnId ?? columnId + const { resolvedTheme } = useTheme() + const isDarkMode = resolvedTheme?.includes('dark') + const { table } = useDataTable() - const [refAreaLeft, setRefAreaLeft] = useState(null) - const [refAreaRight, setRefAreaRight] = useState(null) - const [isSelecting, setIsSelecting] = useState(false) + const chartHighlight = useChartHighlight() + + const showHighlight = + chartHighlight?.left && chartHighlight?.right && chartHighlight?.left !== chartHighlight?.right // REMINDER: date has to be a string for tooltip label to work - don't ask me why const chart = useMemo( @@ -52,101 +69,102 @@ export function TimelineChart({ return { interval, period: calculatePeriod(interval) } }, [data]) - const handleMouseDown: CategoricalChartFunc = (e) => { - if (e.activeLabel) { - setRefAreaLeft(e.activeLabel) - setIsSelecting(true) - } - } - - const handleMouseMove: CategoricalChartFunc = (e) => { - if (isSelecting && e.activeLabel) { - setRefAreaRight(e.activeLabel) - } - } - - const handleMouseUp: CategoricalChartFunc = () => { - if (refAreaLeft && refAreaRight) { - const [left, right] = [refAreaLeft, refAreaRight].sort( - (a, b) => new Date(a).getTime() - new Date(b).getTime() - ) - table.getColumn(columnId)?.setFilterValue([new Date(left), new Date(right)]) - } - setRefAreaLeft(null) - setRefAreaRight(null) - setIsSelecting(false) - } + const highlightActions: ChartHighlightAction[] = useMemo( + () => [ + { + id: 'zoom-in', + label: 'Filter logs to selected range', + icon: , + onSelect: ({ start, end, clear }) => { + const [left, right] = [start, end].sort( + (a, b) => new Date(a).getTime() - new Date(b).getTime() + ) + table.getColumn(resolvedFilterColumnId)?.setFilterValue([new Date(left), new Date(right)]) + clear() + }, + }, + ], + [table, resolvedFilterColumnId] + ) return ( - - + - - { - const date = new Date(value) - if (isNaN(date.getTime())) return 'N/A' - if (timerange.period === '10m') { - return format(date, 'HH:mm:ss') - } else if (timerange.period === '1d') { - return format(date, 'HH:mm') - } else if (timerange.period === '1w') { - return format(date, 'LLL dd HH:mm') - } - return format(date, 'LLL dd, y') + { + if (activeTooltipIndex === undefined || activeTooltipIndex === null) return + chartHighlight.handleMouseDown({ activeLabel, coordinates: activeLabel }) }} - /> - { - const date = new Date(value) - if (isNaN(date.getTime())) return 'N/A' - if (timerange.period === '10m') { - return format(date, 'LLL dd, HH:mm:ss') - } - return format(date, 'LLL dd, y HH:mm') - }} - /> - } - /> - {/* TODO: we could use the `{timestamp, ...rest} = data[0]` to dynamically create the bars but that would mean the order can be very much random */} - - - - {refAreaLeft && refAreaRight && ( - { + if (activeTooltipIndex === undefined || activeTooltipIndex === null) return + chartHighlight.handleMouseMove({ activeLabel, coordinates: activeLabel }) + }} + onMouseUp={chartHighlight.handleMouseUp} + style={{ cursor: 'crosshair' }} + > + + { + const date = new Date(value) + if (isNaN(date.getTime())) return 'N/A' + if (timerange.period === '10m') { + return format(date, 'HH:mm:ss') + } else if (timerange.period === '1d') { + return format(date, 'HH:mm') + } else if (timerange.period === '1w') { + return format(date, 'LLL dd HH:mm') + } + return format(date, 'LLL dd, y') + }} /> - )} - - + {!chartHighlight.popoverPosition && ( + { + const date = new Date(value) + if (isNaN(date.getTime())) return 'N/A' + if (timerange.period === '10m') { + return format(date, 'LLL dd, HH:mm:ss') + } + return format(date, 'LLL dd, y HH:mm') + }} + /> + } + /> + )} + {/* TODO: we could use the `{timestamp, ...rest} = data[0]` to dynamically create the bars but that would mean the order can be very much random */} + + + + {showHighlight && ( + + )} + + + +
) } diff --git a/apps/studio/components/ui/DataTable/providers/DataTableProvider.tsx b/apps/studio/components/ui/DataTable/providers/DataTableProvider.tsx index 9dfd396da82c7..b60950fe331b8 100644 --- a/apps/studio/components/ui/DataTable/providers/DataTableProvider.tsx +++ b/apps/studio/components/ui/DataTable/providers/DataTableProvider.tsx @@ -25,6 +25,8 @@ interface DataTableStateContextType { pagination: PaginationState enableColumnOrdering: boolean searchParameters: QuerySearchParamsType + openRowId: string | undefined + setOpenRowId: (id: string | undefined) => void } interface DataTableBaseContextType { @@ -63,6 +65,8 @@ export function DataTableProvider({ pagination: props.pagination ?? { pageIndex: 0, pageSize: 10 }, enableColumnOrdering: props.enableColumnOrdering ?? false, searchParameters: props.searchParameters ?? ({} as any), + openRowId: props.openRowId, + setOpenRowId: props.setOpenRowId ?? (() => {}), }), [props] ) diff --git a/apps/studio/components/ui/DatabaseSelector.tsx b/apps/studio/components/ui/DatabaseSelector.tsx index 59796bb70e78e..71ed1313537d9 100644 --- a/apps/studio/components/ui/DatabaseSelector.tsx +++ b/apps/studio/components/ui/DatabaseSelector.tsx @@ -9,10 +9,10 @@ import { Button, ButtonProps, cn, - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandGroup, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -123,12 +123,12 @@ export const DatabaseSelector = ({
- - + + {additionalOptions.length > 0 && ( - + {additionalOptions.map((option) => ( - {option.name}

{option.id === selectedDatabaseId && }
- + ))} - + )} - + 7 ? 'h-[210px]' : ''}> {sortedDatabases?.map((database) => { const region = formatDatabaseRegion(database.region) @@ -185,7 +185,7 @@ export const DatabaseSelector = ({ } return ( - {database.identifier === selectedDatabaseId && } - + ) })} - + {IS_PLATFORM && infrastructureReadReplicas && ( - - + { setOpen(false) @@ -235,11 +235,11 @@ export const DatabaseSelector = ({

Create a new read replica

-
-
+ + )} - - + + ) diff --git a/apps/studio/components/ui/EditorPanel/EditorPanel.tsx b/apps/studio/components/ui/EditorPanel/EditorPanel.tsx index afbecfa636311..87a4f2a8b1364 100644 --- a/apps/studio/components/ui/EditorPanel/EditorPanel.tsx +++ b/apps/studio/components/ui/EditorPanel/EditorPanel.tsx @@ -19,12 +19,12 @@ import { useEffect, useRef, useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, HoverCard, HoverCardContent, HoverCardTrigger, @@ -330,23 +330,23 @@ export const EditorPanel = () => { > - - + - + {isLoadingSnippets ? (
Loading snippets...
) : ( - No snippets found. + No snippets found. )} - + {(snippetsData?.content ?? []).map((snippet) => ( - { }} > {snippet.name} - + ))} - -
-
+ + +
{templates.length > 0 && ( @@ -379,15 +379,15 @@ export const EditorPanel = () => { - - - - No templates found. - + + + + No templates found. + {templates.map((template) => ( - onSelectTemplate(template.content)} className="cursor-pointer" @@ -406,7 +406,7 @@ export const EditorPanel = () => {

-
+
{
))} -
-
-
+ + +
)} diff --git a/apps/studio/components/ui/ErrorCodeTooltip/ErrorCodeTooltip.tsx b/apps/studio/components/ui/ErrorCodeTooltip/ErrorCodeTooltip.tsx index 13fd2f4f85072..83e5125a06f90 100644 --- a/apps/studio/components/ui/ErrorCodeTooltip/ErrorCodeTooltip.tsx +++ b/apps/studio/components/ui/ErrorCodeTooltip/ErrorCodeTooltip.tsx @@ -1,17 +1,8 @@ import { ExternalLink } from 'lucide-react' import { useTheme } from 'next-themes' import Image from 'next/image' -import Link from 'next/link' import { useState } from 'react' -import { - cn, - DropdownMenuItem, - DropdownMenuSeparator, - HoverCard, - HoverCardContent, - HoverCardTrigger, - InfoIcon, -} from 'ui' +import { cn, HoverCard, HoverCardContent, HoverCardTrigger, InfoIcon } from 'ui' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { getErrorCodeInfo } from './ErrorCodeTooltip.utils' @@ -117,25 +108,19 @@ export const ErrorCodeTooltip = ({ errorCode, service, children }: ErrorCodeTool )} - - - - Go to Docs - - - - - ) : undefined - } + additionalDropdownItems={[ + { + label: 'Go to Docs', + icon: , + href: docsUrl, + }, + ]} /> diff --git a/apps/studio/components/ui/FunctionSelector.tsx b/apps/studio/components/ui/FunctionSelector.tsx index bb99e250b855b..19c206746e4e3 100644 --- a/apps/studio/components/ui/FunctionSelector.tsx +++ b/apps/studio/components/ui/FunctionSelector.tsx @@ -9,13 +9,13 @@ import { AlertDescription, AlertTitle, Button, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, Popover, PopoverContent, PopoverTrigger, @@ -123,25 +123,25 @@ const FunctionSelector = ({ - - - + + event.stopPropagation() : undefined} > - No functions found - + No functions found + 7 ? 'h-[210px]' : ''}> {!functions.length && ( - {noResultsLabel} - + )} {functions.map((func) => ( - )} - + ))} - - - - + + + { setOpen(false) @@ -182,10 +182,10 @@ const FunctionSelector = ({

New function

-
-
-
-
+ + + +
)} diff --git a/apps/studio/components/ui/Logs/LogsExplorerHeader.tsx b/apps/studio/components/ui/Logs/LogsExplorerHeader.tsx index 5e6cf2c0caac3..8f530ad80361d 100644 --- a/apps/studio/components/ui/Logs/LogsExplorerHeader.tsx +++ b/apps/studio/components/ui/Logs/LogsExplorerHeader.tsx @@ -1,12 +1,21 @@ -import { BookOpen, Check, Copy, ExternalLink, List, X } from 'lucide-react' +import { BookOpen, Check, ChevronsUpDown, Copy, ExternalLink, List, X } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' import { logConstants } from 'shared-data' import { Button, + cn, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, copyToClipboard, + Popover, + PopoverContent, + PopoverTrigger, SidePanel, - Tabs, Tooltip, TooltipContent, TooltipTrigger, @@ -23,6 +32,8 @@ export interface LogsExplorerHeaderProps { const LogsExplorerHeader = ({ subtitle }: LogsExplorerHeaderProps) => { const [showReference, setShowReference] = useState(false) + const [open, setOpen] = useState(false) + const [selectedSchema, setSelectedSchema] = useState(logConstants.schemas[0]) return (
@@ -89,38 +100,63 @@ const LogsExplorerHeader = ({ subtitle }: LogsExplorerHeaderProps) => {
- - {logConstants.schemas.map((schema) => ( - -
- Path - , - - Type - , - ]} - body={schema.fields - .sort((a: any, b: any) => a.path - b.path) - .map((field) => ( - - ))} - /> - - ))} - +
+ + + + + + + + + No source found. + + {logConstants.schemas.map((schema) => ( + { + setSelectedSchema(schema) + setOpen(false) + }} + > + + {schema.name} + + ))} + + + + + +
+ Path + , + + Type + , + ]} + body={selectedSchema.fields.map((field) => ( + + ))} + /> + diff --git a/apps/studio/components/ui/OrganizationProjectSelector.tsx b/apps/studio/components/ui/OrganizationProjectSelector.tsx index 62e5fc72fa05d..38b9e9d64139d 100644 --- a/apps/studio/components/ui/OrganizationProjectSelector.tsx +++ b/apps/studio/components/ui/OrganizationProjectSelector.tsx @@ -5,10 +5,10 @@ import { ReactNode, useEffect, useId, useMemo, useRef, useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandList_Shadcn_, + Command, + CommandGroup, + CommandInput, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -217,7 +217,7 @@ export const OrganizationProjectSelector = ({ } const commandContent = ( - @@ -226,7 +226,7 @@ export const OrganizationProjectSelector = ({ {renderActions(setOpen, { embedded: true })} )} - - - + {renderListContent()} - + {!!renderActions && !embedded && ( <>
{renderActions(setOpen)} )} - - + + ) if (embedded) { diff --git a/apps/studio/components/ui/OrganizationProjectSelector/ProjectCommandItem.tsx b/apps/studio/components/ui/OrganizationProjectSelector/ProjectCommandItem.tsx index 1f7b39463aea3..d9076d799362e 100644 --- a/apps/studio/components/ui/OrganizationProjectSelector/ProjectCommandItem.tsx +++ b/apps/studio/components/ui/OrganizationProjectSelector/ProjectCommandItem.tsx @@ -1,6 +1,6 @@ import { Check } from 'lucide-react' import { ReactNode } from 'react' -import { cn, CommandItem_Shadcn_ } from 'ui' +import { cn, CommandItem } from 'ui' import type { OrgProject } from '@/data/projects/org-projects-infinite-query' @@ -31,7 +31,7 @@ export function ProjectCommandItem({ const disabled = isOptionDisabled?.(project) ?? false return ( - }
)} - + ) } diff --git a/apps/studio/components/ui/SchemaComboBox.tsx b/apps/studio/components/ui/SchemaComboBox.tsx index 673f41754ac63..ce0f5f0fc9f98 100644 --- a/apps/studio/components/ui/SchemaComboBox.tsx +++ b/apps/studio/components/ui/SchemaComboBox.tsx @@ -5,12 +5,12 @@ import { AlertDescription, AlertTitle, Button, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -108,14 +108,14 @@ export const SchemaComboBox = ({ - - - - No schemas found - + + + + No schemas found + 7 ? 'h-[210px]' : ''}> {schemas?.map((schema) => ( - toggleSchema(schema.name)} @@ -125,12 +125,12 @@ export const SchemaComboBox = ({ {selectedSchemas.includes(schema.name) && ( )} - + ))} - - - + + + )} diff --git a/apps/studio/components/ui/SchemaSelector.tsx b/apps/studio/components/ui/SchemaSelector.tsx index 8426adf79e1d9..bc7832fe38c40 100644 --- a/apps/studio/components/ui/SchemaSelector.tsx +++ b/apps/studio/components/ui/SchemaSelector.tsx @@ -6,13 +6,13 @@ import { AlertDescription, AlertTitle, Button, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, Popover, PopoverContent, PopoverTrigger, @@ -149,16 +149,16 @@ export const SchemaSelector = forwardRef( align={align} sameWidthAsTrigger > - - - + + event.stopPropagation() : undefined} > - No schemas found - + No schemas found + 7 ? 'h-[210px]' : ''}> {supportSelectAll && ( - { @@ -174,10 +174,10 @@ export const SchemaSelector = forwardRef( {selectedSchemaName === '*' && ( )} - + )} {schemas.map((schema) => ( - { @@ -193,15 +193,15 @@ export const SchemaSelector = forwardRef( {selectedSchemaName === schema.name && ( )} - + ))} - + {onSelectCreateSchema !== undefined && canCreateSchemas && ( <> - - - + + { onSelectCreateSchema() @@ -214,12 +214,12 @@ export const SchemaSelector = forwardRef( > Create a new schema - - + + )} - - + + )} diff --git a/apps/studio/pages/cli/login.tsx b/apps/studio/pages/cli/login.tsx index 5c9e88f0bc724..e87e1885478dc 100644 --- a/apps/studio/pages/cli/login.tsx +++ b/apps/studio/pages/cli/login.tsx @@ -18,6 +18,7 @@ import CopyButton from '@/components/ui/CopyButton' import { InlineLink } from '@/components/ui/InlineLink' import { createCliLoginSession } from '@/data/cli/login' import { withAuth } from '@/hooks/misc/withAuth' +import { useStaticEffectEvent } from '@/hooks/useStaticEffectEvent' import { buildStudioPageTitle } from '@/lib/page-title' import { useProfile } from '@/lib/profile' import type { NextPageWithLayout } from '@/types' @@ -99,7 +100,7 @@ export const CliLoginScreen = ({ publicKey, tokenName, deviceCode, - navigate, + navigate: navigateProp, }: { isLoggedIn: boolean routerReady: boolean @@ -112,6 +113,9 @@ export const CliLoginScreen = ({ const { profile } = useProfile() const [status, setStatus] = useState({ _tag: 'loading' }) const startedForSessionIdRef = useRef(undefined) + // Keep navigate in a ref so changing the prop never re-triggers the effect + // or cancels an in-flight POST via the isActive cleanup. + const navigate = useStaticEffectEvent(navigateProp) const displayName = profile?.primary_email ?? profile?.username useEffect(() => { @@ -163,7 +167,7 @@ export const CliLoginScreen = ({ return () => { isActive = false } - }, [deviceCode, isLoggedIn, navigate, publicKey, routerReady, sessionId, tokenName]) + }, [deviceCode, isLoggedIn, publicKey, routerReady, sessionId, tokenName, navigate]) if (status._tag === 'loading') { return ( @@ -174,7 +178,7 @@ export const CliLoginScreen = ({
- +
diff --git a/apps/studio/pages/project/[ref]/functions/new.tsx b/apps/studio/pages/project/[ref]/functions/new.tsx index 61bb1bd2011bf..6cb09a857df3b 100644 --- a/apps/studio/pages/project/[ref]/functions/new.tsx +++ b/apps/studio/pages/project/[ref]/functions/new.tsx @@ -10,12 +10,12 @@ import { AiIconAnimation, Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Form, FormControl, FormField, @@ -313,13 +313,13 @@ const NewFunctionPage = () => { - - - - No templates found. - + + + + No templates found. + {templates.map((template) => ( - { {template.description}
- + ))} - - - + + + - - - No results found. - + + + No results found. + {COMMAND_ITEMS.map((navItem) => ( - runCommand(() => router.push(navItem.href as string))} @@ -83,29 +83,29 @@ export function CommandMenu({ ...props }: DialogProps) {
{navItem.label} - + ))} - - - - runCommand(() => setTheme('light'))}> + + + + runCommand(() => setTheme('light'))}> Light - - runCommand(() => setTheme('dark'))}> + + runCommand(() => setTheme('dark'))}> Dark - - runCommand(() => setTheme('classic-dark'))}> + + runCommand(() => setTheme('classic-dark'))}> Classic dark - - runCommand(() => setTheme('system'))}> + + runCommand(() => setTheme('system'))}> System - - -
+ + + ) diff --git a/apps/ui-library/components/tanstack-db-generator/ComboBox.tsx b/apps/ui-library/components/tanstack-db-generator/ComboBox.tsx index 208ab15dc9c43..cb5009d850ebc 100644 --- a/apps/ui-library/components/tanstack-db-generator/ComboBox.tsx +++ b/apps/ui-library/components/tanstack-db-generator/ComboBox.tsx @@ -6,17 +6,18 @@ import { useEffect, useRef, useState } from 'react' import { Button_Shadcn_ as Button, cn, - Command_Shadcn_ as Command, - CommandGroup_Shadcn_ as CommandGroup, - CommandInput_Shadcn_ as CommandInput, - CommandItem_Shadcn_ as CommandItem, - CommandList_Shadcn_ as CommandList, - Popover as Popover, - PopoverContent as PopoverContent, - PopoverTrigger as PopoverTrigger, + Command, + CommandGroup as CommandGroup, + CommandInput, + CommandItem as CommandItem, + CommandList as CommandList, + Popover, + PopoverContent, + PopoverTrigger, ScrollArea, } from 'ui' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' + import { useIntersectionObserver } from '@/hooks/useIntersectionObserver' export interface ComboBoxOption { diff --git a/apps/www/components/CommandMenu/CommandMenu.tsx b/apps/www/components/CommandMenu/CommandMenu.tsx index 0079aa41977cc..45fae07020c06 100644 --- a/apps/www/components/CommandMenu/CommandMenu.tsx +++ b/apps/www/components/CommandMenu/CommandMenu.tsx @@ -1,4 +1,9 @@ -import { CommandHeader, CommandInput, CommandList, CommandMenu } from 'ui-patterns/CommandMenu' +import { + CommandHeader, + CommandMenu, + CommandMenuInput, + CommandMenuList, +} from 'ui-patterns/CommandMenu' import { useChangelogCommand } from 'ui-patterns/CommandMenu/prepackaged/Changelog' import { useDocsAiCommands } from 'ui-patterns/CommandMenu/prepackaged/DocsAi' import { useDocsSearchCommands } from 'ui-patterns/CommandMenu/prepackaged/DocsSearch' @@ -15,9 +20,9 @@ export default function WwwCommandMenuEager() { return ( - + - + ) } diff --git a/apps/www/components/Pricing/UpgradePlan.tsx b/apps/www/components/Pricing/UpgradePlan.tsx index d037156736776..2c7b0d6297ded 100644 --- a/apps/www/components/Pricing/UpgradePlan.tsx +++ b/apps/www/components/Pricing/UpgradePlan.tsx @@ -9,13 +9,13 @@ import { Button, ButtonProps, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - CommandSeparator_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, Dialog, DialogClose, DialogContent, @@ -81,13 +81,13 @@ const UpgradePlan = ({ organizations = [], onClick, size = 'large', planId }: Up - - - - No organizations found. - + + + + No organizations found. + {organizations.map((organization) => ( - { @@ -103,12 +103,12 @@ const UpgradePlan = ({ organizations = [], onClick, size = 'large', planId }: Up )} /> {organization.name} - + ))} - - - - + + + { setValue(currentValue === value ? '' : currentValue) @@ -123,10 +123,10 @@ const UpgradePlan = ({ organizations = [], onClick, size = 'large', planId }: Up )} /> Create a new organization - - - - + + + + diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index ca6182e97b902..9d1357a4aa45b 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -3042,18 +3042,6 @@ export interface AccessTokenRemovedEvent { groups: Omit } -/** - * User clicked the "Upgrade to Pro" CTA in the dashboard header. - * GROWTH-615: always-visible upgrade button in dashboard header for free-plan users. - * - * @group Events - * @source studio - */ -export interface HeaderUpgradeCtaClickedEvent { - action: 'header_upgrade_cta_clicked' - groups: Omit -} - /** * User clicked the primary CTA on a resource exhaustion warning banner. * @@ -3453,7 +3441,6 @@ export type TelemetryEvent = | FlyDeprecationBannerDismissedEvent | FreeMicroUpgradeBannerDismissedEvent | FreeMicroUpgradeBannerCtaClickedEvent - | HeaderUpgradeCtaClickedEvent | AccessTokenCreatedEvent | AccessTokenRemovedEvent | ResourceExhaustionBannerUpgradeClickedEvent diff --git a/packages/ui-patterns/package.json b/packages/ui-patterns/package.json index b1a9707d003c2..85432df4f3cc5 100644 --- a/packages/ui-patterns/package.json +++ b/packages/ui-patterns/package.json @@ -86,13 +86,13 @@ "import": "./src/CommandMenu/api/CommandHeader.tsx", "types": "./src/CommandMenu/api/CommandHeader.tsx" }, - "./CommandMenu/api/CommandInput": { - "import": "./src/CommandMenu/api/CommandInput.tsx", - "types": "./src/CommandMenu/api/CommandInput.tsx" + "./CommandMenu/api/CommandMenuInput": { + "import": "./src/CommandMenu/api/CommandMenuInput.tsx", + "types": "./src/CommandMenu/api/CommandMenuInput.tsx" }, - "./CommandMenu/api/CommandList": { - "import": "./src/CommandMenu/api/CommandList.tsx", - "types": "./src/CommandMenu/api/CommandList.tsx" + "./CommandMenu/api/CommandMenuList": { + "import": "./src/CommandMenu/api/CommandMenuList.tsx", + "types": "./src/CommandMenu/api/CommandMenuList.tsx" }, "./CommandMenu/api/CommandMenu": { "import": "./src/CommandMenu/api/CommandMenu.tsx", @@ -162,17 +162,17 @@ "import": "./src/CommandMenu/index.tsx", "types": "./src/CommandMenu/index.tsx" }, - "./CommandMenu/internal/Command": { - "import": "./src/CommandMenu/internal/Command.tsx", - "types": "./src/CommandMenu/internal/Command.tsx" + "./CommandMenu/internal/CommandMenuItem": { + "import": "./src/CommandMenu/internal/CommandMenuItem.tsx", + "types": "./src/CommandMenu/internal/CommandMenuItem.tsx" }, - "./CommandMenu/internal/CommandEmpty": { - "import": "./src/CommandMenu/internal/CommandEmpty.tsx", - "types": "./src/CommandMenu/internal/CommandEmpty.tsx" + "./CommandMenu/internal/CommandMenuEmpty": { + "import": "./src/CommandMenu/internal/CommandMenuEmpty.tsx", + "types": "./src/CommandMenu/internal/CommandMenuEmpty.tsx" }, - "./CommandMenu/internal/CommandGroup": { - "import": "./src/CommandMenu/internal/CommandGroup.tsx", - "types": "./src/CommandMenu/internal/CommandGroup.tsx" + "./CommandMenu/internal/CommandMenuGroup": { + "import": "./src/CommandMenu/internal/CommandMenuGroup.tsx", + "types": "./src/CommandMenu/internal/CommandMenuGroup.tsx" }, "./CommandMenu/internal/CommandSection": { "import": "./src/CommandMenu/internal/CommandSection.tsx", diff --git a/packages/ui-patterns/src/CommandMenu/api/CommandMenu.tsx b/packages/ui-patterns/src/CommandMenu/api/CommandMenu.tsx index bc0ab4c55fc03..bb2cbbee04c7a 100644 --- a/packages/ui-patterns/src/CommandMenu/api/CommandMenu.tsx +++ b/packages/ui-patterns/src/CommandMenu/api/CommandMenu.tsx @@ -10,7 +10,7 @@ import { ErrorBoundary } from 'react-error-boundary' import { Button, cn, - Command_Shadcn_, + Command, Dialog, DialogContent, DialogDescription, @@ -50,11 +50,11 @@ function Breadcrumb({ className }: { className?: string }) { } const CommandWrapper = forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ children, className, ...props }, ref) => { return ( - {children} - + ) }) -CommandWrapper.displayName = Command_Shadcn_.displayName +CommandWrapper.displayName = Command.displayName function CommandError({ resetErrorBoundary }: { resetErrorBoundary: () => void }) { return ( diff --git a/packages/ui-patterns/src/CommandMenu/api/CommandInput.tsx b/packages/ui-patterns/src/CommandMenu/api/CommandMenuInput.tsx similarity index 92% rename from packages/ui-patterns/src/CommandMenu/api/CommandInput.tsx rename to packages/ui-patterns/src/CommandMenu/api/CommandMenuInput.tsx index c7fc8ceff0ac5..79036c8894de7 100644 --- a/packages/ui-patterns/src/CommandMenu/api/CommandInput.tsx +++ b/packages/ui-patterns/src/CommandMenu/api/CommandMenuInput.tsx @@ -3,7 +3,7 @@ import { useBreakpoint, useDebounce } from 'common' import { forwardRef, useCallback, useEffect, useRef, useState } from 'react' import type React from 'react' -import { cn, CommandInput_Shadcn_ } from 'ui' +import { cn, CommandInput } from 'ui' import { useQuery, useSetQuery } from './hooks/queryHooks' import { useCommandMenuTelemetryContext } from './hooks/useCommandMenuTelemetryContext' @@ -41,9 +41,9 @@ function useFocusInputOnWiderScreens(ref: React.ForwardedRef) return combinedRef } -const CommandInput = forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef +const CommandMenuInput = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { const inputRef = useFocusInputOnWiderScreens(ref) @@ -113,7 +113,7 @@ const CommandInput = forwardRef< return (
- , - React.ComponentPropsWithoutRef +const CommandMenuList = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { const commandSections = useCommands() const query = useQuery() @@ -26,30 +26,30 @@ const CommandList = forwardRef< } return ( - - No results found. + No results found. {commandSections.map((section) => { if (section.commands.every((command) => command.defaultHidden) && !query) return null return ( - + {section.commands .filter((command) => !command.defaultHidden || query) .map((command) => ( - + {command.name} - + ))} - + ) })} - + ) }) -CommandList.displayName = CommandList_Shadcn_.displayName +CommandMenuList.displayName = 'CommandMenuList' -export { CommandList } +export { CommandMenuList } diff --git a/packages/ui-patterns/src/CommandMenu/api/utils.ts b/packages/ui-patterns/src/CommandMenu/api/utils.ts index efbdbcb2d72ec..9c4486a4082ce 100644 --- a/packages/ui-patterns/src/CommandMenu/api/utils.ts +++ b/packages/ui-patterns/src/CommandMenu/api/utils.ts @@ -1,6 +1,6 @@ const escapeAttributeSelector = (str: string) => encodeURIComponent(str) export { escapeAttributeSelector } -export { generateCommandClassNames } from '../internal/Command' +export { generateCommandClassNames } from '../internal/CommandMenuItem' export { orderSectionFirst } from '../internal/state/commandsState' export { PageType } from '../internal/state/pagesState' diff --git a/packages/ui-patterns/src/CommandMenu/index.tsx b/packages/ui-patterns/src/CommandMenu/index.tsx index 98e4833b49384..8e9fbefa99b76 100644 --- a/packages/ui-patterns/src/CommandMenu/index.tsx +++ b/packages/ui-patterns/src/CommandMenu/index.tsx @@ -1,7 +1,7 @@ export * from './api/Badges' export { CommandHeader } from './api/CommandHeader' -export { CommandInput } from './api/CommandInput' -export { CommandList } from './api/CommandList' +export { CommandMenuInput } from './api/CommandMenuInput' +export { CommandMenuList } from './api/CommandMenuList' export { Breadcrumb, CommandMenu, diff --git a/packages/ui-patterns/src/CommandMenu/internal/CommandEmpty.tsx b/packages/ui-patterns/src/CommandMenu/internal/CommandMenuEmpty.tsx similarity index 94% rename from packages/ui-patterns/src/CommandMenu/internal/CommandEmpty.tsx rename to packages/ui-patterns/src/CommandMenu/internal/CommandMenuEmpty.tsx index 3933d1b0e2942..f0ce9f833e91d 100644 --- a/packages/ui-patterns/src/CommandMenu/internal/CommandEmpty.tsx +++ b/packages/ui-patterns/src/CommandMenu/internal/CommandMenuEmpty.tsx @@ -5,7 +5,7 @@ import { cn } from 'ui' import { useQuery } from '../api/hooks/queryHooks' -const CommandEmpty = ({ +const CommandMenuEmpty = ({ children, className, listRef, @@ -37,4 +37,4 @@ const CommandEmpty = ({ ) } -export { CommandEmpty } +export { CommandMenuEmpty } diff --git a/packages/ui-patterns/src/CommandMenu/internal/CommandGroup.tsx b/packages/ui-patterns/src/CommandMenu/internal/CommandMenuGroup.tsx similarity index 59% rename from packages/ui-patterns/src/CommandMenu/internal/CommandGroup.tsx rename to packages/ui-patterns/src/CommandMenu/internal/CommandMenuGroup.tsx index 425792c0a083e..4d4acf40d4966 100644 --- a/packages/ui-patterns/src/CommandMenu/internal/CommandGroup.tsx +++ b/packages/ui-patterns/src/CommandMenu/internal/CommandMenuGroup.tsx @@ -1,14 +1,14 @@ 'use client' import { forwardRef } from 'react' -import { cn, CommandGroup_Shadcn_ } from 'ui' +import { cn, CommandGroup } from 'ui' -const CommandGroup = forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef +const CommandMenuGroup = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { return ( - ) }) -CommandGroup.displayName = CommandGroup_Shadcn_.displayName +CommandMenuGroup.displayName = 'CommandMenuGroup' -export { CommandGroup } +export { CommandMenuGroup } diff --git a/packages/ui-patterns/src/CommandMenu/internal/Command.tsx b/packages/ui-patterns/src/CommandMenu/internal/CommandMenuItem.tsx similarity index 91% rename from packages/ui-patterns/src/CommandMenu/internal/Command.tsx rename to packages/ui-patterns/src/CommandMenu/internal/CommandMenuItem.tsx index 7f113dc6033be..1f44b2533ca10 100644 --- a/packages/ui-patterns/src/CommandMenu/internal/Command.tsx +++ b/packages/ui-patterns/src/CommandMenu/internal/CommandMenuItem.tsx @@ -1,7 +1,7 @@ 'use client' import { forwardRef, type PropsWithChildren } from 'react' -import { cn, CommandItem_Shadcn_ } from 'ui' +import { cn, CommandItem } from 'ui' import { useQuery } from '../api/hooks/queryHooks' import { useCommandMenuTelemetryContext } from '../api/hooks/useCommandMenuTelemetryContext' @@ -45,12 +45,12 @@ data-[disabled=true]:opacity-50 ` ) -interface CommandItemProps extends React.ComponentPropsWithoutRef { +interface CommandItemProps extends React.ComponentPropsWithoutRef { command: ICommand } -const CommandItem = forwardRef< - React.ElementRef, +const CommandMenuItem = forwardRef< + React.ElementRef, PropsWithChildren >(({ children, className, command: _command, ...props }, ref) => { const router = useCrossCompatRouter() @@ -95,7 +95,7 @@ const CommandItem = forwardRef< } return ( - {command.badge?.()}
- + ) }) -CommandItem.displayName = CommandItem_Shadcn_.displayName +CommandMenuItem.displayName = 'CommandMenuItem' -export { CommandItem, generateCommandClassNames } +export { CommandMenuItem, generateCommandClassNames } diff --git a/packages/ui-patterns/src/CommandMenu/prepackaged/DocsAi/DocsAiPage.tsx b/packages/ui-patterns/src/CommandMenu/prepackaged/DocsAi/DocsAiPage.tsx index d6cd0b738e198..f77a11be38c3f 100644 --- a/packages/ui-patterns/src/CommandMenu/prepackaged/DocsAi/DocsAiPage.tsx +++ b/packages/ui-patterns/src/CommandMenu/prepackaged/DocsAi/DocsAiPage.tsx @@ -5,21 +5,14 @@ import { User } from 'lucide-react' import { Fragment, useCallback, useEffect, useRef, useState } from 'react' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' -import { - AiIconAnimation, - Button, - cn, - CommandGroup_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, -} from 'ui' +import { AiIconAnimation, Button, cn, CommandGroup, CommandItem, CommandList } from 'ui' import { markdownComponents } from 'ui-patterns/Markdown' import { StatusIcon } from 'ui/src/components/StatusIcon' import { Breadcrumb, CommandHeader, - CommandInput, + CommandMenuInput, CommandWrapper, generateCommandClassNames, useHistoryKeys, @@ -154,7 +147,7 @@ function PromptInput({ }) return ( - }) { error. This display doesn't actually need the Command Menu, but it's needed for the empty state to work with the input, so this is a somewhat hacktastic way of making it not error. */} - +
void const query = useQuery() return ( - - + void {questions.map((question) => { const key = question.replace(/\s+/g, '_') return ( - { if (!query) { @@ -312,11 +305,11 @@ function EmptyState({ handleSubmit }: { handleSubmit: (message: string) => void > {question} - + ) })} - - + + ) } diff --git a/packages/ui-patterns/src/CommandMenu/prepackaged/DocsSearch/DocsSearchPage.tsx b/packages/ui-patterns/src/CommandMenu/prepackaged/DocsSearch/DocsSearchPage.tsx index d3b34ecc0d57e..ba7454ba5c4d9 100644 --- a/packages/ui-patterns/src/CommandMenu/prepackaged/DocsSearch/DocsSearchPage.tsx +++ b/packages/ui-patterns/src/CommandMenu/prepackaged/DocsSearch/DocsSearchPage.tsx @@ -8,13 +8,13 @@ import { } from 'common' import { Book, ChevronRight, Github, Hash, Loader2, MessageSquare, Search } from 'lucide-react' import { useCallback, useEffect, useRef } from 'react' -import { Button, cn, CommandGroup_Shadcn_, CommandItem_Shadcn_, CommandList_Shadcn_ } from 'ui' +import { Button, cn, CommandGroup, CommandItem, CommandList } from 'ui' import { StatusIcon } from 'ui/src/components/StatusIcon' import { Breadcrumb, CommandHeader, - CommandInput, + CommandMenuInput, CommandWrapper, escapeAttributeSelector, generateCommandClassNames, @@ -191,20 +191,20 @@ const DocsSearchPage = () => { - + - + {hasResults && ('results' in state ? state.results : state.staleResults).map((page, i) => { return ( - - { @@ -227,11 +227,11 @@ const DocsSearchPage = () => {
- + {page.sections.length > 0 && (
{page.sections.map((section, i) => ( - {
- + ))} )} - + ) })} {state.status === 'initial' && ( - + {questions.map((question) => { const key = question.replace(/\s+/g, '_') return ( - { @@ -287,10 +287,10 @@ const DocsSearchPage = () => { > {question} - + ) })} - + )} {state.status === 'loading' && state.staleResults.length === 0 && (
@@ -319,7 +319,7 @@ const DocsSearchPage = () => {
)} -
+ ) } diff --git a/packages/ui-patterns/src/FilterBar/CommandListItem.tsx b/packages/ui-patterns/src/FilterBar/CommandListItem.tsx index 35c1e758c4dfc..a8408a288605e 100644 --- a/packages/ui-patterns/src/FilterBar/CommandListItem.tsx +++ b/packages/ui-patterns/src/FilterBar/CommandListItem.tsx @@ -31,9 +31,9 @@ export function CommandListItem({ )} data-testid={`filter-menu-item-${item.value}`} > - + {includeIcon && item.icon} - {getActionItemLabel(item)} + {getActionItemLabel(item)} {item.operatorSymbol && } diff --git a/packages/ui-patterns/src/FilterBar/FilterBar.tsx b/packages/ui-patterns/src/FilterBar/FilterBar.tsx index 066572f1cdccf..4ffd929a0aa94 100644 --- a/packages/ui-patterns/src/FilterBar/FilterBar.tsx +++ b/packages/ui-patterns/src/FilterBar/FilterBar.tsx @@ -24,6 +24,14 @@ export type FilterBarProps = { supportsOperators?: boolean variant?: FilterBarVariant icon?: React.ReactNode + /** + * Name of the property to use when the user commits free text from the root input. Must match + * a `name` in `filterProperties`. When set, the dropdown shows a "Search : \"...\"" + * item as the first option while the user is typing. Selecting it (Enter) creates a filter + * `{ propertyName, operator: '=', value: typedText }`. If the property has no `=` operator, + * the first operator in its list is used instead. + */ + freeformDefaultProperty?: string onFilterChange: (filters: FilterGroupType) => void /** * Fires only on commit boundaries: menu item selected, operator/property/logical-operator @@ -135,6 +143,7 @@ export const FilterBar = forwardRef(function Fi supportsOperators = false, variant = 'default', icon, + freeformDefaultProperty, }, ref ) { @@ -152,6 +161,7 @@ export const FilterBar = forwardRef(function Fi supportsOperators={supportsOperators} variant={variant} icon={icon} + freeformDefaultProperty={freeformDefaultProperty} > diff --git a/packages/ui-patterns/src/FilterBar/FilterBarContext.tsx b/packages/ui-patterns/src/FilterBar/FilterBarContext.tsx index 1b539c9ed573e..cc0306fbce249 100644 --- a/packages/ui-patterns/src/FilterBar/FilterBarContext.tsx +++ b/packages/ui-patterns/src/FilterBar/FilterBarContext.tsx @@ -71,6 +71,7 @@ export type FilterBarContextValue = { variant: FilterBarVariant actions?: FilterBarAction[] icon?: React.ReactNode + freeformDefaultProperty?: string rootRef: React.RefObject } @@ -98,6 +99,7 @@ export type FilterBarRootProps = { supportsOperators?: boolean variant?: FilterBarVariant icon?: React.ReactNode + freeformDefaultProperty?: string } export type FilterBarVariant = 'default' | 'pill' @@ -120,6 +122,7 @@ export const FilterBarRoot = forwardRef(fun supportsOperators = false, variant = 'default', icon, + freeformDefaultProperty, }: FilterBarRootProps, ref: React.Ref ) { @@ -395,6 +398,7 @@ export const FilterBarRoot = forwardRef(fun variant, actions, icon, + freeformDefaultProperty, rootRef, } diff --git a/packages/ui-patterns/src/FilterBar/FilterGroup.tsx b/packages/ui-patterns/src/FilterBar/FilterGroup.tsx index 3fcbab301955e..34d0a143c4dda 100644 --- a/packages/ui-patterns/src/FilterBar/FilterGroup.tsx +++ b/packages/ui-patterns/src/FilterBar/FilterGroup.tsx @@ -25,6 +25,7 @@ export function FilterGroup({ group, path }: FilterGroupProps) { actions, variant, highlightedConditionPath, + freeformDefaultProperty, handleInputBlur, handleGroupFreeformFocus, handleGroupFreeformChange, @@ -86,6 +87,16 @@ export function FilterGroup({ group, path }: FilterGroupProps) { return pathsEqual(conditionPath, highlightedConditionPath) } + // Free-text search only applies to the root group's input — nested groups don't synthesize + // a "Search " item. + const resolvedFreeformDefaultProperty = useMemo( + () => + path.length === 0 && freeformDefaultProperty + ? filterProperties.find((p) => p.name === freeformDefaultProperty) + : undefined, + [path.length, freeformDefaultProperty, filterProperties] + ) + const items = useMemo( () => buildPropertyItems({ @@ -93,8 +104,17 @@ export function FilterGroup({ group, path }: FilterGroupProps) { inputValue: (isActive ? freeformText : localFreeformValue) || '', actions, supportsOperators, + freeformDefaultProperty: resolvedFreeformDefaultProperty, }), - [filterProperties, isActive, freeformText, localFreeformValue, actions, supportsOperators] + [ + filterProperties, + isActive, + freeformText, + localFreeformValue, + actions, + supportsOperators, + resolvedFreeformDefaultProperty, + ] ) const emptyPlaceholder = useMemo( @@ -220,7 +240,7 @@ export function FilterGroup({ group, path }: FilterGroupProps) { )} e.preventDefault()} diff --git a/packages/ui-patterns/src/FilterBar/menuItems.ts b/packages/ui-patterns/src/FilterBar/menuItems.ts index bff559cfa56a7..bb58c84f5fe43 100644 --- a/packages/ui-patterns/src/FilterBar/menuItems.ts +++ b/packages/ui-patterns/src/FilterBar/menuItems.ts @@ -70,10 +70,23 @@ export function buildPropertyItems(params: { inputValue: string supportsOperators?: boolean actions?: FilterBarAction[] + freeformDefaultProperty?: FilterProperty }): MenuItem[] { - const { filterProperties, inputValue, supportsOperators, actions } = params + const { filterProperties, inputValue, supportsOperators, actions, freeformDefaultProperty } = + params const items: MenuItem[] = [] + const trimmedInput = inputValue.trim() + if (freeformDefaultProperty && trimmedInput.length > 0) { + items.push({ + value: '__freeform_search__', + label: `Search ${freeformDefaultProperty.label.toLowerCase()}: "${trimmedInput}"`, + isFreeformSearch: true, + freeformPropertyName: freeformDefaultProperty.name, + freeformValue: trimmedInput, + }) + } + items.push( ...filterProperties .filter((prop) => prop.label.toLowerCase().includes(inputValue.toLowerCase())) @@ -84,7 +97,6 @@ export function buildPropertyItems(params: { items.push({ value: 'group', label: 'New Group' }) } - const trimmedInput = inputValue.trim() if (actions && trimmedInput.length > 0) { actions.forEach((action) => { items.push({ diff --git a/packages/ui-patterns/src/FilterBar/types.ts b/packages/ui-patterns/src/FilterBar/types.ts index eac3b5ddaf207..19056763e696a 100644 --- a/packages/ui-patterns/src/FilterBar/types.ts +++ b/packages/ui-patterns/src/FilterBar/types.ts @@ -99,6 +99,9 @@ export type MenuItem = { operatorSymbol?: string isDefaultOperator?: boolean defaultValue?: string + isFreeformSearch?: boolean + freeformPropertyName?: string + freeformValue?: string } export type GroupedMenuItem = { diff --git a/packages/ui-patterns/src/FilterBar/useCommandHandling.ts b/packages/ui-patterns/src/FilterBar/useCommandHandling.ts index 4e31aa47aae8f..61119c48a9ada 100644 --- a/packages/ui-patterns/src/FilterBar/useCommandHandling.ts +++ b/packages/ui-patterns/src/FilterBar/useCommandHandling.ts @@ -146,6 +146,35 @@ export function useCommandHandling({ return } + if (item.isFreeformSearch && item.freeformPropertyName) { + if (!activeInput || activeInput.type !== 'group') return + const property = filterProperties.find((p) => p.name === item.freeformPropertyName) + if (!property) return + + const currentPath = activeInput.path + const group = findGroupByPath(activeFilters, currentPath) + if (!group) return + + const operators = property.operators ?? ['='] + const defaultOperator = + operators.find((op) => (isFilterOperatorObject(op) ? op.value : op) === '=') ?? + operators[0] + const operatorValue = isFilterOperatorObject(defaultOperator) + ? defaultOperator.value + : defaultOperator + + let updatedFilters = addFilterToGroup(activeFilters, currentPath, property) + const newPath = [...currentPath, group.conditions.length] + updatedFilters = updateNestedOperator(updatedFilters, newPath, operatorValue) + updatedFilters = updateNestedValue(updatedFilters, newPath, item.freeformValue ?? '') + commitFilters(updatedFilters) + onFreeformTextChange('') + setTimeout(() => { + setActiveInput({ type: 'group', path: currentPath }) + }, 0) + return + } + if (item.value === 'group') { handleGroupCommand() return @@ -174,6 +203,8 @@ export function useCommandHandling({ activeInput, activeFilters, freeformText, + filterProperties, + onFreeformTextChange, setActiveInput, handleGroupCommand, handleValueCommand, diff --git a/packages/ui-patterns/src/FilterBar/utils.test.ts b/packages/ui-patterns/src/FilterBar/utils.test.ts index e67524d7b1f8d..cafa8ac2014ae 100644 --- a/packages/ui-patterns/src/FilterBar/utils.test.ts +++ b/packages/ui-patterns/src/FilterBar/utils.test.ts @@ -17,7 +17,6 @@ import { isSyncOptionsFunction, removeFromGroup, resolvePropertyChange, - truncateText, updateNestedLogicalOperator, updateNestedOperator, updateNestedPropertyName, @@ -477,24 +476,6 @@ describe('FilterBar Utils', () => { }) }) - describe('truncateText', () => { - it('returns text unchanged if under max length', () => { - expect(truncateText('hello', 10)).toBe('hello') - }) - - it('returns text unchanged if exactly max length', () => { - expect(truncateText('hello', 5)).toBe('hello') - }) - - it('truncates text and adds ellipsis if over max length', () => { - expect(truncateText('hello world', 5)).toBe('hello...') - }) - - it('handles empty string', () => { - expect(truncateText('', 10)).toBe('') - }) - }) - describe('getActionItemLabel', () => { it('returns original label for non-action items', () => { const item: MenuItem = { value: 'test', label: 'Test Label' } @@ -516,14 +497,16 @@ describe('FilterBar Utils', () => { expect(getActionItemLabel(item)).toBe('Ask AI: "Find users"') }) - it('truncates long input values at 30 characters', () => { + it('returns full input value (no truncation — CSS handles overflow)', () => { const item: MenuItem = { value: 'ai', label: 'Filter by AI', isAction: true, actionInputValue: 'Find all users who registered in the last 30 days', } - expect(getActionItemLabel(item)).toBe('Ask AI: "Find all users who registered ..."') + expect(getActionItemLabel(item)).toBe( + 'Ask AI: "Find all users who registered in the last 30 days"' + ) }) }) diff --git a/packages/ui-patterns/src/FilterBar/utils.ts b/packages/ui-patterns/src/FilterBar/utils.ts index 76cf3551a7cc3..c89a39e846b8a 100644 --- a/packages/ui-patterns/src/FilterBar/utils.ts +++ b/packages/ui-patterns/src/FilterBar/utils.ts @@ -326,14 +326,9 @@ export function groupMenuItemsByOperator(items: MenuItem[]): MenuItemGroup[] { })) } -export function truncateText(text: string, maxLength: number): string { - if (text.length <= maxLength) return text - return text.slice(0, maxLength) + '...' -} - export function getActionItemLabel(item: MenuItem): string { if (item.isAction && item.actionInputValue) { - return `Ask AI: "${truncateText(item.actionInputValue, 30)}"` + return `Ask AI: "${item.actionInputValue}"` } return item.label } diff --git a/packages/ui-patterns/src/McpUrlBuilder/assets/copilot-icon-dark.svg b/packages/ui-patterns/src/McpUrlBuilder/assets/copilot-icon-dark.svg new file mode 100755 index 0000000000000..0411bb1685898 --- /dev/null +++ b/packages/ui-patterns/src/McpUrlBuilder/assets/copilot-icon-dark.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/ui-patterns/src/McpUrlBuilder/assets/copilot-icon.svg b/packages/ui-patterns/src/McpUrlBuilder/assets/copilot-icon.svg new file mode 100755 index 0000000000000..300c0caa0c900 --- /dev/null +++ b/packages/ui-patterns/src/McpUrlBuilder/assets/copilot-icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/ui-patterns/src/McpUrlBuilder/components/ClientSelectDropdown.tsx b/packages/ui-patterns/src/McpUrlBuilder/components/ClientSelectDropdown.tsx index 288d14726f10d..14fde11eb2004 100644 --- a/packages/ui-patterns/src/McpUrlBuilder/components/ClientSelectDropdown.tsx +++ b/packages/ui-patterns/src/McpUrlBuilder/components/ClientSelectDropdown.tsx @@ -5,12 +5,12 @@ import { useState } from 'react' import { Button, cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, Popover, PopoverContent, PopoverTrigger, @@ -50,7 +50,7 @@ export const ClientSelectDropdown = ({ function renderClient(client: McpClient) { return ( - onSelectClient(client.key)} @@ -71,7 +71,7 @@ export const ClientSelectDropdown = ({ size={15} className={cn('ml-auto', client.key === selectedClient.key ? 'opacity-100' : 'opacity-0')} /> - + ) } @@ -109,21 +109,21 @@ export const ClientSelectDropdown = ({ - - - - No results found. + + + + No results found. {groups ? ( groups.map((group) => ( - + {group.clients.map(renderClient)} - + )) ) : ( - {clients.map(renderClient)} + {clients.map(renderClient)} )} - - + + ) diff --git a/packages/ui-patterns/src/McpUrlBuilder/constants.tsx b/packages/ui-patterns/src/McpUrlBuilder/constants.tsx index eeecf578c7426..6b73ce26f257e 100644 --- a/packages/ui-patterns/src/McpUrlBuilder/constants.tsx +++ b/packages/ui-patterns/src/McpUrlBuilder/constants.tsx @@ -6,6 +6,7 @@ import type { AntigravityMcpConfig, ClaudeCodeMcpConfig, CodexMcpConfig, + CopilotMcpConfig, FactoryMcpConfig, GeminiMcpConfig, GooseMcpConfig, @@ -309,6 +310,60 @@ export const MCP_CLIENTS: McpClient[] = [ ) }, }, + { + key: 'copilot-cli', + label: 'GitHub Copilot', + icon: 'copilot', + hasDistinctDarkIcon: true, + configFile: '~/.copilot/mcp-config.json', + externalDocsUrl: + 'https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers', + transformConfig: (config): CopilotMcpConfig => { + return { + mcpServers: { + supabase: { + type: 'http', + url: config.mcpServers.supabase.url, + }, + }, + } + }, + primaryInstructions: (_config, onCopy) => { + const config = _config as CopilotMcpConfig + const command = `copilot mcp add --transport http supabase "${config.mcpServers.supabase.url}"` + return ( +
+

+ Add the MCP server to your GitHub Copilot config using the command line: +

+ onCopy('command')} + /> +
+ ) + }, + alternateInstructions: (_config, onCopy) => ( +
+

+ After configuring the MCP server, authenticate by running: +

+ onCopy('command')} + /> +

+ Follow the on-screen instructions to complete the authentication flow. +

+
+ ), + }, { key: 'antigravity', label: 'Antigravity', @@ -576,7 +631,7 @@ export const MCP_CLIENTS: McpClient[] = [ export const MCP_CLIENT_GROUPS = [ { heading: 'AI Agent CLI', - keys: ['claude-code', 'codex', 'gemini-cli', 'opencode', 'factory'], + keys: ['claude-code', 'codex', 'gemini-cli', 'copilot-cli', 'opencode', 'factory'], }, { heading: 'Web Clients', diff --git a/packages/ui-patterns/src/McpUrlBuilder/types.ts b/packages/ui-patterns/src/McpUrlBuilder/types.ts index 8d7cb59ed4425..3b19577127071 100644 --- a/packages/ui-patterns/src/McpUrlBuilder/types.ts +++ b/packages/ui-patterns/src/McpUrlBuilder/types.ts @@ -166,10 +166,20 @@ export interface AntigravityMcpConfig { } } +export interface CopilotMcpConfig extends McpClientBaseConfig { + mcpServers: { + supabase: { + type: 'http' + url: string + } + } +} + // Union of all possible config types export type McpClientConfig = | AntigravityMcpConfig | ClaudeCodeMcpConfig + | CopilotMcpConfig | ClaudeDesktopMcpConfig | CodexMcpConfig | CursorMcpConfig diff --git a/packages/ui-patterns/src/McpUrlBuilder/utils/mcpIconAssets.ts b/packages/ui-patterns/src/McpUrlBuilder/utils/mcpIconAssets.ts index 9641d13370bba..59bbd0232dc85 100644 --- a/packages/ui-patterns/src/McpUrlBuilder/utils/mcpIconAssets.ts +++ b/packages/ui-patterns/src/McpUrlBuilder/utils/mcpIconAssets.ts @@ -2,6 +2,8 @@ import antigravityIcon from '../assets/antigravity-icon.svg' import claudeIcon from '../assets/claude-icon.svg' +import copilotDarkIcon from '../assets/copilot-icon-dark.svg' +import copilotIcon from '../assets/copilot-icon.svg' import cursorIcon from '../assets/cursor-icon.svg' import factoryDarkIcon from '../assets/factory-icon-dark.svg' import factoryIcon from '../assets/factory-icon.svg' @@ -27,6 +29,7 @@ type McpClientIconAsset = { const MCP_CLIENT_ICON_ASSETS = { antigravity: { light: antigravityIcon, dark: antigravityIcon }, claude: { light: claudeIcon, dark: claudeIcon }, + copilot: { light: copilotIcon, dark: copilotDarkIcon }, cursor: { light: cursorIcon, dark: cursorIcon }, factory: { light: factoryIcon, dark: factoryDarkIcon }, 'gemini-cli': { light: geminiCliIcon, dark: geminiCliIcon }, diff --git a/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.tsx b/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.tsx index d683197688b06..3d29ab996f133 100644 --- a/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.tsx +++ b/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.tsx @@ -4,7 +4,7 @@ import { useRouter } from 'next/router' import { useEffect } from 'react' import { ErrorBoundary } from 'react-error-boundary' import { useWindowSize } from 'react-use' -import { CommandEmpty_Shadcn_, Sheet, SheetContent } from 'ui' +import { CommandEmpty, Sheet, SheetContent } from 'ui' import { cn } from 'ui/src/lib/utils' const MobileSheetNav: React.FC<{ @@ -53,7 +53,7 @@ const MobileSheetNav: React.FC<{ className )} > - }>{children} + }>{children} ) diff --git a/packages/ui-patterns/src/multi-select/multi-select.tsx b/packages/ui-patterns/src/multi-select/multi-select.tsx index 4311a2aee1f45..837e273e2b9d7 100644 --- a/packages/ui-patterns/src/multi-select/multi-select.tsx +++ b/packages/ui-patterns/src/multi-select/multi-select.tsx @@ -8,11 +8,11 @@ import React, { isValidElement, ReactElement, useEffect } from 'react' import { Badge, cn, - Command_Shadcn_ as Command, - CommandEmpty_Shadcn_ as CommandEmpty, - CommandInput_Shadcn_ as CommandInput, - CommandItem_Shadcn_ as CommandItem, - CommandList_Shadcn_ as CommandList, + Command as Command, + CommandEmpty as CommandEmpty, + CommandInput as CommandInput, + CommandItem as CommandItem, + CommandList as CommandList, Popover, PopoverAnchor, PopoverContent, diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index c39bd29b88d38..0ad715c9a9982 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -63,17 +63,7 @@ export * from './src/components/shadcn/ui/toggle-group' export * from './src/components/shadcn/ui/toggle' export * from './src/components/shadcn/ui/card' -export { - Command as Command_Shadcn_, - CommandDialog as CommandDialog, - CommandInput as CommandInput_Shadcn_, - CommandList as CommandList_Shadcn_, - CommandEmpty as CommandEmpty_Shadcn_, - CommandGroup as CommandGroup_Shadcn_, - CommandItem as CommandItem_Shadcn_, - CommandShortcut as CommandShortcut_Shadcn_, - CommandSeparator as CommandSeparator_Shadcn_, -} from './src/components/shadcn/ui/command' +export * from './src/components/shadcn/ui/command' export * from './src/components/shadcn/ui/context-menu'