Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fruity-hoops-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@cerberus-design/data-grid': patch
'@cerberus-design/react': patch
'@cerberus-design/signals': patch
---

Fix Compiler warnings
8 changes: 0 additions & 8 deletions .oxlintrc.json

This file was deleted.

6 changes: 3 additions & 3 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@cerberus/tokens": "workspace:*",
"@docsearch/css": "^5.0.2",
"@docsearch/react": "^5.0.2",
"next": "16.3.0",
"next": "16.3.1",
"react": "catalog:jsx",
"react-dom": "catalog:jsx"
},
Expand All @@ -32,7 +32,7 @@
"@expressive-code/plugin-line-numbers": "^0.44.1",
"@mdx-js/loader": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@next/mdx": "^16.3.0",
"@next/mdx": "^16.3.1",
"@pandacss/dev": "catalog:pandacss",
"@pandacss/types": "catalog:pandacss",
"@shikijs/rehype": "^4.4.3",
Expand All @@ -45,7 +45,7 @@
"autoprefixer": "^10.5.4",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.21.0",
"eslint-config-next": "16.3.0",
"eslint-config-next": "16.3.1",
"eslint-import-resolver-typescript": "^4.4.5",
"hast-util-to-jsx-runtime": "2.3.6",
"rehype-autolink-headings": "^7.1.0",
Expand Down
11 changes: 11 additions & 0 deletions oxlint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from 'oxlint'

