Skip to content
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
76 changes: 76 additions & 0 deletions e2e/docs/snippet-conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Snippet conventions (e2e code tabs + Hex moduledoc)

User-facing strings (`*_code`, `*_heex`, `events_*`, `api_*`) must be copy-paste ready outside the e2e app.

## Component classes

On the host, use the base class plus Corex BEM modifiers only:

```heex
<.checkbox class="checkbox checkbox--accent" />
<.carousel class="carousel carousel--accent carousel--rounded-xl" />
```

Omit `id` on the component in snippets unless the example is API or client-event wiring (`getElementById`, `Corex.Component.set_*`). E2e previews keep `id` in `*_example` for tests only.

Allowed on related primitives in slots when needed: `class="icon"` on `<.heroicon>`, `class="button button--sm"` on `<.action>`.

## Forbidden in snippets

Layout Tailwind on wrapper elements:

- `class="flex flex-col gap-2"` on `<motion.div>` or `<form>`
- `class="layout__row"`, `w-full` on forms used only for demo layout
- Extra wrappers whose only job is page layout

Put layout in `*_example` functions and LiveView previews only.

## Data and routes

- Inline `Corex.List.new([...])`, `Corex.Content.new([...])`, `Corex.Image.new("/images/beach.jpg", alt: "...")`
- Not `E2eWeb.Demos.*.gallery_images()` or `basic_items()`
- Routes in generic Hex snippets: `to="#"`, `action="/your/path"`, `src="/images/avatar.png"`
- E2e controller form previews and their doc snippets (routes under `/:locale`): `action={~p"/component/form"}`

## Elixir event handlers

```elixir
def handle_event("event_name", params, socket) do
IO.inspect(params, label: "event_name")
{:noreply, socket}
end
```

Use `E2eWeb.Demos.DocExamples.event_handler_snippet/2` for consistency.

## JavaScript vs TypeScript

- JS: `const el = document.getElementById(...)` and untyped listeners
- TS: `const el: HTMLElement | null`, `(event: Event)`, `CustomEvent<DetailType>` — never delegate TS to JS verbatim

Listener tabs use `console.log(event.detail)`. LiveView `pushEvent` belongs in colocated hooks only.

## Code tabs by page type

| Page | Tabs |
|------|------|
| Anatomy / Style | Heex |
| Events server | Heex + Elixir |
| Events client | Heex + JS + TS |
| API client binding | Heex |
| API client JS | Heex + JS + TS |
| API server | Heex + Elixir |
| Form (LiveView) | Heex + Elixir (+ File for uploads) |
| Pattern (LiveView + data) | Heex + Elixir + Data |

## Shared fragments

Prefer [`doc_examples.ex`](../lib/e2e_web/demos/doc_examples.ex) `code_*` functions for repeated item lists and event handler bodies.

## Checklist before merging

- [ ] Snippet has no `E2eWeb.`, `~p"`, `MyApp.`
- [ ] Snippet has no layout `flex` / `gap-` on wrappers
- [ ] Host has `class="<component>"` (+ modifiers if documenting style)
- [ ] JS and TS tabs differ when both exist
- [ ] Hex moduledoc matches e2e tab content for the same example
4 changes: 2 additions & 2 deletions e2e/lib/e2e_web/demos/combobox_demo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ defmodule E2eWeb.Demos.ComboboxDemo do
])}
>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(to_string(item.value))} />
<Flagpack.flag name={String.to_existing_atom(to_string(item.value))} />
{item.label}
</:item>
<:trigger>
Expand Down Expand Up @@ -201,7 +201,7 @@ defmodule E2eWeb.Demos.ComboboxDemo do
])}
>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(to_string(item.value))} />
<Flagpack.flag name={String.to_existing_atom(to_string(item.value))} />
{item.label}
</:item>
<:trigger>
Expand Down
4 changes: 2 additions & 2 deletions e2e/lib/e2e_web/demos/select_demo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ defmodule E2eWeb.Demos.SelectDemo do
Country of residence
</:label>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(to_string(item.value))} />
<Flagpack.flag name={String.to_existing_atom(to_string(item.value))} />
{item.label}
</:item>
<:trigger>
Expand Down Expand Up @@ -225,7 +225,7 @@ defmodule E2eWeb.Demos.SelectDemo do
])}
>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(to_string(item.value))} />
<Flagpack.flag name={String.to_existing_atom(to_string(item.value))} />
{item.label}
</:item>
<:trigger>
Expand Down
50 changes: 26 additions & 24 deletions lib/components/accordion.ex
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,8 @@ defmodule Corex.Accordion do
alias Phoenix.LiveView
alias Phoenix.LiveView.JS

