API Reference devaction_ui v#0.2.3

Copy Markdown View Source

Modules

Sections that open and close. Built on <details>, so it works with the keyboard, in assistive technology and with no JavaScript. In exclusive mode (the default) opening one closes the others, which the browser does itself through the shared name of the <details>.

The field that talks to the AI: a pill in ink with the amber dot, a text field and a round send button. It is a form, so Enter sends; the LiveView gets event with %{name => text} (%{"prompt" => "..."} by default). The field is required, so an empty send never leaves the browser.

A short message under a form or at the top of a screen: it worked, the AI did something, here is how it is, this failed. One line of title and a line or two of text, with an icon, in the darker text of its tone. kind: :risk is announced as an alert; the others as a status.

A turning ring of light around something, for the AI at work. It wraps its content, which keeps its own surface; the ring is the wrapper's padding, painted by a conic gradient whose angle is animated.

The card of a sign-in screen: a header with a soft grid and an amber glow behind an icon tile, a title and a line of text, then the form, and optionally a quieter strip at the bottom for the other way in ("No account? Create one"). One screen is one card, or a card and a note under it.

The page around a sign-in screen: a framed panel with two columns. On the left the product (its mark and name, a headline and a few reasons to sign in); on the right whatever the screen needs, usually an AuthCard; along the bottom a copyright line and a few links. The columns wrap on a narrow screen, the product above the form.

Suggestions while you type, in groups (lessons, pull requests, tasks), with the part that matched in bold and an optional last row that asks the AI. The server owns the search: the field sends event (debounced by debounce ms) with the text, and the caller answers with groups; this component draws the field and the list and moves the active row with ↑ ↓, opens it with Enter and runs the AI row with ⌘ + Enter (or Ctrl + Enter). Esc closes the list, typing opens it again. The list also closes when the focus leaves the whole component, a moment after, so the click that chose a row still lands.

A person as two letters in a circle, and the stack of them overlapping by 8px. The letters come from the name (initials/1); the tone only tells people apart, it never carries meaning, and tone: :auto picks one of four colours (indigo, green, red, gray) from a stable hash of the name, so the same person always has the same colour. A circle with no name is decoration, so a stack names its people for assistive technology once, as a group.

Three small signals: count/1, the round bubble with a number (4 pull requests waiting), dot/1, the status dot (connected, reviewing) and chip/1, the pill with a small avatar and a name. A dot can pulse, and stops under prefers-reduced-motion; it is drawn in the brighter dot shade of its tone.

A full-width strip at the top of a page or a section that tells the whole screen something: news, a degraded service, an expiring plan, a lost connection. A bold lead and a line of text, an icon in front, one action link at the end and a button to dismiss it.

Where the page is: Today / SGE / PR #142. The last item is the current page and always carries aria-current="page", whether it is a link or plain text.

The buttons of the canvas, as a button or as a link, in the same colors and sizes.

A month or a week of days with their events, as an agenda of deliveries: each event is a coloured chip by kind (risk, warning, ok, info, neutral). It is a grid (role="grid", rows, column headers, cells) with one cell in the tab order and the arrows, Home, End and Page keys moving between days (DevactionUI.Calendar hook); pressing a day, or Enter on it, sends select_event with phx-value-date, and the previous and next buttons send prev_event and next_event.

Where the events of one day go in a column of hours: the pure geometry behind the week view with hours.

A tinted block that says something to the reader: a note, what the AI did, a tip, a warning, a decision. Color is never alone: it always carries a title or an icon slot, and an :alert role when it is urgent.

Light charts in plain SVG, drawn on the server: a line with two series, horizontal bars, columns, a ring and the sparkline of a number card. No library and no script: the hover is CSS (and keyboard focus shows the same).

The arithmetic of the charts, with no markup: where a value sits on an axis, the path of a line, an area, a column with a rounded top and a slice of a ring. Every function is pure, so the charts can be drawn on the server and tested without a browser.

