Skip to content

Commit 93ec55d

Browse files
committed
Add interactive example to React form reducers article
1 parent 3edd737 commit 93ec55d

File tree

10 files changed

+341
-73
lines changed

10 files changed

+341
-73
lines changed

assets/css/app.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
--z-menu: 50;
1515
}
1616

17+
:root {
18+
--colors-blue-500: theme('colors.blue.500');
19+
--colors-red-500: theme('colors.red.500');
20+
}
21+
1722
a:not([class]):not(.prose *) {
1823
display: var(--link--display);
1924
padding-left: var(--link--padding-x);

lib/components_guide_web/controllers/cheatsheets_controller.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ defmodule ComponentsGuideWeb.CheatsheetsController do
1414
render(conn, "index.html", article: "intro", wasm_source: @wasm_source)
1515
end
1616

17-
@articles ["rxjs", "error-messages", "cloud-limits"]
17+
@articles ["rxjs", "error-messages", "cloud-limits", "astro"]
1818

1919
def show(conn, %{"id" => article}) when article in @articles do
2020
render(conn, "index.html", article: article)

lib/components_guide_web/live/view_source.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ defmodule ComponentsGuideWeb.ViewSourceLive do
3737
for={:editor}
3838
id="view_source_form"
3939
phx-submit="submitted"
40-
class="max-w-2xl mx-auto space-y-2"
40+
class="max-w-2xl mt-12 mx-auto space-y-2"
4141
>
4242
4343
<fieldset y-y y-stretch class="gap-1">

lib/components_guide_web/template_engines/markdown_engine.ex

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,20 @@ defmodule ComponentsGuideWeb.TemplateEngines.MarkdownEngine do
1616
# "-md-" <> slug
1717
end
1818

19+
def h2_add_slug_processor(node) do
20+
html_fragment = Earmark.Transform.transform([node])
21+
inner_text = Floki.parse_fragment!(html_fragment) |> Floki.text() |> String.trim()
22+
id = slug(inner_text)
23+
24+
node
25+
|> Earmark.AstTools.merge_atts_in_node(id: slug(inner_text))
26+
end
27+
1928
def compile(path, name) do
2029
IO.puts("compile #{path} #{name}")
2130

2231
registered_processors = [
23-
{"h2",
24-
fn node ->
25-
html_fragment = Earmark.Transform.transform([node])
26-
27-
inner_text = Floki.parse_fragment!(html_fragment) |> Floki.text() |> String.trim()
28-
29-
id = slug(inner_text)
30-
31-
node
32-
|> Earmark.AstTools.merge_atts_in_node(id: slug(inner_text))
33-
end}
32+
{"h2", &h2_add_slug_processor/1}
3433
]
3534

3635
# |> Earmark.TagSpecificProcessors.new()
@@ -42,13 +41,72 @@ defmodule ComponentsGuideWeb.TemplateEngines.MarkdownEngine do
4241
registered_processors: registered_processors
4342
)
4443

45-
html =
46-
path
47-
|> File.read!()
48-
|> Earmark.as_html!(options)
44+
file_contents = File.read!(path)
45+
46+
{front_matter, markdown} =
47+
case file_contents do
48+
"---\n---\n" <> markdown ->
49+
{nil, markdown}
50+
51+
"---\n" <> rest ->
52+
[front_matter, markdown] = String.split(rest, "\n---\n", parts: 2)
53+
{front_matter, markdown}
54+
55+
markdown ->
56+
{nil, markdown}
57+
end
58+
4959

5060
# |> Earmark.as_html!(%Earmark.Options{code_class_prefix: "language-", smartypants: false, postprocessor: &map_ast/1})
5161

62+
# html = Regex.replace(regex, html, fn whole, name, content -> "!" <> name <> "!" <> content end)
63+
64+
# html = Regex.replace(regex, html, fn whole, name, content -> "<div><%= live_render(@conn, ComponentsGuideWeb.FakeSearchLive, session: %{}) %></div>" end)
65+
66+
case front_matter do
67+
nil ->
68+
html = Earmark.as_html!(markdown, options)
69+
html = todo_remove_old_custom_elements(html, name)
70+
html |> EEx.compile_string(engine: Phoenix.HTML.Engine, file: path, line: 1)
71+
72+
s ->
73+
html = Earmark.as_html!(markdown, options)
74+
quote do
75+
unquote(Code.string_to_quoted!(s, file: path))
76+
77+
unquote(
78+
html
79+
|> EEx.compile_string(engine: Phoenix.LiveView.HTMLEngine, file: path, line: 1)
80+
)
81+
end
82+
end
83+
84+
# TODO: use Heex?
85+
# unless Macro.Env.has_var?(__CALLER__, {:assigns, nil}) do
86+
# raise "~H requires a variable named \"assigns\" to exist and be set to a map"
87+
# end
88+
89+
# options = [
90+
# engine: Phoenix.LiveView.HTMLEngine,
91+
# file: __CALLER__.file,
92+
# line: __CALLER__.line + 1,
93+
# module: __CALLER__.module,
94+
# indentation: meta[:indentation] || 0
95+
# ]
96+
97+
# EEx.compile_string(expr, options)
98+
99+
# case live? do
100+
# true ->
101+
# html |> EEx.compile_string(engine: Phoenix.HTML.Engine, file: path, line: 1)
102+
103+
# false ->
104+
105+
# end
106+
# html |> EEx.compile_string(engine: Phoenix.LiveView.Engine, file: path, line: 1)
107+
end
108+
109+
defp todo_remove_old_custom_elements(html, name) do
52110
# regex = ~r{<live-([\w-]+)>(.+)</live-([\w-]+)>}
53111
regex = ~r{<live-([\w-]+)>([^<]+)</live-([^>]+)>}
54112
# regex = ~r{<live-([\w-]+)>}
@@ -85,35 +143,7 @@ defmodule ComponentsGuideWeb.TemplateEngines.MarkdownEngine do
85143
end
86144
end)
87145