import Corex.Api.Doc

import Corex.Helpers,
only: [
validate_content_items_required!: 2,
Expand Down Expand Up @@ -1253,8 +1255,7 @@ defmodule Corex.Accordion do
"""
end

@doc type: :api
@doc ~S"""
api_doc(~S"""
Open or close items from `phx-click`. Pass a list (`["lorem"]`), a comma string (`"lorem,donec"`), or `[]` to close all.

```heex
Expand All @@ -1277,7 +1278,8 @@ defmodule Corex.Accordion do
})
);
```
"""
""")

def set_value(accordion_id, value) when is_binary(accordion_id) do
JS.dispatch("corex:accordion:set-value",
to: "##{accordion_id}",
Expand All @@ -1286,8 +1288,7 @@ defmodule Corex.Accordion do
)
end

@doc type: :api
@doc ~S"""
api_doc(~S"""
Open or close items from `handle_event`. Pushes `accordion_set_value` (no reply event).

```heex
Expand All @@ -1304,7 +1305,8 @@ defmodule Corex.Accordion do
{:noreply, Corex.Accordion.set_value(socket, "my-accordion", value)}
end
```
"""
""")

def set_value(socket, accordion_id, value)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(accordion_id) do
RespondTo.push_set_value(
Expand All @@ -1315,8 +1317,7 @@ defmodule Corex.Accordion do
)
end

@doc type: :api
@doc ~S"""
api_doc(~S"""
Read open items from `phx-click`. Dispatches `corex:accordion:value`. Optional `respond_to:` `:server` (default), `:client`, or `:both`.

| | Reply | Payload |
Expand Down Expand Up @@ -1349,7 +1350,8 @@ defmodule Corex.Accordion do
```

`values` is a list of open item `value` strings, or `nil`.
"""
""")

def value(accordion_id, opts) when is_binary(accordion_id) and is_list(opts) do
JS.dispatch("corex:accordion:value",
to: "##{accordion_id}",
Expand All @@ -1364,8 +1366,7 @@ defmodule Corex.Accordion do

def value(accordion_id) when is_binary(accordion_id), do: value(accordion_id, [])

@doc type: :api
@doc ~S"""
api_doc(~S"""
Read open items from `handle_event` (`accordion_value`). Same replies as [`value/2`](#value/2).

| Reply | Payload |
Expand All @@ -1390,7 +1391,8 @@ defmodule Corex.Accordion do
{:noreply, assign(socket, :open_items, values)}
end
```
"""
""")

def value(socket, accordion_id, opts)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(accordion_id) and
is_list(opts) do
Expand All @@ -1401,8 +1403,7 @@ defmodule Corex.Accordion do
)
end

@doc type: :api
@doc ~S"""
api_doc(~S"""
Read the focused item from `phx-click`. Dispatches `corex:accordion:focused`. Optional `respond_to:` `:server` (default), `:client`, or `:both`.

| | Reply | Payload |
Expand Down Expand Up @@ -1433,7 +1434,8 @@ defmodule Corex.Accordion do
{:noreply, assign(socket, :focused_item, item)}
end
```
"""
""")

def focused(accordion_id, opts) when is_binary(accordion_id) and is_list(opts) do
JS.dispatch("corex:accordion:focused",
to: "##{accordion_id}",
Expand All @@ -1448,8 +1450,7 @@ defmodule Corex.Accordion do

def focused(accordion_id) when is_binary(accordion_id), do: focused(accordion_id, [])

@doc type: :api
@doc ~S"""
api_doc(~S"""
Read the focused item from `handle_event` (`accordion_focused`). Same replies as [`focused/2`](#focused/2).

| Reply | Payload |
Expand All @@ -1474,7 +1475,8 @@ defmodule Corex.Accordion do
{:noreply, assign(socket, :focused_item, item)}
end
```
"""
""")

def focused(socket, accordion_id, opts)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(accordion_id) and
is_list(opts) do
Expand All @@ -1485,8 +1487,7 @@ defmodule Corex.Accordion do
)
end

@doc type: :api
@doc ~S"""
api_doc(~S"""
Read expanded, focused, and disabled state for one item from `phx-click`. Dispatches `corex:accordion:item-state`. Optional `disabled:` and `respond_to:` `:server` (default), `:client`, or `:both`.

| | Reply | Payload |
Expand Down Expand Up @@ -1517,7 +1518,8 @@ defmodule Corex.Accordion do
{:noreply, assign(socket, :item_state, {item, state})}
end
```
"""
""")

def item_state(accordion_id, item_value, opts)
when is_binary(accordion_id) and is_binary(item_value) and is_list(opts) do
disabled = Keyword.get(opts, :disabled, false)
Expand All @@ -1544,8 +1546,7 @@ defmodule Corex.Accordion do
item_state(accordion_id, item_value, [])
end

@doc type: :api
@doc ~S"""
api_doc(~S"""
Read item state from `handle_event` (`accordion_item_state`). Same replies as [`item_state/3`](#item_state/3).

| Reply | Payload |
Expand All @@ -1570,7 +1571,8 @@ defmodule Corex.Accordion do
{:noreply, assign(socket, :item_state, {item, state})}
end
```
"""
""")

def item_state(socket, accordion_id, item_value, opts)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(accordion_id) and
is_binary(item_value) and is_list(opts) do
Expand Down
4 changes: 2 additions & 2 deletions lib/components/combobox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ defmodule Corex.Combobox do
])}
>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(to_string(item.value))} />
<Flagpack.flag name={String.to_existing_atom(to_string(item.value))} />
{item.label}
</:item>
<:trigger>
Expand Down Expand Up @@ -108,7 +108,7 @@ defmodule Corex.Combobox do
])}
>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(to_string(item.value))} />
<Flagpack.flag name={String.to_existing_atom(to_string(item.value))} />
{item.label}
</:item>
<:trigger>
Expand Down
2 changes: 1 addition & 1 deletion lib/components/listbox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ defmodule Corex.Listbox do
}>
<:label>Country of residence</:label>
<:item :let={%{item: entry}}>
<Flagpack.flag name={String.to_atom(entry.value)} />
<Flagpack.flag name={String.to_existing_atom(to_string(entry.value))} />
{entry.label}
</:item>
<:item_indicator><.heroicon name="hero-check" /></:item_indicator>
Expand Down
4 changes: 2 additions & 2 deletions lib/components/select.ex
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ defmodule Corex.Select do
Country of residence
</:label>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(to_string(item.value))} />
<Flagpack.flag name={String.to_existing_atom(to_string(item.value))} />
{item.label}
</:item>
<:trigger>
Expand Down Expand Up @@ -102,7 +102,7 @@ defmodule Corex.Select do
])}
>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(to_string(item.value))} />
<Flagpack.flag name={String.to_existing_atom(to_string(item.value))} />
{item.label}
</:item>
<:trigger>
Expand Down
Loading
Loading