phenixdigital / phoenix_storybook Goto Github PK
View Code? Open in Web Editor NEWA pluggable storybook for your Phoenix components.
License: MIT License
A pluggable storybook for your Phoenix components.
License: MIT License
Hi!
Any ideas how we should document this kind of %Story{}
?
<.tabs>
<.tab is_active to="/">Home</.tab>
<.tab link_type="a" to="/" label="About" />
</.tabs>
I've tried block
:
defmodule BrmblWeb.Storybook.Components.Navigation.Tabs do
use PhxLiveStorybook.Entry, :component
import BrmblWeb.UI.Navigation.Tabs
def function, do: &BrmblWeb.UI.Navigation.Tabs.tabs/1
def stories do
[
%Story{
id: :tabs,
attributes: %{},
block: """
<.tab is_active to="/" label="Tab1" />
<.tab to="/hi" label="Tab2" />
"""
}
]
end
end
Which yields an opaque error:
Compiling 2 files (.ex)
== Compilation error in file lib/brmbl_web/storybook.ex ==
** (CompileError) lib/brmbl_web/storybook.ex: an error occured while rendering story tabs
(stdlib 3.17.2) lists.erl:1358: :lists.mapfoldl/3
(stdlib 3.17.2) lists.erl:1359: :lists.mapfoldl/3
(stdlib 3.17.2) lists.erl:1358: :lists.mapfoldl/3
(phoenix_live_view 0.17.11) expanding macro: Phoenix.LiveView.Helpers.inner_block/2
Confirmed that plain old HTML inside a block works fine:
def stories do
[
%Story{
id: :tabs,
attributes: %{},
block: """
<a>hi</a>
<a>hi</a>
"""
}
]
end
Can we (do we need to) use sigil_H inside our story definitions?
๐๐ผ Hi there! First of all congrats on this fantastic project ๐ ๐๐ผ !
I'm using the main
version and trying to mount the storybook in /
. However, although following the guides, I cannot make it work since it can't load assets:
[debug] ** (PhxLiveStorybook.EntryNotFound) unknown entry ["assets", "js", "components.js"]
(phx_live_storybook 0.4.0) lib/phx_live_storybook/live/entry_live.ex:42: PhxLiveStorybook.EntryLive.handle_params/3
(phoenix_live_view 0.17.11) lib/phoenix_live_view/utils.ex:369: anonymous fn/5 in Phoenix.LiveView.Utils.call_handle_params!/5
(telemetry 1.1.0) /Users/ricardogarciavega/projects/cabify/elixir/phx_storybook/deps/telemetry/src/telemetry.erl:320: :telemetry.span/3
(phoenix_live_view 0.17.11) lib/phoenix_live_view/static.ex:271: Phoenix.LiveView.Static.call_mount_and_handle_params!/5
(phoenix_live_view 0.17.11) lib/phoenix_live_view/static.ex:110: Phoenix.LiveView.Static.render/3
(phoenix_live_view 0.17.11) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
(phoenix 1.6.12) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
(phx_storybook 0.1.0) lib/phx_storybook_web/endpoint.ex:1: PhxStorybookWeb.Endpoint.plug_builder_call/2
(phx_storybook 0.1.0) lib/plug/debugger.ex:136: PhxStorybookWeb.Endpoint."call (overridable 3)"/2
(phx_storybook 0.1.0) lib/phx_storybook_web/endpoint.ex:1: PhxStorybookWeb.Endpoint.call/2
(phoenix 1.6.12) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4
(cowboy 2.9.0) /Users/ricardogarciavega/projects/cabify/elixir/phx_storybook/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.9.0) /Users/ricardogarciavega/projects/cabify/elixir/phx_storybook/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
(cowboy 2.9.0) /Users/ricardogarciavega/projects/cabify/elixir/phx_storybook/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
(stdlib 4.0) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
[info] Sent 404 in 22ms
[debug] ** (PhxLiveStorybook.AssetNotFound) unknown asset %{"asset" => ["js", "app.js"]}
(phx_live_storybook 0.4.0) lib/phx_live_storybook/controllers/asset_not_found_controller.ex:12: PhxLiveStorybook.AssetNotFoundController.asset/2
(phx_live_storybook 0.4.0) lib/phx_live_storybook/controllers/asset_not_found_controller.ex:1: PhxLiveStorybook.AssetNotFoundController.action/2
(phx_live_storybook 0.4.0) lib/phx_live_storybook/controllers/asset_not_found_controller.ex:1: PhxLiveStorybook.AssetNotFoundController.phoenix_controller_pipeline/2
(phoenix 1.6.12) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
(phx_storybook 0.1.0) lib/phx_storybook_web/endpoint.ex:1: PhxStorybookWeb.Endpoint.plug_builder_call/2
(phx_storybook 0.1.0) lib/plug/debugger.ex:136: PhxStorybookWeb.Endpoint."call (overridable 3)"/2
(phx_storybook 0.1.0) lib/phx_storybook_web/endpoint.ex:1: PhxStorybookWeb.Endpoint.call/2
(phoenix 1.6.12) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4
(cowboy 2.9.0) /Users/ricardogarciavega/projects/cabify/elixir/phx_storybook/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.9.0) /Users/ricardogarciavega/projects/cabify/elixir/phx_storybook/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
(cowboy 2.9.0) /Users/ricardogarciavega/projects/cabify/elixir/phx_storybook/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
(stdlib 4.0) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Here's the current configuration I'm using:
# config/config.exs
# Storybook config
config :phx_storybook, PhxStorybookWeb.Storybook,
content_path: Path.expand("../storybook", __DIR__),
css_path: "/assets/css/components.css",
js_path: "/assets/js/components.js"
# lib/phx_storybook_web/router.ex
# ...
scope "/" do
storybook_assets()
end
scope "/" do
pipe_through :browser
live_storybook("/",
otp_app: :phx_storybook,
backend_module: PhxStorybookWeb.Storybook
)
end
# ...
And here is what my assets folder looks like:
assets
โโโ css
โย ย โโโ app.css
โย ย โโโ components.css
โโโ js
โย ย โโโ app.js
โย ย โโโ components.js
โย ย โโโ hooks.js
โโโ tailwind.config.js
โโโ vendor
Any idea of what is going on?
Thanks in advance and keep it up!
If I have a component that can pass an argument to its inner block that can be accessed via a let
property, how do we do it in a story?
For example, my component looks like this:
~H"""
<.my_component let={m} ...>
<div class={m.classes}>...</div>
</.my_component>
"""
I tried something like:
def stories do
[
%Story{
id: :default,
attributes: %{
id: "default",
let: m
},
block: """
<div class={m.classes}>...</div>
"""
}
]
end
But of course, this will produce an undefined function m/0
.
Instead of generating the following code:
<.chart height={200} type={:bar}
data={%{__struct__: PhenixStorybook.Components.Chart.ChartData, datasets: [%{backgroundColor: ["rgba(255, 99, 132, 0.2)", "rgba(54, 162, 235, 0.2)"], borderWidth: 1, data: [12, 19, 3, 5, 2, 3], label: "# of Votes"}], labels: ["Red", "Blue", "Yellow", "Green"]}}
/>
We should get
<.chart height={200} type={:bar}
data={%ChartData{datasets: [%{backgroundColor: ["rgba(255, 99, 132, 0.2)", "rgba(54, 162, 235, 0.2)"], borderWidth: 1, data: [12, 19, 3, 5, 2, 3], label: "# of Votes"}], labels: ["Red", "Blue", "Yellow", "Green"]}}
/>
If I press cmd+opt+i on a mac, the quick search panel is opened instead of the browser inspector (Firefox and Chrome).
The search panel is also opened when I only type /
(opt + i in my keyboard layout). I don't know whether that's intended, but the shortcut rendered in the UI is cmd+k, and putting shortcuts on the option or alt key always leads to trouble, since the option and alt key are used to access characters and symbols. I can't type a /
in the search input now, which isn't too bad, but I also cannot enter a /
in any input I render in a component, which is kind of bad when it comes to testing input validation and sanitation.
The readme states "All config settings, only the :content_path key is mandatory."
but with this config
config :brmbl, BrmblWeb.Storybook,
content_path: Path.expand("../storybook", __DIR__),
css_path: "/css/app.css",
# js_path: "/js/app.js",
title: "My Rad storybook"
The project doesn't compile.
app1_1 | == Compilation error in file lib/brmbl_web/storybook.ex ==
app1_1 | ** (FunctionClauseError) no function clause matching in Keyword.get/3
app1_1 |
app1_1 | The following arguments were given to Keyword.get/3:
app1_1 |
app1_1 | # 1
app1_1 | nil
app1_1 |
app1_1 | # 2
app1_1 | :"/components"
app1_1 |
app1_1 | # 3
app1_1 | []
app1_1 |
app1_1 | Attempted function clauses (showing 1 out of 1):
app1_1 |
app1_1 | def get(+keywords+, +key+, +default+) when -is_list(keywords)- and +is_atom(key)+
app1_1 |
app1_1 | (elixir 1.13.4) lib/keyword.ex:352: Keyword.get/3
app1_1 | (phx_live_storybook 0.3.0) lib/phx_live_storybook/entries.ex:62: anonymous fn/5 in PhxLiveStorybook.Entries.recursive_scan/3
app1_1 | (elixir 1.13.4) lib/enum.ex:2396: Enum."-reduce/3-lists^foldl/2-0-"/3
app1_1 | (phx_live_storybook 0.3.0) lib/phx_live_storybook/entries.ex:55: PhxLiveStorybook.Entries.recursive_scan/3
app1_1 | (phx_live_storybook 0.3.0) expanding macro: PhxLiveStorybook.__using__/1
app1_1 | lib/brmbl_web/storybook.ex:3: BrmblWeb.Storybook (module)
app1_1 | (elixir 1.13.4) expanding macro: Kernel.use/2
app1_1 | lib/brmbl_web/storybook.ex:3: BrmblWeb.Storybook (module)
Upcoming liveview 0.18.0
will ship with the ability to declare component assigns.
phx_live_storybook Attr
API is intentionally very close with declarative component assigns, so the idea is that:
liveview 0.17
or less, only storybook attributes are usedliveview 0.18+
, then component attributes and storybook attributes are merged (storybook ones have higher priority)When rendering a component code snippet, we don't want to see the JS struct itself but the code the evaluated to the JS struct.
The user icon for a StoryEntry
isn't being placed correctly in relation to it's name.
It looks like the story_class
is missing these two classes: lsb-flex
and lsb-items-center
.
We are currently using src when using (optional) iframe rendering.
This triggers one additional HTTP request for every story rendered (1 story = 1 iframe = 1 HTTP GET)
We could instead use iframe srcdoc which allows inline rendering.
As of #78 storybook content will now be lazy-compiled.
The sidebar will still be reflecting the file hierarchy under the storybook content folder.
But we need enough information to eagerly build a functional sidebar with optional custom folder names, folder icons, and story icons. That's why we will introduce *.index.exs
files that will be eagerly compiled (in all envs) to provide all info required by the sidebar.
The storybook content folder will look like this:
- mix.exs
- lib/
- storybook/
- components/
- components.index.exs
- buttons/
- buttons.index.exs
- button.story.exs
- dropdown.story.exs
- guides/
- guides.index.ex
- guidelines.story.exs
*.index.exs
files are optional and look like this
defmodule Storybook.Components.Buttons.Index do
use PhxLiveStorybook.Index
def folder_name, do: "Custom Name"
# we might use a tuple here to use different icon provides (FontAwesome / Heroicons)
def icon, do: "fa fa-button"
def sidebar_item("button"), do: [name: "custom story name", icon: "fa fa-banana"]
# we did not define anything for the "dropdown" which will default to [name: "Dropdown", icon: nil]
end
First, this project's design is advance and useful.
Is this project open to use?
Depending on the component, you may want:
There should be some options to enable or disable these behaviors.
I'd like to mix different components together in the same Entry. Something like this:
defmodule BrmblWeb.Storybook.Components.Typography do
use PhxLiveStorybook.Entry, :component
alias BrmblWeb.UI.Typography
def description, do: "Typography components"
def stories do
[
%Story{
id: :h1,
function: &Typography.h1/1,
attributes: %{},
slots: [~s|.h1 heading|]
},
%Story{
id: :h2,
function: &Typography.h2/1,
attributes: %{},
slots: [~s|.h2 heading|]
}
]
end
end
That doesn't work since a :component
assumes all the same :function
:
app1_1 | == Compilation error in file lib/brmbl_web/storybook.ex ==
app1_1 | ** (KeyError) key :function not found
app1_1 | (phx_live_storybook 0.3.0) expanding struct: PhxLiveStorybook.Story.__struct__/1
app1_1 | storybook/components/typography.exs:16: BrmblWeb.Storybook.Components.Typography.stories/0
Is this a good idea? If so, do you have any plans to add this already (or can you share any tips on how you might like to see it implemented?)
On the demo project if you open:
Some ghost stories (hello & world) are visible.
Josรฉ speaking:
generally speaking: module configuration is the default, config files should be the fallback
So be it! ๐
By default, the storybook should not be using premium (paid) icons, but free icons.
It should switch to premium icons if user has provided his own API key
phoenix_storybook
/ PhoenixStorybook
everywhere
Instead of writing
phx-click="set-assign/:story_id/show/true"
phx-click="toggle-assign/:story_id/show"
we should write
phx-click={JS.push("assign", values: %{story_id: :story_id, show: true})}
phx-click={JS.push("toggle", values: %{story_id: :story_id, attr: :show})}
Component & pages .exs
files are compiled as a whole at compilation time (triggered by the compilation of the storybook backend module defined in the user application).
As the codebase grows, this could induce increased compilation times.
Components & pages exs
files should instead:
prod
envI just looked through the README and noticed that the story files are .ex
files, but one also needs to supply a path for where those files are. I'm wondering if those files should rather be .exs
files, so they're not compiled by default, but only used by the storybook. I feel like this is a similar setup to test files, which also are .exs
files.
Write mix phx_storybook.install
task that will:
storybook.js
filestorybook.css
fileThrowing this idea out there...
TailwindUI has a nifty idea where their storybook (for paid users) you to resize the container, showing behaviours at different breakpoints:
@cblavier what do you think about this?
Given this component
def custom_link(%{link_type: "a"} = assigns) do
~H"""
<%= Phoenix.HTML.Link.link([to: @to, class: @class] ++ @rest,
do: if(@inner_block, do: render_slot(@inner_block), else: @label)
) %>
"""
end
and this Story (without a slot
)
%Story{id: :default, attributes: %{label: "Link", to: "/"}},
%Story{id: :slot, attributes: %{to: "/"}, slots: [~s|Default slot|]},
It wont render anything in the first Story in the storybook.
It appears that the Story is always passing an inner_block
, slot or not.
This is a workaround, but it's not ideal
- do: if(@inner_block, do: render_slot(@inner_block), else: @label)
+ do: if(@label, do: @label, else: render_slot(@inner_block))
They currently are .exs
files, but still are evaluated at compilation time, so they could be .ex
as well.
Dixit Josรฉ:
I think compiling them is likely better because the compiler is much smarter than code evaluation
They also should be suffixed by default to help with browsing code:
typography_page.ex
button_stories.ex
The suffix should be ignored when guessing the entry nice name (as a page title, or sidebar label)
In the component playground, a new tab (next to the Attributes tab) should be available for all live components (not for stateless) to inspect their current state.
It feels strange to duplicate the module for a function component on both the component/0
and the function/0
callback. I'm not sure I completely understand the system yet, but isn't the function/0
return value enough to figure out the module it's on?
The icons in the sidebar aren't rendering correctly for me.
<div class="lsb lsb-flex lsb-items-center lsb-py-3 lg:lsb-py-1.5 -lsb-ml-2 lsb-group lsb-cursor-pointer lsb-group hover:lsb-text-indigo-600" phx-click="close-folder" phx-target="1" phx-value-path="/admin/storybook/components/feedback">
<i class="fa-solid fa-caret-down lsb lsb-pl-1 lsb-pr-2"></i>
</div>
From my investigation, it's because the lsb
class defines a font-family
property which cascades down to the icon element and overrides the font-family
of the fa-*
classes. When I added !important
to the fa-*
font-family
property, the icons render properly.
This ticket must be done after #35
The component playground should now be able to render all stories of a single StoryGroup
at once. As in #35 one should be able:
When the playground is initialized from the StoryGroup
all attributes that have different values across stories are locked and cannot be edited in the attributes tab.
For instance, with the following StoryGroup
the color
attribute will be locked whereas text
and icon
will still be editable.
When updating an editable field, all stories are updated at once.
%StoryGroup{
id: :colors,
stories:
for color <- ~w(info primary success warning danger)a do
%Story{
id: color,
attributes: %{
text: "My badge",
icon: "fa fa-icon",
color: color
}
}
end
}
I thought I could prevent my global styles from bleeding into storybook with this:
html:not(.lsb),
body:not(.lsb),
.lsb-sandbox {
/* ... */
}
However, the storybook theme dropdown also has the sandbox classes:
<div class="... lsb-sandbox myapp-web">
Is that by design? I would have expected the sandbox classes to only be added to the containers around my own components.
I want to say thank you for your great work on phx_live_storybook!
We have one issue when using Date Picker in the Storybook. The date picker is not shown, unless storybook page is refreshed once.
We are using https://flowbite.com/docs/plugins/datepicker which is basically a Tailwind CSS date picker.
Is it because of the components are rendered during compile time and refreshing the page re-renders the component and activating the hook?
Thank you.
In liveview 0.18.0 block & slots are not declared as attributes.
https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#module-slots
We should do the same.
I'm seeing a 403 error on GET http://localhost:4000/storybook/assets/js/app.js.
This results in deeply nested menu items not being expandable:
Request details:
GET /storybook/assets/js/app.js HTTP/1.1
Host: localhost:4000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://localhost:4000/storybook/components/button/icon
Connection: keep-alive
Cookie: NOPE
Sec-Fetch-Dest: script
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: same-origin
Response
HTTP/1.1 403 Forbidden
cache-control: max-age=0, private, must-revalidate
content-length: 76286
content-type: text/html; charset=utf-8
cross-origin-window-policy: deny
date: Thu, 18 Aug 2022 11:53:59 GMT
permissions-policy: accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), navigation-override=(self), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(self), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(self), gamepad=(), speaker-selection=(), conversion-measurement=(), focus-without-user-activation=(), hid=(), idle-detection=(), serial=(), sync-script=(), trust-token-redemption=(), vertical-scroll=()
referrer-policy: strict-origin-when-cross-origin
server: Cowboy
x-content-type-options: nosniff
x-download-options: noopen
x-frame-options: SAMEORIGIN
x-permitted-cross-domain-policies: none
x-request-id: FwxuTP1B_mn4B1UAAM4F
x-xss-protection: 1; mode=block
Phoenix shows this error page:
Plug.CSRFProtection.InvalidCrossOriginRequestError at GET /storybook/assets/js/app.js
security warning: an embedded <script> tag on another site requested protected JavaScript (if you know what you're doing, disable forgery protection for this route)
Strangely phoenix is responding content-type: text/html; charset=utf-8
but in lib/phx_live_storybook/templates/layout/root.html.heex
, indeed the correct mime type is set:
15: <script phx-track-static type="text/javascript" src={application_static_path(@conn, path)}></script>
17: <script type="text/javascript" src={asset_path(@conn, "js/app.js")}></script>
Stacktrace
[debug] ** (Plug.CSRFProtection.InvalidCrossOriginRequestError) security warning: an embedded <script> ta
g on another site requested protected JavaScript (if you know what you're doing, disable forgery protecti
on for this route)
(plug 1.13.6) lib/plug/csrf_protection.ex:392: Plug.CSRFProtection.ensure_same_origin_and_csrf_token!/3
(elixir 1.13.4) lib/enum.ex:2396: Enum."-reduce/3-lists^foldl/2-0-"/3
(plug 1.13.6) lib/plug/conn.ex:1690: Plug.Conn.run_before_send/2
(plug 1.13.6) lib/plug/conn.ex:450: Plug.Conn.send_file/5
(plug 1.13.6) lib/plug/static.ex:308: Plug.Static.send_entire_file/3
(brmbl 0.1.0) BrmblWeb.Router.storybook_assets/2
(brmbl 0.1.0) lib/brmbl_web/router.ex:1: BrmblWeb.Router.__pipe_through9__/1
(phoenix 1.6.11) lib/phoenix/router.ex:346: Phoenix.Router.__call__/2
(brmbl 0.1.0) lib/brmbl_web/endpoint.ex:1: BrmblWeb.Endpoint.plug_builder_call/2
(brmbl 0.1.0) lib/brmbl_web/endpoint.ex:3: anonymous fn/3 in BrmblWeb.Endpoint."call (overridable 3)"/2
(appsignal 2.1.7) lib/appsignal/instrumentation.ex:10: Appsignal.Instrumentation.instrument/1
(brmbl 0.1.0) lib/plug/debugger.ex:136: BrmblWeb.Endpoint."call (overridable 4)"/2
(brmbl 0.1.0) lib/brmbl_web/endpoint.ex:1: BrmblWeb.Endpoint."call (overridable 5)"/2
(brmbl 0.1.0) lib/plug/error_handler.ex:80: BrmblWeb.Endpoint.call/2
(phoenix 1.6.11) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4
(cowboy 2.9.0) /home/g/src/brmbl/app/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.9.0) /home/g/src/brmbl/app/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
(cowboy 2.9.0) /home/g/src/brmbl/app/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
(stdlib 3.17.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Unless --no-tailwind
option is passed to generator:
@utilities
layer explaining how their styling will be sandboxedconfig :tailwind
, config/config.exs
config/dev.exs
Plus (no matter the --no-tailwind-option
)
js/storybook.js
in config/config.exs
esbuild args~r"storybook/.*(exs)$"
in config/dev.exs
live_reload patternsHello, how should I go about using forms and inputs in the storybook? using the Phoenix.HTML.Form
struct directly causes browser errors which are not likely to be very useful to share.
Uncaught TypeError: e is null
Here is how I'm currently supporting my inputs in storybook:
%Story{
id: :select_error_state,
attributes: %{
type: :select,
select_options: ["Foo", "Bar", "Fizz", "Buzz"],
prompt: "Select a variable name",
class: "",
form: %Phoenix.HTML.Form{data: %{foo: "Foo"}, errors: [foo: {"can't be set to Foo", []}]},
field: :foo
}
},
A global search menu (tailwind.css.com like) should be globally available within the storybook to search across all the storybook entries (pages & components)
The UI might look like this: https://tailwindui.com/components/application-ui/navigation/command-palettes#component-f55d785e34cd53505459e68e32af879f
The command palette is:
cmd-K
global shortcutcmd-K
to open, type to search, up/down to select, enter to open, esc to cancelIn the component Playground a new tab (next to the Attributes tab) should be available for all components.
This tab will print, in a <pre>...</pre>
monospace font fashion:
handle_info
& handle_event
) received by the componenthandle_info
& handle_event
) received by the parent LiveView (PlaygroundPreviewLive
) excepted the {:new_attributes, pid, attrs}
which is for internal useIf messages are received when the Logs tab is not active, a badge will appear next to the tab name with the number of unread logs. The badge is cleared as soon as the tab is opened.
By using TailwindCSS important selector strategy with the .lsb-sandbox
class, we force users into putting the lsb-sandbox
class on their own application layout.
This should be a custom class, set in configuration.
Sandboxing guide should be amended
First of all, thank you guys for your work on this!
I am having a hard time adding a modal to the storybook which is supposed to be something like this https://petal.build/components/modals. For a modal to show/hide, it needs a button, so in the sandbox it must also show a button and a modal that is hidden by default.
I already tried doing something like this:
alias MyComponents.Modal
def function, do: &my_modal/1
def description, do: "Modals sit on top of an applications main window"
def stories do
[
%StoryGroup{
id: :sizes,
description: "Different sizes of modal",
stories: [
%Story{
id: :default_modal,
attributes: %{title: "Testing"}
}
]
}
]
end
# For storybook purposes only
def my_modal(assigns) do
~H"""
<button
class="block text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
type="button"
data-modal-toggle="default-modal"
>
Toggle modal
</button>
<Modal.modal title="Testing123">Some testing</Modal.modal>
"""
end
This should work, although the documentation now points to my_modal/1
instead of just Modal.modal/1
.
Are there any plans to pass a layout option to the sandbox so it will house the component instead of just a div/iframe option? Or in the current version, do you suggest any workaround to make this work?
Hi!
LSB version: 0.2.0
I've noticed the LSB styles are fighting with app styles imported via css_path
It appears LSB combines its own, and my_app
's css together in the document <head>
. This may cause components rendered inside %Variation
elements to inherit styling from LSB, and not the app.
In my specific case, the use of a LSB text classes on <body>
(lsb-font-inter lsb-text-base
etc) are overriding my Tailwind base styles, which are added to <html>
in preflight in my css bundle.
What are your plans here? I see that Storybook renders each Story in its own iframe.
Anything we can help with?
Currently, the component playground is initialized from the first Story
declared in the component entry file (or, if the first item is a group, from the first story in the first group).
The idea would be to keep this behavior as a default, but to be also able to open the component playground with any story:
?story_id=my_story
)Handling of StoryGroup
is not part of this issue; it will be described in another ticket.
One should be able to render its storybook components with different themes (to theme the components, not the storybook itself).
Setup themes with
config :phx_live_storybook, MyApp,
themes: [default: "Default Theme", colorful: "Colorful"]
Once configured a dropdown will be visible in the header with different themes. When updated:
?theme=colorful
) and persistent across the navigation%{theme: :colorful}
)Surface is currently supporting attributes options with the values
and values!
keys :
Currently, we have options values and values! for that.
values is meant to be a common list of values but it does not represent the list of all possible values. For instance, you may have an attribute color where you want to define a set of values to be listed in the catalogue, like red, green, blue or even semantic values like primary secondary, etc. however, the user can also set any other valid color, like #FF0000 and so on.
values! on the other hand, is strict, i.e. it lists all possible values so we can do static validation not only in the catalogue but also in the compiler itself.
These options will soon be upstreamed to LiveView.
The storybook should be consistent with it.
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.