Skip to content

Commit

Permalink
Allow NavList to paginate multiple groups (primer#2406)
Browse files Browse the repository at this point in the history
Co-authored-by: strackoverflow <strackoverflow@users.noreply.github.com>
  • Loading branch information
strackoverflow and strackoverflow authored Nov 30, 2023
1 parent 57586da commit fb9bf25
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 109 deletions.
5 changes: 5 additions & 0 deletions .changeset/rotten-trees-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/view-components': patch
---

Fix an issue where multiple groups could not be paginated within the same NavList
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 1 addition & 85 deletions app/components/primer/beta/nav_list.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,9 @@
/* eslint-disable custom-elements/expose-class-on-global */
import {controller, target, targets} from '@github/catalyst'
import {controller, targets} from '@github/catalyst'

@controller
export class NavListElement extends HTMLElement {
@targets items: HTMLElement[]
@target showMoreItem: HTMLElement
@targets focusMarkers: HTMLElement[]

connectedCallback(): void {
this.setShowMoreItemState()
}

get showMoreDisabled(): boolean {
return this.showMoreItem.hasAttribute('aria-disabled')
}

set showMoreDisabled(value: boolean) {
if (value) {
this.showMoreItem.setAttribute('aria-disabled', 'true')
} else {
this.showMoreItem.removeAttribute('aria-disabled')
}
this.showMoreItem.classList.toggle('disabled', value)
}

set currentPage(value: number) {
this.showMoreItem.setAttribute('data-current-page', value.toString())
}

get currentPage(): number {
return parseInt(this.showMoreItem.getAttribute('data-current-page') as string) || 1
}

get totalPages(): number {
return parseInt(this.showMoreItem.getAttribute('data-total-pages') as string) || 1
}

get paginationSrc(): string {
return this.showMoreItem.getAttribute('src') || ''
}

selectItemById(itemId: string | null): boolean {
if (!itemId) return false
Expand Down Expand Up @@ -134,55 +99,6 @@ export class NavListElement extends HTMLElement {
e.stopPropagation()
}

private async showMore(e: Event) {
e.preventDefault()
if (this.showMoreDisabled) return
this.showMoreDisabled = true
let html
try {
const paginationURL = new URL(this.paginationSrc, window.location.origin)
this.currentPage++
paginationURL.searchParams.append('page', this.currentPage.toString())
const response = await fetch(paginationURL)
if (!response.ok) return
html = await response.text()
if (this.currentPage === this.totalPages) {
this.showMoreItem.hidden = true
}
} catch (err) {
// Ignore network errors
this.showMoreDisabled = false
this.currentPage--
return
}
const fragment = this.#parseHTML(document, html)
fragment?.querySelector('li > a')?.setAttribute('data-targets', 'nav-list.focusMarkers')
const listId = (e.target as HTMLElement).closest('button')!.getAttribute('data-list-id')!
const list = document.getElementById(listId)!
list.append(fragment)
this.focusMarkers.pop()?.focus()
this.showMoreDisabled = false
}

private setShowMoreItemState() {
if (!this.showMoreItem) {
return
}

if (this.currentPage < this.totalPages) {
this.showMoreItem.hidden = false
} else {
this.showMoreItem.hidden = true
}
}

#parseHTML(document: Document, html: string): DocumentFragment {
const template = document.createElement('template')
// eslint-disable-next-line github/no-inner-html
template.innerHTML = html
return document.importNode(template.content, true)
}

#findSelectedNavItemById(itemId: string): HTMLElement | null {
// First we compare the selected link to data-item-id for each nav item
for (const navItem of this.items) {
Expand Down
12 changes: 7 additions & 5 deletions app/components/primer/beta/nav_list/group.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<% with_post_list_content do %>
<% if show_more_item? %>
<%= show_more_item %>
<nav-list-group>
<% with_post_list_content do %>
<% if show_more_item? %>
<%= show_more_item %>
<% end %>
<% end %>
<% end %>
<% render_parent %>
<% render_parent %>
</nav-list-group>
4 changes: 2 additions & 2 deletions app/components/primer/beta/nav_list/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ class Group < Primer::Alpha::ActionList
system_arguments[:hidden] = true
system_arguments[:href] = "#"
system_arguments[:data] ||= {}
system_arguments[:data][:target] = "nav-list.showMoreItem"
system_arguments[:data][:action] = "click:nav-list#showMore"
system_arguments[:data][:target] = "nav-list-group.showMoreItem"
system_arguments[:data][:action] = "click:nav-list-group#showMore"
system_arguments[:data][:current_page] = "1"
system_arguments[:data][:total_pages] = pages.to_s
system_arguments[:label_arguments] = {
Expand Down
97 changes: 97 additions & 0 deletions app/components/primer/beta/nav_list_group_element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {controller, target, targets} from '@github/catalyst'

@controller
export class NavListGroupElement extends HTMLElement {
@target showMoreItem: HTMLElement
@targets focusMarkers: HTMLElement[]

connectedCallback(): void {
this.setShowMoreItemState()
}

get showMoreDisabled(): boolean {
return this.showMoreItem.hasAttribute('aria-disabled')
}

set showMoreDisabled(value: boolean) {
if (value) {
this.showMoreItem.setAttribute('aria-disabled', 'true')
} else {
this.showMoreItem.removeAttribute('aria-disabled')
}
this.showMoreItem.classList.toggle('disabled', value)
}

set currentPage(value: number) {
this.showMoreItem.setAttribute('data-current-page', value.toString())
}

get currentPage(): number {
return parseInt(this.showMoreItem.getAttribute('data-current-page') as string) || 1
}

get totalPages(): number {
return parseInt(this.showMoreItem.getAttribute('data-total-pages') as string) || 1
}

get paginationSrc(): string {
return this.showMoreItem.getAttribute('src') || ''
}

private async showMore(e: Event) {
e.preventDefault()
if (this.showMoreDisabled) return
this.showMoreDisabled = true
let html
try {
const paginationURL = new URL(this.paginationSrc, window.location.origin)
this.currentPage++
paginationURL.searchParams.append('page', this.currentPage.toString())
const response = await fetch(paginationURL)
if (!response.ok) return
html = await response.text()
if (this.currentPage === this.totalPages) {
this.showMoreItem.hidden = true
}
} catch (err) {
// Ignore network errors
this.showMoreDisabled = false
this.currentPage--
return
}
const fragment = this.#parseHTML(document, html)
fragment?.querySelector('li > a')?.setAttribute('data-targets', 'nav-list-group.focusMarkers')
const listId = (e.target as HTMLElement).closest('button')!.getAttribute('data-list-id')!
const list = document.getElementById(listId)!
list.append(fragment)
this.focusMarkers.pop()?.focus()
this.showMoreDisabled = false
}

private setShowMoreItemState() {
if (!this.showMoreItem) {
return
}

if (this.currentPage < this.totalPages) {
this.showMoreItem.hidden = false
} else {
this.showMoreItem.hidden = true
}
}

#parseHTML(document: Document, html: string): DocumentFragment {
const template = document.createElement('template')
// eslint-disable-next-line github/no-inner-html
template.innerHTML = html
return document.importNode(template.content, true)
}
}

declare global {
interface Window {
NavListGroupElement: typeof NavListGroupElement
}
}

window.NavListGroupElement = NavListGroupElement
1 change: 1 addition & 0 deletions app/components/primer/primer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import './focus_group'
import './alpha/image_crop'
import './alpha/modal_dialog'
import './beta/nav_list'
import './beta/nav_list_group_element'
import './alpha/segmented_control'
import './alpha/toggle_switch'
import './alpha/tool_tip'
Expand Down
19 changes: 12 additions & 7 deletions demo/app/controllers/nav_list_items_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
class NavListItemsController < ApplicationController
layout false

PAGES = {
2 => [
{ label: "Bachelor Chow", href: "/foods/bachelor-chow" },
{ label: "LöBrau", href: "/foods/lobrau" }
]
}.freeze
FOODS = [
{ label: "Bachelor Chow", href: "/foods/bachelor-chow" },
{ label: "LöBrau", href: "/foods/lobrau" },
{ label: "Taco Bellevue Hospital", href: "/foods/taco-bellevue-hospital" },
{ label: "Olde Fortran", href: "/foods/olde-fortran" },
{ label: "Space Honey", href: "/foods/space-honey" },
{ label: "Spice Weasel", href: "/foods/spice-weasel" },
]

def index
@data = PAGES[params[:page].to_i]
items_per_page = 2
# the first page is already shown in the nav list, so we need to offset the starting index
start_index = (params[:page].to_i - items_per_page) * items_per_page
@data = FOODS.slice(start_index, items_per_page)
end
end
11 changes: 10 additions & 1 deletion previews/primer/beta/nav_list_preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,23 @@ def top_level_items
# @snapshot
def show_more_item
render(Primer::Beta::NavList.new(aria: { label: "My favorite foods" })) do |list|
list.with_group do |group|
list.with_group(id: "foods") do |group|
group.with_heading(title: "My favorite foods")
group.with_item(label: "Popplers", href: "/foods/popplers")
group.with_item(label: "Slurm", href: "/foods/slurm")
group.with_show_more_item(label: "Show more foods", src: UrlHelpers.nav_list_items_path, pages: 2) do |item|
item.with_trailing_visual_icon(icon: :plus)
end
end

list.with_group(id: "snacks") do |group|
group.with_heading(title: "My favorite snacks")
group.with_item(label: "Popplers", href: "/foods/popplers")
group.with_item(label: "Slurm", href: "/foods/slurm")
group.with_show_more_item(label: "Show more snacks", src: UrlHelpers.nav_list_items_path, pages: 4) do |item|
item.with_trailing_visual_icon(icon: :plus)
end
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion test/components/beta/nav_list_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def test_max_nesting_depth
def test_show_more_item
render_preview(:show_more_item)

assert_selector("[data-target='nav-list.showMoreItem']", visible: false, text: "Show more")
assert_selector("[data-target='nav-list-group.showMoreItem']", visible: false, text: "Show more")
end

def test_disallow_subitems_and_trailing_action
Expand Down
28 changes: 20 additions & 8 deletions test/system/beta/nav_list_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,30 @@ def test_collapses_group
def test_shows_more_items
visit_preview(:show_more_item)

assert_selector "li.ActionListItem", count: 2
assert_selector "li", text: "Popplers"
assert_selector "li", text: "Slurm"
assert_selector "#foods li.ActionListItem", count: 2
assert_selector "#foods li", text: "Popplers"
assert_selector "#foods li", text: "Slurm"

# use #find here to wait for the button to become enabled
find("button", text: "Show more foods").click

assert_selector "li.ActionListItem", count: 4
assert_selector "li", text: "Popplers"
assert_selector "li", text: "Slurm"
assert_selector "li", text: "Bachelor Chow"
assert_selector "li", text: "LöBrau"
assert_selector "#foods li.ActionListItem", count: 4
assert_selector "#foods li", text: "Popplers"
assert_selector "#foods li", text: "Slurm"
assert_selector "#foods li", text: "Bachelor Chow"
assert_selector "#foods li", text: "LöBrau"

# ensure second group is unaffected
assert_selector "#snacks li.ActionListItem", count: 2
assert_selector "#snacks li", text: "Popplers"
assert_selector "#snacks li", text: "Slurm"

find("button", text: "Show more snacks").click

assert_selector "#snacks li", text: "Popplers"
assert_selector "#snacks li", text: "Slurm"
assert_selector "#snacks li", text: "Bachelor Chow"
assert_selector "#snacks li", text: "LöBrau"
end

def test_js_api_allows_selecting_item_by_id
Expand Down

0 comments on commit fb9bf25

Please sign in to comment.