88-
# html = Regex.replace(regex, html, fn whole, name, content -> "!" <> name <> "!" <> content end)
89-
90-
# html = Regex.replace(regex, html, fn whole, name, content -> "<div><%= live_render(@conn, ComponentsGuideWeb.FakeSearchLive, session: %{}) %></div>" end)
91-
92-
html |> EEx.compile_string(engine: Phoenix.HTML.Engine, file: path, line: 1)
93-
94-
# TODO: use Heex?
95-
# unless Macro.Env.has_var?(__CALLER__, {:assigns, nil}) do
96-
# raise "~H requires a variable named \"assigns\" to exist and be set to a map"
97-
# end
98-
99-
# options = [
100-
# engine: Phoenix.LiveView.HTMLEngine,
101-
# file: __CALLER__.file,
102-
# line: __CALLER__.line + 1,
103-
# module: __CALLER__.module,
104-
# indentation: meta[:indentation] || 0
105-
# ]
106-
107-
# EEx.compile_string(expr, options)
108-
109-
# case live? do
110-
# true ->
111-
# html |> EEx.compile_string(engine: Phoenix.HTML.Engine, file: path, line: 1)
112-
113-
# false ->
114-
115-
# end
116-
# html |> EEx.compile_string(engine: Phoenix.LiveView.Engine, file: path, line: 1)
146+
html
117147
end
118148

119149
# defp map_ast({"h2", attrs, content, options}), do: {"h2", [class: "red"], content, %{class: "blue"}}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
a = 4 + 3
3+
class = "italic"
4+
---
5+
6+
# Astro
7+
8+
<p class={class}><%= a %>?</p>
9+
<p class={class}><%= class %></p>
10+
11+
<script>
12+
window.customElements.define('custom-element', class extends HTMLElement {
13+
connectedCallback() {
14+
console.log('connectedCallback called');
15+
const name = this.getAttribute('name');
16+
const source = this;
17+
18+
// Attempt to wait until the inner template has been added as a child node.
19+
const observer = new MutationObserver((mutationList, observer) => {
20+
outer: for (const mutation of mutationList) {
21+
for (const node of mutation.addedNodes.values()) {
22+
if (node instanceof HTMLTemplateElement) {
23+
doDefine();
24+
observer.disconnect();
25+
break outer;
26+
}
27+
}
28+
}
29+
});
30+
observer.observe(this, { childList: true });
31+
32+
// class NewElement extends HTMLElement {
33+
34+
// }
35+
36+
function doDefine() {
37+
window.customElements.define(name, class extends HTMLElement {
38+
ensureTemplate() {
39+
if (this.hasAddedTemplate) return;
40+
41+
const template = source.querySelector('template');
42+
const fragment = template.content.cloneNode(true);
43+
this.attachShadow({mode: 'open'}).appendChild(fragment);
44+
// this.append(fragment);
45+
46+
this.hasAddedTemplate = true;
47+
}
48+
49+
connectedCallback() {
50+
this.ensureTemplate();
51+
}
52+
53+
static get observedAttributes() {
54+
const template = source.querySelector('template');
55+
const slots = template.content.querySelectorAll('slot');
56+
const slotNames = Array.from(slots, slot => slot.name);
57+
return slotNames;
58+
}
59+
60+
attributeChangedCallback(name, oldValue, newValue) {
61+
this.ensureTemplate();
62+
for (const node of this.querySelectorAll(`slot[name="${name}"]`).values()) {
63+
node.remove();
64+
}
65+
this.append(Object.assign(this.ownerDocument.createElement('span'), { textContent: newValue, slot: name }));
66+
}
67+
});
68+
}
69+
}
70+
});
71+
</script>
72+
73+
<script>
74+
window.customElements.define('custom-element3', class extends HTMLElement {
75+
connectedCallback() {
76+
const name = this.getAttribute('name');
77+
const source = this;
78+
console.log(this.innerHTML);
79+
80+
window.customElements.define(name, class extends HTMLElement {
81+
connectedCallback() {
82+
const template = source.querySelector('template');
83+
const fragment = template.content.cloneNode(true);
84+
this.attachShadow({mode: 'open'}).appendChild(fragment);
85+
}
86+
});
87+
}
88+
});
89+
</script>
90+
91+
<custom-element name="hello-there">
92+
<template>Hello <slot name="subject">World</slot>.</template>
93+
</custom-element>
94+
95+
<hello-there></hello-there>
96+
<hello-there><span slot="subject">Universe</span></hello-there>
97+
<hello-there subject="Props"></hello-there>
98+
99+
<custom-element name="custom-script">
100+
<template>
101+
<script>console.log("Inside template", document.currentScript)</script>
102+
</template>
103+
</custom-element>
104+
105+
<custom-script></custom-script>

