-
-
Save swanson/722f890c7fb495443af3f699f25e30e5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div data-controller="table-scroll" | |
data-table-scroll-nav-shown-class="flex" | |
data-table-scroll-nav-hidden-class="hidden" | |
data-table-scroll-button-disabled-class="text-gray-200" | |
data-table-scroll-indicator-visible-class="text-blue-600" | |
> | |
<div class="flex items-center justify-center space-x-2 py-3 text-gray-600" data-table-scroll-target="navBar"> | |
<%= button_tag data: { table_scroll_target: "leftButton", action: "table-scroll#scrollLeft" } do %> | |
<%= svg_icon("left-arrow", class: "h-5") %> | |
<% end %> | |
<% 5.times do %> | |
<%= svg_icon("dot", class: "h-2 text-gray-200", "data-table-scroll-target": "columnVisibilityIndicator") %> | |
<% end %> | |
<%= button_tag data: { table_scroll_target: "rightButton", action: "table-scroll#scrollRight" } do %> | |
<%= svg_icon("right-arrow", class: "h-5") %> | |
<% end %> | |
</div> | |
<div class="flex flex-col mx-auto"> | |
<div class="overflow-x-auto" data-table-scroll-target="scrollArea"> | |
<table class="min-w-full"> | |
<thead class="bg-blue-800 text-white"> | |
<tr> | |
<th class="table-header" data-table-scroll-target="column">Product</th> | |
<th class="table-header" data-table-scroll-target="column">Price</th> | |
<th class="table-header" data-table-scroll-target="column">SKU</th> | |
<th class="table-header" data-table-scroll-target="column">Sold</th> | |
<th class="table-header" data-table-scroll-target="column">Net Sales</th> | |
</tr> | |
</thead> | |
<tbody class="divide-y"> | |
<% @products.each_with_index do |p, idx| %> | |
<%= tag.tr(class: { "bg-white": idx.even?, "bg-gray-50": idx.odd? }) do %> | |
<td class="table-cell font-bold tracking-wide"> | |
<%= p.name %> | |
</td> | |
<td class="table-cell"> | |
<%= number_to_currency(p.price / 100.0) %> | |
</td> | |
<td class="table-cell"> | |
<%= p.sku %> | |
</td> | |
<td class="table-cell"> | |
<%= p.quantity %> | |
</td> | |
<td class="table-cell"> | |
<%= number_to_currency(p.net_sales / 100.0) %> | |
</td> | |
<% end %> | |
<% end %> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "stimulus"; | |
function supportsIntersectionObserver() { | |
return ( | |
"IntersectionObserver" in window || | |
"IntersectionObserverEntry" in window || | |
"intersectionRatio" in window.IntersectionObserverEntry.prototype | |
); | |
} | |
export default class extends Controller { | |
static targets = [ | |
"navBar", | |
"scrollArea", | |
"column", | |
"leftButton", | |
"rightButton", | |
"columnVisibilityIndicator", | |
]; | |
static classes = [ | |
"navShown", | |
"navHidden", | |
"buttonDisabled", | |
"indicatorVisible", | |
]; | |
connect() { | |
this.startObservingColumnVisibility(); | |
} | |
disconnect() { | |
this.stopObservingColumnVisibility(); | |
} | |
startObservingColumnVisibility() { | |
if (!supportsIntersectionObserver()) { | |
console.warn(`This browser doesn't support IntersectionObserver`); | |
return; | |
} | |
this.intersectionObserver = new IntersectionObserver( | |
this.updateScrollNavigation.bind(this), | |
{ | |
root: this.scrollAreaTarget, | |
threshold: 0.99, // otherwise, the right-most column sometimes won't be considered visible in some browsers, rounding errors, etc. | |
} | |
); | |
this.columnTargets.forEach((headingEl) => { | |
this.intersectionObserver.observe(headingEl); | |
}); | |
} | |
stopObservingColumnVisibility() { | |
if (this.intersectionObserver) { | |
this.intersectionObserver.disconnect(); | |
} | |
} | |
updateScrollNavigation(observerRecords) { | |
observerRecords.forEach((record) => { | |
record.target.dataset.isVisible = record.isIntersecting; | |
}); | |
this.toggleScrollNavigationVisibility(); | |
this.updateColumnVisibilityIndicators(); | |
this.updateLeftRightButtonAffordance(); | |
} | |
toggleScrollNavigationVisibility() { | |
const allColumnsVisible = | |
this.columnTargets.length > 0 && | |
this.columnTargets[0].dataset.isVisible === "true" && | |
this.columnTargets[this.columnTargets.length - 1].dataset.isVisible === | |
"true"; | |
if (allColumnsVisible) { | |
this.navBarTarget.classList.remove(this.navShownClass); | |
this.navBarTarget.classList.add(this.navHiddenClass); | |
} else { | |
this.navBarTarget.classList.add(this.navShownClass); | |
this.navBarTarget.classList.remove(this.navHiddenClass); | |
} | |
} | |
updateColumnVisibilityIndicators() { | |
this.columnTargets.forEach((headingEl, index) => { | |
const indicator = this.columnVisibilityIndicatorTargets[index]; | |
if (indicator) { | |
indicator.classList.toggle( | |
this.indicatorVisibleClass, | |
headingEl.dataset.isVisible === "true" | |
); | |
} | |
}); | |
} | |
updateLeftRightButtonAffordance() { | |
const firstColumnHeading = this.columnTargets[0]; | |
const lastColumnHeading = this.columnTargets[this.columnTargets.length - 1]; | |
this.updateButtonAffordance( | |
this.leftButtonTarget, | |
firstColumnHeading.dataset.isVisible === "true" | |
); | |
this.updateButtonAffordance( | |
this.rightButtonTarget, | |
lastColumnHeading.dataset.isVisible === "true" | |
); | |
} | |
updateButtonAffordance(button, isDisabled) { | |
if (isDisabled) { | |
button.setAttribute("disabled", ""); | |
button.classList.add(this.buttonDisabledClass); | |
} else { | |
button.removeAttribute("disabled"); | |
button.classList.remove(this.buttonDisabledClass); | |
} | |
} | |
scrollLeft() { | |
// scroll to make visible the first non-fully-visible column to the left of the scroll area | |
let columnToScrollTo = null; | |
for (let i = 0; i < this.columnTargets.length; i++) { | |
const column = this.columnTargets[i]; | |
if (columnToScrollTo !== null && column.dataset.isVisible === "true") { | |
break; | |
} | |
if (column.dataset.isVisible === "false") { | |
columnToScrollTo = column; | |
} | |
} | |
this.scrollAreaTarget.scroll(columnToScrollTo.offsetLeft, 0); | |
} | |
scrollRight() { | |
// scroll to make visible the first non-fully-visible column to the right of the scroll area | |
let columnToScrollTo = null; | |
for (let i = this.columnTargets.length - 1; i >= 0; i--) { | |
// right to left | |
const column = this.columnTargets[i]; | |
if (columnToScrollTo !== null && column.dataset.isVisible === "true") { | |
break; | |
} | |
if (column.dataset.isVisible === "false") { | |
columnToScrollTo = column; | |
} | |
} | |
this.scrollAreaTarget.scroll(columnToScrollTo.offsetLeft, 0); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment