diff --git a/README.md b/README.md index 6a1ee6f..4505d0c 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,63 @@ fn my_other_view(model: Bool) { } ``` +### Helpers for creating elements + +It's a common feature in CSS-in-JS to have something like + +```js +const MyComponent = styled.div` + display: flex; + flex-direction: column; +` +``` + +While we can't target the exact same thing with sketch (because Gleam does not +allow to execute code like this), we have a somewhat similar interface: +[`sketch/lustre/element.element()`](https://hexdocs.pm/sketch/sketch/lustre.html#element). +`sketch/lustre/element` exposes 4 functions to help you build your custom +components easily, by leveraging on Gleam and its type-checker. +`sketch/lustre/element.element()` forwards the attributes and children to +`lustre/element.element()`, and adds styles along the way! + +```gleam +import sketch +import sketch/lustre/element as sketch_element + +/// my_component will be infered as +/// my_component: fn (Attributes(msg), List(Element(msg))) -> Element(msg) +fn my_component(attributes, children) { + sketch_element.element("div", attributes, children, [ + sketch.display("flex"), + sketch.flex_direction("column"), + ]) +} +``` + +While this could feel at first too much, with lot of boilerplate, you could +define a snippet in your favorite editor to create such elements more easily. +It's only 2 more lines of code than a standard CSS-in-JS framework, but here you +leverage the full power of Gleam and Lustre! Give a try, it could quickly become +your de-facto way to define partially-applied functions! + +> [!INFO] +> +> You may wonder why there's no namespace like `sketch/lustre/element/html` like +> Lustre does. We're still trying to figure out the best API to use for sketch +> to integrate properly in Lustre! For sure, such an interface will be added +> along the way! Meanwhile, you only have to type the tag name by hand, instead +> of calling the function, and you still can leverage on the different features +> of sketch, like `memo` & `dynamic`! + +> [!INFO] +> +> If you have some feedbacks on that interface, feel free to open a discussion, +> or talk about it on the gleam Discord! We're constantly trying to improve it, +> to provide a first class Developer Experience! + +`sketch/lustre/element` has 4 functions, mainly to be able to simply use `class` +& `dynamic`, and putting some `memo` on them if you need it. + ## Use with Shadow DOM Sketch can work with a Shadow DOM, in order to hide the compiled styles from the diff --git a/src/sketch/lustre.gleam b/src/sketch/lustre.gleam index 8a000fc..fa0eba5 100644 --- a/src/sketch/lustre.gleam +++ b/src/sketch/lustre.gleam @@ -64,7 +64,9 @@ fn put_in_head(el: Element(a), content: String) { } @target(erlang) -pub fn ssr(el: Element(a), cache: Cache) { +/// Take an Element, and overloads the content with the correct styles from sketch. +/// Can only be used on BEAM. +pub fn ssr(el: Element(a), cache: Cache) -> Element(a) { cache |> sketch.render() |> result.map(fn(content) { diff --git a/src/sketch/lustre/element.gleam b/src/sketch/lustre/element.gleam new file mode 100644 index 0000000..ddedc86 --- /dev/null +++ b/src/sketch/lustre/element.gleam @@ -0,0 +1,93 @@ +import gleam/list +import lustre/attribute.{type Attribute} +import lustre/element.{type Element} +import sketch.{type Style} as s + +/// Equivalent to +/// ```gleam +/// fn component() { +/// let class = +/// sketch.class(styles) +/// |> sketch.to_lustre +/// element.element(tag, [class, ..attributes], children) +/// } +/// ``` +pub fn element( + tag tag: String, + attributes attributes: List(Attribute(msg)), + children children: List(Element(msg)), + styles styles: List(Style(media, pseudo_selector)), +) { + s.class(styles) + |> s.to_lustre() + |> list.prepend(attributes, _) + |> element.element(tag, _, children) +} + +/// Equivalent to +/// ```gleam +/// fn component() { +/// let class = +/// sketch.class(styles) +/// |> sketch.memo +/// |> sketch.to_lustre +/// element.element(tag, [class, ..attributes], children) +/// } +/// ``` +pub fn memo( + tag tag: String, + attributes attributes: List(Attribute(msg)), + children children: List(Element(msg)), + styles styles: List(Style(media, pseudo_selector)), +) { + s.class(styles) + |> s.memo() + |> s.to_lustre() + |> list.prepend(attributes, _) + |> element.element(tag, _, children) +} + +/// Equivalent to +/// ```gleam +/// fn component() { +/// let class = +/// sketch.dynamic(id, styles) +/// |> sketch.to_lustre +/// element.element(tag, [class, ..attributes], children) +/// } +/// ``` +pub fn dynamic( + tag tag: String, + attributes attributes: List(Attribute(msg)), + children children: List(Element(msg)), + id id: String, + styles styles: List(Style(media, pseudo_selector)), +) { + s.dynamic(id, styles) + |> s.to_lustre() + |> list.prepend(attributes, _) + |> element.element(tag, _, children) +} + +/// Equivalent to +/// ```gleam +/// fn component() { +/// let class = +/// sketch.dynamic(id, styles) +/// |> sketch.memo +/// |> sketch.to_lustre +/// element.element(tag, [class, ..attributes], children) +/// } +/// ``` +pub fn memo_dynamic( + tag tag: String, + attributes attributes: List(Attribute(msg)), + children children: List(Element(msg)), + id id: String, + styles styles: List(Style(media, pseudo_selector)), +) { + s.dynamic(id, styles) + |> s.to_lustre() + |> list.prepend(attributes, _) + |> element.element(tag, _, children) +}