Partas.Solid
1.0.2
dotnet add package Partas.Solid --version 1.0.2
NuGet\Install-Package Partas.Solid -Version 1.0.2
<PackageReference Include="Partas.Solid" Version="1.0.2" />
<PackageVersion Include="Partas.Solid" Version="1.0.2" />
<PackageReference Include="Partas.Solid" />
paket add Partas.Solid --version 1.0.2
#r "nuget: Partas.Solid, 1.0.2"
#addin nuget:?package=Partas.Solid&version=1.0.2
#tool nuget:?package=Partas.Solid&version=1.0.2
<div id="top"></div>
<br />
<div align="center"> <a href="https://github.com/shayanhabibi/Partas.Solid" target="_blank"> <img src="https://github.com/shayanhabibi/Partas.Solid/blob/master/Public/Partas_d00b_00a%20icon.png" height="42px"/> </a> <h3 align="center">Partas.Solid</h3> <p align="center"> <img src="https://www.solidjs.com/img/logo/without-wordmark/logo.svg" height="24px" style="border-radius:8px;" /> <kbd>Solid-JS wrapper in Oxpecker style.</kbd> <img src="https://fsharp.org/img/logo/fsharp256.png" height="24px" /> </p> </div>
<div align="center">
</div>
Getting Started
Related Repositories
Solid-ui (Shadcn port) Partas.Solid.UI
Bindings for different libraries Partas.Solid.Bindings
Oxpecker.Solid
This is an opinionated fork of Oxpecker.Solid that keeps the original DSL style, but more aggressively transforms F# input to produce correct JSX.
Please support the original release.
Differences
<details> <summary>Aggressive transformation of AST</summary> <p>
Reduce undefined behaviour
I feel this iteration has less specific pattern matchers, which prevents what might some deem as undocumented behaviour.
As an example, currently Oxpecker would perform the following conversion:
let mutable show = true
[<SolidComponent>]
let Button () =
let this = button() {
"some boiler plate"
}
div(class'="MyButton") {
if show then this else ()
}
export let show = createAtom(true);
export function Button() {
return <button>
some boiler plate
</button>;
}
As opposed to Partas:
export let show = createAtom(true);
export function Button() {
const this$ = <button>
some boiler plate
</button>;
return <div class="MyButton">
{show() ? this$ : undefined}
</div>;
}
Using ternary conditional expressions in solid-js works, although there is
also the <Match>
or <Show>
tags.
</p>
<p align="right">(<a href="#top">back to top</a>)</p> </details>
<details> <summary> Ability to define custom components/tags and use them in the same DSL style </summary>
<p>
Partas.Solid provides an extra attribute which can be applied to members of a Tag type definition using props
as the self identifier. The self identifier, props, allows type safe access to your defined properties which can be set in Oxpecker style.
[<Erase>]
type MyCustomDiv() =
inherit div()
[<Erase>]
member val bordered: bool = jsNative with get,set
[<SolidTypeComponent>]
member props.constructor =
// the props self identifier is a requirement
// the member name has no influence on output
div(class' = if props.bordered then "border border-border" else "") { props.children }
[<SolidComponent>]
let App() =
MyCustomDiv(bordered = true) {
"Hello world!"
}
</p>
<p align="right">(<a href="#top">back to top</a>)</p>
</details>
Example
See the Partas.Solid.UI.Playground for a comprehensive example of a component library built ENTIRELY in F# using <kbd>Partas.Solid</kbd>, <kbd>Tailwind</kbd> and a host of other libraries like <kbd>TanStack Table</kbd> and <kbd>Kobalte</kbd>.
<details>
<summary> A comprehensive component and example output </summary>
[<Erase>]
type Sidebar() =
inherit div()
member val side: sidebar.Side = unbox null with get,set
member val variant: sidebar.Variant = unbox null with get,set
member val collapsible: sidebar.Collapsible = unbox null with get,set
[<SolidTypeComponentAttribute>]
member props.constructor =
props.side <- Left
props.variant <- sidebar.Sidebar
props.collapsible <- Offcanvas
let ctx = Context.useSidebar()
let (isMobile, state, openMobile, setOpenMobile) = (ctx.isMobile, ctx.state, ctx.openMobile, ctx.openMobile)
Switch() {
Match(when' = (props.collapsible = sidebar.None)) {
div(class' = Lib.cn [|
"test flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground"
props.class'
|]).spread props
{ props.children }
}
Match(when' = isMobile()) {
Sheet( open' = openMobile(), onOpenChange = !!setOpenMobile )
.spread props {
SheetContent(
class' = "w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden",
position = !!props.side
).data("sidebar", !!sidebar.Sidebar)
.data("mobile", "true")
.style'(createObj [ "--sidebar-width" ==> sidebarWidthMobile ])
{ div(class' = "flex size-full flex-col") { props.children } }
}
}
Match(when' = (isMobile() |> not)) {
// gap handler on desktop
div(
class' = Lib.cn [|
"relative h-svh w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear"
"group-data-[collapsible=offcanvas]:w-0"
"group-data-[side=right]:rotate-180"
if (props.variant = sidebar.Floating || props.variant = sidebar.Inset) then
"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
else "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
|]
)
div(
class' = Lib.cn [|
"fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex"
if props.side = sidebar.Left then
"left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
else "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]"
// Adjust the padding for floating and inset variants.
if props.variant = sidebar.Floating || props.variant = sidebar.Inset then
"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
else "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l"
props.class'
|]
).spread props
{
div(
class' = "flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
).data("sidebar", !!sidebar.Sidebar)
{ props.children }
}
}
}
export function Sidebar(props) {
props = mergeProps({
side: "left", variant: "sidebar", collapsible: "offcanvas",
}, props);
const [PARTAS_LOCAL, PARTAS_OTHERS] = splitProps(props, ["collapsible", "class", "children", "side", "variant"]);
const ctx = Context_useSidebar();
const isMobile = ctx.isMobile;
return <Switch>
<Match when={PARTAS_LOCAL.collapsible === "none"}>
<div
class={twMerge(clsx(["test flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground", PARTAS_LOCAL.class]))}
{...PARTAS_OTHERS} bool:n $={false}>
{PARTAS_LOCAL.children}
</div>
</Match>
<Match when={isMobile()}>
<Sheet open={ctx.openMobile()}
onOpenChange={ctx.openMobile}
{...PARTAS_OTHERS} bool:n $={false}>
<SheetContent class="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
position={PARTAS_LOCAL.side}
data-sidebar="sidebar"
data-mobile="true"
style={{
"--sidebar-width": sidebar_sidebarWidthMobile,
}}>
<div class="flex size-full flex-col">
{PARTAS_LOCAL.children}
</div>
</SheetContent>
</Sheet>
</Match>
<Match when={!isMobile()}>
<div
class={twMerge(clsx(["relative h-svh w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear", "group-data-[collapsible=offcanvas]:w-0", "group-data-[side=right]:rotate-180", ((PARTAS_LOCAL.variant === "floating") ? (true) : (PARTAS_LOCAL.variant === "inset")) ? ("group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]") : ("group-data-[collapsible=icon]:w-[--sidebar-width-icon]")]))}/>
<div
class={twMerge(clsx(["fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex", (PARTAS_LOCAL.side === "left") ? ("left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]") : ("right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]"), ((PARTAS_LOCAL.variant === "floating") ? (true) : (PARTAS_LOCAL.variant === "inset")) ? ("p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]") : ("group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l"), PARTAS_LOCAL.class]))}
{...PARTAS_OTHERS} bool:n $={false}>
<div
class="flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
data-sidebar="sidebar">
{PARTAS_LOCAL.children}
</div>
</div>
</Match>
</Switch>;
}
</details>
Dev
To develop the plugin, ensure you exclude the plugin on compilation:
fable --exclude Partas.Solid.FablePlugin --noCache -o output -e .fs.jsx --run dotnet restore
There are a suite of tests to run to help inform if any changes have broken something else.
I've done my best to heavily document the plugin and the method of transformations.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- Fable.Browser.Dom (>= 2.18.1)
- Fable.Core (>= 4.5.0)
- FSharp.Core (>= 9.0.100)
- Partas.Solid.FablePlugin (>= 1.0.2)
NuGet packages (28)
Showing the top 5 NuGet packages that depend on Partas.Solid:
Package | Downloads |
---|---|
Partas.Solid.Primitives
Bindings for Solid-js primitives |
|
Partas.Solid.TanStack.Table
Bindings for TanStack.Table for Partas.Solid |
|
Partas.Solid.Kobalte
Bindings for Kobalte for Partas.Solid |
|
Partas.Solid.Lucide
Bindings for Solid-Lucide in Partas.Solid |
|
Partas.Solid.Primitives.Keyboard
Bindings for Solid-js primitives |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated | |
---|---|---|---|
1.0.2 | 22 | 6/6/2025 | |
1.0.1 | 19 | 6/6/2025 | |
1.0.0 | 78 | 6/5/2025 | |
1.0.0-alpha97 | 89 | 5/31/2025 | |
1.0.0-alpha96 | 83 | 5/31/2025 | |
1.0.0-alpha92 | 46 | 5/31/2025 | |
1.0.0-alpha91 | 128 | 5/29/2025 | |
1.0.0-alpha9 | 128 | 5/29/2025 | |
1.0.0-alpha8 | 128 | 5/29/2025 | |
1.0.0-alpha7 | 129 | 5/29/2025 | |
1.0.0-alpha6 | 128 | 5/29/2025 | |
1.0.0-alpha5 | 135 | 5/25/2025 | |
1.0.0-alpha4 | 210 | 5/24/2025 | |
1.0.0-alpha3 | 106 | 5/23/2025 | |
1.0.0-alpha2 | 106 | 5/23/2025 | |
1.0.0-alpha11 | 128 | 5/29/2025 | |
1.0.0-alpha1 | 134 | 5/22/2025 | |
0.2.35 | 126 | 5/17/2025 | |
0.2.34 | 151 | 4/29/2025 | |
0.2.33 | 147 | 4/29/2025 | |
0.2.32 | 160 | 4/22/2025 | |
0.2.31 | 149 | 4/21/2025 | |
0.2.29 | 152 | 4/20/2025 | |
0.2.28 | 180 | 4/16/2025 | |
0.2.27 | 166 | 3/22/2025 | |
0.2.26 | 83 | 3/22/2025 | |
0.2.25 | 74 | 3/22/2025 | |
0.2.24 | 101 | 3/21/2025 | |
0.2.23 | 121 | 3/21/2025 | |
0.2.22 | 204 | 3/20/2025 | |
0.2.21 | 143 | 3/20/2025 | |
0.2.20 | 143 | 3/20/2025 | |
0.2.19 | 145 | 3/19/2025 | |
0.2.18 | 135 | 3/16/2025 | |
0.2.17 | 135 | 3/16/2025 | |
0.2.16 | 115 | 3/14/2025 | |
0.2.15 | 133 | 3/14/2025 | |
0.2.14 | 152 | 3/12/2025 | |
0.2.13 | 162 | 3/12/2025 | |
0.2.12 | 157 | 3/12/2025 | |
0.2.11 | 161 | 3/11/2025 | |
0.2.10 | 175 | 3/9/2025 | |
0.2.9 | 237 | 3/9/2025 | |
0.2.8 | 247 | 3/9/2025 | |
0.2.7 | 243 | 3/9/2025 | |
0.2.6 | 269 | 3/8/2025 | |
0.2.5 | 287 | 3/7/2025 | |
0.2.4 | 287 | 3/7/2025 | |
0.2.3 | 271 | 3/7/2025 | |
0.2.2 | 267 | 3/7/2025 | |
0.2.1 | 278 | 3/7/2025 | |
0.2.0 | 289 | 3/6/2025 | |
0.1.0 | 190 | 3/1/2025 |
HOTFIX: Nuget collision with historical local version.