WIP (pre-alpha) Ash UI Extension & Component Lib

Hey all. πŸ‘‹ I'm happy to tease the near public release of Plegethon (provisional title), a (very experimental) Ash UI extension and LiveView component library! It's inspired by Petal, AshAdmin, and AshAuthenticationPhoenix. I'm trying very hard to design in the ability to configure/extend nearly every aspect of the components, and to have an Ash-core level of escape hatches/overrides in the extension config. The idea is that you can declare UI config in your Ash resource, then use that in "smart components" that know how to introspect the Ash resource to build themselves. Kind of like AshPhoenix.Form w/ auto: true, but for the UI (examples in images). The override system for components is very similar to AshAuthenticationPhoenix, if you've used that. It adds some other goodies like taking in a color_theme map, and using that to generate a json colors file for Tailwind/Tails and even custom themed (light & dark) Makeup css. πŸ™‚ The components go from whole-page level down to smaller things like forms, filter forms for queries, and datatables. Forms support groups & nesting, and I'm currently working on a wizard syntax. Still working on finishing up form relationships & autocomplete components, that should be ready soon. I have an awesome datatable component in another project, I just need to port it over to this lib. I've been working for quite some time on this, and I'm coming close to releasing this publicly under MIT. I'm interested in opening limited access to some brave souls that want to dig in and help get this to the beta phase. Particularly those are somewhat familiar with writing Ash extensions and/or .heex components and willing to submit PRs. This really isn't ready to be used unless you want to get your hands dirty -- lots of stuff is missing! If you're interested in jumping in, let me know via a DM and I'll consider giving you GitHub access within the next few days. πŸš€
No description
No description
No description
56 Replies
frankdugan3
frankdugan3OPβ€’3y ago
^ One thing to note in the default overrides image is that the class overrides are functions that are passed all the component's assigns, so you can get very specific! There is a merge_classes helper that also runs them through Tails.classes, passing in the components class props as well. This means your custom overrides can still be reliably overridden by props without weird Tailwind conflicts. πŸ™‚
ZachDaniel
ZachDanielβ€’3y ago
Very very excited about this!!! Will absolutely test this out with AshHq and could maybe even see rewriting ash admin with these standardized components!
frankdugan3
frankdugan3OPβ€’3y ago
Did a little cleanup on the override system, and I think I'm pretty happy with it! As an example:
defmodule Phlegethon.Components.Form.Label do
use Phlegethon.Components.Base,
overrides: [
class: "the class of the label"
]

@doc """
Renders a label.
"""
attr :class, :any, default: nil
attr :for, :string, default: nil
slot :inner_block, required: true

def label(assigns) do
~H"""
<label for={@for} class={class_for(:class, assigns)}>
<%= render_slot(@inner_block) %>
</label>
"""
end
end
defmodule Phlegethon.Components.Form.Label do
use Phlegethon.Components.Base,
overrides: [
class: "the class of the label"
]

@doc """
Renders a label.
"""
attr :class, :any, default: nil
attr :for, :string, default: nil
slot :inner_block, required: true

