Class: Forms::CheckboxGroup

Inherits:
Phlex::HTML
  • Object
show all
Defined in:
lib/forms/checkbox_group.rb

Overview

A batched checkbox group for an array-valued field (the tag/facet-picker shape). Renders a set of checkboxes sharing ONE array-valued field name (user[tag_ids][]), with a leading empty-array hidden field so an empty selection still submits, and derives each box's checked state from the resolved value: of its item against the model's current set.

f.checkbox_group(:tag_ids, Tag.all, value: :id,
item_label: ->(t) { t.name.presence || t.slug }, variant: :pill, size: :sm)

The item value/text accessors (value:/label:/item_label:) live on the BUILDER (Forms::Field#checkbox_group), which pre-resolves each item to { value:, label:, checked:, id: } before this leaf renders. This leaf is presentation-only — it receives the resolved options: array, never the raw accessors. variant: :stack (default) | :inline | :pill — layout only, zero JS size: daisyUI checkbox size modifier (:xs :sm :md :lg :xl)

Accessible name (issue #17): role="group" needs one so a screen reader announces the group when focus enters a checkbox. The leaf does NOT invent its own naming API — extra attributes pass straight through to the group div, so the caller names it with plain HTML/ARIA:

f.checkbox_group(:tag_ids, Tag.all, value: :id, aria: { label: "Tags" })
f.checkbox_group(:tag_ids, Tag.all, value: :id, aria: { labelledby: "hdr" })

Through f.field, the builder points the group at the Control's own visible

The checked set is passed in pre-resolved by the builder (Field#checkbox_group matches the model's current value by each item's resolved value:), so the component itself stays presentation-only. Each checkbox's markup is delegated to DaisyUI::Checkbox so its size class is a literal, scanner-visible token.

Direct Known Subclasses

Plain::CheckboxGroup

Constant Summary collapse

VARIANT_CLASSES =

variant -> the container class. The pill variant uses Tailwind's has-[:checked]: to style the active label with no JS.

{
  stack: "flex flex-col gap-2",
  inline: "flex flex-wrap gap-4",
  pill: "flex flex-wrap gap-2"
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(name:, id:, options:, variant: :stack, size: nil, error: false, **attributes) ⇒ CheckboxGroup

Returns a new instance of CheckboxGroup.



46
47
48
49
50
51
52
53
54
55
# File 'lib/forms/checkbox_group.rb', line 46

def initialize(name:, id:, options:, variant: :stack, size: nil, error: false, **attributes)
  @name = name          # already the array name: "user[tag_ids][]"
  @id = id
  @options = options    # [{ value:, label:, checked:, id: }, ...]
  @variant = variant
  @size = size
  @error = error
  @attributes = attributes
  super()
end

Instance Method Details

#view_templateObject



57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/forms/checkbox_group.rb', line 57

def view_template
  # Empty-array hidden field so an empty selection still submits (the same
  # convention as collection_check_boxes).
  input(type: "hidden", name: @name, value: "")

  # class is the per-checkbox styling seam (see render_checkbox), not a group
  # attribute — everything else the caller passed lands on the group so aria:,
  # data:, id: etc. pass straight through.
  div(class: group_classes, role: "group", "aria-invalid": @error || nil,
    **@attributes.except(:class)) do
    @options.each { |option| item(option) }
  end
end