-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
252 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: | ||
|
||
 | ||
|
||
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 | ||
|
||
 | ||
|
||
### Missing required property | ||
|
||
 | ||
|
||
### Unknown property | ||
|
||
 | ||
|
||
### Unhandled event | ||
|
||
 | ||
|
||
## 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). |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.