Flicker.Theme (Flicker v0.1.0)

Copy Markdown View Source

A class-per-part theme map for the select component (ADR-002).

Flicker never hardcodes a CSS framework. Instead every visually distinct part of the rendered markup has a named key here, and a preset supplies the class string for each part.

Three presets ship:

  • vanilla/0 — plain, framework-free flicker-* class names a host hooks their own CSS onto. The default.
  • tailwind/0 — Tailwind utility classes, no component library.
  • daisy_ui/0 — daisyUI component classes (input, menu, dropdown-content, ...).

Every preset covers every key below — see Flicker.ThemeTest for the assertion that keeps that true as keys are added.

Parts

  • :wrapper — the outermost element.
  • :search_input — the text input.
  • :clear_button — the button that clears the current selection.
  • :listbox — the results dropdown.
  • :option — a single result row.
  • :option_active — added to the active (keyboard-highlighted) option; the JS hook toggles this class client-side.
  • :option_label — the option's primary label span (default rendering only; an :option slot owns its own markup).
  • :option_sublabel — the option's secondary/sublabel span (default rendering only).
  • :suggestion — a facet key/value suggestion row (Spec 003); rendered in place of :option when the listbox is suggesting facets rather than records, so suggestions can look distinct from results.
  • :suggestion_token — the status:/active token span inside a suggestion row (kbd/mono treatment).
  • :loading_state — the listbox's loading row.
  • :empty_state — the listbox's no-results row.
  • :error_state — the listbox's error row.
  • :facet_editor — Spec 019 facet-editor part.
  • :facet_editor_header — Spec 019 facet-editor part.
  • :facet_editor_body — Spec 019 facet-editor part.
  • :facet_editor_footer — Spec 019 facet-editor part.
  • :facet_editor_sheet — the bottom-sheet variant of the pop-out. The shipped framework presets fold the sheet behaviour into :facet_editor itself with max-sm: utilities, since a 252px calendar in a dropdown is unusable on a phone; this part exists for a vanilla host that wants to target the sheet with its own CSS.
  • :preset_rail — Spec 019 facet-editor part.
  • :preset_group_label — Spec 019 facet-editor part.
  • :preset_row — Spec 019 facet-editor part.
  • :preset_row_selected — Spec 019 facet-editor part.
  • :preset_row_range — Spec 019 facet-editor part.
  • :calendar — Spec 019 facet-editor part.
  • :calendar_nav — Spec 019 facet-editor part.
  • :calendar_nav_button — Spec 019 facet-editor part.
  • :calendar_month_label — Spec 019 facet-editor part.
  • :calendar_weekday — Spec 019 facet-editor part.
  • :calendar_grid — Spec 019 facet-editor part.
  • :calendar_day — Spec 019 facet-editor part.
  • :calendar_day_today — Spec 019 facet-editor part.
  • :calendar_day_selected — Spec 019 facet-editor part.
  • :calendar_day_in_range — Spec 019 facet-editor part.
  • :calendar_day_edge — Spec 019 facet-editor part.
  • :calendar_day_disabled — Spec 019 facet-editor part.
  • :dial — Spec 019 facet-editor part.
  • :dial_track — Spec 019 facet-editor part.
  • :dial_fill — Spec 019 facet-editor part.
  • :dial_thumb — Spec 019 facet-editor part.
  • :dial_input — Spec 019 facet-editor part.
  • :dial_value_label — Spec 019 facet-editor part.
  • :switch — Spec 019 facet-editor part.
  • :switch_thumb — Spec 019 facet-editor part.
  • :switch_on — Spec 019 facet-editor part.
  • :editor_error — Spec 019 facet-editor part.
  • :facet_count — the match count rendered beside a facet value (Spec 021).
  • :facet_count_zero — added to a value row whose count is 0. Dimmed rather than hidden, and still selectable: hiding it answers "why did that option disappear?" with silence, where 0 answers it.
  • :facet_pill_invalid — a committed-facet pill whose value failed to cast (Spec 023). Carries the error styling; the message itself renders in :facet_error_message, never as a tooltip.
  • :facet_error_message — the visible reason text beside an invalid pill.
  • :facet_error_icon — the error glyph, so the state is never signalled by colour alone.
  • :facet_correction — a suggested replacement value ("did you mean...").
  • :facet_correction_accept — the accept affordance on the top suggestion.
  • :dispatch_blocked — the visible "filter not applied" state under on_invalid: :require.
  • :results_stale — added to the listbox while a new search is in flight and previous results are still on screen (Spec 020). The previous set stays rendered rather than the list emptying and refilling; this part is what marks it as not-yet-current.
  • :facet_trigger_hint — the discoverability affordance naming the facet-entry trigger character ("Type @ to filter", Spec 024). Only rendered when facet_trigger is set, and referenced by the input's aria-describedby so the affordance is announced on focus rather than only being visible.
  • :dispatch_hint — the "press Enter to search" affordance shown beside the input while dispatch: :enter holds undispatched text (Spec 020). Also the target of the input's aria-describedby in that state.
  • :hint — the "keep typing to narrow results" / min-chars hint row; also the tail row shown once a paginate-d list hits max_windows or a provider ignoring :offset is detected (Spec 010) — windowing degrades into this same hint rather than a separate one.
  • :loading_more — paginate-d select only (Spec 010): the themed row rendered at the listbox tail while the next window loads.
  • :chip_list — multi-select: the wrapper around the selected chips.
  • :chip — multi-select: a single selected-value chip.
  • :chip_remove — multi-select: the per-chip remove button.
  • :multi_field — the bordered field box that holds tokens (multi-select chips, or Flicker.search/1's committed facet pills) and the text input together, so the tokens sit inside the input, not above it. Replaces :wrapper+:search_input's border for those cases.
  • :multi_input — the borderless text input inside :multi_field (the box owns the border; the input just grows to fill).
  • :multi_clear — multi-select: the "clear all" button inside :multi_field, vertically centred in the box (in normal flow, unlike the single-select :clear_button which overlays its input).
  • :facet_pill_list — Flicker.search/1 (Spec 012): the wrapper around the committed-facet pills inside :multi_field.
  • :facet_pill — Flicker.search/1: a single committed-facet pill. Its field name is exposed on hover (a title) rather than always shown.
  • :facet_pill_field — the pill's small field label (e.g. "Status"), shown without a colon so the pill reads as a labelled value rather than as the key:value text it came from (Spec 012).
  • :facet_pill_value — the pill's primary value label (e.g. "Active").
  • :facet_pill_remove — the pill's remove (×) button.
  • :selected_stack — multi-select with a :selected slot (Spec 013): the container around the custom-rendered selected items (e.g. an avatar stack); replaces :chip_list when a :selected slot is given.
  • :selected_item — the per-item wrapper inside :selected_stack (positioning context for its remove control).
  • :selected_overflow — the "+N" token shown when the selection exceeds max_visible.
  • :kbd_hint — the <kbd> discoverability hint rendered when activate_with_keyboard is set (Spec 006); also used for the footer's ↑↓/↵/esc hints in Flicker.palette/1 (Spec 008).
  • :backdrop — Flicker.palette/1: the full-viewport overlay backdrop behind the panel (Spec 008).
  • :panel — Flicker.palette/1: the centred dialog panel.
  • :palette_input — Flicker.palette/1: the large search input (in place of :search_input, which the plain select still uses).
  • :group_header — a non-interactive row rendered before the first result of each new result.group (Spec 008); unused when no result in the list carries a :group.
  • :footer — Flicker.palette/1: the panel's footer row of keyboard hints.
  • :footer_hint — Flicker.palette/1: the individual <kbd> chips inside the footer (↑↓/↵/esc). Distinct from :kbd_hint, which overlays the select input and is absolutely positioned — the footer chips sit in normal flow, so reusing :kbd_hint piled them all at the panel's right edge.
  • :palette_close — Flicker.palette/1: the panel's close control. Distinct from :clear_button (which overlays the select input, absolute positioned) so the close control doesn't land on top of the palette input.

Resolution

resolve/1 merges, in increasing precedence: vanilla/0, then config :flicker, default_theme:, then a per-component override:

config :flicker, default_theme: %{search_input: "my-input"}

<Flicker.select theme={%{listbox: "my-listbox"}} ... />

An override may be a %Flicker.Theme{} struct (replaces the base wholesale) or a map/keyword list of just the parts to change (merged onto the base with struct!/2).

Summary

Types

An override: a full theme, or a partial map/keyword list of parts.

t()

A class-per-part theme map.

Functions

The daisyUI preset: input, menu, dropdown-content, and friends.

Resolves the effective theme for a component instance, merging (lowest to highest precedence) vanilla/0, config :flicker, :default_theme, and component_override.

The Tailwind preset: utility classes only, no component library.

The default preset: plain, framework-free flicker-* class names.

Types

override()

@type override() :: t() | map() | keyword() | nil

An override: a full theme, or a partial map/keyword list of parts.

t()

@type t() :: %Flicker.Theme{
  backdrop: String.t(),
  calendar: String.t(),
  calendar_day: String.t(),
  calendar_day_disabled: String.t(),
  calendar_day_edge: String.t(),
  calendar_day_in_range: String.t(),
  calendar_day_selected: String.t(),
  calendar_day_today: String.t(),
  calendar_grid: String.t(),
  calendar_month_label: String.t(),
  calendar_nav: String.t(),
  calendar_nav_button: String.t(),
  calendar_weekday: String.t(),
  chip: String.t(),
  chip_list: String.t(),
  chip_remove: String.t(),
  clear_button: String.t(),
  dial: String.t(),
  dial_fill: String.t(),
  dial_input: String.t(),
  dial_thumb: String.t(),
  dial_track: String.t(),
  dial_value_label: String.t(),
  dispatch_blocked: String.t(),
  dispatch_hint: String.t(),
  editor_error: String.t(),
  empty_state: String.t(),
  error_state: String.t(),
  facet_correction: String.t(),
  facet_correction_accept: String.t(),
  facet_count: String.t(),
  facet_count_zero: String.t(),
  facet_editor: String.t(),
  facet_editor_body: String.t(),
  facet_editor_footer: String.t(),
  facet_editor_header: String.t(),
  facet_editor_sheet: String.t(),
  facet_error_icon: String.t(),
  facet_error_message: String.t(),
  facet_pill: String.t(),
  facet_pill_field: String.t(),
  facet_pill_invalid: String.t(),
  facet_pill_list: String.t(),
  facet_pill_remove: String.t(),
  facet_pill_value: String.t(),
  facet_trigger_hint: String.t(),
  footer: String.t(),
  footer_hint: String.t(),
  group_header: String.t(),
  hint: String.t(),
  kbd_hint: String.t(),
  listbox: String.t(),
  loading_more: String.t(),
  loading_state: String.t(),
  multi_clear: String.t(),
  multi_field: String.t(),
  multi_input: String.t(),
  option: String.t(),
  option_active: String.t(),
  option_label: String.t(),
  option_sublabel: String.t(),
  palette_close: String.t(),
  palette_input: String.t(),
  panel: String.t(),
  preset_group_label: String.t(),
  preset_rail: String.t(),
  preset_row: String.t(),
  preset_row_range: String.t(),
  preset_row_selected: String.t(),
  results_stale: String.t(),
  search_input: String.t(),
  selected_item: String.t(),
  selected_overflow: String.t(),
  selected_stack: String.t(),
  suggestion: String.t(),
  suggestion_token: String.t(),
  switch: String.t(),
  switch_on: String.t(),
  switch_thumb: String.t(),
  wrapper: String.t()
}

A class-per-part theme map.

Functions

daisy_ui()

@spec daisy_ui() :: t()

The daisyUI preset: input, menu, dropdown-content, and friends.

<Flicker.select theme={Flicker.Theme.daisy_ui()} ... />

or globally via config :flicker, default_theme: Flicker.Theme.daisy_ui().

resolve(component_override \\ nil)

@spec resolve(override()) :: t()

Resolves the effective theme for a component instance, merging (lowest to highest precedence) vanilla/0, config :flicker, :default_theme, and component_override.

tailwind()

@spec tailwind() :: t()

The Tailwind preset: utility classes only, no component library.

<Flicker.select theme={Flicker.Theme.tailwind()} ... />

or globally via config :flicker, default_theme: Flicker.Theme.tailwind().

vanilla()

@spec vanilla() :: t()

The default preset: plain, framework-free flicker-* class names.