S
SolidJS2mo ago
zobweyt

Using JSX in custom directives

Suppose you want to create a convenient tooltip directive that can be easily used by providing only the content of it. This works fine for simple tooltips. However, what if you want to create more complex, custom tooltips? For example, you want to wrap the element as a trigger and use it in combination with a component from libraries like https://kobalte.dev/docs/core/components/tooltip or https://corvu.dev/docs/primitives/tooltip
import { type Accessor, createEffect, createSignal } from "solid-js";
import { render } from "solid-js/web";

export default function tooltip(el: HTMLElement, title: Accessor<string>) {
createEffect(() => {
el.title = title();
});
}

declare module "solid-js" {
namespace JSX {
interface DirectiveFunctions {
tooltip: typeof tooltip;
}
}
}

function App() {
const [count, setCount] = createSignal<number>(0);

return (
<button
onClick={() => setCount((c) => c + 1)}
use:tooltip="Push to increment count"
>
{count()}
</button>
);
}

render(() => <App />, document.getElementById("app")!);
import { type Accessor, createEffect, createSignal } from "solid-js";
import { render } from "solid-js/web";

export default function tooltip(el: HTMLElement, title: Accessor<string>) {
createEffect(() => {
el.title = title();
});
}

declare module "solid-js" {
namespace JSX {
interface DirectiveFunctions {
tooltip: typeof tooltip;
}
}
}

function App() {
const [count, setCount] = createSignal<number>(0);

return (
<button
onClick={() => setCount((c) => c + 1)}
use:tooltip="Push to increment count"
>
{count()}
</button>
);
}

render(() => <App />, document.getElementById("app")!);
https://playground.solidjs.com/anonymous/eb41151c-59ca-460f-aa45-26ea80525e01 Is it possible to use JSX inside the directive to wrap the <button> with a <Tooltip> component?
6 Replies
Alex Lohr
Alex Lohr2mo ago
You cannot directly influence the return value, but you can create a portal for the tooltip and event listeners for the trigger.
zobweyt
zobweytOP2mo ago
How would you create a portal? What do you mean? Using document.appendChild()?
Alex Lohr
Alex Lohr2mo ago
Basically, you call Portal({ get children() { return <Tooltip .../> } }).
zobweyt
zobweytOP2mo ago
I came up with this. However, it renders the whole element as a <Portal>. Can you render it in the same exact spot?
import { type Accessor, createSignal } from "solid-js";
import { render, Portal } from "solid-js/web";

import { Tooltip } from "@kobalte/core/tooltip";

export default function tooltip(el: HTMLElement, title: Accessor<string>) {
Portal({
get children() {
return (
<Tooltip>
<Tooltip.Trigger as={"span"}>{el}</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content>{title()}</Tooltip.Content>
</Tooltip.Portal>
</Tooltip>
);
},
});
}

declare module "solid-js" {
namespace JSX {
interface DirectiveFunctions {
tooltip: typeof tooltip;
}
}
}

function App() {
const [count, setCount] = createSignal<number>(0);

return (
<button
onClick={() => setCount((c) => c + 1)}
use:tooltip="Push to increment count"
>
{count()}
</button>
);
}

render(() => <App />, document.getElementById("app")!);
import { type Accessor, createSignal } from "solid-js";
import { render, Portal } from "solid-js/web";

import { Tooltip } from "@kobalte/core/tooltip";

export default function tooltip(el: HTMLElement, title: Accessor<string>) {
Portal({
get children() {
return (
<Tooltip>
<Tooltip.Trigger as={"span"}>{el}</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content>{title()}</Tooltip.Content>
</Tooltip.Portal>
</Tooltip>
);
},
});
}

declare module "solid-js" {
namespace JSX {
interface DirectiveFunctions {
tooltip: typeof tooltip;
}
}
}

function App() {
const [count, setCount] = createSignal<number>(0);

return (
<button
onClick={() => setCount((c) => c + 1)}
use:tooltip="Push to increment count"
>
{count()}
</button>
);
}

render(() => <App />, document.getElementById("app")!);
Alex Lohr
Alex Lohr2mo ago
Unfortunately, no, but you can use the ref to move the tooltip.
zobweyt
zobweytOP2mo ago
I came up with this, but now it seems like you need to re-invent the whole <Tooltip> component
export default function tooltip(trigger: HTMLElement, title: Accessor<string>) {
const [ref, setRef] = createSignal<HTMLElement | undefined>(undefined);

onMount(() => {
trigger.addEventListener("mouseover", () => {
Portal({
ref: setRef,
children: title(),
});
});
});

createEffect(() => {
trigger.addEventListener("mouseleave", () => {
ref()?.remove();
});
});
}
export default function tooltip(trigger: HTMLElement, title: Accessor<string>) {
const [ref, setRef] = createSignal<HTMLElement | undefined>(undefined);

onMount(() => {
trigger.addEventListener("mouseover", () => {
Portal({
ref: setRef,
children: title(),
});
});
});

createEffect(() => {
trigger.addEventListener("mouseleave", () => {
ref()?.remove();
});
});
}
Want results from more Discord servers?
Add your server