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('')}
/>
-
-
+
+
{emptyMessage}
-
-
+
+
{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 && (
-
-
-
+
+
+
{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 (
-
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 (
-
-
-
- {/* 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 (
+
+
+
+ {/* 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) => (
-
- ))}
- />
-
- ))}
-
+
+
+
+ }
+ >
+ {selectedSchema?.name ?? 'Select source...'}
+
+
+
+
+
+
+ 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'