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

added optional trigger component in config

removed trigger image setting completely
3 changes: 3 additions & 0 deletions examples/solid/basic/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ function App() {
<Router />

<TanStackDevtools
config={{
customTrigger: () => <h1>hello world</h1>,
}}
plugins={[
{
name: 'TanStack Query',
Expand Down
32 changes: 22 additions & 10 deletions packages/devtools/src/components/trigger.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,49 @@
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'
import TanStackLogo from './tanstack-logo.png'
import type { Accessor } from 'solid-js'

export const Trigger = ({
isOpen,
setIsOpen,
image = TanStackLogo,
}: {
export const Trigger = (props: {
isOpen: Accessor<boolean>
setIsOpen: (isOpen: boolean) => void
image: string
}) => {
const { settings } = useDevtoolsSettings()
const [containerRef, setContainerRef] = createSignal<HTMLElement>()
const styles = useStyles()
const buttonStyle = createMemo(() => {
return clsx(
styles().mainCloseBtn,
styles().mainCloseBtnPosition(settings().position),
styles().mainCloseBtnAnimation(isOpen(), settings().hideUntilHover),
styles().mainCloseBtnAnimation(props.isOpen(), settings().hideUntilHover),
)
})

createEffect(() => {
const triggerComponent = settings().customTrigger
const el = containerRef()
if (triggerComponent && el) {
triggerComponent(el, {
theme: settings().theme,
})
}
})

return (
<Show when={!settings().triggerHidden}>
<button
type="button"
aria-label="Open TanStack Devtools"
class={buttonStyle()}
onClick={() => setIsOpen(!isOpen())}
onClick={() => props.setIsOpen(!props.isOpen())}
>
<img src={image || TanStackLogo} alt="TanStack Devtools" />
<Show
when={settings().customTrigger}
fallback={<img src={TanStackLogo} alt="TanStack Devtools" />}
>
<div ref={setContainerRef} />
</Show>
</button>
</Show>
)
Expand Down
18 changes: 12 additions & 6 deletions packages/devtools/src/context/devtools-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type TriggerPosition =
| 'middle-left'
| 'middle-right'

type TriggerProps = {
theme: 'light' | 'dark'
}

export type DevtoolsStore = {
settings: {
/**
Expand Down Expand Up @@ -61,15 +65,17 @@ export type DevtoolsStore = {
* @default "dark"
*/
theme: 'light' | 'dark'
/**
* The image used for the dev tools trigger
* @default TanStackLogo
*/
triggerImage: string

/**
* 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
*/
customTrigger?: (el: HTMLElement, props: TriggerProps) => void
}
state: {
activeTab: TabName
Expand All @@ -95,8 +101,8 @@ export const initialState: DevtoolsStore = {
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light',
triggerImage: '',
triggerHidden: false,
customTrigger: undefined,
},
state: {
activeTab: 'plugins',
Expand Down
6 changes: 1 addition & 5 deletions packages/devtools/src/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,7 @@ export default function DevTools() {
: true
}
>
<Trigger
isOpen={isOpen}
setIsOpen={toggleOpen}
image={settings().triggerImage}
/>
<Trigger isOpen={isOpen} setIsOpen={toggleOpen} />
<MainPanel isResizing={isResizing} isOpen={isOpen}>
<ContentPanel
ref={(ref) => (panelRef = ref)}
Expand Down
8 changes: 1 addition & 7 deletions packages/devtools/src/tabs/settings-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,7 @@ export const SettingsTab = () => {
}
checked={settings().triggerHidden}
/>
<Input
label="Trigger Image"
description="Specify the URL of the image to use for the trigger"
value={settings().triggerImage}
placeholder="Default TanStack Logo"
onChange={(value) => setSettings({ triggerImage: value })}
/>

<Select
label="Theme"
description="Choose the theme for the devtools panel"
Expand Down
73 changes: 64 additions & 9 deletions packages/react-devtools/src/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ type PluginRender =
| JSX.Element
| ((el: HTMLElement, theme: 'dark' | 'light') => JSX.Element)

type TriggerProps = {
theme: 'dark' | 'light'
}

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

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

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

export interface TanStackDevtoolsReactInit {
/**
* Array of plugins to be used in the devtools.
Expand All @@ -81,7 +107,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 +131,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 +155,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 +213,22 @@ export const TanStackDevtools = ({
[plugins],
)

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

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

{triggerContainer && TriggerComponent
? createPortal(<>{TriggerComponent}</>, triggerContainer)
: null}
</>
)
}
33 changes: 30 additions & 3 deletions packages/solid-devtools/src/core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ export type TanStackDevtoolsSolidPlugin = Omit<
*/
name: string | SolidPluginRender
}
interface TriggerProps {
theme: 'light' | 'dark'
}
export interface TanStackDevtoolsInit {
/**
* Array of plugins to be used in the devtools.
Expand All @@ -98,7 +101,14 @@ export interface TanStackDevtoolsInit {
* 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?: Omit<Partial<TanStackDevtoolsConfig>, 'customTrigger'> & {
/**
* An optional custom function to render the dev tools trigger component.
*/
customTrigger?:
| ((el: HTMLElement, props: TriggerProps) => JSX.Element)
| JSX.Element
}
/**
* Configuration for the TanStack Devtools client event bus.
*/
Expand All @@ -125,17 +135,34 @@ export default function SolidDevtoolsCore({
})),
)

const convertTrigger = (el: HTMLElement, props: TriggerProps) => {
const Trigger = config?.customTrigger

return (
<Portal mount={el}>
{typeof Trigger === 'function' ? Trigger(el, props) : Trigger}
</Portal>
)
}
const [devtools] = createSignal(
new TanStackDevtoolsCore({
config,
config: {
...config,
customTrigger: (el, props) => convertTrigger(el, props),
},
eventBusConfig,
plugins: pluginsMap(),
}),
)
let devToolRef: HTMLDivElement | undefined

createEffect(() => {
devtools().setConfig({ config })
devtools().setConfig({
config: {
...config,
customTrigger: (el, props) => convertTrigger(el, props),
},
})
})

// Update plugins when they change
Expand Down
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading