11import { Layout } from '../../design/common/layout'
2- import { RepoCard } from '../../design/components/repo -card'
3- import { Select } from '../../design/components/select '
2+ import { FeaturedCard } from '../../design/components/featured -card'
3+ import { Tag } from '../../design/components/tag '
44import type { Catalog , CatalogEntry } from '../../catalog/types'
55import { url } from '../../build/config'
66import type { SiteConfig } from '../../build/config'
77
88declare module 'hono/jsx' {
99 namespace JSX {
1010 interface IntrinsicElements {
11- 'catalog-filter ' : { children ?: any }
11+ 'side-nav ' : { children ?: any }
1212 }
1313 }
1414}
1515
16+ const CATEGORY_LABEL : Record < CatalogEntry [ 'category' ] , string > = {
17+ product : 'Product' ,
18+ tool : 'Tool' ,
19+ workshop : 'Workshop' ,
20+ prototype : 'Prototype' ,
21+ fork : 'Fork' ,
22+ uncategorized : 'Uncategorized' ,
23+ }
24+
25+ type Section = { id : string ; label : string ; entries : CatalogEntry [ ] ; featured ?: boolean }
26+
1627export function WorkIndex ( {
1728 catalog,
1829 config,
@@ -21,72 +32,86 @@ export function WorkIndex({
2132 config : SiteConfig
2233} ) {
2334 const visible = catalog . filter ( ( e ) => ! e . hidden )
24- const sorted = [ ...visible ] . sort ( defaultSort )
35+ const featured = visible . filter ( ( e ) => e . featured )
36+ const rest = visible . filter ( ( e ) => ! e . featured )
37+
38+ const sections : Section [ ] = [
39+ ...( featured . length > 0 ? [ { id : 'featured' , label : 'Featured' , entries : featured , featured : true } ] : [ ] ) ,
40+ { id : 'active' , label : 'Active' , entries : rest . filter ( ( e ) => e . tier === 'active' ) } ,
41+ { id : 'available' , label : 'Available' , entries : rest . filter ( ( e ) => e . tier === 'unreviewed' || e . tier === 'as-is' ) } ,
42+ { id : 'archived' , label : 'Archived' , entries : rest . filter ( ( e ) => e . tier === 'archived' ) } ,
43+ ] . filter ( ( s ) => s . entries . length > 0 )
44+
45+ // Sort non-featured entries within each section by pushedAt descending
46+ for ( const section of sections ) {
47+ if ( ! section . featured ) {
48+ section . entries . sort ( ( a , b ) => b . pushedAt . localeCompare ( a . pushedAt ) )
49+ }
50+ }
51+
52+ const navItems = sections . map ( ( s ) => ( {
53+ href : `#${ s . id } ` ,
54+ label : s . featured ? s . label : `${ s . label } (${ s . entries . length } )` ,
55+ } ) )
2556
2657 return (
2758 < Layout title = "Work" config = { config } >
2859 < h1 > Our work</ h1 >
2960 < p class = "work-index__intro" >
3061 Flexion's public portfolio — tools we've built, prototypes we've shipped, and
31- open-source projects we actively contribute to. Use the filters to explore by
32- tier or category. See our{ ' ' }
62+ open-source projects we actively contribute to. See our{ ' ' }
3363 < a href = { url ( '/work/health/' , config . basePath ) } > stewardship scorecard</ a > for
3464 how each repo measures up.
3565 </ p >
36- < catalog-filter >
37- < form class = "catalog-filter" >
38- < fieldset >
39- < legend > Filter</ legend >
40- < Select name = "tier" label = "Tier" >
41- < option value = "" > All</ option >
42- < option value = "active" > Active</ option >
43- < option value = "unreviewed" > Unreviewed</ option >
44- < option value = "as-is" > As-is</ option >
45- < option value = "archived" > Archived</ option >
46- </ Select >
47- < Select name = "category" label = "Category" >
48- < option value = "" > All</ option >
49- < option value = "product" > Product</ option >
50- < option value = "tool" > Tool</ option >
51- < option value = "workshop" > Workshop</ option >
52- < option value = "prototype" > Prototype</ option >
53- < option value = "fork" > Fork</ option >
54- < option value = "uncategorized" > Uncategorized</ option >
55- </ Select >
56- </ fieldset >
57- </ form >
58- < ul class = "work-index__list" >
59- { sorted . map ( ( entry , i ) => (
60- < >
61- { entry . featured && i === 0 ? (
62- < li class = "work-index__section-heading" data-featured = "true" role = "presentation" aria-hidden = "true" >
63- < h2 > Featured</ h2 >
64- </ li >
65- ) : null }
66- { ! entry . featured && ( i === 0 || sorted [ i - 1 ] . featured ) ? (
67- < li class = "work-index__section-heading" role = "presentation" aria-hidden = "true" >
68- < h2 > All projects</ h2 >
66+
67+ < div class = "l-sidebar" >
68+ < side-nav >
69+ < nav class = "side-nav" aria-label = "Work sections" >
70+ < ul class = "side-nav__list" >
71+ { navItems . map ( ( { href, label } ) => (
72+ < li class = "side-nav__item" >
73+ < a class = "side-nav__link" href = { href } > { label } </ a >
6974 </ li >
70- ) : null }
71- < li data-tier = { entry . tier } data-category = { entry . category } data-featured = { entry . featured ? 'true' : undefined } >
72- < RepoCard entry = { entry } basePath = { config . basePath } />
73- </ li >
74- </ >
75+ ) ) }
76+ </ ul >
77+ </ nav >
78+ </ side-nav >
79+
80+ < div class = "l-stack" data-space = "xl" >
81+ { sections . map ( ( section ) => (
82+ < section id = { section . id } aria-labelledby = { `${ section . id } -heading` } >
83+ < h2 id = { `${ section . id } -heading` } > { section . label } </ h2 >
84+ { section . featured ? (
85+ < div class = "work-index__featured-grid" >
86+ { section . entries . map ( ( entry ) => (
87+ < FeaturedCard entry = { entry } basePath = { config . basePath } />
88+ ) ) }
89+ </ div >
90+ ) : (
91+ < ul class = "work-list" >
92+ { section . entries . map ( ( entry ) => (
93+ < li class = "work-list__item" >
94+ < div class = "work-list__header" >
95+ < a class = "work-list__name" href = { url ( `/work/${ entry . name } /` , config . basePath ) } >
96+ { entry . name }
97+ </ a >
98+ < Tag variant = { `category-${ entry . category } ` } >
99+ { CATEGORY_LABEL [ entry . category ] }
100+ </ Tag >
101+ </ div >
102+ { entry . overlay ?. summary || entry . description ? (
103+ < p class = "work-list__desc" >
104+ { entry . overlay ?. summary ?? entry . description }
105+ </ p >
106+ ) : null }
107+ </ li >
108+ ) ) }
109+ </ ul >
110+ ) }
111+ </ section >
75112 ) ) }
76- </ ul >
77- </ catalog-filter >
113+ </ div >
114+ </ div >
78115 </ Layout >
79116 )
80117}
81-
82- function defaultSort ( a : CatalogEntry , b : CatalogEntry ) : number {
83- if ( a . featured !== b . featured ) return a . featured ? - 1 : 1
84- const tierRank : Record < CatalogEntry [ 'tier' ] , number > = {
85- active : 0 ,
86- unreviewed : 1 ,
87- 'as-is' : 2 ,
88- archived : 3 ,
89- }
90- if ( tierRank [ a . tier ] !== tierRank [ b . tier ] ) return tierRank [ a . tier ] - tierRank [ b . tier ]
91- return b . pushedAt . localeCompare ( a . pushedAt )
92- }
0 commit comments