chore: upgrade to react-19 (#45886)

- Most changes are related to either types or `useRef` usages (it now
requires an initial value).
- also updated `vaul` to its latest version and haven't noticed any
change ([design-system
demo](https://design-system-git-react-19-supabase.vercel.app/design-system/docs/components/drawer))

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Upgraded workspace to React 19.

* **Bug Fixes**
* Improved null-safety and ref handling across editors, UI components,
shortcuts, and markdown/image rendering to reduce runtime errors.
* Safer event/timeout/interval cleanup and more robust command/context
handling.

* **Chores**
  * Bumped vaul dependency versions.

* **Documentation**
* Type and TypeScript accuracy improvements for clearer developer
feedback.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45886)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Gildas Garcia authored and GitHub committed 2026-05-15 16:04:41 +02:00
1 parent bffe49bb1d
commit 86a3f8b03d
59 files changed
+1543 -1537

No files matched your search

@@ -30,11 +30,17 @@ export function ComponentExample({
src: _,
...props
}: ComponentExampleProps) {
const [Example, Code, ...Children] = React.Children.toArray(children) as React.ReactElement[]
const [Example, Code, ...Children] = React.Children.toArray(children) as React.ReactElement<{
children: React.ReactNode
'data-rehype-pretty-code-fragment'?: unknown
}>[]
const codeString = React.useMemo(() => {
if (typeof Code?.props['data-rehype-pretty-code-fragment'] !== 'undefined') {
const [, Button] = React.Children.toArray(Code.props.children) as React.ReactElement[]
const [, Button] = React.Children.toArray(Code.props.children) as React.ReactElement<{
value?: string
__rawString__?: string
}>[]
return Button?.props?.value || Button?.props?.__rawString__ || null
}
}, [Code])
@@ -58,7 +64,7 @@ export function ComponentExample({
</TabsTrigger>
</TabsList>
{
extractedClassNames ? (
extractedClassNames && codeString ? (
<CopyWithClassNames
value={codeString}
classNames={extractedClassNames}
@@ -9,21 +9,6 @@ import { Index } from '../__registry__'
import { styles } from '../registry/styles'
import { UnistNode, UnistTree } from '@/types/unist'
// ComponentType
function inspectComponentProps<T>(component: React.ComponentType<T>): void {
// Assert the component's props type
const defaultProps = (component as React.ComponentType<any>).defaultProps || {}
// console.log('Component props:')
// console.log('----------------')
for (const propName in defaultProps) {
const propType = typeof defaultProps[propName]
console.log(`${propName}: ${propType}`)
}
}
export function rehypeComponent() {
return async (tree: UnistTree) => {
visit(tree, (node: UnistNode) => {
+4 -2
View File
@@ -1,13 +1,14 @@
import matter from 'gray-matter'
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import { type ComponentProps } from 'react'
import CliGlobalFlagsHandler from '~/components/reference/enrichments/cli/CliGlobalFlagsHandler'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import { components } from '~/features/docs/MdxBase.shared'
import { RefSubLayout } from '~/features/docs/Reference.ui'
import { cache_fullProcess_withDevCacheBust } from '~/features/helpers.fs'
import { REF_DOCS_DIRECTORY } from '~/lib/docs'
import matter from 'gray-matter'
import { type ComponentProps } from 'react'
import { CodeBlock } from '../ui/CodeBlock/CodeBlock'
async function getRefMarkdownInternal(relPath: string) {
@@ -52,6 +53,7 @@ function MDXRemoteRefs({ source }: MDXRemoteRefsProps) {
CliGlobalFlagsHandler,
}
// @ts-expect-error: Gildas - This is because RefSubLayout is a namespace containing multiple components. It works but the MDXClient types are stricter
return <MDXRemoteBase source={source} components={refComponents} />
}
@@ -123,8 +123,8 @@ export function ReferenceNavigationScrollHandler({
children,
...rest
}: PropsWithChildren & HTMLAttributes<HTMLDivElement>) {
const parentRef = useRef<HTMLElement>()
const ref = useRef<HTMLDivElement>(null)
const parentRef = useRef<HTMLElement | null>(null)
const ref = useRef<HTMLDivElement | null>(null)
const initialScrollHappened = useContext(ReferenceContentInitiallyScrolledContext)
useEffect(() => {
@@ -12,7 +12,7 @@ import {
} from '@/state/table-editor-table'
type ShortcutsProps = {
gridRef: RefObject<DataGridHandle>
gridRef: RefObject<DataGridHandle | null>
rows: SupaRow[]
}
@@ -42,7 +42,7 @@ export const TemplateEditor = ({ template }: TemplateEditorProps) => {
PermissionAction.UPDATE,
'custom_config_gotrue'
)
const editorRef = useRef<editor.IStandaloneCodeEditor>()
const editorRef = useRef<editor.IStandaloneCodeEditor>(null)
// [Joshen] Error state is handled in the parent
const { data: authConfig, isSuccess } = useAuthConfigQuery({ projectRef })
@@ -48,7 +48,7 @@ export const RLSCodeEditor = ({
editorRef,
monacoRef,
}: RLSCodeEditorProps) => {
const hasValue = useRef<any>()
const hasValue = useRef<editor.IContextKey<boolean>>(null)
const monaco = useMonaco()
const placeholderId = `monaco-placeholder-${id}`
@@ -104,7 +104,9 @@ export const RLSCodeEditor = ({
}
const onChangeContent: OnChange = (value) => {
hasValue.current.set((value ?? '').length > 0)
if (hasValue.current) {
hasValue.current.set((value ?? '').length > 0)
}
const placeholderEl = document.getElementById(placeholderId) as HTMLElement | null
if (placeholderEl) {
@@ -17,8 +17,8 @@ import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
interface UseAuthUsersShortcutsParams {
gridRef: RefObject<DataGridHandle>
searchInputRef: RefObject<HTMLInputElement>
gridRef: RefObject<DataGridHandle | null>
searchInputRef: RefObject<HTMLInputElement | null>
users: User[]
selectedUsers: Set<unknown>
setSelectedUsers: Dispatch<SetStateAction<Set<unknown>>>
@@ -70,7 +70,7 @@ export const PlanUpdateSidePanel = () => {
selectedOrganization?.managed_by === MANAGED_BY.STRIPE_PROJECTS
const { mutate: sendEvent } = useSendEventMutation()
const originalPlanRef = useRef<string>()
const originalPlanRef = useRef<string>(undefined)
const [showExitSurvey, setShowExitSurvey] = useState(false)
const [showUpgradeSurvey, setShowUpgradeSurvey] = useState(false)
@@ -69,7 +69,7 @@ export const billingMetricUnit = (pricingMetric: PricingMetric) => {
}
}
export const generateUpgradeReasons = (originalPlan?: string, upgradedPlan?: string) => {
export const generateUpgradeReasons = (originalPlan?: string | null, upgradedPlan?: string) => {
const reasons = [
'Current plan limits are not enough for me',
'I want better customer support from Supabase',
@@ -46,7 +46,7 @@ const InlineWidget = ({
const lineNumber = beforeLineNumber ?? afterLineNumber
const key = `${id}-${lineNumber.toString()}`
const containerElement = useMemo(() => document.createElement('div'), [])
const zoneIdRef = useRef<string>()
const zoneIdRef = useRef<string>(null)
const viewZoneRef = useRef<{
top: number
height: number
@@ -7,7 +7,7 @@ import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
interface UseFilesBucketsShortcutsParams {
searchInputRef: RefObject<HTMLInputElement>
searchInputRef: RefObject<HTMLInputElement | null>
setFilterString: Dispatch<SetStateAction<string>>
sortBucket: STORAGE_BUCKET_SORT
setSortBucket: (value: STORAGE_BUCKET_SORT) => void
@@ -5,8 +5,8 @@ import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
interface UseFileExplorerHeaderShortcutsParams {
uploadButtonRef: RefObject<HTMLInputElement>
searchInputRef: RefObject<HTMLInputElement>
uploadButtonRef: RefObject<HTMLInputElement | null>
searchInputRef: RefObject<HTMLInputElement | null>
canUpdateStorage: boolean
hasBreadcrumbs: boolean
isSearching: boolean
@@ -138,7 +138,7 @@ export function useAttachmentUpload() {
}
interface AttachmentUploadDisplayProps {
uploadButtonRef: RefObject<HTMLInputElement>
uploadButtonRef: RefObject<HTMLInputElement | null>
isFull: boolean
uploadedDataUrls: string[]
addFile: () => void
@@ -34,7 +34,7 @@ vi.mock('next/head', async () => {
(child) => React.isValidElement(child) && child.type === 'title'
)
if (!React.isValidElement(titleElement)) return
if (!React.isValidElement<{ children: ReactNode }>(titleElement)) return
const titleText = React.Children.toArray(titleElement.props.children).join('')
document.title = titleText
@@ -19,7 +19,7 @@ export interface ConnectingStateProps {
const ConnectingState = ({ project }: ConnectingStateProps) => {
const { ref } = useParams()
const checkProjectConnectionIntervalRef = useRef<number>()
const checkProjectConnectionIntervalRef = useRef<number>(null)
const { setProjectPostgrestStatus } = useSetProjectPostgrestStatus()
const { invalidateProjectDetailsQuery } = useInvalidateProjectDetailsQuery()
@@ -31,14 +31,18 @@ const ConnectingState = ({ project }: ConnectingStateProps) => {
// pingPostgrest timeouts in 2s, wait for another 2s before checking again
checkProjectConnectionIntervalRef.current = window.setInterval(testProjectConnection, 4000)
return () => {
clearInterval(checkProjectConnectionIntervalRef.current)
if (checkProjectConnectionIntervalRef.current) {
clearInterval(checkProjectConnectionIntervalRef.current)
}
}
}, [project])
const testProjectConnection = async () => {
const result = await pingPostgrest(project.ref)
if (result) {
clearInterval(checkProjectConnectionIntervalRef.current)
if (checkProjectConnectionIntervalRef.current) {
clearInterval(checkProjectConnectionIntervalRef.current)
}
setProjectPostgrestStatus(project.ref, 'ONLINE')
await invalidateProjectDetailsQuery(project.ref)
}
@@ -56,7 +56,7 @@ vi.mock('next/head', async () => {
(child) => React.isValidElement(child) && child.type === 'title'
)
if (!React.isValidElement(titleElement)) return
if (!React.isValidElement<{ children: ReactNode }>(titleElement)) return
const titleText = React.Children.toArray(titleElement.props.children).join('')
document.title = titleText
@@ -12,7 +12,7 @@ import type { AssistantModelId } from '@/lib/ai/model.utils'
export interface FormProps {
/* The ref for the textarea, optional. Exposed for the CommandsPopover to attach events. */
textAreaRef?: React.RefObject<HTMLTextAreaElement>
textAreaRef?: React.RefObject<HTMLTextAreaElement | null>
/* The loading state of the form */
loading: boolean
/* The disabled state of the form */
@@ -1,7 +1,7 @@
import { untrustedSql } from '@supabase/pg-meta'
import dynamic from 'next/dynamic'
import Link from 'next/link'
import {
import React, {
isValidElement,
memo,
ReactNode,
@@ -135,8 +135,8 @@ const baseMarkdownComponents = {
h3: Heading3,
code: InlineCode,
a: Hyperlink,
img: ({ src }: JSX.IntrinsicElements['img']) => (
<span className="text-foreground-light font-mono">[Image: {src}]</span>
img: ({ src }: React.JSX.IntrinsicElements['img']) => (
<span className="text-foreground-light font-mono">[Image: {src?.toString()}]</span>
),
}
@@ -169,7 +169,7 @@ export function MessageMarkdown({
() => ({
...markdownComponents,
...baseMarkdownComponents,
pre: (props: JSX.IntrinsicElements['pre']) => (
pre: (props: React.JSX.IntrinsicElements['pre']) => (
<MarkdownPre id={id} isLoading={isLoading} readOnly={readOnly}>
{props.children}
</MarkdownPre>
@@ -206,8 +206,11 @@ export const MarkdownPre = ({
})
const childArray = Array.isArray(children) ? children : [children]
const codeElement = childArray.find((child): child is ReactElement => isValidElement(child))
const codeProps = codeElement?.props || {}
const codeElement = childArray.find(
(child): child is ReactElement<{ className?: string; children: ReactNode }> =>
isValidElement<{ className?: string; children: ReactNode }>(child)
)
const codeProps = codeElement?.props || ({} as { className?: string; children: ReactNode })
const language = codeProps.className?.replace('language-', '') || 'sql'
const codeChildren = codeProps.children
const rawContent = Array.isArray(codeChildren)
@@ -8,7 +8,7 @@ export function useAutoScroll({ enabled = true }: UseAutoScrollProps = {}) {
const [container, setContainer] = useState<HTMLDivElement | null>(null)
const [isSticky, setIsSticky] = useState(true)
const isStickyRef = useRef(true)
const lastScrollHeightRef = useRef<number>()
const lastScrollHeightRef = useRef<number>(null)
const ref = useCallback((element: HTMLDivElement | null) => {
if (element) {
@@ -36,7 +36,7 @@ export function useAutoScroll({ enabled = true }: UseAutoScrollProps = {}) {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
if (
lastScrollHeightRef.current !== undefined &&
lastScrollHeightRef.current != null &&
container.scrollHeight !== lastScrollHeightRef.current
) {
lastScrollHeightRef.current = container.scrollHeight
@@ -85,7 +85,11 @@ const LazyChartWrapper = ({ children }: PropsWithChildren) => {
}
}, [])
return <div ref={ref}>{React.cloneElement(children as React.ReactElement, { isVisible })}</div>
return (
<div ref={ref}>
{React.cloneElement(children as React.ReactElement<{ isVisible: boolean }>, { isVisible })}
</div>
)
}
/**
@@ -39,7 +39,7 @@ interface CodeEditorProps {
explainCode: CodeEditorActions
closeAssistant: CodeEditorActions
}>
editorRef?: MutableRefObject<editor.IStandaloneCodeEditor | undefined>
editorRef?: MutableRefObject<editor.IStandaloneCodeEditor | null>
onInputChange?: (value?: string) => void
}
@@ -62,10 +62,10 @@ export const CodeEditor = ({
const monaco = useMonaco()
const { data: project } = useSelectedProjectQuery()
const hasValue = useRef<any>()
const ref = useRef<editor.IStandaloneCodeEditor>()
const hasValue = useRef<editor.IContextKey<boolean>>(null)
const ref = useRef<editor.IStandaloneCodeEditor>(null)
const editorRef = editorRefProps || ref
const monacoRef = useRef<Monaco>()
const monacoRef = useRef<Monaco>(null)
const { runQuery, placeholderFill, formatDocument, explainCode, closeAssistant } = {
...DEFAULT_ACTIONS,
@@ -175,7 +175,9 @@ export const CodeEditor = ({
}
const onChangeContent: OnChange = (value) => {
hasValue.current.set((value ?? '').length > 0)
if (hasValue.current) {
hasValue.current.set((value ?? '').length > 0)
}
setShowPlaceholder(!value)
onInputChange(value)
}
@@ -191,7 +193,9 @@ export const CodeEditor = ({
monacoRef.current !== undefined
) {
const editor = editorRef.current
if (editor == null) return
const monaco = monacoRef.current
if (monaco == null) return
editor.addCommand(
monaco.KeyCode.Tab,
@@ -14,7 +14,7 @@ export function DebouncedComponent({
children,
}: DebouncedComponentProps) {
const [shouldRender, setShouldRender] = useState(false)
const timeoutRef = useRef<NodeJS.Timeout>()
const timeoutRef = useRef<NodeJS.Timeout>(null)
const prevValueRef = useRef(value)
const isInitialMount = useRef(true)
@@ -123,7 +123,7 @@ export const EditorPanel = () => {
const [showResults, setShowResults] = useState(true)
const [isSaveDialogOpen, setIsSaveDialogOpen] = useState(false)
const [saveStatus, setSaveStatus] = useState<'idle' | 'success' | 'error'>('idle')
const saveStatusTimerRef = useRef<ReturnType<typeof setTimeout>>()
const saveStatusTimerRef = useRef<ReturnType<typeof setTimeout>>(null)
const originalSnippetRef = useRef<{ sql: string; name: string } | null>(null)
const refocusEditor = () => {
@@ -145,7 +145,9 @@ export const EditorPanel = () => {
const showSaveSuccess = () => {
setSaveStatus('success')
clearTimeout(saveStatusTimerRef.current)
if (saveStatusTimerRef.current) {
clearTimeout(saveStatusTimerRef.current)
}
saveStatusTimerRef.current = setTimeout(() => setSaveStatus('idle'), 2000)
}
const [isTemplatesOpen, setIsTemplatesOpen] = useState(false)
+1 -1
View File
@@ -28,7 +28,7 @@ const SqlEditor = ({
onInputChange = noop,
}: SqlEditorProps) => {
const monaco = useMonaco()
const editorRef = useRef<any>()
const editorRef = useRef<any>(null)
useEffect(() => {
if (monaco) {
+2 -2
View File
@@ -1,7 +1,7 @@
import { useEffect, useRef } from 'react'
export function useChanged<T>(value: T): boolean {
const prev = useRef<T>()
const prev = useRef<T>(null)
const changed = prev.current !== value
useEffect(() => {
@@ -12,7 +12,7 @@ export function useChanged<T>(value: T): boolean {
}
export function useChangedSync<T>(value: T): boolean {
const prev = useRef<T>()
const prev = useRef<T>(null)
const changed = prev.current !== value
prev.current = value
@@ -70,7 +70,7 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
else return TEMPLATES.filter((x) => x.label !== 'Metadata IP')
}, [logsShowMetadataIpTemplate])
const editorRef = useRef<editor.IStandaloneCodeEditor>()
const editorRef = useRef<editor.IStandaloneCodeEditor>(null)
const [editorId] = useState<string>(uuidv4())
const { search, setSearch, timestampStart, timestampEnd, setTimeRange } = useLogsUrlState()
const defaultHelper = useMemo(() => getDefaultHelper(EXPLORER_DATEPICKER_HELPERS), [])
@@ -27,7 +27,7 @@ const JWTKeysLegacyPage: NextPageWithLayout = () => {
const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus
const jwtSecretUpdateError = data?.jwtSecretUpdateError
const previousJwtSecretUpdateStatus = useRef<JwtSecretUpdateStatus>()
const previousJwtSecretUpdateStatus = useRef<JwtSecretUpdateStatus>(undefined)
const { Failed, Updated, Updating } = JwtSecretUpdateStatus
const jwtSecretUpdateErrorMessage =
JWT_SECRET_UPDATE_ERROR_MESSAGES[jwtSecretUpdateError as JwtSecretUpdateError]
+1 -1
View File
@@ -61,7 +61,7 @@
"ui": "workspace:*",
"ui-patterns": "workspace:*",
"unist-util-visit": "^5.0.0",
"vaul": "^0.9.6",
"vaul": "^1.1.2",
"y-monaco": "^0.1.6",
"y-protocols": "^1.0.7",
"yjs": "^13.6.29",
@@ -14,7 +14,7 @@ import { SurveyChapterSection } from './components/SurveyChapterSection'
import { SurveySectionBreak } from './components/SurveySectionBreak'
interface FloatingTocProps {
tocRef: RefObject<HTMLDivElement>
tocRef: RefObject<HTMLDivElement | null>
isTocOpen: boolean
setIsTocOpen: (open: boolean) => void
activeChapter: number
+3 -3
View File
@@ -1,11 +1,11 @@
import createGlobe from 'cobe'
import { useCallback, useEffect, useRef } from 'react'
import { useTheme } from 'next-themes'
import { debounce } from 'lib/helpers'
import { useTheme } from 'next-themes'
import { useCallback, useEffect, useRef } from 'react'
const Globe = () => {
const { resolvedTheme } = useTheme()
const canvasRef = useRef<any>()
const canvasRef = useRef<any | null>(null)
let rotation: number = 0
let width: number = 0
@@ -2,20 +2,21 @@ import 'swiper/css'
import 'swiper/css/a11y'
import 'swiper/css/navigation'
import 'swiper/css/controller'
import React, { useEffect, useRef, useState } from 'react'
import { useBreakpoint } from 'common'
import SectionContainer from 'components/Layouts/SectionContainer'
import { ChevronLeft, ChevronRight } from 'lucide-react'
import { useTheme } from 'next-themes'
import Image from 'next/image'
import Link from 'next/link'
import { Button, cn } from 'ui'
import { useTheme } from 'next-themes'
import { Swiper, SwiperClass, SwiperRef, SwiperSlide } from 'swiper/react'
import { Controller, Navigation, A11y } from 'swiper/modules'
import SectionContainer from 'components/Layouts/SectionContainer'
import { mainDays, WeekDayProps } from './data'
import React, { useEffect, useRef, useState } from 'react'
import { useWindowSize } from 'react-use'
import { useBreakpoint } from 'common'
import { A11y, Controller, Navigation } from 'swiper/modules'
import { Swiper, SwiperClass, SwiperRef, SwiperSlide } from 'swiper/react'
import { Button, cn } from 'ui'
import { mainDays, WeekDayProps } from './data'
import { DayLink } from './lw15.components'
import { ChevronLeft, ChevronRight } from 'lucide-react'
const LW15MainStage = ({ className }: { className?: string }) => {
const { resolvedTheme } = useTheme()
@@ -181,7 +182,7 @@ const CardBG = ({ day }: { day: WeekDayProps }) => (
interface Props {
className?: string
slides: WeekDayProps[]
swiperRef: React.RefObject<SwiperRef>
swiperRef: React.RefObject<SwiperRef | null>
setControlledSwiper: (swiper: SwiperClass) => void
controlledSwiper: SwiperClass | null
}
@@ -1,3 +1,4 @@
import { LW15_TWEET_TEXT, LW15_URL } from '~/lib/constants'
import { useBreakpoint } from 'common'
import dayjs from 'dayjs'
import { Check } from 'lucide-react'
@@ -5,8 +6,8 @@ import { useTheme } from 'next-themes'
import Link from 'next/link'
import { useEffect, useRef, useState } from 'react'
import { Button } from 'ui'
import useConfData from '../hooks/use-conf-data'
import { LW15_TWEET_TEXT, LW15_URL } from '~/lib/constants'
import supabase from '../supabase'
export default function LW15TicketShare() {
@@ -17,7 +18,7 @@ export default function LW15TicketShare() {
const [_imgReady, setImgReady] = useState(false)
const [_loading, setLoading] = useState(false)
const isLessThanMd = useBreakpoint()
const downloadLink = useRef<HTMLAnchorElement>()
const downloadLink = useRef<HTMLAnchorElement | undefined>(undefined)
const link = `${LW15_URL}/tickets/${username}?lw=15&t=${dayjs(new Date()).format('DHHmmss')}`
const permalink = encodeURIComponent(link)
const text = LW15_TWEET_TEXT
@@ -1,5 +1,5 @@
import React from 'react'
import { useTheme } from 'next-themes'
import React from 'react'
function getRandomNumber(min: number, max: number) {
var range = max - min
@@ -10,7 +10,7 @@ function getRandomNumber(min: number, max: number) {
const DeployGlobally = ({ isHovered }: { isHovered: boolean }) => {
const { resolvedTheme } = useTheme()
const ref = React.useRef<any>()
const ref = React.useRef<any | null>(null)
const states = ['1', '2', '3']
const colors = {
green: resolvedTheme?.includes('dark') ? '#1CF7C3' : '#00B99F',
@@ -1,11 +1,11 @@
import React, { useEffect, useRef } from 'react'
import { useTheme } from 'next-themes'
import { DEFAULT_EASE } from '~/lib/animations'
import { LazyMotion, domAnimation, m } from 'framer-motion'
import { domAnimation, LazyMotion, m } from 'framer-motion'
import { useTheme } from 'next-themes'
import React, { useEffect, useRef } from 'react'
const PGvectorImg = ({ isHovered }: { isHovered: boolean }) => {
const { resolvedTheme } = useTheme()
const svgRef = useRef<any>()
const svgRef = useRef<any | null>(null)
const colors = {
circles1: resolvedTheme?.includes('dark') ? '#105C3B' : '#008B4F',
@@ -1,11 +1,11 @@
import React, { useEffect, useRef } from 'react'
import { useTheme } from 'next-themes'
import { DEFAULT_EASE } from '~/lib/animations'
import { useTheme } from 'next-themes'
import React, { useEffect, useRef } from 'react'
const SecureAndScalableImg = ({ isHovered }: { isHovered: boolean }) => {
const { resolvedTheme } = useTheme()
const shieldsRef = useRef<any>()
const mainRef = useRef<any>()
const shieldsRef = useRef<any | null>(null)
const mainRef = useRef<any | null>(null)
const colors = {
shieldsStroke: resolvedTheme?.includes('dark') ? '#494949' : '#D5D5D5',
@@ -19,7 +19,7 @@ export function AnimatedCounter({
const countRef = useRef(value)
const lastUpdateRef = useRef(0)
const isVisibleRef = useRef(false)
const rafIdRef = useRef<number>()
const rafIdRef = useRef<number | undefined>(undefined)
const formatNumber = useCallback(
(num: number) => {
@@ -40,7 +40,7 @@ function mdxComponents(callback: Dispatch<SetStateAction<string | null>>) {
img: (
props: React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>
) => {
return <img {...props} onClick={() => callback(props.src!)} />
return <img {...props} onClick={() => callback(props.src!.toString())} />
},
}
@@ -192,6 +192,7 @@ function Partner({ partner, overview }: PartnerData) {
{'error' in overview ? (
<p>Error rendering integration page: {overview.error.message}</p>
) : (
// @ts-expect-error: Gildas - This is because CodeHide put its components under the CH namespace. It works but the MDXClient types are stricter
<MDXClient {...overview} components={mdxComponents(setFocusedImage)} />
)}
</div>
+1 -1
View File
@@ -9,7 +9,7 @@ type ResultBox<T> = { v: T }
* React hook for creating a value exactly once
*/
export function useConstant<T>(fn: () => T): T {
const ref = useRef<ResultBox<T>>()
const ref = useRef<ResultBox<T> | null>(null)
if (!ref.current) {
ref.current = { v: fn() }
+1 -1
View File
@@ -4,7 +4,7 @@ import { debounce } from 'lodash'
import { useEffect, useMemo, useRef } from 'react'
export const useDebounce = (callback: () => void, delay: number = 1000) => {
const ref = useRef<any>()
const ref = useRef<any | null>(null)
useEffect(() => {
ref.current = callback
@@ -14,7 +14,7 @@ import { useCallback, useEffect, useId, useMemo, useReducer, useRef } from 'reac
const useSearchParamsShallow = () => {
const EVENT_NAME = 'supabase.events.packages.common.useSearchParamsShallow'
const id = useId()
const timeoutHandle = useRef<ReturnType<typeof setTimeout>>()
const timeoutHandle = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
const reducer = useCallback(
(_: URLSearchParams, action: { target: 'int' | 'ext'; newParams: URLSearchParams }) => {
+2 -2
View File
@@ -2,14 +2,14 @@ import {
createElement,
forwardRef,
ForwardRefExoticComponent,
ReactSVG,
RefAttributes,
SVGElementType,
SVGProps,
} from 'react'
import defaultAttributes from './defaultAttributes'
export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][]
export type IconNode = [elementName: SVGElementType, attrs: Record<string, string>][]
export type SVGAttributes = Partial<SVGProps<SVGSVGElement>>
type ComponentAttributes = RefAttributes<SVGSVGElement> & SVGAttributes
@@ -8,7 +8,7 @@ import { cn } from 'ui/src/lib/utils'
export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
/* The ref for the textarea, optional. Exposed for the CommandsPopover to attach events. */
textAreaRef?: React.RefObject<HTMLTextAreaElement>
textAreaRef?: React.RefObject<HTMLTextAreaElement | null>
/* The loading state of the form */
loading: boolean
/* The disabled state of the form */
@@ -5,8 +5,8 @@
import { Popover as _RadixPopover } from 'radix-ui'
import {
ComponentPropsWithoutRef,
ComponentRef,
createRef,
ElementRef,
forwardRef,
useEffect,
useRef,
@@ -24,12 +24,12 @@ import { Popover, PopoverAnchor, PopoverContent } from 'ui/src/components/shadcn
import { cn } from 'ui/src/lib/utils/cn'
const AssistantCommandsPopover = forwardRef<
ElementRef<typeof Popover>,
ComponentRef<typeof Popover>,
ComponentPropsWithoutRef<typeof Popover> & {
/* The children to render - this is where the AssistantChatForm should be placed */
children: React.ReactNode
/* The ref for the textarea - used with the AssistantChatForm */
textAreaRef: React.RefObject<HTMLTextAreaElement>
textAreaRef: React.RefObject<HTMLTextAreaElement | null>
/* The function to handle the value change */
setValue: (value: string) => void
/* The value of the textarea */
@@ -15,7 +15,7 @@ function useFocusInputOnWiderScreens(ref: React.ForwardedRef<HTMLInputElement>)
const isBelowSmSynchronous = useRef(isBelowSm)
isBelowSmSynchronous.current = isBelowSm
const internalRef = useRef<HTMLInputElement>()
const internalRef = useRef<HTMLInputElement | null>(null)
const combinedRef = (element: HTMLInputElement) => {
if (ref instanceof Function) {
ref(element)
@@ -32,7 +32,7 @@ const useRegisterCommands = (
options.enabled ??= true
const prevEnabled = useRef<boolean | undefined>(options.enabled)
const unsubscribe = useRef<() => void>()
const unsubscribe = useRef<(() => void) | undefined>(undefined)
/**
* useEffect handles the registration on first render, since React runs the
@@ -110,7 +110,7 @@ interface UseAiChatOptions {
}
const useAiChat = ({ messageTemplate = (message) => message, setIsLoading }: UseAiChatOptions) => {
const eventSourceRef = useRef<SSE>()
const eventSourceRef = useRef<SSE | undefined>(undefined)
const messageIdempotencyKey = useRef(0)
const [isResponding, setIsResponding] = useState(false)
@@ -71,7 +71,7 @@ export type FilterBarContextValue = {
actions?: FilterBarAction[]
icon?: React.ReactNode
rootRef: React.RefObject<HTMLDivElement>
rootRef: React.RefObject<HTMLDivElement | null>
}
const FilterBarContext = createContext<FilterBarContextValue | null>(null)
+1 -1
View File
@@ -1,6 +1,6 @@
import { useLayoutEffect, useState } from 'react'
export const useMeasuredWidth = <T extends HTMLElement>(ref: React.RefObject<T>) => {
export const useMeasuredWidth = <T extends HTMLElement>(ref: React.RefObject<T | null>) => {
const [measuredWidth, setMeasuredWidth] = useState<number | null>(null)
useLayoutEffect(() => {
@@ -1,5 +1,5 @@
import { LazyMotion, m } from 'framer-motion'
import { PropsWithChildren } from 'react'
import React, { PropsWithChildren } from 'react'
import { createElement, SyntaxHighlighterProps } from 'react-syntax-highlighter'
// Make sure to return the specific export containing the feature bundle.
@@ -7,7 +7,7 @@ const loadFramerFeatures = () => import('./framer-features').then((res) => res.d
type RendererElementNode = {
type: 'element'
tagName: keyof JSX.IntrinsicElements | React.ComponentType<any>
tagName: keyof React.JSX.IntrinsicElements | React.ComponentType<any>
properties: { className: any[]; [key: string]: any }
children: RendererNode[]
}
@@ -25,7 +25,7 @@ export type WrapperProps = PropsWithChildren<{
export type TransformRendererProps = {
search: (text: string) => boolean
wrapper: ({ children, text }: WrapperProps) => JSX.Element
wrapper: ({ children, text }: WrapperProps) => React.JSX.Element
}
/**
@@ -235,7 +235,7 @@ function splitSpaceElements(nodes: RendererNode[]) {
* that the syntax highlighter can work with.
*/
function elementToRendererElementNode(
element: ({ children, text }: WrapperProps) => JSX.Element,
element: ({ children, text }: WrapperProps) => React.JSX.Element,
text: string
): RendererElementNode {
const {
+1 -1
View File
@@ -9,7 +9,7 @@ import { useSnapshot } from 'valtio'
import { ConsentToast } from './ConsentToast'
export const useConsentToast = () => {
const consentToastId = useRef<string | number>()
const consentToastId = useRef<string | number | undefined>(undefined)
const snap = useSnapshot(consentState)
const acceptAll = useCallback(() => {
@@ -4,15 +4,20 @@ import { cva, VariantProps } from 'class-variance-authority'
import { Check, ChevronsUpDown, X as RemoveIcon } from 'lucide-react'
// @ts-ignore Required to avoid TS error: The inferred type of MultiSelectorContent cannot be named without a reference to @radix-ui
import type { Popover as PopoverPrimitive } from 'radix-ui'
import React, { useEffect } from 'react'
import { Badge, cn, Popover, PopoverAnchor, PopoverContent, PopoverContentProps } from 'ui'
import React, { isValidElement, ReactElement, useEffect } from 'react'
import {
Command,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
} from 'ui/src/components/shadcn/ui/command'
Badge,
cn,
Command_Shadcn_ as Command,
CommandEmpty_Shadcn_ as CommandEmpty,
CommandInput_Shadcn_ as CommandInput,
CommandItem_Shadcn_ as CommandItem,
CommandList_Shadcn_ as CommandList,
Popover,
PopoverAnchor,
PopoverContent,
PopoverContentProps,
} from 'ui'
import { SIZE_VARIANTS, SIZE_VARIANTS_DEFAULT } from 'ui/src/lib/constants'
interface MultiSelectContextProps {
@@ -504,7 +509,9 @@ const MultiSelectorList = React.forwardRef<
const options = !!children
? Array.isArray(children)
? (children as React.ReactNode[])
: typeof children === 'object' && 'props' in children
: typeof children === 'object' &&
'props' in children &&
isValidElement<{ children: ReactElement[] }>(children)
? children.props.children
: []
: []
+1 -1
View File
@@ -40,7 +40,7 @@
"sonner": "^1.5.0",
"tailwind-merge": "^3.5.0",
"tailwindcss": "catalog:",
"vaul": "^0.9.9"
"vaul": "^1.1.2"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.6.0",
+2 -2
View File
@@ -3,7 +3,7 @@
import { cva, VariantProps } from 'class-variance-authority'
import { Loader2 } from 'lucide-react'
import { Slot } from 'radix-ui'
import { cloneElement, forwardRef, isValidElement } from 'react'
import { cloneElement, forwardRef, isValidElement, ReactNode } from 'react'
import { SIZE_VARIANTS, SIZE_VARIANTS_DEFAULT } from '../../lib/constants'
import { cn } from '../../lib/utils/cn'
@@ -268,7 +268,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
}}
>
{asChild ? (
isValidElement(children) ? (
isValidElement<{ children: ReactNode }>(children) ? (
cloneElement(
children,
undefined,
@@ -13,7 +13,7 @@ export interface ExpandingTextAreaProps extends React.TextareaHTMLAttributes<HTM
/**
* This is a custom TextArea component that expands based on the content.
*/
const ExpandingTextArea = forwardRef<HTMLTextAreaElement, ExpandingTextAreaProps>(
const ExpandingTextArea = forwardRef<HTMLTextAreaElement | null, ExpandingTextAreaProps>(
({ className, value, ...props }, ref) => {
const internalRef = useRef<HTMLTextAreaElement | null>(null)
@@ -23,7 +23,7 @@ const ShadowScrollArea = React.forwardRef<HTMLDivElement, ShadowScrollAreaProps>
{ className, containerClassName, children, stickyLastColumn, outerContainerRef, ...props },
_ref
) => {
const containerRef = React.useRef<HTMLDivElement>(null)
const containerRef = React.useRef<HTMLDivElement | null>(null)
const { hasHorizontalScroll, canScrollLeft, canScrollRight } = useHorizontalScroll(containerRef)
const stickyColumnShadow = cn(
@@ -1,6 +1,6 @@
import * as React from 'react'
export const useHorizontalScroll = (ref: React.RefObject<HTMLDivElement>) => {
export const useHorizontalScroll = (ref: React.RefObject<HTMLDivElement | null>) => {
const [hasHorizontalScroll, setHasHorizontalScroll] = React.useState(false)
const [canScrollLeft, setCanScrollLeft] = React.useState(false)
const [canScrollRight, setCanScrollRight] = React.useState(false)
+1390 -1406
View File
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -14,8 +14,8 @@ catalog:
'@supabase/ssr': 0.10.2
'@supabase/supabase-js': 2.105.4
'@types/node': ^22.0.0
'@types/react': ^18.3.0
'@types/react-dom': ^18.3.0
'@types/react': ^19.2.14
'@types/react-dom': ^19.2.3
'@vitejs/plugin-react': ^6.0.1
'@vitest/coverage-v8': ^4.1.4
'@vitest/ui': ^4.1.4
@@ -25,8 +25,8 @@ catalog:
next-themes: ^0.4.6
postcss: ^8.5.10
radix-ui: ^1.4.3
react: ^18.3.0
react-dom: ^18.3.0
react: ^19.2.6
react-dom: ^19.2.6
recharts: ^2.15.4
tailwindcss: ^4.2.4
tsx: 4.20.3