Skip to content

Commit

Permalink
feat: add lustre elements creation
Browse files Browse the repository at this point in the history
  • Loading branch information
ghivert committed Jul 10, 2024
1 parent 8a69f4e commit 7b0a02f
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 1 deletion.
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,59 @@ 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!

> 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`!
> 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
Expand Down
4 changes: 3 additions & 1 deletion src/sketch/lustre.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
93 changes: 93 additions & 0 deletions src/sketch/lustre/element.gleam
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 7b0a02f

Please sign in to comment.