Overview
In Solid, a component is a function that runs once and returns DOM. Partas.Solid gives you two ways to write that function in F#:
[<SolidComponent>]on aletbinding.[<SolidTypeComponent>]on a member of a type. The type's properties become the component's props.
Both tell the Fable plugin to rewrite the body into JSX. Without one, the DSL compiles to nothing useful.
SolidComponent
[<SolidComponent>] works on any let binding. Its parameters stay ordinary F# parameters.
[<StringEnum>]
type ButtonVariant =
| Primary
| Ghost
[<SolidComponent>]
let Button (title: string) (variant: ButtonVariant) =
button (
style =
(match variant with
| Primary -> "background: #2563eb; color: white; margin-right: .5em"
| Ghost -> "background: transparent; margin-right: .5em")
) {
title
}
[<SolidComponent>]
let ButtonExample () =
let variant, setVariant = createSignal Ghost
div () {
Button "Primary" Primary
Button "Ghost" Ghost
Button "Responsive" (variant ())
button (onClick = fun _ -> setVariant (if variant () = Primary then Ghost else Primary)) {
"Click me"
}
}
export function Button(title, variant) {
return <button style={(variant === "ghost") ? "background: transparent; margin-right: .5em" : "background: #2563eb; color: white; margin-right: .5em"}>
{title}
</button>;
}
export function ButtonExample() {
const patternInput = createSignal("ghost");
const variant = patternInput[0];
return <div>
{untrack(() => Button("Primary", "primary"))}
{untrack(() => Button("Ghost", "ghost"))}
{untrack(() => Button("Responsive", variant()))}
<button onClick={(_arg) => {
patternInput[1]((variant() === "primary") ? "ghost" : "primary");
}}>
Click me
</button>
</div>;
}
In the JSX tab, a [<SolidComponent>] with parameters is called as a plain function, Button("Primary", "primary"), not used as a <Button /> tag. That is fine for small helpers. For a component other code will use, reach for [<SolidTypeComponent>]: it becomes a real tag with named props, for F# and JavaScript callers alike. See SolidComponent for the details.
SolidTypeComponent
This attribute defines a custom tag: a type whose properties are the props, with the body in a member marked [<SolidTypeComponent>].
[<Erase>]
type Greeting() =
inherit div()
[<Erase>]
member val name: string = unbox null with get, set
[<Erase>]
member val loud: bool = unbox null with get, set
[<SolidTypeComponent>]
member props.View =
props.name <- "world"
div(style = (if props.loud then "font-weight: bold" else "")).spread props {
$"Hello, {props.name}!"
}
[<SolidComponent>]
let GreetingExample () =
let loud, setLoud = createSignal false
div () {
Greeting()
Greeting(name = "Partas", loud = loud ())
button (onClick = fun _ -> setLoud (not (loud ()))) { "Toggle loud" }
}
export function Greeting(props) {
props = merge({
name: "world",
}, props);
const PARTAS_OTHERS = omit(props, "loud", "name");
return <div style={props.loud ? "font-weight: bold" : ""}
{...PARTAS_OTHERS} n$={false}>
{concat("Hello, ", props.name, "!")}
</div>;
}
export function GreetingExample() {
const patternInput = createSignal(false);
const loud = patternInput[0];
return <div>
<Greeting />
<Greeting name="Partas"
loud={loud()} />
<button onClick={(_arg) => {
patternInput[1](!loud());
}}>
Toggle loud
</button>
</div>;
}
inherit div()givesGreetingevery attribute adivhas, as well as its ownnameandloud.props.name <- "world"sets a default. The plugin turns assignments to props intoprops = merge({...}, props)..spread propspasses on every prop the body does not read. The plugin collects the reads intoconst PARTAS_OTHERS = omit(props, ...)and spreads that.- Prop reads stay as
props.xin the JSX, so they stay reactive. - The member becomes a function named after the type. The member name, here
View, does not matter.
Greeting is now used like any built-in tag. The JSX output page shows the exact shapes.
A type without a [<SolidTypeComponent>] member describes a component that already exists in JavaScript. Import it with [<Import>] and declare its properties:
namespace Partas.Solid.MyBindings
open Fable.Core
open Partas.Solid
[<Import("Tooltip", "some-js-library")>]
[<Erase>]
type Tooltip() =
inherit div()
[<Erase>]
member val placement: string = unbox null with get, set
Tooltip(placement = "top", class' = "tip") { "More info" }
See SolidTypeComponent for the rules the member must follow, and tag interfaces for the interfaces a custom tag can implement instead of inheriting.
