Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rendering moon components outside surface templates [MDS-248] #421

Merged
merged 1 commit into from
Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ node_modules
#iDEA
*.iml
/.idea
/.vscode
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ If `./run-locally-dev.sh` is not working, try the following steps:
`asdf plugin add nodejs`
2. Run `asdf install`. This will install all the tool versions specified in the _.tool_versions_ file
Or you can install each tool version manually by typing:
`asdf install erlang` # and follow https://github.com/asdf-vm/asdf-erlang#asdf-erlang on fail (e.g. `export KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac"`)
`asdf install erlang` # and follow https://github.com/asdf-vm/asdf-erlang#asdf-erlang on fail (e.g. `export KERL_CONFIGURE_OPTIONS="--without-wx --without-javac"`)
`asdf install elixir`
`asdf install nodejs`

Expand Down
2 changes: 1 addition & 1 deletion lib/moon/components/select/dropdown/icon.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Moon.Components.Select.Dropdown.Icon do

~F"""
<div class={@class} style={@style}>
{Moon.RenderHelpers.render_component(module, props)}
{Moon.Helpers.MoonRender.moon_component(module, props)}
</div>
"""
end
Expand Down
63 changes: 63 additions & 0 deletions lib/moon/helpers/moon_render.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
defmodule Moon.Helpers.MoonRender do
@moduledoc """
Functions for rendering Moon components in HEEX/SLIM/... (not Surface) templates
"""

import Phoenix.LiveView.HTMLEngine, only: [component: 3]
import Phoenix.Component, only: [live_component: 1]

defp get_default_props(module) do
Enum.reduce(module.__props__(), %{__context__: %{}}, fn
%{name: name, opts: opts}, acc ->
Map.put(acc, name, Keyword.get(opts, :default))
end)
end

defp transform_slots(
props = %{inner_block: [%{__slot__: :inner_block, inner_block: inner_block}]}
) do
props
|> Map.delete(:inner_block)
|> Map.put(:default, [%{__slot__: :default, inner_block: inner_block}])
end

defp transform_slots(props), do: props

@doc "used for rendering stateless Moon componet"
def moon_component(module, props) do
component(
&module.render/1,
get_default_props(module) |> Map.merge(transform_slots(props)),
{__ENV__.module, __ENV__.function, __ENV__.file, __ENV__.line}
)
end

def moon_component(props = %{module: module}),
do: moon_component(module, props |> Map.delete(:module))

@doc "used for rendering live (stateful) Moon componet"
def moon_live_component(props = %{module: module}) do
get_default_props(module) |> Map.merge(transform_slots(props)) |> live_component()
end

def moon_live_component(module, props),
do: moon_live_component(Map.put(props, :module, module))

defp get_render_function(module) do
%{kind: :component} = module.__live__()
&moon_live_component/1
rescue
[UndefinedFunctionError, MatchError] ->
&moon_component/1
end

@doc """
A function for rendering Surface components inside non-surface templates
usage: `<.moon module="Button">hit me!</.moon>`
Please note that only limited functional is supported, e.g. context get & named
slots are out of scope, sorry.
"""
def moon(props = %{module: module}) do
get_render_function(module).(props)
end
end
39 changes: 0 additions & 39 deletions lib/moon/render_helpers.ex

This file was deleted.

1 change: 1 addition & 0 deletions lib/moon_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ defmodule MoonWeb do
import Routes, only: [static_path: 2]

import MoonWeb.Helpers.Routes
import MoonWeb.Helpers.Html
end
end

Expand Down
4 changes: 1 addition & 3 deletions lib/moon_web/components/code_snippet.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ defmodule MoonWeb.Components.CodeSnippet do

def render(assigns) do
~F"""
<pre class="moon-design-dark w-full bg-goku-100 overflow-scroll p-4 text-moon-14 text-bulma-100 rounded-moon-s-sm">
<#slot />
</pre>
<pre class="moon-design-dark w-full bg-goku-100 overflow-scroll p-4 text-moon-14 text-bulma-100 rounded-moon-s-sm"><code><#slot /></code></pre>
"""
end
end
2 changes: 1 addition & 1 deletion lib/moon_web/components/left_menu.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ defmodule MoonWeb.Components.LeftMenu do
<SidebarLink route={Pages.Components.CarouselPage}>Carousel</SidebarLink>
<Accordion
is_content_inside={false}
id="left-menu-components-select"
id="left-menu-components-charts"
open_by_default={active_page_contains(@active_page, Pages.Components.Charts)}
>
<:title>Charts</:title>
Expand Down
4 changes: 3 additions & 1 deletion lib/moon_web/components/page_section.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ defmodule MoonWeb.Components.PageSection do
"flex flex-col w-full gap-6",
"basis-1/2": @image
}>
<h2 :if={@title} class="text-moon-24 font-medium">{@title}</h2>
<a :if={@title} href={"#" <> get_as_anchor_id(@title)}>
<h2 id={get_as_anchor_id(@title)} class="text-moon-24 font-medium">{@title}</h2>
</a>
<div class="w-full flex flex-col gap-2 text-moon-16">
<#slot />
</div>
Expand Down
6 changes: 1 addition & 5 deletions lib/moon_web/components/preview_code_button.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule MoonWeb.Components.PreviewCodeButton do
@moduledoc false

use Moon.StatelessComponent
use MoonWeb, :stateless_component

alias Moon.Components.Button