export default defineConfig({
plugins: ['import', 'react', 'react-perf'],

ignorePatterns: [
'packages/tokens/src/data/**/*',
'styled-system/**/*',
'!styled-system/panda.config.ts',
],
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"husky": "^9.1.7",
"lint-staged": "^17.3.0",
"oxfmt": "catalog:oxc",
"oxlint": "^1.75.0"
"oxlint": "^1.79.0"
},
"lint-staged": {
"**/*": "oxfmt --write --no-error-on-unmatched-pattern",
Expand Down
9 changes: 5 additions & 4 deletions packages/data-grid/src/components/viewport.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface GridViewportProps {
}

export const GridViewport = memo(function GridViewport(props: GridViewportProps) {
const { rootRef, overlays } = props

const viewportRef = useRef<HTMLDivElement>(null)

const store = useDataGridContext()
Expand All @@ -34,10 +36,9 @@ export const GridViewport = memo(function GridViewport(props: GridViewportProps)
const shouldLock = createComputed(() => rowCount <= 0)

const hasNonSkeleton = useMemo<boolean>(() => {
const overlays = props.overlays
if (!overlays) return false
return overlays?.pending !== 'skeleton'
}, [props.overlays])
}, [overlays])

if (allColsHidden) {
return <NoColumnsLayout />
Expand Down Expand Up @@ -87,14 +88,14 @@ export const GridViewport = memo(function GridViewport(props: GridViewportProps)

<Show
when={rowCount > 0}
fallback={<NoContentOverlay custom={props.overlays?.noContent} />}
fallback={<NoContentOverlay custom={overlays?.noContent} />}
>
{() => <TableRows viewportRef={viewportRef} />}
</Show>

<Show when={pending}>{() => <PendingOverlay variant={pendingVariant} />}</Show>

<PopoverContent ref={props.rootRef} />
<PopoverContent ref={rootRef} />
</Scrollable>
)
})
Expand Down
4 changes: 3 additions & 1 deletion packages/data-grid/src/ui/popover.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ interface PopoverContentProps {
}

export function PopoverContent(props: PopoverContentProps) {
const { ref } = props

const store = useDataGridContext()
const featureOpen = useRead(store.featureOpen)

Expand All @@ -78,7 +80,7 @@ export function PopoverContent(props: PopoverContentProps) {
const data = meta[featureOpen.key as keyof typeof meta]

return (
<Portal container={props.ref}>
<Portal container={ref}>
<PopoverParts.Positioner>
<PopoverParts.Content
bgColor="page.bg.100"
Expand Down
50 changes: 26 additions & 24 deletions packages/react/src/components/admonition/match-avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { type ElementType } from 'react'
import type { AdmonitionVariantProps } from 'styled-system/recipes'
import { useRef, type ReactNode } from 'react'
import { useCerberusContext } from '../../context/cerberus'
import { Avatar } from '../avatar/avatar'

Expand All @@ -11,11 +11,11 @@ import { Avatar } from '../avatar/avatar'
*/

interface AvatarRefProps {
page: ReactNode
info: ReactNode
success: ReactNode
warning: ReactNode
danger: ReactNode
page: ElementType
info: ElementType
success: ElementType
warning: ElementType
danger: ElementType
}

type MatchAvatarProps = AdmonitionVariantProps
Expand All @@ -31,22 +31,24 @@ export function MatchAvatar(props: MatchAvatarProps) {
dangerNotification: DangerIcon,
} = icons

// cache the el since the result is static and the component is client side
const elRef = useRef<AvatarRefProps>({
page: <Avatar gradient="charon-light" fallback={<InfoIcon />} size="sm" />,
info: (
<Avatar gradient="amphiaraus-dark" fallback={<InfoIcon />} size="sm" />
),
success: (
<Avatar gradient="thanatos-dark" fallback={<SuccessIcon />} size="sm" />
),
warning: (
<Avatar gradient="asphodel-light" fallback={<WarningIcon />} size="sm" />
),
danger: (
<Avatar gradient="hades-light" fallback={<DangerIcon />} size="sm" />
),
})

return <>{elRef.current[palette]}</>
const ICON_MAP: AvatarRefProps = {
page: InfoIcon,
info: InfoIcon,
success: SuccessIcon,
warning: WarningIcon,
danger: DangerIcon,
}

const gradient = GRADIENT_MAP[palette]
const Fallback = ICON_MAP[palette]

return <Avatar gradient={gradient} fallback={<Fallback />} size="sm" />
}

const GRADIENT_MAP: Record<string, string> = {
page: 'charon-light',
info: 'amphiaraus-dark',
success: 'thanatos-dark',
warning: 'asphodel-light',
danger: 'hades-light',
}
10 changes: 5 additions & 5 deletions packages/react/src/components/button/button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { createContext, useContext, useMemo } from 'react'
import { createContext, useContext } from 'react'
import { css, cx } from 'styled-system/css'
import { Box } from 'styled-system/jsx'
import { button } from 'styled-system/recipes'
Expand Down Expand Up @@ -40,8 +40,7 @@ function ButtonEl(props: ButtonProps) {
const { pending = false, ...nativeProps } = elProps

const styles = button(variantProps)

const value = useMemo(() => ({ pending }), [pending])
const value = { pending }

return (
<ButtonContext.Provider value={value}>
Expand All @@ -61,10 +60,11 @@ function ButtonEl(props: ButtonProps) {
* a loading spinner.
*/
export function ButtonIcon(props: CerberusProps<'div'>) {
const { children, ref } = props
const { pending } = useContext(ButtonContext)
return (
<Show when={pending} fallback={<>{props.children}</>}>
<Box data-scope="button" data-part="button-spinner" ref={props.ref} w="4">
<Show when={pending} fallback={<>{children}</>}>
<Box data-scope="button" data-part="button-spinner" ref={ref} w="4">
<Spinner />
</Box>
</Show>
Expand Down
45 changes: 16 additions & 29 deletions packages/react/src/hooks/useTheme.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
'use client'

import {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useState,
} from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import {
type ColorModes,
type CustomThemes,
Expand Down Expand Up @@ -50,8 +44,17 @@ export function useTheme<C extends string = DefaultThemes>(
options: UseThemeOptions<C> = {},
): ThemeContextValue<C> {
const { updateMode, updateTheme, cache } = options
const [theme, setTheme] = useState<CustomThemes<C>>(defaultTheme)
const [colorMode, setColorMode] = useState<ColorModes>(defaultColorMode)

const [theme, setTheme] = useState<CustomThemes<C>>(() => {
if (typeof window === 'undefined') return defaultTheme
const stored = localStorage.getItem(THEME_KEY)
return (stored as CustomThemes<C>) ?? defaultTheme
})
const [colorMode, setColorMode] = useState<ColorModes>(() => {
if (typeof window === 'undefined') return defaultColorMode
const stored = localStorage.getItem(MODE_KEY)
return (stored as ColorModes) ?? defaultColorMode
})

const handleThemeChange = useCallback(
(newTheme: C) => {
Expand All @@ -69,24 +72,11 @@ export function useTheme<C extends string = DefaultThemes>(
[updateMode],
)

useLayoutEffect(() => {
const theme = localStorage.getItem(THEME_KEY)
if (theme) {
setTheme(theme as CustomThemes<C>)
}
}, [])

useLayoutEffect(() => {
const mode = localStorage.getItem(MODE_KEY)
if (mode) {
setColorMode(mode as ColorModes)
}
}, [])

// Synchronization effects are now limited with compiler strictly to updating
// external systems (DOM attributes & caching updates)
useEffect(() => {
const root = document.documentElement
root.dataset.pandaTheme = theme

if (cache) {
localStorage.setItem(THEME_KEY, theme)
}
Expand All @@ -96,17 +86,14 @@ export function useTheme<C extends string = DefaultThemes>(
const root = document.documentElement

if (colorMode === 'system') {
root.dataset.colorMode = window.matchMedia('(prefers-color-scheme: dark)')
.matches
root.dataset.colorMode = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
} else {
root.dataset.colorMode = colorMode
}

if (cache) {
localStorage.setItem(MODE_KEY, colorMode)
}
if (cache) localStorage.setItem(MODE_KEY, colorMode)
}, [colorMode, cache])

return useMemo(
Expand Down
21 changes: 9 additions & 12 deletions packages/signals/src/react/store-context.client.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { createContext, PropsWithChildren, useContext, useRef } from 'react'
import { createContext, PropsWithChildren, useContext, useState } from 'react'

export interface StoreProviderProps<TStore> {
createStore: () => TStore
Expand Down Expand Up @@ -43,18 +43,15 @@ export function createStoreContext<TStore>(name?: string) {
const StoreContext = createContext<TStore | null>(null)

// 2. The Provider handles the lazy SSR initialization securely
function StoreProvider(props: PropsWithChildren<StoreProviderProps<TStore>>) {
const storeRef = useRef<TStore | null>(null)
function StoreProvider({
children,
createStore,
}: PropsWithChildren<StoreProviderProps<TStore>>) {
// useState with a callback guarantees createStore() is only executed
// once upon the initial render, satisfying the compiler's purity rules.
const [store] = useState(() => createStore())

if (storeRef.current === null) {
storeRef.current = props.createStore()
}

return (
<StoreContext.Provider value={storeRef.current}>
{props.children}
</StoreContext.Provider>
)
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>
}
StoreProvider.displayName = name ?? 'CerberusStoreProvider'

Expand Down
12 changes: 4 additions & 8 deletions packages/signals/src/react/useStore.client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useRef } from 'react'
import { useState } from 'react'

/**
* ## Using global stores within React components
Expand All @@ -27,11 +27,7 @@ import { useRef } from 'react'
* - [createSignalStore](https://cerberus.digitalu.design/docs/signals/create-store-context)
*/
export function useStore<T>(storeFactory: () => T): T {
const storeRef = useRef<T | null>(null)

if (storeRef.current === null) {
storeRef.current = storeFactory()
}

return storeRef.current as T
// oxlint-disable-next-line react-hooks/exhaustive-deps
const [cachedStore] = useState<T>(() => storeFactory())
return cachedStore
}
Loading