Skip to content

Commit

Permalink
Update README and add LICENSE.md
Browse files Browse the repository at this point in the history
  • Loading branch information
msaraiva committed Dec 8, 2019
1 parent 33d392a commit 96b3a85
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 8 deletions.
22 changes: 22 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# MIT License

Copyright (c) 2019 Marlus Saraiva

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
238 changes: 230 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,245 @@
# Surface

## *** Proof of Concept ***
A component based library for **Phoenix LiveView**.

A component based library for Phoenix
Built on top of the new [LiveComponent](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveComponent.html)
API, Surface provides a more declarative way to express and use components in Phoenix.

Here's an example:

![Example](images/example.png?raw=true)

A lot of the concepts behind it were borrowed from some of the most popular frontend
solutions like `React` and `Vue.js`.

## How does it work?

Surface works as a pre-processor at compile time. It translates components defined in an extended
HTML-like syntax into regular Phoenix templates. It also translates standard HTML nodes allowing
us to extend their behaviour adding new features like syntatic sugar on attributes definition,
directives, scoped styles, validation and more.

In order to have your code translated, you need to use the `~H` sigil when defining your templates.

## Features

* Components as modules
* Declarative properties
* Built-in directives (`:for`, `:if`, ...)
* Syntatic sugar for CSS class properties
* Children components as data
* Contexts
* Compile time checking
* Editor/tools integration
* And more already planned...

> **Note:** Some of the features are still experimental and subject to change.
## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `surface` to your list of dependencies in `mix.exs`:
Install Phoenix LiveView following the [installation guide](https://hexdocs.pm/phoenix_live_view/installation.html).
Then add `surface` to the list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:surface, "~> 0.1.0"}
{:surface, git: "https://github.com/msaraiva/surface.git"}
]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/surface](https://hexdocs.pm/surface).
In order to have Surface functions and macros avaialble in regular Phoenix views,
add the following import to your web file in `lib/my_app_web.ex`:

```elixir
# lib/my_app_web.ex

...

def view do
quote do
...
import Surface
end
end
```

## Defining components

To create a component you need to define a module and `use` one of the available component types:

* **Surface.Component** - A functional (stateless) component.
* **Surface.LiveComponent** - A live (stateless or stateful) component. A wrapper around `Phoenix.LiveComponent`.
* **Surface.LiveView** - A wrapper component around `Phoenix.LiveView`.
* **Surface.DataComponent** - A component that serves as a customizable data holder for the parent component.
* **Surface.MacroComponent** - A low-level component which is responsible for translating its own content at compile time.

### Example

```elixir
# A functional steteless component

defmodule Button do
use Surface.Component

property click, :event
property kind, :string, default: "is-info"

def render(assigns) do
~H"""
<button class="button {{ @kind }}" phx-click={{ @click }} style="margin: 0px 5px">
{{ @inner_content.() }}
</button>
"""
end
end

# A live steteful component

defmodule Dialog do
use Surface.LiveComponent

property title, :string, required: true

def mount(socket) do
{:ok, assign(socket, show: false)}
end

def render(assigns) do
~H"""
<div class={{ "modal", isActive: @show }}>
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ @title }}</p>
</header>
<section class="modal-card-body">
{{ @inner_content.() }}
</section>
<footer class="modal-card-foot" style="justify-content: flex-end">
<Button click="hide">Ok</Button>
</footer>
</div>
</div>
"""
end

# Public API

def show(dialog_id) do
send_update(__MODULE__, id: dialog_id, show: true)
end

# Event handlers

def handle_event("show", _, socket) do
{:noreply, assign(socket, show: true)}
end

def handle_event("hide", _, socket) do
{:noreply, assign(socket, show: false)}
end
end

# A live view component

defmodule Example do
use Surface.LiveView

alias Surface.Components.Button

def render(assigns) do
~H"""
<Dialog title="Alert" id="dialog">
This <b>Dialog</b> is a stateful component. Cool!
</Dialog>

<Button click="show_dialog">Click to open the dialog</Button>
"""
end

def handle_event("show_dialog", _, socket) do
Dialog.show("dialog")
{:noreply, socket}
end
end
```

## Directives

Directives are built-in attributes that can modify the translated code of a component
at compile time. Currently, the following directives are supported:

* `:for` - Iterates over a list (generator) and renders the content of the tag (or component)
for each item in the list.

* `:if` - Conditionally render a tag (or component). The code will be rendered if the expression
is evaluated to a truthy value.

* `:bindings` - Defines the name of the variables (bindings) in the current scope that represent
the values passed internally by the component when calling the `@inner_content` function.

### Example

```jsx
<div>
<div class="header" :if={{ @showHeader }}>
The Header
</div>
<ul>
<li :for={{ item <- @items }}>
{{ item }}
</li>
</ul>
</div>
```

## Static checking

Since components are ordinary Elixir modules, some static checking is already provided
by the compiler. Addicionally, we added a few extra warnings to improve user experience.
Here are some examples:

### Module not available

![Example](images/module_not_available.png?raw=true)

### Missing required property

![Example](images/required_property.png?raw=true)

### Unknown property

![Example](images/unknown_property.png?raw=true)

### Unhandled event

![Example](images/unhandled_event.png?raw=true)

## Tooling

Some experimental work on tooling around the library has been done. Here's a few of them:

### VS Code

- [x] Syntax highlighting

### ElixirSense

- [x] Jump to definition of modules (components)
- [ ] Jump to definition of properties
- [ ] Auto-complete/suggestions for properties (WIP)
- [ ] Show documentation on hover for components and properties

### Other tools

Having a standard way of defining components with typed properties allows us to
enhance tools that introspect information from modules. One already discussed was
the possibility to have `ex_doc` query that information to provide standard
documentation for properties, events, bindings, etc.

## License

Copyright (c) 2019, Marlus Saraiva.

Surface source code is licensed under the [MIT License](LICENSE.md).
Binary file added images/example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/module_not_available.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/required_property.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/unhandled_event.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/unknown_property.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 96b3a85

Please sign in to comment.