Expand Down Expand Up @@ -39,8 +39,4 @@ defmodule MoonWeb.Components.PreviewCodeButton do
</div>
"""
end

def get_as_anchor_id(title) do
title |> String.replace(" ", "-")
end
end
2 changes: 1 addition & 1 deletion lib/moon_web/components/sidebar_link.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule MoonWeb.Components.SidebarLink do
def render(assigns) do
~F"""
<a
data-phx-link="patch"
data-phx-link="redirect"
data-phx-link-state="push"
data-moon-active={@active_page == @route}
href={live_path(MoonWeb.Endpoint, @route, theme_name: @theme_name, direction: @direction)}
Expand Down
16 changes: 13 additions & 3 deletions lib/moon_web/components/started/for_developer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,31 @@ defmodule MoonWeb.Components.Started.ForDeveloper do
alias MoonWeb.Components.CodeSnippet
alias MoonWeb.Components.PageSection

data(theme_name, :string, from_context: :theme_name)
data(direction, :string, from_context: :direction)

def render(assigns) do
~F"""
<PageSection title="Requirements">
<p>
Can be used for new and existing Phoenix project that uses LiveView 0.7.x
Can be used for new and existing Phoenix project that uses LiveView
</p>
<p>
Moon Surface components can be used from SLIM and EEX, but recommended new format is
Moon Surface components
<a
class="text-piccolo-100 font-medium transition-colors duration-200 hover:text-hit visited:text-hit"
href={"#{live_path(MoonWeb.Endpoint, MoonWeb.Pages.PhoenixUsagePage, theme_name: @theme_name, direction: @direction)}"}
>
can be used from SLIM and EEX templates
</a>
, but recommended format is
<a
class="text-piccolo-100 font-medium transition-colors duration-200 hover:text-hit visited:text-hit"
href="https://surface-ui.org/"
rel="nofollow"
target="_blank"
>
Surface
Surface v0.7.x
</a>
+
<a
Expand Down
9 changes: 9 additions & 0 deletions lib/moon_web/helpers/html.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule MoonWeb.Helpers.Html do
@moduledoc """
Some interal helpers for html-escaping and transforming
"""

def get_as_anchor_id(title) do
title |> String.replace(" ", "-")
end
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule MoonWeb.Hooks.Globals do
defmodule MoonWeb.Hooks.SetGlobalParams do
@moduledoc """
Setting globals - site-wide params for (almost) all LiveViews
"""
Expand Down
129 changes: 129 additions & 0 deletions lib/moon_web/pages/phoenix_usage_page.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
defmodule MoonWeb.Pages.PhoenixUsagePage do
@moduledoc false

use MoonWeb, :live_view

alias MoonWeb.Components.CodeSnippet
alias MoonWeb.Components.Page
alias MoonWeb.Components.PageSection

data(breadcrumbs, :any,
default: [
%{
to: "/usage_with_phoenix_templates",
name: "Usage with Phoenix templates"
}
]
)

def render(assigns) do
~F"""
<Page {=@theme_name} {=@active_page} {=@breadcrumbs} {=@direction}>
<h1 class="text-moon-32 font-semibold">Usage with Phoenix templates</h1>
<PageSection>
<p>
If you find a Moon component you want to use in your existing non-surface templates the recommended way
is to replace the live views (or components) that will use the desired component with their Surface's counterparts. See
<a
class="text-piccolo-100 font-medium transition-colors duration-200 hover:text-hit visited:text-hit"
href="https://surface-ui.org/usage_with_phoenix_templates"
rel="nofollow"
target="_blank"
>
Surface guides
</a>
</p>
<p>
Another solution is to use Moon helpers for rendering components in PhoenixLiveView templates, i.e. using ~H or in *.heex files, is partially
supported, however, this approach is limited and mostly not recommended. Keep in mind that Surface extends Liveview, adding extra features
like slots and contexts, that are partially supported by helpers.
</p>
</PageSection>
<PageSection title="Examples">
<p>Imagine you want to insert Button component to a template. Using Surface your code will look like</p>
<CodeSnippet>{surface_code()}</CodeSnippet>
<p>The same with LiveView only</p>
<CodeSnippet>{liveview_code()}</CodeSnippet>
<p>
Please note that moon_component function can render only stateless component. For stateful one you should use
moon_live_component function. And moon function can be used for both types of components.
</p>
</PageSection>
<PageSection title="Context">
<p>
Context usage is a bit tricky. You have to pass it as a Map to the __context__ attribute. And it will not be populated to the
child components within template. See examples
</p>
<CodeSnippet>{surface_context_code()}</CodeSnippet>
<CodeSnippet>{liveview_context_code()}</CodeSnippet>
</PageSection>
</Page>
"""
end

def surface_code() do
"""
alias Moon.Components.Button

def render(assigns) do
~F\"""
...
<Button variant="secondary">Secondary</Button>
...
\"""
end
"""
end

def liveview_code() do
"""
alias Moon.Components.Button

import Moon.Helpers.MoonRender

def render(assigns) do
~H\"""
...
<.moon module={Button} variant="secondary">Secondary</.moon>

# or, if you wish more explicit naming - use for stateless component
<.moon_component module={Button} variant="secondary">Secondary</.moon_component>
...
\"""
end
"""
end

def surface_context_code() do
"""
# with Surface
def render(assigns) do
~F\"""
...
<Context put={locale: @locale}>
<Button variant="secondary">Secondary</Button>
</Context>
...
\"""
end
"""
end

def liveview_context_code() do
"""
# without Surface
def render(assigns) do
~H\"""
...
<.moon module={Button}
variant="secondary"
__context__={%{locale: @locale}}
>
Secondary
</.moon>
...
\"""
end
"""
end
end
Loading