Catalog CrdtEditor

<.comment_popover>

ConnieUI.CrdtEditor

The director is paid a fixed fee for twelve shooting days and a day rate for pickups.

HEEx
<div class="h-[28rem] w-xl">
  <ConnieUI.Typography.muted size="md">
    The director is paid a fixed fee for
    <span class="comment-anchor-selected">twelve shooting days</span>
    and a day rate for pickups.
  </ConnieUI.Typography.muted>
  <ConnieUI.CrdtEditor.comment_popover
    anchor={@anchor}
    close={JS.push("comment:close")}
    id="catalog-comment-popover"
  >
    <ConnieUI.Layouts.vstack gap="md">
      <ConnieUI.Chat.comment_message
        :for={message <- Enum.take(@thread.messages, 2)}
        name={message.author}
      >
        <:avatar>
          <ConnieUI.Avatars.avatar image={message.image} size="size-6" />
        </:avatar>
        <:meta>{message.at}</:meta>
        {message.body}
      </ConnieUI.Chat.comment_message>
    </ConnieUI.Layouts.vstack>
  </ConnieUI.CrdtEditor.comment_popover>
</div>

Variant matrix

Every combination of one or two attributes, side by side.

Controls

:string
:string

opens without taking the focus, so the selection it opened over stays live to be copied. The first key typed goes to the composer inside instead

:atom

`:thread` is a panel the host's conversation sits in. `:composer` draws no panel: what it holds (`ConnieUI.Chat.composer_form/1`'s `:pill`) is the surface, and the arrow is drawn as part of it, turning brand with its ring while it has the focus

Set by the example

anchor close class

Docs

Floating surface for one comment thread, anchored over the editor. `anchor` is the box the panel belongs to, in viewport pixels: the comment's passage for a thread (`comment:open`) and the Comment button for a new one (`comment:start`), both of which send the same four edges, so one popover serves both. The panel is fixed rather than scrolled with the document. What goes in it is the host's business: this component knows nothing about threads. Placement is Floating UI's: below the anchor and lined up with its start, flipped above it when there is no room below, shifted to stay on screen, with an arrow on the edge that faces the anchor, pointing at it. The server renders the unflipped position, so the panel is where it belongs before the hook has run. The panel follows its passage. The box the server was sent is where the passage was when it opened; once the hook runs it measures the lit passage itself (`.comment-anchor-selected`, which the editor draws on the open thread's words and on the words a new comment is about) and keeps the panel on it as the document scrolls, hiding it while the passage is out of view. The sent box stands in only when nothing is lit, which is a thread whose words have been deleted. It opens out of its arrow, scaling up from where the arrow's tip is as it fades in, and shrinks back into it on the way out, the way Figma's comment composer does. A reader who asked for reduced motion gets the fade alone. `variant` says whether the popover draws the panel (`:thread`) or what it holds is the surface (`:composer`, the pill a new comment is written in). The arrow is a square turned on its corner, the two edges facing the anchor drawn in the panel's ring colour and the rest over the panel, so it covers the ring where the two meet and reads as one outline. The panel opens over the paper, which is the same white it is, so neither is told apart by its fill: the panel by its shadow and a `primary` ring, and the arrow by the same ring colour and a shadow of its own, cast away from the panel so it never falls on the conversation. A composer a reader's selection opened by itself (`keep_selection`) leaves the focus where it was, so the selection is still there to copy. The first key they type without Cmd, Ctrl or Alt goes to the composer, so writing the comment needs no click (`forwardFirstKey/2` in `popover.js`). It carries no close button. A comment panel is read alongside the document rather than instead of it, so it goes away on Escape or as soon as the reader presses something else on the page. Escape closes the innermost thing first: with the @ menu or a message's menu open, it closes that and keeps the draft, and the next one closes the panel (`closesPanel/2` in `comment_popover_keys.js`). What counts as "something else" is `isOutside/2` in `popover.js`, which keeps the panel up for the flyouts its own controls open and for the anchors that move it to another thread.
Attribute Type Default Description
id :string "comment-popover"
anchor * :map
`%{top:, left:, bottom:, right:}` in viewport pixels
close * {:struct, Phoenix.LiveView.JS}
how the panel is dismissed. Required: it is the dialog's only way out, by Escape or by pressing anything on the page that is not part of the panel. There is no close button, so a host that does not pass this leaves the reader with a panel they cannot put away
label :string "Comment thread"
keep_selection :boolean false
opens without taking the focus, so the selection it opened over stays live to be copied. The first key typed goes to the composer inside instead
variant :atom :thread
`:thread` is a panel the host's conversation sits in. `:composer` draws no panel: what it holds (`ConnieUI.Chat.composer_form/1`'s `:pill`) is the surface, and the arrow is drawn as part of it, turning brand with its ring while it has the focus :thread | :composer
class :any nil
Slot Slot attributes Description
inner_block *