* upgrade primer/react * upgrade * using deprecated * remove lib" * Upgrade primer/react: Upgrade Label (#28502) update Label to primer/react 35.2.2 * fix merge conflicts * primer/react v35: update ActionList (#28467) * Update to v35 ActionList for LearningTrack * Update to v35 ActionList for ArticleList * Update to v35 ActionList for ProductArticleList * Update to v35 ActionList for TableOfContents * Update to v35 ActionList for ProductCollapsibleSection * Update to v35 ActionList for RestCollapsibleSection * Update to v35 ActionList for SidebarHomepage * Update to v35 ActionList for MiniTocs * Update to v35 ActionList for Search * Extra div for rendering test * One less div for rendering test * All the style updates for v35 ActionList * Works without setting as an li which is already the default (didn't before for some reason) * Use deprecated ItemInput for now * Picker update for primer/react (#28485) * update picker * inline picker for mobile * set width to auto * Update components/ui/Picker/Picker.tsx Co-authored-by: Kevin Heis <heiskr@users.noreply.github.com> * update * Update Picker.tsx * update onselect * checking language code * move language cookie setting to language picker Co-authored-by: Kevin Heis <heiskr@users.noreply.github.com> * Resolve package merge conflicts * fresh npm install * Primer update UnderlineNav (#28582) * update underlinenav for primer/react update * update tests * update switches test * update one last label * update header test" * remove href in underlinenav * update rendering tests * update cursor * primer/react v35: update DropDownMenu to ActionMenu (#28576) * Update to v35 ActionMenu for ArticleCards * Update to v35 ActionMenu for Search * Set button to inline-block * Put the props on the overlay * Update test for ActionMenu markup * update package * update package lock * primer/react v35: CodeLanguagePicker update from SelectMenu to ActionMenu (#28625) * Use octicon for menu down arrow * Update to v35 ActionMenu for CodeLanguagePicker * update to SubNav Co-authored-by: Grace Park <gracepark@github.com> * update package-lock Co-authored-by: Robert Sese <734194+rsese@users.noreply.github.com> Co-authored-by: Kevin Heis <heiskr@users.noreply.github.com>
155 lines
5.6 KiB
TypeScript
155 lines
5.6 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react'
|
|
|
|
import { ArticleGuide, useProductGuidesContext } from 'components/context/ProductGuidesContext'
|
|
import { useTranslation } from 'components/hooks/useTranslation'
|
|
import { ArticleCard } from './ArticleCard'
|
|
import { ActionList, ActionMenu } from '@primer/react'
|
|
import { ItemInput } from '@primer/react/lib/deprecated/ActionList/List'
|
|
|
|
const PAGE_SIZE = 9
|
|
export const ArticleCards = () => {
|
|
const { t } = useTranslation('product_guides')
|
|
const guideTypes: Record<string, string> = t('guide_types')
|
|
const { allTopics, includeGuides } = useProductGuidesContext()
|
|
const [numVisible, setNumVisible] = useState(PAGE_SIZE)
|
|
const [typeFilter, setTypeFilter] = useState<ItemInput | undefined>()
|
|
const [topicFilter, setTopicFilter] = useState<ItemInput | undefined>()
|
|
const [filteredResults, setFilteredResults] = useState<Array<ArticleGuide>>([])
|
|
const typesRef = useRef<HTMLDivElement>(null)
|
|
const topicsRef = useRef<HTMLDivElement>(null)
|
|
const articleCardRef = useRef<HTMLUListElement>(null)
|
|
|
|
useEffect(() => {
|
|
setNumVisible(PAGE_SIZE)
|
|
setFilteredResults(
|
|
(includeGuides || []).filter((card) => {
|
|
const matchesType = card.type === typeFilter?.key
|
|
const matchesTopic = card.topics.some((key) => key === topicFilter?.key)
|
|
return (typeFilter?.key ? matchesType : true) && (topicFilter?.key ? matchesTopic : true)
|
|
})
|
|
)
|
|
}, [typeFilter, topicFilter])
|
|
|
|
const clickDropdown = (e: React.RefObject<HTMLDivElement>) => {
|
|
if (e === typesRef && typesRef.current) typesRef.current.focus()
|
|
if (e === topicsRef && topicsRef.current) topicsRef.current.focus()
|
|
}
|
|
|
|
const loadMore = () => {
|
|
if (articleCardRef.current) {
|
|
const childListLength = articleCardRef.current.childElementCount
|
|
// Leading semi-colon due to prettier to prevent possible ASI failures
|
|
// Need to explicitly type assert as HTMLDivElement as focus property missing from dom type definitions for Element.
|
|
;(articleCardRef.current.childNodes.item(childListLength - 1) as HTMLDivElement).focus()
|
|
}
|
|
setNumVisible(numVisible + PAGE_SIZE)
|
|
}
|
|
|
|
const isUserFiltering = typeFilter !== undefined || topicFilter !== undefined
|
|
|
|
const guides = isUserFiltering ? filteredResults : includeGuides || []
|
|
|
|
const types = Object.entries(guideTypes).map(([key, val]) => {
|
|
return { text: val, key }
|
|
}) as ItemInput[]
|
|
|
|
types.unshift({ text: t('filters.all'), key: undefined })
|
|
|
|
const topics = allTopics?.map((topic) => {
|
|
return { text: topic, key: topic }
|
|
}) as ItemInput[]
|
|
|
|
topics.unshift({ text: t('filters.all'), key: undefined })
|
|
|
|
return (
|
|
<div>
|
|
<label htmlFor="guide-filter-form">{t('filter_instructions')}</label>
|
|
<form name="guide-filter-form" className="mt-2 mb-5 d-flex d-flex">
|
|
<div data-testid="card-filter-types">
|
|
<div
|
|
onClick={() => clickDropdown(typesRef)}
|
|
onKeyDown={() => clickDropdown(typesRef)}
|
|
role="button"
|
|
tabIndex={-1}
|
|
className="text-uppercase f6 color-fg-muted d-block"
|
|
>
|
|
{t('filters.type')}
|
|
</div>
|
|
<ActionMenu anchorRef={typesRef}>
|
|
<ActionMenu.Button>{typeFilter ? typeFilter.text : t('filters.all')}</ActionMenu.Button>
|
|
<ActionMenu.Overlay aria-label="types" data-testid="types-dropdown">
|
|
<ActionList selectionVariant="single">
|
|
{types.map((type) => {
|
|
return (
|
|
<ActionList.Item onSelect={() => setTypeFilter(type)} key={type.text}>
|
|
{type.text}
|
|
</ActionList.Item>
|
|
)
|
|
})}
|
|
</ActionList>
|
|
</ActionMenu.Overlay>
|
|
</ActionMenu>
|
|
</div>
|
|
|
|
<div data-testid="card-filter-topics" className="mx-4">
|
|
<div
|
|
onClick={() => clickDropdown(topicsRef)}
|
|
onKeyDown={() => clickDropdown(topicsRef)}
|
|
role="button"
|
|
tabIndex={-1}
|
|
className="text-uppercase f6 color-fg-muted d-block"
|
|
>
|
|
{t('filters.topic')}
|
|
</div>
|
|
<ActionMenu anchorRef={topicsRef}>
|
|
<ActionMenu.Button>
|
|
{topicFilter ? topicFilter.text : t('filters.all')}
|
|
</ActionMenu.Button>
|
|
<ActionMenu.Overlay aria-label="topics" data-testid="topics-dropdown">
|
|
<ActionList selectionVariant="single">
|
|
{topics.map((topic) => {
|
|
return (
|
|
<ActionList.Item onSelect={() => setTopicFilter(topic)} key={topic.text}>
|
|
{topic.text}
|
|
</ActionList.Item>
|
|
)
|
|
})}
|
|
</ActionList>
|
|
</ActionMenu.Overlay>
|
|
</ActionMenu>
|
|
</div>
|
|
</form>
|
|
|
|
<div role="status" className="color-fg-muted">
|
|
{guides.length === 0
|
|
? t('guides_found.none')
|
|
: guides.length === 1
|
|
? t('guides_found.one')
|
|
: t('guides_found.multiple').replace('{n}', guides.length)}
|
|
</div>
|
|
|
|
<ul ref={articleCardRef} className="d-flex flex-wrap mr-0 mr-md-n6 mr-lg-n8">
|
|
{guides.slice(0, numVisible).map((card) => {
|
|
return (
|
|
<ArticleCard
|
|
tabIndex={-1}
|
|
key={card.href}
|
|
card={card}
|
|
typeLabel={guideTypes[card.type]}
|
|
/>
|
|
)
|
|
})}
|
|
</ul>
|
|
|
|
{guides.length > numVisible && (
|
|
<button
|
|
className="col-12 mt-5 text-center text-bold color-fg-accent btn-link"
|
|
onClick={loadMore}
|
|
>
|
|
{t('load_more')}
|
|
</button>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|