lib/components_guide_web/templates/layout/_banner.html.heex

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
<header role="banner" class="bg-gray-900 text-white">
1+
<header role="banner" class="bg-black text-white/90">
22
<nav role="navigation" aria-label="Primary" class="w-full flex items-center lg:px-2 text-base text-center">
3-
<details class="relative" data-links="block p-3 underline-on-hover">
3+
<details class="relative">
44
<summary class="block w-48 text-left pl-6 text-base uppercase font-bold tracking-wider">
5-
Menu
5+
Navigate to
66
</summary>
7-
<details-menu role="menu" class="absolute z-[var(--z-menu)] top-full left-0 flex flex-col px-4 py-2 text-left text-lg whitespace-nowrap bg-gray-900 rounded shadow-lg">
8-
<%= link("React Playground", to: '/react-playground') %>
9-
<%= link("Developer Calendar", to: '/calendar') %>
10-
<%= link("Accessibility First", to: '/accessibility-first') %>
11-
<%= link("React + TypeScript", to: '/react+typescript') %>
12-
<%= link("Web Standards", to: '/web-standards') %>
13-
<%= link("Composable Systems", to: '/composable-systems') %>
7+
<details-menu role="menu" class="absolute z-[var(--z-menu)] top-full left-0 flex flex-col py-2 text-left text-lg font-bold whitespace-nowrap text-blue-800 bg-white/80 rounded shadow-lg backdrop-blur-xl">
8+
<%= link("React + TypeScript", to: "/react+typescript", class: "py-3 px-6 hover:text-blue-200 hover:bg-gray-900") %>
9+
<%= link("React Playground", to: "/react-playground", class: "py-3 px-6 hover:text-blue-200 hover:bg-gray-900") %>
10+
<%= link("Accessibility First", to: "/accessibility-first", class: "py-3 px-6 hover:text-blue-200 hover:bg-gray-900") %>
11+
<%= link("Web Standards", to: "/web-standards", class: "py-3 px-6 hover:text-blue-200 hover:bg-gray-900") %>
12+
<%= link("Composable Systems", to: "/composable-systems", class: "py-3 px-6 hover:text-blue-200 hover:bg-gray-900") %>
13+
<%= link("Developer Calendar", to: "/calendar", class: "py-3 px-6 hover:text-blue-200 hover:bg-gray-900") %>
1414
</details-menu>
1515
</details>
1616

1717
<hr class="mx-auto">
1818

19-
<a href="/" class="text-center block px-3 py-2 md:py-4 text-sm leading-tight md:text-lg md:leading-tight font-bold italic uppercase tracking-wide hover:bg-gray-800">
20-
<span>Components <br>·Guide</span>
19+
<a href="/" class="text-center block px-3 py-2 md:py-4 text-sm md:text-lg font-bold italic uppercase tracking-wider hover:bg-gray-800">
20+
<span>Components.Guide</span>
2121
</a>
2222

2323
<hr class="mx-auto">

lib/components_guide_web/templates/react_typescript/_nav.html.heex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<li><%= link(@conn, "Fundamentals", to: "/react+typescript") %></li>
55
<li><%= link(@conn, "Testing", to: "/react+typescript/testing") %></li>
66
<li><%= link(@conn, "Reducer Patterns", to: "/react+typescript/reducer-patterns") %></li>
7+
<li><%= link(@conn, "Form Reducers", to: "/react+typescript/form-reducers") %></li>
78
<li><%= link(@conn, "Zero Hook Dependencies", to: "/react+typescript/zero-hook-dependencies") %></li>
89
<li><%= link(@conn, "Hooks in a Concurrent World", to: "/react+typescript/hooks-concurrent-world") %></li>
910
<li hidden><%= link(@conn, "Event Handlers", to: "/react+typescript/event-handlers") %></li>

0 commit comments

Comments
 (0)