surface-ui / surface_bulma Goto Github PK
View Code? Open in Web Editor NEWA set of simple Surface components based on Bulma
License: MIT License
A set of simple Surface components based on Bulma
License: MIT License
First components that I can think of:
If you are working on any of these, please leave a comment and I will edit this list. If I missed something, let me know!
I was trying to use bulma's CSS styles sing CDN and when I try to put inside the layout_view.ex
.
The phoenix not compile.
I installed doing by:
npm install --save-dev bulma
and adding on the app.scss
:
@import 'bulma';
If not solved yet, could we have a discussion on how to do stuff like Modal where you pass it a show true/false.
To use a modal (from this lib) you need a liveview or a live component that controls it.
How do we set up demos in surface catalogue for componets like that?
At the moment, I am implementing some edit forms to use Modal.Card
, like this one:
Before the last update, I had implemented a Modal.Card
myself, which I got rid of now because it had a similar design. It looks reasonably well, but there are some minor issues:
ESC
-Button or clicking the background would be niceCard
component into the Form
(see below), but I want to show the form submit button inside the modal footer.<Form ...>
<Card ...>
<Header>Title</Header>
...some form fields....
<Footer>
<Form.Submit .../>
<Button ...>Cancel</Button>
</Footer>
</Card>
</Form>
mix test test/bulma_test/surface_bulma/form_test.exs
I am looking forward to use surface bulma on a project.
I have a Table with a date column and I'm trying to use the sort_by
prop described in the documentation for Column:
<Column label="Date" sort_by={{ {fn i -> Map.get(i, :datefield) end, &Date.compare/2} }}>
{{ formatted_date(row.inserted_at) }}
</Column>
This results in the following error:
invalid value for attribute "phx-value-value". Expected a type that implements the String.Chars protocol (e.g. string, boolean, integer, atom, ...), got: {#Function<1.56407787/1 in MyAppWeb.Live.Components.Table.render/1>, &Date.compare/2}
I'm thinking the problem happens when SurfaceBulma.Table
passes col.sort_by
as the phx-value-value
prop for the anchor tag for sorted_click
events, since phx-value-value
appears to need a string but is getting the tuple instead?
Or am I doing something wrong? I'm pretty new to both Surface and LiveView. Thanks in advance!
When the data
prop of SurfaceBelma.Table
is changed, if the internal sorted_by
assign is nil, then sorted_data/1
returns the new data
value only if the sorted_data
assign is nil, which is true only on mount.
As a result, the table does not render changes made to the data
prop until it is remounted or resorted.
(As a workaround, I was able to force a refresh by calling send_update/3
to trigger an update, passing sorted_data
as an assign set to the updated value of data
, so that it would be the value returned by sorted_data/1
.)
If I can find the time in the next few days I'll try to submit a fix, but I wanted to get this recorded before I forget.
Thanks for all the work going on here to build reusable components for live_view using Bulma.
Recently the new implementation of Panel
replaced my own rather naive one, but a default slot is missing. Looking into it, the new Panel seems to be a stateful component handling switching tabs etc.
Since now I had the impression that most components in surface_bulma
were intentionally stateless and focused on composing the structure needed to make use of Bulma properly.
Concerning Panel
, it should work without any tabs at all, as they are optional, like multiple panel-blocks
. And for handling tab switching, it has to be considered whether it is just an UI matter and could be done on the client (via apine.js, etc.) or if it requires behavior on the server (data fetching, etc) that should be handled by LiveView.
Imho, surface_bulma
should enable both by keeping components as lean and stateless as possible by focusing on composing the presentation needed for Bulma. On top of that, there could be components that make use of these presentations to enable behaviours such as stateful tabs.
Another example would be a Dropdown
component. I am working on an autocomplete component based on Bulmas Dropdown, because it is very flexible in displaying the content. In context of an autocomplete function it does make sense to handle state in my component, as there is open/closing the dropdown, a search string, fetching data, selecting an item, switching the trigger component from an input to a more beautiful representation of the item, etc.
But in general, if the Dropdown
is just presenting a static bunch of information or links that are followed, a stateless component with properties setting the required classes ("is-active"/"is-hoverable") should be fine. So the component user has the choice how to toggle its state. And when properly thought out, it is easy to extend with stateful features.
Yet another example would be static tables vs interactive ones that enable sorting and such.
Sorry for writing that much, I just thought it would be wise to point out the direction of the library before we start implementing many components this way or the other.
The other point is that I'd probably wait until surface will work well with live_view >= 0.17 before diving into bulma components more deeply. The recent changes decenty messed up my development environment 😄
This requires scss work and will not work with CDN linked bulma.
Not sure what to do here.
Expose the scss stuff and let people link it in as an asset?
The scss to enable this is pretty trivial: https://gist.github.com/alalfakawma/b94de00f31895b91077a2e3f71ace1fe
But in general a lot around bulma revolves around altering variables in a scss setting, so the library should probably enable a way to utilize scss stuff from priv or asset folder, then expose that back out as something you can use in a ´link´ element.
Would appreciate feedback / oppinions @msaraiva :)
Some chosen deps
:
[ {:phoenix, "~> 1.7.7"},
{:phoenix_ecto, "~> 4.4"},
{:phoenix_html, "~> 3.3"},
{:phoenix_live_dashboard, "~> 0.7.0"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 0.18.18"},
{:surface, "~> 0.10.0"},
{:surface_bulma, github: "surface-ui/surface_bulma"},
# ...
]
Minimal reproduction:
defmodule PainWeb.Order do
use PainWeb, :surface_live_view
alias SurfaceBulma.Title
def render(assigns) do
~F"""
<Title size="3" class="" >Schedule an appointment</Title>
"""
end
end
...renders:
defmodule PainWeb.Order do
use PainWeb, :surface_live_view
alias SurfaceBulma.Title
def render(assigns) do
~F"""
<Title size="3">Schedule an appointment</Title>
"""
end
end
...errors:
Exception:
** (ArgumentError) argument error
:erlang.++(["title", "is-3", {:"is-spaced", nil} | nil], [])
(surface 0.10.0) lib/surface/type_handler/css_class.ex:35: Surface.TypeHandler.CssClass.expr_to_value/3
(surface 0.10.0) lib/surface/type_handler.ex:150: Surface.TypeHandler.expr_to_value!/7
(surface_bulma 0.4.0) lib/surface_bulma/title.ex:18: anonymous fn/2 in SurfaceBulma.Title."render (overridable 1)"/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:398: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:544: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
(elixir 1.15.4) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:396: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:544: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
(elixir 1.15.4) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:396: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:544: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
(elixir 1.15.4) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:396: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:139: Phoenix.LiveView.Diff.render/3
(phoenix_live_view 0.18.18) lib/phoenix_live_view/static.ex:252: Phoenix.LiveView.Static.to_rendered_content_tag/4
(phoenix_live_view 0.18.18) lib/phoenix_live_view/static.ex:135: Phoenix.LiveView.Static.render/3
(phoenix_live_view 0.18.18) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
(phoenix 1.7.7) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
(pain 0.1.0) lib/pain_web/endpoint.ex:1: PainWeb.Endpoint.plug_builder_call/2
Code:
nofile
No code available.
Called with 2 arguments
["title", "is-3", {:"is-spaced", nil} | nil]
[]
lib/surface/type_handler/css_class.ex
30 expr_to_value(value, opts, ctx)
31 end
32
33 def expr_to_value(clauses, opts, ctx) do
34 value =
35> Enum.reduce(clauses ++ opts, [], fn item, classes ->
36 case item do
37 list when is_list(list) ->
38 case expr_to_value(list, [], ctx) do
39 {:ok, new_classes} -> Enum.reverse(new_classes) ++ classes
40 error -> error
lib/surface/type_handler.ex
145 IOHelper.compile_error(message, meta.file, meta.line)
146 end
147 end
148
149 def expr_to_value!(type, name, clauses, opts, module, original, ctx) do
150> case handler(type).expr_to_value(clauses, opts, ctx) do
151 {:ok, value} ->
152 value
153
154 {:error, value} ->
155 message = runtime_error_message(type, name, value, module, original)
lib/surface_bulma/title.ex
13 prop spaced, :boolean
14
15 slot default
16
17 def render(assigns) do
18> ~F"""
19 <h1 :if={@size == "1"} class={["title", "is-1", "is-spaced": @spaced] ++ @class}><#slot /></h1>
20 <h2 :if={@size == "2"} class={["title", "is-2", "is-spaced": @spaced] ++ @class}><#slot /></h2>
21 <h3 :if={@size == "3"} class={["title", "is-3", "is-spaced": @spaced] ++ @class}><#slot /></h3>
22 <h4 :if={@size == "4"} class={["title", "is-4", "is-spaced": @spaced] ++ @class}><#slot /></h4>
23 <h5 :if={@size == "5"} class={["title", "is-5", "is-spaced": @spaced] ++ @class}><#slot /></h5>
lib/phoenix_live_view/diff.ex
393 changed?
394 ) do
395 {_counter, diff, children, pending, components, template} =
396 traverse_dynamic(
397 socket,
398> invoke_dynamic(rendered, false),
399 %{},
400 pending,
401 components,
402 template,
403 changed?
lib/phoenix_live_view/diff.ex
539 Enum.reduce(dynamic, {0, %{}, children, pending, components, template}, fn
540 entry, {counter, diff, children, pending, components, template} ->
541 child = Map.get(children, counter)
542
543 {serialized, child_fingerprint, pending, components, template} =
544> traverse(socket, entry, child, pending, components, template, changed?)
545
546 # If serialized is nil, it means no changes.
547 # If it is an empty map, then it means it is a rendered struct
548 # that did not change, so we don't have to emit it either.
549 diff =
lib/enum.ex
No code available.
lib/phoenix_live_view/diff.ex
391 components,
392 template,
393 changed?
394 ) do
395 {_counter, diff, children, pending, components, template} =
396> traverse_dynamic(
397 socket,
398 invoke_dynamic(rendered, false),
399 %{},
400 pending,
401 components,
lib/phoenix_live_view/diff.ex
539 Enum.reduce(dynamic, {0, %{}, children, pending, components, template}, fn
540 entry, {counter, diff, children, pending, components, template} ->
541 child = Map.get(children, counter)
542
543 {serialized, child_fingerprint, pending, components, template} =
544> traverse(socket, entry, child, pending, components, template, changed?)
545
546 # If serialized is nil, it means no changes.
547 # If it is an empty map, then it means it is a rendered struct
548 # that did not change, so we don't have to emit it either.
549 diff =
lib/enum.ex
No code available.
lib/phoenix_live_view/diff.ex
391 components,
392 template,
393 changed?
394 ) do
395 {_counter, diff, children, pending, components, template} =
396> traverse_dynamic(
397 socket,
398 invoke_dynamic(rendered, false),
399 %{},
400 pending,
401 components,
lib/phoenix_live_view/diff.ex
539 Enum.reduce(dynamic, {0, %{}, children, pending, components, template}, fn
540 entry, {counter, diff, children, pending, components, template} ->
541 child = Map.get(children, counter)
542
543 {serialized, child_fingerprint, pending, components, template} =
544> traverse(socket, entry, child, pending, components, template, changed?)
545
546 # If serialized is nil, it means no changes.
547 # If it is an empty map, then it means it is a rendered struct
548 # that did not change, so we don't have to emit it either.
549 diff =
lib/enum.ex
No code available.
lib/phoenix_live_view/diff.ex
391 components,
392 template,
393 changed?
394 ) do
395 {_counter, diff, children, pending, components, template} =
396> traverse_dynamic(
397 socket,
398 invoke_dynamic(rendered, false),
399 %{},
400 pending,
401 components,
lib/phoenix_live_view/diff.ex
134 render(%{socket | fingerprints: new_fingerprints()}, rendered, new_components(uuids))
135 end
136
137 def render(%{fingerprints: prints} = socket, %Rendered{} = rendered, components) do
138 {diff, prints, pending, components, nil} =
139> traverse(socket, rendered, prints, %{}, components, nil, true)
140
141 # cid_to_component is used by maybe_reuse_static and it must be a copy before changes.
142 # However, given traverse does not change cid_to_component, we can read it now.
143 {cid_to_component, _, _} = components
144
lib/phoenix_live_view/static.ex
247 Phoenix.HTML.Tag.content_tag(tag, "", attrs)
248 end
249
250 defp to_rendered_content_tag(socket, tag, view, attrs) do
251 rendered = Utils.to_rendered(socket, view)
252> {_, diff, _} = Diff.render(socket, rendered, Diff.new_components())
253 Phoenix.HTML.Tag.content_tag(tag, {:safe, Diff.to_iodata(diff)}, attrs)
254 end
255
256 defp load_live!(view_or_component, kind) do
257 case view_or_component.__live__() do
lib/phoenix_live_view/static.ex
130 {:data, data_attrs}
131 | extended_attrs
132 ]
133
134 try do
135> {:ok, to_rendered_content_tag(socket, tag, view, attrs), socket.assigns}
136 catch
137 :throw, {:phoenix, :child_redirect, redirected, flash} ->
138 {:stop, Utils.replace_flash(%{socket | redirected: redirected}, flash)}
139 end
140
lib/phoenix_live_view/controller.ex
34 end
35 end
36
37 """
38 def live_render(%Plug.Conn{} = conn, view, opts \\ []) do
39> case LiveView.Static.render(conn, view, opts) do
40 {:ok, content, socket_assigns} ->
41 conn
42 |> Phoenix.Controller.put_view(LiveView.Static)
43 |> Phoenix.Controller.render(
44 "template.html",
lib/phoenix/router.ex
425 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
426 halted_conn
427
428 %Plug.Conn{} = piped_conn ->
429 try do
430> plug.call(piped_conn, plug.init(opts))
431 else
432 conn ->
433 measurements = %{duration: System.monotonic_time() - start}
434 metadata = %{metadata | conn: conn}
435 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
lib/pain_web/endpoint.ex
1> defmodule PainWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :pain
3
4 # The session will be stored in the cookie and signed,
5 # this means its contents can be read but not tampered with.
6 # Set :encryption_salt if you would also like to encrypt it.
%{}
%{"_csrf_token" => "B6waDcMoAdKS0i7arAxuhW9p"}
Because surface_bulma >= 0.4.0 depends on surface ~> 0.7.0 and your app depends on surface ~> 0.11, surface_bulma >= 0.4.0 is forbidden.
So, because your app depends on surface_bulma ~> 0.4, version solving failed.
Sources:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.