diff --git a/Gemfile.lock b/Gemfile.lock index 25d9ebe..5ee8f43 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -133,7 +133,7 @@ GEM logger (~> 1.5) concurrent-ruby (1.3.7) connection_pool (3.0.2) - crass (1.0.6) + crass (1.0.7) date (3.5.1) debug (1.11.1) irb (~> 1.10) @@ -498,7 +498,7 @@ CHECKSUMS childprocess (5.1.0) sha256=9a8d484be2fd4096a0e90a0cd3e449a05bc3aa33f8ac9e4d6dcef6ac1455b6ec concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0 connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a - crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d + crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295 date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6 docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e diff --git a/app/javascript/controllers/chip_overflow_controller.js b/app/javascript/controllers/chip_overflow_controller.js new file mode 100644 index 0000000..cf687a7 --- /dev/null +++ b/app/javascript/controllers/chip_overflow_controller.js @@ -0,0 +1,74 @@ +import { Controller } from "@hotwired/stimulus" + +// Collapses a wrapping row of chips down to a fixed number of rows, with a +// toggle button at the end of the last visible row to reveal the rest. +export default class extends Controller { + static targets = ["chip", "button"] + static values = { rows: { type: Number, default: 2 }, expanded: Boolean } + + connect() { + // Default to expanded when a chip in this category is already selected, so + // the active preference is visible without having to click "Show more". + if (this.chipTargets.some((chip) => chip.querySelector("input")?.checked)) { + this.expandedValue = true + } + this.onResize = () => this.layout() + this.resizeObserver = new ResizeObserver(this.onResize) + this.resizeObserver.observe(this.element) + window.addEventListener("resize", this.onResize) + this.layout() + } + + disconnect() { + if (this.resizeObserver) this.resizeObserver.disconnect() + window.removeEventListener("resize", this.onResize) + } + + toggle() { + this.expandedValue = !this.expandedValue + this.layout() + } + + layout() { + if (!this.hasButtonTarget) return + + // Reset: reveal everything so positions can be measured. + this.chipTargets.forEach((chip) => (chip.hidden = false)) + this.buttonTarget.hidden = true + + if (this.expandedValue) { + this.buttonTarget.textContent = "Show less" + this.buttonTarget.hidden = false + return + } + + const tops = [...new Set(this.chipTargets.map((chip) => Math.round(chip.offsetTop)))].sort( + (a, b) => a - b, + ) + + // Already fits within the row budget — nothing to collapse. + if (tops.length <= this.rowsValue) return + + const cutoff = tops[this.rowsValue - 1] + let hidden = 0 + this.chipTargets.forEach((chip) => { + if (Math.round(chip.offsetTop) > cutoff) { + chip.hidden = true + hidden++ + } + }) + + // Reveal the button at the end of the last visible row. If it wrapped past + // the cutoff, hide trailing chips until it fits. + this.buttonTarget.hidden = false + const visibleChips = this.chipTargets.filter((chip) => !chip.hidden) + let i = visibleChips.length - 1 + while (Math.round(this.buttonTarget.offsetTop) > cutoff && i >= 0) { + visibleChips[i].hidden = true + hidden++ + i-- + } + + this.buttonTarget.textContent = `Show more (+${hidden})` + } +} diff --git a/app/views/scenarios/_additional_preferences.html.erb b/app/views/scenarios/_additional_preferences.html.erb index ed452df..dc3b276 100644 --- a/app/views/scenarios/_additional_preferences.html.erb +++ b/app/views/scenarios/_additional_preferences.html.erb @@ -14,14 +14,16 @@ <% roots = roots_by_type[klass_name] %> <% next if roots.blank? %>

<%= klass_name.constantize.tab_label %>

-
+
<% roots.each do |cat| %> -
<% end %>