@@ -2,6 +2,26 @@ import React from 'react';
22import { useLocation } from '@reach/router' ;
33import { groupedRoutes } from '../../routes' ;
44
5+ /**
6+ * Converts a hyphenated or lowercase string to sentence case
7+ * Example: "design-tokens" -> "Design tokens"
8+ * Example: "colors" -> "Colors"
9+ */
10+ const toSentenceCase = ( str ) => {
11+ if ( ! str ) return str ;
12+ return str
13+ . split ( '-' )
14+ . map ( ( word , index ) => {
15+ if ( index === 0 ) {
16+ // Capitalize first letter of first word only
17+ return word . charAt ( 0 ) . toUpperCase ( ) + word . slice ( 1 ) . toLowerCase ( ) ;
18+ }
19+ // Keep all other words lowercase
20+ return word . toLowerCase ( ) ;
21+ } )
22+ . join ( ' ' ) ;
23+ } ;
24+
525export const SectionGalleryWrapper = ( {
626 section,
727 subsection,
@@ -11,6 +31,7 @@ export const SectionGalleryWrapper = ({
1131 parseSubsections,
1232 initialLayout,
1333 isFullWidth,
34+ onlyShowInGalleryData = false ,
1435 children,
1536} ) => {
1637 let sectionRoutes = subsection
@@ -24,12 +45,50 @@ export const SectionGalleryWrapper = ({
2445 </ div >
2546 ) ;
2647 }
27- if ( ! includeSubsections || parseSubsections ) {
48+
49+ // If includeSubsections is true and we're at the section level (not a specific subsection),
50+ // we need to include subsections themselves as items (not their contents)
51+ if ( includeSubsections && ! subsection && groupedRoutes [ section ] ) {
52+ const allRoutes = { } ;
53+ // First, add top-level items (non-subsections)
54+ Object . entries ( sectionRoutes ) . forEach ( ( [ navName , routeData ] ) => {
55+ if ( navName === 'isSubsection' || navName === 'sortValue' || navName === 'subsectionSortValue' ) {
56+ return ;
57+ }
58+ if ( typeof routeData !== 'object' || routeData === null ) {
59+ return ;
60+ }
61+ // Only add if it's not a subsection
62+ if ( ! routeData . isSubsection ) {
63+ allRoutes [ navName ] = routeData ;
64+ }
65+ } ) ;
66+
67+ // Then, add subsections themselves as single items (not their contents)
68+ Object . entries ( groupedRoutes [ section ] ) . forEach ( ( [ navName , routeData ] ) => {
69+ if ( navName === 'isSubsection' || navName === 'sortValue' || navName === 'subsectionSortValue' ) {
70+ return ;
71+ }
72+ if ( typeof routeData !== 'object' || routeData === null ) {
73+ return ;
74+ }
75+ // If this is a subsection, add the subsection itself as an item
76+ if ( routeData . isSubsection ) {
77+ allRoutes [ navName ] = routeData ;
78+ }
79+ } ) ;
80+
81+ sectionRoutes = allRoutes ;
82+ } else if ( ! includeSubsections || parseSubsections ) {
2883 const sectionRoutesArr = Object . entries ( sectionRoutes ) ;
2984 // loop through galleryItems object and build new object to handle subsections
3085 sectionRoutes = sectionRoutesArr . reduce ( ( acc , [ navName , routeData ] ) => {
31- // exit immediately if current item is isSubsection flag
32- if ( navName === 'isSubsection' ) {
86+ // exit immediately if current item is isSubsection flag or other metadata properties
87+ if ( navName === 'isSubsection' || navName === 'sortValue' || navName === 'subsectionSortValue' ) {
88+ return acc ;
89+ }
90+ // Skip primitive values (metadata properties like sortValue are numbers)
91+ if ( typeof routeData !== 'object' || routeData === null ) {
3392 return acc ;
3493 }
3594 // add current item
@@ -40,8 +99,11 @@ export const SectionGalleryWrapper = ({
4099 if ( parseSubsections && routeData . isSubsection ) {
41100 // loop through each subsection item & add
42101 Object . entries ( routeData ) . map ( ( [ subitemName , subitemData ] ) => {
43- if ( subitemName !== 'isSubsection' ) {
44- acc [ subitemName ] = subitemData ;
102+ if ( subitemName !== 'isSubsection' && subitemName !== 'sortValue' && subitemName !== 'subsectionSortValue' ) {
103+ // Skip primitive values
104+ if ( typeof subitemData === 'object' && subitemData !== null ) {
105+ acc [ subitemName ] = subitemData ;
106+ }
45107 }
46108 } ) ;
47109 }
@@ -53,11 +115,32 @@ export const SectionGalleryWrapper = ({
53115 const [ searchTerm , setSearchTerm ] = React . useState ( '' ) ;
54116 const [ layoutView , setLayoutView ] = React . useState ( initialLayout ) ;
55117 const filteredItems = Object . entries ( sectionRoutes ) . filter (
56- ( [ itemName , { slug } ] ) =>
118+ ( [ itemName , itemData ] ) => {
119+ // Skip metadata properties
120+ if ( itemName === 'isSubsection' || itemName === 'sortValue' || itemName === 'subsectionSortValue' ) {
121+ return false ;
122+ }
123+ // Skip primitive values (metadata properties)
124+ if ( typeof itemData !== 'object' || itemData === null ) {
125+ return false ;
126+ }
127+ // For subsections, slug will be computed later from first page
128+ // For regular items, they must have a slug
129+ if ( ! itemData . isSubsection && ! itemData . slug ) {
130+ return false ;
131+ }
132+ const slug = itemData . slug ;
133+ // For subsections without slug yet, we'll compute it later, so don't filter by slug
134+ if ( ! slug ) {
135+ return itemName . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ;
136+ }
57137 // exclude current gallery page from results - check for trailing /
58- ! location . pathname . endsWith ( slug ) &&
59- ! location . pathname . endsWith ( `${ slug } /` ) &&
60- itemName . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
138+ return (
139+ ! location . pathname . endsWith ( slug ) &&
140+ ! location . pathname . endsWith ( `${ slug } /` ) &&
141+ itemName . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
142+ ) ;
143+ }
61144 ) ;
62145 const sectionGalleryItems = filteredItems
63146 . sort ( ( [ itemName1 ] , [ itemName2 ] ) => itemName1 . localeCompare ( itemName2 ) )
@@ -76,8 +159,35 @@ export const SectionGalleryWrapper = ({
76159 }
77160 const { sources, isSubsection = false } = itemData ;
78161 // Subsections don't have title or id, default to itemName aka sidenav text
79- const title = itemData . title || itemName ;
80- const id = itemData . id || title ;
162+ // Convert itemName to sentence case if no title is provided
163+ let title = itemData . title || toSentenceCase ( itemName ) ;
164+ let id = itemData . id || title ;
165+
166+ // For extensions section, try to extract extension name from slug to match JSON keys
167+ // This handles cases where extensions have id: Overview or other IDs but we need to match JSON keys
168+ // The JSON keys are dasherized (e.g., "component-groups"), so we extract from slug
169+ if ( section === 'extensions' && itemData . slug && galleryItemsData ) {
170+ // Extract extension name from slug like /extensions/topology/overview -> topology
171+ // or /extensions/component-groups/overview -> component-groups
172+ // Also handle /extensions/react-topology/... -> topology (remove react- prefix)
173+ const slugParts = itemData . slug . split ( '/' ) . filter ( Boolean ) ;
174+ if ( slugParts . length >= 2 && slugParts [ 0 ] === 'extensions' ) {
175+ let extensionName = slugParts [ 1 ] ; // e.g., "component-groups" or "react-topology"
176+ // Remove "react-" prefix if present (e.g., "react-topology" -> "topology")
177+ if ( extensionName . startsWith ( 'react-' ) ) {
178+ extensionName = extensionName . replace ( / ^ r e a c t - / , '' ) ;
179+ }
180+ // Check if this extension name exists in galleryItemsData
181+ if ( galleryItemsData [ extensionName ] ) {
182+ // Use extension name as id for JSON lookup (TextSummary converts to dasherized)
183+ id = extensionName ;
184+ // Update title to extension name in sentence case if id was "Overview" or matches itemName
185+ if ( itemData . id === 'Overview' || itemName === 'Overview' || ! itemData . title ) {
186+ title = toSentenceCase ( extensionName ) ;
187+ }
188+ }
189+ }
190+ }
81191 // Display beta label if tab other than a '-next' tab is marked Beta
82192 const isDeprecated =
83193 ! isSubsection &&
@@ -138,6 +248,23 @@ export const SectionGalleryWrapper = ({
138248 id,
139249 galleryItemsData,
140250 } ;
251+ } )
252+ . filter ( ( item ) => {
253+ // If onlyShowInGalleryData is true, filter to only items that exist in galleryItemsData
254+ if ( ! onlyShowInGalleryData || ! galleryItemsData ) {
255+ return true ;
256+ }
257+ // Try matching by itemName first (already in dasherized format from routes)
258+ if ( galleryItemsData [ item . itemName ] ) {
259+ return true ;
260+ }
261+ // Convert id to dasherized format to match JSON keys (lowercase, spaces to hyphens)
262+ const dasherizedId = item . id
263+ . split ( ' ' )
264+ . join ( '-' )
265+ . toLowerCase ( ) ;
266+ // Check if this item exists in galleryItemsData
267+ return galleryItemsData [ dasherizedId ] !== undefined ;
141268 } ) ;
142269
143270 return (
0 commit comments