One message of the conversation with the AI. The AI speaks on the left, with its mark beside the bubble and the corner nearest the mark squared; the person speaks on the right in ink. A message that did something (a lesson sent, a task made) is drawn :highlight, in the amber of "the AI did this" with the strong text of that tone; a message from :me ignores highlight. The mark of the AI (the avatar slot) sits in a 28px rounded box of the dark --bubble-mark colour.

Boxes to tick and options to pick, as the canvas draws them: the checkbox (with the partial state of "select all"), the radio with its description and the choice card. They are real <input>s, so the browser, the form and the keyboard do the work; the look comes from the checkbox, radio and choice-card classes.

A block of code in mono, given as the code attribute. lang: :heex colours it (components, slots, attributes, the special :if/:for/phx-* ones, strings, expressions, comments) and lang: :elixir colours Elixir, both through DevactionUI.Highlight; the code inside a HEEx {} or <% %> is coloured as Elixir too. tone: :dark is the ink block of a suggestion; :risk and :ok are the "your code" and "idiomatic" blocks of a lesson, tinted red and green; :neutral is the quiet tint. The dark block has fixed colours on purpose: it is a dark surface in both themes, and so is its syntax palette (the tinted tones take theirs from the tone). It keeps its own line breaks and scrolls sideways instead of widening the page.