def label(assigns) do
~H"""
<label for={@for} class={class_for(:class, assigns)}>
<%= render_slot(@inner_block) %>
</label>
"""
end
end
override Label do
set :class, "block text-sm font-black text-root-fg dark:text-root-fg-dark"
end
override Label do
set :class, "block text-sm font-black text-root-fg dark:text-root-fg-dark"
end
The class_for macro looks up the override for the module, and figures out how to apply the override (whether it's a function or simple binary/list). So you can also do e.g.:
set :class, &__MODULE__.label_class/1
# ...
def label_class(assigns) do
#...
set :class, &__MODULE__.label_class/1
# ...
def label_class(assigns) do
#...
It then assumes that it should append the attribute with the same key as the override to the end of the class list for runtime overrides, but it also accepts a different key (or list of keys) to get a different override value from the assigns. For example:
# smart_form.ex
defp render_field(%{field: %Phlegethon.Resource.Form.FieldGroup{}} = assigns) do
~H"""
<section class={class_for(:field_group_class, assigns, [:field, :class])}>
# smart_form.ex
defp render_field(%{field: %Phlegethon.Resource.Form.FieldGroup{}} = assigns) do
~H"""
<section class={class_for(:field_group_class, assigns, [:field, :class])}>
This really cleans up the code a lot, ensures classes always run through Tails.classes and makes it easier to grep what's going on! As an added bonus, it makes it trivial to make your own components that leverage all that tooling. Just add use Phlegethon.Components.Base and set your overrides. πŸ™‚ And one more thing: Many of the basic components have the same API as the Phoenix 1.7 core_components, so you can probably swap out core_components for Phlegethon.Components, and have it work out of the box w/ Phoenix generated LiveViews.
ZachDaniel
ZachDanielβ€’3y ago
That sounds awesome!
Terris
Terrisβ€’3y ago
Wow. This is next level. I will use it. A component library that combines resources and tails is the ash way.. but might need something else like Flowbite or headlessUI
dblack
dblackβ€’3y ago
Love the look of this, awesome work! I had to google Phlegethon (turns out I'm not up on Greek mythology). 'River of fire', long may Ash inspire all the names
frankdugan3
frankdugan3OPβ€’3y ago
Yeah, every once in a while I have to do something to justify spending 5 semesters learning Greek. πŸ™ƒ I have been hard at work grinding out more of the essential stuff. Did a lot of refinement on the override system. For one thing, the overrides are set at compile time via application config, so given config.exs:
config :phlegethon, :overrides, [MyApp.CustomOverrides, Phlegethon.Overrides.Default]
config :phlegethon, :overrides, [MyApp.CustomOverrides, Phlegethon.Overrides.Default]
The component is able to access them at compile time, allowing validation in attributes:
attr :color, :string,
default: @override_for[:default_color],
values: @override_for[:colors],
doc: "the color of the progress bar"
attr :color, :string,
default: @override_for[:default_color],
values: @override_for[:colors],
doc: "the color of the progress bar"
And a public function is also provided, allowing runtime access as well:
<%= for color <- Progress.override_for(:colors) do %>
<%= for color <- Progress.override_for(:colors) do %>
This took quite a bit of refactoring, so I'm glad I haven't opened it just yet as that would have been painful for everyone else to endure. πŸ˜„
frankdugan3
frankdugan3OPβ€’3y ago
Another thing I'm quite proud of is I think I came up w/ some great improvements to the Phoenix Flash message component. I made this component a little smarter in that it can accept JSON encoded strings, and it extracts some info from them, such as ttl (time-to-live for auto-closing flashes) and also a title. This allows for some really nice flash messages. This is all done w/ a hook so it's snappy on the client-side, and it also intelligently resets the ttl if the content of the flash has changed, so you don't get a quick-close if you overwrite the same flash type. All of this is configurable, of course. To use the JSON flash, there's a simple encoder function:
socket
|> put_flash(
:success,
encode_flash(~s|User "#{user.name}" successfully created!|, title: "User Generated")
)
socket
|> put_flash(
:success,
encode_flash(~s|User "#{user.name}" successfully created!|, title: "User Generated")
)
No description
No description
frankdugan3
frankdugan3OPβ€’3y ago
Putting a lot of effort into generating good docs. πŸ™‚
No description
frankdugan3
frankdugan3OPβ€’3y ago
Well, after a long day/night and a lot of caffeine, I added an override prop to Phlegethon.Component (by extending/rewriting half of Phoenix.Component.Declarative). πŸ”₯
defmodule Phlegethon.Components.Alert do
use Phlegethon.Component

@doc """
A generic alert component.
"""
@doc type: :component

overridable :color, :string,
required: true,
values: :colors
doc: "the color of the alert"
overridable :class, :class
attr :rest, :global
slot :inner_block, required: true, doc: "the content of the alert"
def alert(assigns) do
~H"""
<div class={@class} {@rest}>
<%= render_slot(@inner_block) %>
</div>
"""
end
end
defmodule Phlegethon.Components.Alert do
use Phlegethon.Component

@doc """
A generic alert component.
"""
@doc type: :component

overridable :color, :string,
required: true,
values: :colors
doc: "the color of the alert"
overridable :class, :class
attr :rest, :global
slot :inner_block, required: true, doc: "the content of the alert"
def alert(assigns) do
~H"""
<div class={@class} {@rest}>
<%= render_slot(@inner_block) %>
</div>
"""
end
end
override Alert, :alert do
set :class, &__MODULE__.alert_class/1
set :color, "info"
set :colors, ~w[info success warning danger]
end
override Alert, :alert do
set :class, &__MODULE__.alert_class/1
set :color, "info"
set :colors, ~w[info success warning danger]
end
So a few notes about the behavior/options: - All overrides get merged in as the default value - required: true option will raise an error if no overrides provide a value -- at compile time! πŸ˜„ - values option can be either an atom, which will pull in overrides for it, or a list, which cannot be overridden - All types can be either the type itself, or an arrity 1 function that accepts assigns and returns the type itself - Adds the :class type, which additionally handles passing in assigns and running if it's a function, and runs it through Tails.classes Edit: hit enter too early, lol
ZachDaniel
ZachDanielβ€’3y ago
πŸ”₯ πŸ”₯ πŸ”₯ That looks really awesome gimme gimme gimme πŸ˜†
frankdugan3
frankdugan3OPβ€’3y ago
HalfLife 3 soon. πŸ˜‰ I uhh... learned a lot about macros doing this, lol A demo:
override Core, :alert do
set :class, &__MODULE__.alert_class/1
# set :color, "info"
set :colors, ~w[info success warning danger]
end
override Core, :alert do
set :class, &__MODULE__.alert_class/1
# set :color, "info"
set :colors, ~w[info success warning danger]
end
== Compilation error in file lib/phlegethon/components/core.ex ==
** (CompileError) lib/phlegethon/components/core.ex:15: cannot find an override setting for :color, please ensure you define one in a configured override module
== Compilation error in file lib/phlegethon/components/core.ex ==
** (CompileError) lib/phlegethon/components/core.ex:15: cannot find an override setting for :color, please ensure you define one in a configured override module
And when corrected, notice the default and values extraction:
%{
doc: "the color of the alert",
line: 15,
name: :color,
opts: [default: "info", values: ["info", "success", "warning", "danger"]],
required: true,
type: :string
}
%{
doc: "the color of the alert",
line: 15,
name: :color,
opts: [default: "info", values: ["info", "success", "warning", "danger"]],
required: true,
type: :string
}
ZachDaniel
ZachDanielβ€’3y ago
Question: can child components be overriden with this system? Or non-simple values like functions? Wondering if we could use this to get some of the flexibility we want in ash_authentication_phoenix Since you essentially modeled it after what @jart did there (but it seems have spent much more time on that part of the pattern) Just wondering if there is some synergy that can be achieved with this.
frankdugan3
frankdugan3OPβ€’3y ago
- Basically if you define an override prop, the logic for generic types is this: prop || override || nil. - For CSS types, it ends up like this: Tails.classes(override || nil, prop). - If you mark an override prop as required, it will throw an error if you don't have an override defined in some included override file - ALL override types can be functions, in which case they will be passed the assigns:
def merge_function_overrides(assigns, []), do: assigns

def merge_function_overrides(assigns, [{name, override} | overrides]) do
if Map.has_key?(assigns, name) do
assigns
else
Map.put(
assigns,
name,
case override do
function when is_function(function, 1) -> apply(function, [assigns])
other -> other
end
)
end
|> merge_function_overrides(overrides)
end

def merge_class_overrides(assigns, []), do: assigns

def merge_class_overrides(assigns, [{name, override} | overrides]) do
assigns
|> Map.put(
name,
Tails.classes([
case override do
function when is_function(function, 1) -> apply(function, [assigns])
other -> other
end,
assigns[name] || nil
])
)
|> merge_class_overrides(overrides)
end
def merge_function_overrides(assigns, []), do: assigns

def merge_function_overrides(assigns, [{name, override} | overrides]) do
if Map.has_key?(assigns, name) do
assigns
else
Map.put(
assigns,
name,
case override do
function when is_function(function, 1) -> apply(function, [assigns])
other -> other
end
)
end
|> merge_function_overrides(overrides)
end

def merge_class_overrides(assigns, []), do: assigns

def merge_class_overrides(assigns, [{name, override} | overrides]) do
assigns
|> Map.put(
name,
Tails.classes([
case override do
function when is_function(function, 1) -> apply(function, [assigns])
other -> other
end,
assigns[name] || nil
])
)
|> merge_class_overrides(overrides)
end
The assigns get merged in this order: normal defaults -> prop assigns -> override functions -> class type override functions.
%{unquote_splicing(defaults)}
|> Map.merge(assigns)
|> merge_function_overrides(unquote(function_overrides))
|> merge_class_overrides(unquote(class_overrides))
|> Map.put(:__given__, assigns)
%{unquote_splicing(defaults)}
|> Map.merge(assigns)
|> merge_function_overrides(unquote(function_overrides))
|> merge_class_overrides(unquote(class_overrides))
|> Map.put(:__given__, assigns)
One big difference from the AshAuthenticationPhoenix override system is that it does not support passing in runtime overrides, just overriding via props. In practice, I'm not sure if that matters or not. But I needed compile-time override definitions to make components behave the way I wanted.
ZachDaniel
ZachDanielβ€’3y ago
oh, interesting.
frankdugan3
frankdugan3OPβ€’3y ago
Also, at this point, I don't think there is any shared implementation code. It's more "inspired by" at this point, lol
ZachDaniel
ZachDanielβ€’3y ago
Makes sense. I think there would likely still be some potential synergy there
frankdugan3
frankdugan3OPβ€’3y ago
I'm a bit biased, but I think this could make quite a splash. In Phoenix-land, even outside Ash. πŸ™ƒ
ZachDaniel
ZachDanielβ€’3y ago
I think you are very much correct πŸ˜„ May also be a big draw for people to use Ash if its got stuff to integrate in fancy ways Is the stuff like smart form in its own separate dependency? Or is it all one package?
frankdugan3
frankdugan3OPβ€’3y ago
ATM, Ash is a hard dependency, but that's just because I haven't looked up how to do optional dependencies/optional compilation. Should be no reason why it would need Ash, and I have the smart components in separate modules that could optionally compile.
ZachDaniel
ZachDanielβ€’3y ago
Honestly, its pretty much just what it sounds like
frankdugan3
frankdugan3OPβ€’3y ago
I plan to put it all in the same package, but to have optional deps.
ZachDaniel
ZachDanielβ€’3y ago
you mark the dep as optional, and then wrap those modules in if Code.ensure_loaded?(Ash) do
frankdugan3
frankdugan3OPβ€’3y ago
Cool. πŸ˜„ How about testing?
ZachDaniel
ZachDanielβ€’3y ago
Well, in your test/dev environments the dep is always present
frankdugan3
frankdugan3OPβ€’3y ago
Marking it as optional only count for :prod env?
ZachDaniel
ZachDanielβ€’3y ago
I don't know the best way to test without it though yeah, exactly
frankdugan3
frankdugan3OPβ€’3y ago
Awesome.
ZachDaniel
ZachDanielβ€’3y ago
You're using spark for the DSLs right? Ah, I guess you aren't because you've got top level things 😒
frankdugan3
frankdugan3OPβ€’3y ago
For the Ash extension? Yeah. Not for the overrides, though. Would it make sense to?
ZachDaniel
ZachDanielβ€’3y ago
If we supported top level builders, yeah but alas, we do not
frankdugan3
frankdugan3OPβ€’3y ago
Ahh, right.
ZachDaniel
ZachDanielβ€’3y ago
I'd add it though, just for you
frankdugan3
frankdugan3OPβ€’3y ago
Well, it's pretty simple anyway. Would be no reason not to swap later, I've already done all the work for it at this point, lol You can take a look soon, open to any feedback on it,. Once I finish this refactor of the core components w/ my new API, I'll give you access even though it's still a bit of a mess in a lot of places. πŸ˜„
jart
jartβ€’3y ago
I'd add it though, just for you
He really wants to add this he keeps asking me if I want it
ZachDaniel
ZachDanielβ€’3y ago
lol I just don't want the lack of it to stop people from using it
jart
jartβ€’3y ago
nah.
frankdugan3
frankdugan3OPβ€’3y ago
...Will you add it and refactor to use it? πŸ™ƒ
jart
jartβ€’3y ago
I'm going to wind up with a DSL like:
component do
prop :foo
slot :bar
event :wat
end
component do
prop :foo
slot :bar
event :wat
end
it's fine
ZachDaniel
ZachDanielβ€’3y ago
lol, probably not I could see Ash using it for the basic resource things like:
use Ash.Resource

description "..."
...
use Ash.Resource

description "..."
...
frankdugan3
frankdugan3OPβ€’3y ago
Thinking it over, perhaps using Spark would be great even if the only benefit I got from it was not having to fight w/ the formatter on re-adding parenthesis every time I have a compile error. lol
jart
jartβ€’3y ago
Now you’re getting it.
frankdugan3
frankdugan3OPβ€’3y ago
For those interested, the repo is now public! https://github.com/frankdugan3/phlegethon There is a lot to work on and lots of stuff half-finished, but I feel like the component API and docs are pretty stable. Here is a list of the top priorities I will be working on this week: - Finish up the Core components (replacement of core_components.ex - <.modal> needs polish - <.table> needs polish - Flesh out the <.smart_form> component - Port my <.smart_data_table> component - Trim out all the deprecated cruft that doesn't need to be in info.ex (ignore that file for now) and finish re-working the tests to work in the lib (they were from a different app originally) - Add in the ability to do multi-step forms in the extension and <.smart_form> component (aka wizard) - Add a nice autocomplete/multiselect component, and make it ash-smart for use in forms w/ relationship management
GitHub
GitHub - frankdugan3/phlegethon: An Ash user interface extension wi...
An Ash user interface extension with smart Phoenix components. - GitHub - frankdugan3/phlegethon: An Ash user interface extension with smart Phoenix components.
frankdugan3
frankdugan3OPβ€’3y ago
To clarify the current state, it's not quite usable as a component lib just yet, so this is more of a thing for those interested in development and checking out the overridable component API, and the :class type. I think it makes for a very composable and clean experience. If you follow the simple dev instructions in the readme, you can check out the component previewer and documentation for yourself. :ashley:
frankdugan3
frankdugan3OPβ€’3y ago
I did try and put some effort into decent docs out of the gate, in particular the override modules and components are self-documenting (it extends Phoenix.Component), and any functions used in overridables will link to the appropriate override module that defines them. It should be very easy to click right through any part of a component or override module and find the source right away.
No description
No description
No description
No description
No description
frankdugan3
frankdugan3OPβ€’3y ago
And FWIW, the project is already almost 8K LoC! No wonder it's been taking me so long to get it out the door. πŸ˜…
───────────────────────────────────────────────────────────────────────────────
Language Files Lines Blanks Comments Code Complexity
───────────────────────────────────────────────────────────────────────────────
Elixir 63 7198 999 370 5829 396
Markdown 6 331 83 0 248 0
JavaScript 4 234 19 39 176 18
JSON 2 82 0 0 82 0
Shell 2 24 6 2 16 3
CSS 1 5 1 0 4 0
License 1 21 4 0 17 0
gitignore 1 33 10 10 13 0
───────────────────────────────────────────────────────────────────────────────
Total 80 7928 1122 421 6385 417
───────────────────────────────────────────────────────────────────────────────
Estimated Cost to Develop (organic) $189,240
Estimated Schedule Effort (organic) 7.31 months
Estimated People Required (organic) 2.30
───────────────────────────────────────────────────────────────────────────────
Processed 236899 bytes, 0.237 megabytes (SI)
───────────────────────────────────────────────────────────────────────────────
───────────────────────────────────────────────────────────────────────────────
Language Files Lines Blanks Comments Code Complexity
───────────────────────────────────────────────────────────────────────────────
Elixir 63 7198 999 370 5829 396
Markdown 6 331 83 0 248 0
JavaScript 4 234 19 39 176 18
JSON 2 82 0 0 82 0
Shell 2 24 6 2 16 3
CSS 1 5 1 0 4 0
License 1 21 4 0 17 0
gitignore 1 33 10 10 13 0
───────────────────────────────────────────────────────────────────────────────
Total 80 7928 1122 421 6385 417
───────────────────────────────────────────────────────────────────────────────
Estimated Cost to Develop (organic) $189,240
Estimated Schedule Effort (organic) 7.31 months
Estimated People Required (organic) 2.30
───────────────────────────────────────────────────────────────────────────────
Processed 236899 bytes, 0.237 megabytes (SI)
───────────────────────────────────────────────────────────────────────────────
Been hard at work over the past couple weeks! The core components is just about "ready" to be used after a significant refactor. I no longer hack Phoenix.Component.Declarative (too much maintenance), and instead use a macro to add the overrides at runtime. There are still quite a few compile time checks and optimizations to ensure things work without much head-scratching. One example is fuzzy-matching on attribute names when it can't match up attribute with override. Going for Elm-level errors. πŸ˜„
attr :classy, :any
def flash(assigns) do
assigns =
assigns
|> assign_overridable(:class, class?: true, required?: true)
attr :classy, :any
def flash(assigns) do
assigns =
assigns
|> assign_overridable(:class, class?: true, required?: true)
** (CompileError) lib/phlegethon/components/core.ex:196: Phlegethon.Component - Invalid Overridable Option

* Component: Phlegethon.Components.Core.flash/1
* Prop: attr :class
* Overridable: assign_overridable(:class, ...)
* Reason: unable to find prop :class on this component
* Similar existing prop names: :classy

Currently only "attr" props are supported.
** (CompileError) lib/phlegethon/components/core.ex:196: Phlegethon.Component - Invalid Overridable Option

* Component: Phlegethon.Components.Core.flash/1
* Prop: attr :class
* Overridable: assign_overridable(:class, ...)
* Reason: unable to find prop :class on this component
* Similar existing prop names: :classy

Currently only "attr" props are supported.
jart
jartβ€’3y ago
Nicely done
frankdugan3
frankdugan3OPβ€’3y ago
Found a nice way to cleanly extend Phoenix.Component.attr/3, so this is the final(?) component override API:
defmodule MyApp.Components.ExternalLink do
@moduledoc """
An external link component.
"""
use Phlegethon.Component

attr :overrides, :list, default: nil, doc: @overrides_attr_doc
attr :class, :tails_classes, overridable: true, required: true
attr :href, :string, required: true
attr :rest, :global, include: ~w[download hreflang referrerpolicy rel target type]
slot :inner_block, required: true

def external_link(assigns) do
assigns = assign_overridables(assigns)
~H"""
<a class={@class} href={@href}} {@rest}>
<%= render_slot(@inner_block) %>
</a>
"""
end
end
defmodule MyApp.Components.ExternalLink do
@moduledoc """
An external link component.
"""
use Phlegethon.Component

attr :overrides, :list, default: nil, doc: @overrides_attr_doc
attr :class, :tails_classes, overridable: true, required: true
attr :href, :string, required: true
attr :rest, :global, include: ~w[download hreflang referrerpolicy rel target type]
slot :inner_block, required: true

def external_link(assigns) do
assigns = assign_overridables(assigns)
~H"""
<a class={@class} href={@href}} {@rest}>
<%= render_slot(@inner_block) %>
</a>
"""
end
end
Has compile-time checks to catch any missing calls/incompatible options. The overridable attrs are accumulated and assigned (in order of definition) by assign_overridables/1. All of the components and documentation have been updated to the new new API. Been dog-fooding it and it's starting to feel pretty good, though I may be biased. πŸ˜„
No description
No description
No description
No description
frankdugan3
frankdugan3OPβ€’3y ago
Oh, and the default style is a dark/light variant on the one Phoenix gives you out of the box.
No description
No description
frankdugan3
frankdugan3OPβ€’3y ago
Just pushed up the basic version of an autocomplete component, along with support for simple relationships in SmartForm. For simple use:
<.simple_form for={@form} phx-change="validate" phx-submit="save">
<.live_component
module={Phlegethon.Components.Autocomplete}
id="fiend_id_autocomplete"
field={@form[:friend_id]}
label="Friend"
search_fn={search_friends/1}
lookup_fn={lookup_friend/1} />
<:actions>
<.button>Save</.button>
</:actions>
</.simple_form>
<.simple_form for={@form} phx-change="validate" phx-submit="save">
<.live_component
module={Phlegethon.Components.Autocomplete}
id="fiend_id_autocomplete"
field={@form[:friend_id]}
label="Friend"
search_fn={search_friends/1}
lookup_fn={lookup_friend/1} />
<:actions>
<.button>Save</.button>
</:actions>
</.simple_form>
There are lots of props/overrides to configure lots of behavior, including a slot for custom templates for the options. To use an autocomplete for simple relationships in Ash w/ a SmartForm:
field :best_friend_id do
label "Best Friend"
type :autocomplete
prompt "Search friends for your bestie"
autocomplete_option_label_key :name_email
end
field :best_friend_id do
label "Best Friend"
type :autocomplete
prompt "Search friends for your bestie"
autocomplete_option_label_key :name_email
end
argument :best_friend_id, :uuid
change manage_relationship(:best_friend_id, :best_friend, type: :append_and_remove)
argument :best_friend_id, :uuid
change manage_relationship(:best_friend_id, :best_friend, type: :append_and_remove)
belongs_to :best_friend, __MODULE__, api: ComponentPreviewer.Ash.Api
belongs_to :best_friend, __MODULE__, api: ComponentPreviewer.Ash.Api
It makes use of hooks and Phoenix.JS so that all the things like moving selection, intercepting key events etc. are all done client-slide for a snappy feel. It should also be relatively accessible, but I'm no expert on that. 🀞 There's still a lot more to add, such as loading indicators, supporting multiple entries, supporting more complex relationship types, and allowing templates for adding new entries on the fly. And of course all the bugs that will naturally pop up. Will be adding to it bit-by-bit!
Terris
Terrisβ€’3y ago
Would it be possible to create a channel for phlegethon or do you want questions/discussion in github issues?
ZachDaniel
ZachDanielβ€’3y ago
Where do you stand on this @frankdugan3 ? Should I make a phlegethon channel or a maple ui channel πŸ˜†
frankdugan3
frankdugan3OPβ€’3y ago
Hmm... I think stick w/ phlegethon channel for now. I don't want to change it twice, and I haven't made up my mind on maple ui as a name yet. πŸ˜„
ZachDaniel
ZachDanielβ€’3y ago
#pyro
frankdugan3
frankdugan3OPβ€’3y ago
I don't mind questions/discussion on either, but if there is an existing issue on the topic, would be better to continue it on the issue to keep it all in one place.
Terris
Terrisβ€’3y ago
Maple is easier than phegle.. <checks notes> I posted my findings in #pyro which I'm quickly learning how to spell.

Did you find this page helpful?