Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/rare-moles-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tanstack/react-devtools': minor
'@tanstack/devtools': minor
---

added optional trigger component in config
40 changes: 32 additions & 8 deletions packages/devtools/src/components/trigger.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Show, createMemo } from 'solid-js'
import { Show, createEffect, createMemo, createSignal } from 'solid-js'
import clsx from 'clsx'
import { useDevtoolsSettings } from '../context/use-devtools-context'
import { useStyles } from '../styles/use-styles'
Expand All @@ -15,6 +15,7 @@ export const Trigger = ({
image: string
}) => {
const { settings } = useDevtoolsSettings()
const [containerRef, setContainerRef] = createSignal<HTMLElement>()
const styles = useStyles()
const buttonStyle = createMemo(() => {
return clsx(
Expand All @@ -23,16 +24,39 @@ export const Trigger = ({
styles().mainCloseBtnAnimation(isOpen(), settings().hideUntilHover),
)
})

createEffect(() => {
const triggerComponent = settings().triggerComponent
const el = containerRef()
if (triggerComponent && el) {
triggerComponent(el, {
theme: settings().theme,
image: image || TanStackLogo,
isOpen,
setIsOpen,
hideUntilHover: settings().hideUntilHover,
position: settings().position,
})
}
})

return (
<Show when={!settings().triggerHidden}>
<button
type="button"
aria-label="Open TanStack Devtools"
class={buttonStyle()}
onClick={() => setIsOpen(!isOpen())}
<Show
when={settings().triggerComponent}
fallback={
<button
type="button"
aria-label="Open TanStack Devtools"
class={buttonStyle()}
onClick={() => setIsOpen(!isOpen())}
>
<img src={image || TanStackLogo} alt="TanStack Devtools" />
</button>
}
>
<img src={image || TanStackLogo} alt="TanStack Devtools" />
</button>
<div ref={setContainerRef} />
</Show>
</Show>
)
}
17 changes: 17 additions & 0 deletions packages/devtools/src/context/devtools-store.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Accessor } from 'solid-js'
import type { TabName } from '../tabs'
import type { TanStackDevtoolsPlugin } from './devtools-context'

Expand All @@ -18,6 +19,15 @@ type TriggerPosition =
| 'middle-left'
| 'middle-right'

type TriggerProps = {
isOpen: Accessor<boolean>
setIsOpen: (isOpen: boolean) => void
theme: 'light' | 'dark'
image: string
position: TriggerPosition
hideUntilHover: boolean
}

export type DevtoolsStore = {
settings: {
/**
Expand Down Expand Up @@ -70,6 +80,12 @@ export type DevtoolsStore = {
* Whether the trigger should be completely hidden or not (you can still open with the hotkey)
*/
triggerHidden?: boolean
/**
* An optional custom function to render the dev tools trigger component.
* If provided, it replaces the default trigger button.
* @default undefined
*/
triggerComponent?: (el: HTMLElement, props: TriggerProps) => void
}
state: {
activeTab: TabName
Expand Down Expand Up @@ -97,6 +113,7 @@ export const initialState: DevtoolsStore = {
: 'light',
triggerImage: '',
triggerHidden: false,
triggerComponent: undefined,
},
state: {
activeTab: 'plugins',
Expand Down
87 changes: 78 additions & 9 deletions packages/react-devtools/src/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ type PluginRender =
| JSX.Element
| ((el: HTMLElement, theme: 'dark' | 'light') => JSX.Element)

type TriggerProps = {
theme: 'dark' | 'light'
image: string
position:
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right'
| 'middle-left'
| 'middle-right'
isOpen: boolean
setIsOpen: (isOpen: boolean) => void
hideUntilHover: boolean
}

type TriggerRender =
| JSX.Element
| ((el: HTMLElement, props: TriggerProps) => JSX.Element)

export type TanStackDevtoolsReactPlugin = Omit<
TanStackDevtoolsPlugin,
'render' | 'name'
Expand Down Expand Up @@ -57,6 +76,24 @@ export type TanStackDevtoolsReactPlugin = Omit<
name: string | PluginRender
}

type TanStackDevtoolsReactConfig = Omit<
Partial<TanStackDevtoolsConfig>,
'triggerComponent'
> & {
/**
* Optional custom trigger component for the devtools.
* It can be a React element or a function that renders one.
*
* Example:
* ```jsx
* {
* triggerComponent: <CustomTriggerComponent />,
* }
* ```
*/
triggerComponent?: TriggerRender
}

export interface TanStackDevtoolsReactInit {
/**
* Array of plugins to be used in the devtools.
Expand All @@ -81,7 +118,7 @@ export interface TanStackDevtoolsReactInit {
* initial state of the devtools when it is started for the first time. Afterwards,
* the settings are persisted in local storage and changed through the settings panel.
*/
config?: Partial<TanStackDevtoolsConfig>
config?: TanStackDevtoolsReactConfig
/**
* Configuration for the TanStack Devtools client event bus.
*/
Expand All @@ -105,6 +142,17 @@ const convertRender = (
}))
}

const convertTrigger = (
Component: TriggerRender,
setComponent: React.Dispatch<React.SetStateAction<JSX.Element | null>>,
e: HTMLElement,
props: TriggerProps,
) => {
const element =
typeof Component === 'function' ? Component(e, props) : Component
setComponent(element)
}

export const TanStackDevtools = ({
plugins,
config,
Expand All @@ -118,13 +166,19 @@ export const TanStackDevtools = ({
const [titleContainers, setTitleContainers] = useState<
Record<string, HTMLElement>
>({})
const [triggerContainer, setTriggerContainer] = useState<HTMLElement | null>(
null,
)

const [PluginComponents, setPluginComponents] = useState<
Record<string, JSX.Element>
>({})
const [TitleComponents, setTitleComponents] = useState<
Record<string, JSX.Element>
>({})
const [TriggerComponent, setTriggerComponent] = useState<JSX.Element | null>(
null,
)

const pluginsMap: Array<TanStackDevtoolsPlugin> = useMemo(
() =>
Expand Down Expand Up @@ -170,14 +224,25 @@ export const TanStackDevtools = ({
[plugins],
)

const [devtools] = useState(
() =>
new TanStackDevtoolsCore({
config,
eventBusConfig,
plugins: pluginsMap,
}),
)
const [devtools] = useState(() => {
const { triggerComponent, ...coreConfig } = config || {}
return new TanStackDevtoolsCore({
config: {
...coreConfig,
triggerComponent: triggerComponent
? (el, props) => {
setTriggerContainer(el)
convertTrigger(triggerComponent, setTriggerComponent, el, {
...props,
isOpen: props.isOpen(),
})
}
: undefined,
},
eventBusConfig,
plugins: pluginsMap,
})
})

useEffect(() => {
devtools.setConfig({
Expand Down Expand Up @@ -215,6 +280,10 @@ export const TanStackDevtools = ({
createPortal(<>{TitleComponents[key]}</>, titleContainer),
)
: null}

{triggerContainer && TriggerComponent
? createPortal(<>{TriggerComponent}</>, triggerContainer)
: null}
</>
)
}