A colour field with a panel: a square for saturation and brightness, a hue strip, an optional opacity strip, a row of preset colours and, in a browser that has one, the eyedropper. It always sends hex (#RRGGBB, or #RRGGBBAA with alpha) in the text field named name, so a form reads it like any other field and phx-change hears every change. The hook sits on a wrapper with the id <id>-picker (the field keeps id itself), and rest goes to that wrapper. disabled locks the field, the swatch and the panel, required goes to the text field.

A select with a search box: the button shows the choice, the panel has a field that filters the list while you type (ignoring case and accents), ↑ ↓ move, Enter chooses, Esc closes, and an empty result says so. ↓ or ↑ on the button opens it. The value goes in a hidden input named name, and the hook fires input on it so phx-change hears it. Options are %{value: ..., label: ..., hint: ...}; hint is a small mono text at the right.

The ⌘K palette as a library piece: a dialog near the top with a large field, an optional row of scopes (all, pull requests, tasks...), the results in groups with their hints, an "ask the AI" action with the typed text and a footer that teaches the keys. ↑ ↓ move the active row, ↵ opens it, ⌘↵ asks the AI and Esc closes it.

A modal that asks before something that cannot be taken back, such as publishing a review to GitHub. It is a DevactionUI.Modal with the question as the title, the explanation as the text and the two buttons in the footer, so it takes the focus in, keeps it inside, closes with Esc and a click outside (unless dismissible={false}) and gives the focus back to what opened it. The buttons are the caller's, so the confirming one can say what it does ("Publish", not "OK"). There is no close button in the corner unless close_label is given.

A menu of actions that opens on a right click, or on Shift + F10 (or the menu key) while the focus is in the area it belongs to, at the pointer and not under a button (DevactionUI.ContextMenu hook). The area is the area slot, such as the row of a list; the menu is the same as the one DevactionUI.Dropdown draws (icons, a shortcut at the right of an item, separators, a danger item) and it takes the same item slots, so the items are written once: with a more slot (the "···" icon) and a more_label the target also gets a button at its right that opens those same items as a dropdown, for whoever has no right button.

The cookie notice, a bordered card anchored to the bottom of the page.

A read-only value in monospace with a Copy button, for what a person must take elsewhere: a webhook secret, an API key, an invite link, a repository URL. An eye button shows or hides the value (masked with bullets, the first and last characters kept so the person can tell which one it is), and the Copy button says copied_label for a moment after it writes to the clipboard.

The dialog before something that cannot be undone: stop monitoring a repository, delete a project. The red button stays locked until the person types the name of the thing, so it is never a reflex. A click outside does not close it, only Esc, Cancel and the close button do.

A table for lists that are worked on: sticky header, sortable columns (aria-sort, the server sorts and the table only draws the arrows), optional row selection with a tri-state header checkbox and a bulk bar that appears while something is selected, two densities, and the states of a list: loading, error and empty.

A month calendar that picks one day or a range, and the small chip that says how near a deadline is.

A button that shows a range of dates and opens, in a DevactionUI.Popover, a panel to change it: a column of ready-made ranges (Today, Last 7 days, This month, a milestone of the project, Custom), two months side by side with the band of the range, a dot on today and a preview of the range under the pointer, and a footer with how many days are picked, Cancel and Apply. An optional column on the right (the aside slot) takes whatever goes with the range, such as DevactionUI.TimeField.

A list of terms and their values, one row each, with a hairline between the rows: the facts of a client, a repository, a pull request. It is a real description list (<dl> with a <dt> and a <dd> per row), so a screen reader announces each value with its term.

The diff of one file in a review: a header with the path, the lines added and removed and a "viewed" checkbox, and the unified diff as a table with the old and the new line numbers, a gutter with the button that adds a comment, and tinted rows for the added and the removed lines. line_thread/1 is the conversation that hangs from a line.

A thin line, or a line with a short word in the middle (the "OR" between a social button and a form). The word is in mono capitals and is given by the caller. A divider with a word is a separator for assistive technology, and the word is read out.

A panel at the side of the screen to edit without leaving it: a profile, a task, an item of a contract. The default is floating (12 px from the edges, rounded, with a footer that stays); it can sit docked against the edge, come from the left, and on a phone it is a sheet that rises from the bottom, with a handle. The variant :sheet is that bottom sheet at any width.

A button that opens a menu of actions or a choice (sort by deadline, by value, by risk). The menu opens under the button (above it when there is no room below); ↑ ↓ Home End move between items, Enter or Space chooses, Esc closes and returns to the button, a click outside closes it (DevactionUI.Dropdown hook).

The place to drop a file: a dashed area with an icon, what it takes and the limits. It wraps a LiveView upload (allow_upload/3), so the same area takes a drop and a click, and highlights while a file is over it (DevactionUI.Dropzone hook). What went wrong with a file is shown under it, from errors, tied to the file field with aria-describedby, and the errors of the upload itself (upload_errors/1, such as too many files) are added to them, written with the texts of error_labels.

A rich text editor for delivery notes, scope and long comments: a toolbar over a TipTap (ProseMirror) surface, the way the canvas draws it (.editor, .editor-toolbar, .editor-btn, .editor-content).

What a list or a screen shows when there is nothing in it yet. Every empty one says what the area is, why it is empty and the one next step.

The label, the hint, the error and the counter around any input, wired to it for assistive technology. The input is whatever the caller puts in the slot: an InputGroup, a Textarea, a select, a plain <input>.

The tree of changed files of a review: folders and files with a status letter (added, modified, deleted), a badge with the number of findings, a header with the count and the lines added and removed, a search field, filter pills and a legend of the letters.

A button that opens a menu of filter groups; a group opens a submenu of ticks, checkboxes or radios. Ticking does not close the menu. The menu is a form: it sends on Apply (phx-submit), or on every tick with phx-change, as %{"filters" => %{"status" => ["progress"], "who" => ["CA"]}}. A count on each group and on the button follows the ticks, except for an option marked default, which is what the filter already means.

The round pills of a filter bar: on or off, with a ×, and the dashed one that adds a filter.

Splits HEEx or Elixir source into tokens a code block can colour: {kind, text} pairs whose texts, joined, are the source again. It is cheap and never fails: what it does not know, or what is cut short, is plain :text.

A card of more about a name, a number or a link (a person with a few figures, a pull request with what blocks it) that opens when the pointer rests on its trigger for 400 ms or the keyboard focus reaches it, and closes when both have left (DevactionUI.HoverCard hook). The trigger is the slot, drawn with a dotted underline; the card is the inner_block. It adds information and never the only way to do something: whatever is in it must also be on the page the trigger leads to.

A text field with what the canvas puts around it: an icon at the start, an action at the end (show the password, clear, copy) and add-ons on either side (R$, https://, .devaction.app). The frame carries the focus ring, the error and the disabled look, so the field inside stays a plain <input>.

A board of columns with cards that move: drag a card (with a mouse, a pen or a finger) to another column or to another place in its own, or focus it and press Alt + arrow keys (←→ between columns, ↑↓ within one). The hook moves the card in the page at once and tells the LiveView, which stays the source of truth: it receives the event with %{"card" => id, "from" => column, "to" => column, "index" => position} and re-renders. To refuse a move, answer with {:reply, %{rejected: true}, socket} and the hook puts the card back where it was.

A key cap: ⌘K, ESC, ↵. Mono 11px with a thicker bottom edge, as the canvas draws the keyboard hints.

A dialog for a decision that interrupts: confirm, tell that something worked, fill a short form. To edit without losing the context use DevactionUI.Drawer; to ask before something that cannot be undone use danger_dialog/1.

A field for an amount of money: the person types digits and the field shows 1.234,56, the last digits being the minor units (cents). It is the Brazilian real by default (prefix R$, decimal ,, group ., precision 2) and any other currency through those attrs

The panel that lists what happened since the person last looked.

A number field with a minus and a plus, for a quantity that is changed a step at a time (hours, seats, a rate): the buttons are disabled at the limits, the field is a spinbutton that says its value (and its unit) and its limits, and typing in it works as well. The arrows, PageUp and PageDown (ten steps), Home and End (the limits) are the hook DevactionUI.NumberInput, and what is typed is held inside min and max when the field is left.

The boxes of a one-time code, one character each: the code sent by SMS or e-mail, a two-factor code. Typing a digit fills its box and moves on; pasting the code fills every box; Backspace clears a box and, on an empty one, steps back; the arrows, Home and End move between boxes (DevactionUI.OtpInput hook, logic in text.js).

The head of a screen as the canvas lays it out: the breadcrumb above, then the title with its status tag and the actions at the end, an optional line of text, and the tabs under it. It only places the pieces; each one is the caller's (a DevactionUI.Breadcrumb, a DevactionUI.Tag, buttons, tabs).

Page links: previous, a window of numbers around the current one with the first and last page and an ellipsis where it skips, and next.

The canvas card: a soft wrap around a bordered card, and the mono eyebrow/1 caption that opens it. The card inside takes the card colour in both themes, so it reads against the wrap around it. Its padding and gap follow size (:sm 22px and 14px, :md 24px and 20px, :lg 26px and 20px); inner_class overrides them. For the dashed box of a list with nothing in it, use DevactionUI.EmptyState.

The meter under a new-password field: four bars that fill as the password meets the rules, a line saying how strong it is, and the rules with a mark for each that is met. The server computes it from the text (evaluate/2) and the field sends it on phx-change; nothing is kept in the browser.

A PDF viewer with signature fields: a rail of page thumbnails, a toolbar, the page and a panel of signers.

A field of removable pills, for labels, e-mails, stacks: type and press Enter (or a comma) to add one, Backspace on an empty field removes the last, the × on a pill removes that one. Each pill is a hidden input named name[], so the form submits the list as it stands; the hook fires input on the box so phx-change hears every change. The browser owns the pills after mount (phx-update="ignore"): give it a new id to reset it from the server.

A panel anchored to a button, with an arrow: the trigger opens and closes it, Esc and a press outside (pointerdown, so touch works) close it, focus goes into it when it opens and back to the trigger when it closes. It is not modal and does not trap the focus: Tab past its last control closes it and the focus goes on to what follows the trigger, and Shift+Tab before its first control closes it and returns to the trigger.

A table of plans with a billing toggle and a card per plan.

A progress bar (role="progressbar") with a value from 0 to 100 and the tone it fills with: ink by default, amber for what the AI did, green when it is done. :segmented shows the same value in segments pieces (the steps of a job), :thin is the 4 px line of the work in the background, and indeterminate is for what has no end in sight: it shimmers (still, under reduced motion) and gives no value.

A notice for a job that takes a while: it fills as the job advances (progress, 0 to 100), with a ring of light while it runs. When the LiveView sets state to :done it waits a moment (1.6 s, paused while the pointer or the focus is on it), leaves, and tells the LiveView (on_dismiss, with phx-value-id) to drop it. An :error never leaves by itself: the person reads it, tries again through the action slot, or closes it. The icon slot receives the state, so the mark can be a spinner while it runs, a check when it is done and a warning when it fails.

Panels side by side (or one above the other) with a divider between each two that the person moves: drag it with a mouse, a pen or a finger, press ← → (↑ ↓ in a vertical group; Shift takes a larger step) or Home and End on it, and a double click shares the two panels around it equally (DevactionUI.Resizable hook). Each panel has a size in percent, the sizes of the group add up to 100, and a panel may have a min and a max; moving a divider gives what one panel gains to the next and never takes either past its limits.

A control to pick one of a few options that change the view at once (list or board, week or month): a pill with a segment per option. It is a group of toggle buttons with aria-pressed; the caller keeps the value and learns of a choice through event (phx-value-value). A segment can have an icon, a count, or only an icon (the label is then read by assistive technology). full stretches it to the width of its box. An option can be disabled, take its own class, and send patch or navigate (a path) instead of the event, for a filter that lives in the URL.

A hairline that separates, in three forms: the plain line (separator/1), the line with a short text in the middle, such as the one between a social sign-in and a form (separator/1 with label), and an inline row of small facts divided by vertical bars (inline/1). Any of them can stand upright with orientation: :vertical.

The side menu of an app: 236 px wide, or 64 px when collapsed, where each item keeps its icon and shows its name in a tooltip on hover and on keyboard focus. The current item has aria-current="page". The caller keeps whether it is collapsed (collapsed) and gets the press of the toggle in on_toggle, which needs a toggle_label (without one it raises). It is hidden below the md breakpoint, where the app has its own navigation; responsive={false} keeps it on every screen.

A field to sign: draw with the mouse, the finger or the pen, or type the name; clear and start again. It writes what was signed into hidden inputs for the form: the method, the typed name or the SVG of the stroke (DevactionUI.Signature hook, exportSvg). The SVG is the drawing only, in its own coordinates, so it can be kept and drawn back at any size. A drawing is accepted by the server only after safe_path?/1.

A placeholder for what is loading: a bar (:line), a round shape (:circle, 36 px) or a block (:block, 40 px), with a shimmer that stops under prefers-reduced-motion or with animate={false}. A line has three heights, size :md (14 px, a text line), :sm (12 px, a row of a list) and :xs (10 px, the label of a number); for anything else size it with class (h-4 w-40). The shimmer runs from the soft line to the --shimmer token and back.

A slider for a value inside a range, and a range slider for two (a from and a to). Both are native <input type="range">, so the keyboard (arrows, Home, End, Page keys) and assistive technology work as the browser does them; the look is the canvas's. The value is shown beside the track and is the LiveView's, sent by the form's phx-change. Values, limits and step are numbers: integers or floats.

A list whose rows the person puts in order: drag a row by its handle (a mouse, a pen or a finger), press the up and down buttons of a row (off at the ends), or focus the handle and press Space to pick the row up, ↑ ↓ to move it (Home and End go to the ends), Space or Enter to drop it and Esc to put it back (DevactionUI.SortableList hook). A row has a handle, a mono code, a label, a status text at the right and the two buttons.

A spinning ring for work in progress, one turn in 0.8 s. The label is read by screen readers and never drawn. size is :md (22 px, the canvas's) or :sm (16 px, for inside a button); tone colors the moving arc (ink by default).

A number with its name: the mono caption over a large value, then a line below with a small trend tag and a hint. By default it is a bordered card (bordered); highlight is the inverted version, white on ink, for the one number a screen is about. The value is the slot, and its unit (%, /12, k) sits beside it in the muted 24px of the canvas.

The steps of an onboarding or of a creation wizard, with the state of each: :done, :current, :error and :todo. Horizontal, each step is a bar over a dot, a label and a line of help; vertical, it is a checklist with a rail. The current step carries aria-current="step", and the state is also written (not only coloured) for assistive technology.

A row of steps with connectors: done (ink, with a check), the current one (amber ring) and those still to come (a thin outline). current counts from 1; the labels are the caller's text.

A switch for what takes effect at once, with no save button (for a choice that only counts after saving, use a checkbox). It is a <button role="switch">: aria-checked says the state, the keyboard toggles it and the caller does the work in its own phx-click. By default it writes no hidden input, so it does not take part in a form's submit: the state is the LiveView's, kept by the click. Give it a name (the field mode) and it renders a hidden input <id>-field with "true" or "false", which the DevactionUI.Switch hook keeps the same as the button, firing input so the form's phx-change hears it; a press then works with no phx-click at all. While the change is being kept, saving puts a spinner in the thumb and the control ignores presses; saved_note/1 is the "saved now" the caller shows after.

Tabs that switch panels without a round trip: variant: :boxed is the pill group, :line the underlined row. A small hook moves the selection with ← → Home End (roving tabindex, aria-selected) and shows the panel of the selected tab. With orientation: :vertical the tabs are a column beside the panels, the list says aria-orientation="vertical" and the keys are ↑ ↓ Home End. Each tab may carry a count, in a count_tone if it should stand out.

The small mono label of the canvas: EM RISCO, AGUARDANDO, EM DIA, APRENDIZADO, PLANEJADO.

A text area as the canvas draws it: it grows with its text (field-sizing: content, up to 12 lines), has an optional footer for actions (attach, mention, improve with the AI) and a counter that turns red near the limit. The label and the help text are the caller's; this is the field.

A start time and an end time with the suggestions around the one being edited and how long the span is: what a time sheet or a booking asks for. The fields are text inputs with a clock icon that take digits and lay them out as HH:MM (930 becomes 09:30 when the field is left); under them a list shows the steps around the time of the field in focus, each with its length from the start when the end is being edited (10:30 · 1 h 30 min), the chosen one inverted; below, a Duration row and a note, such as the time zone and the step.

Things that happened, in order, on a thin line: each item has a dot in a tone, a title, its time on the line below and optional text. The colour of the dot is decoration; give an item a tone_label ("Failed") so assistive technology reads what the colour says, and a datetime to make its time a machine-readable <time>.

A timer of hours for a task: a small caption of its state, the time in large mono digits, what is being timed, and two round buttons: start or pause, and stop and log. The LiveView owns the truth (it is not lost on a reload): the seconds already counted (base) and, while it runs, the moment it started (started_at, in seconds since the epoch). The browser only draws the passing seconds between two updates (DevactionUI.Timer hook); elapsed/3 is what the server counts when it logs. The stop-and-log button stays disabled while nothing has been counted (log_event is not sent then).

A pile of toasts that opens into a list when you point at it: notices pushed from the server that never cover the screen. Put one toast_group/1 in the layout and push toasts to it from any LiveView with put_toast/4, update_toast/3, dismiss_toast/2 and clear_toasts/1.

The tones of the canvas as class strings, so every component that tints something (a callout, a finding, an alert, a progress bar) takes the same colors from the same tokens and flips with the theme.

A short hint on hover and on keyboard focus, drawn by CSS and tied to its trigger by a small hook (DevactionUI.Tooltip). The trigger is the slot; text is what it says. The hook puts aria-describedby on the first focusable control inside the slot (the screen reader only reads the description of the focused element, so it cannot sit on the wrapper) and lets Esc dismiss the hint while the pointer or the focus is still there. It is for a name or a short explanation, never for something the person must read to continue: on a screen with no hover it does not appear.