Skip to content

Commit a1b34c3

Browse files
mattobeeCopilot
andcommitted
Scaffold FilteredListLayout (alpha)
Adds a new alpha-status layout component for the common sidebar + filter input + filtered results page shape used across GitHub (Issues, PRs, Discussions, etc.). v1 mirrors SplitPageLayout's defaults — full-bleed, dividers on, sticky pane — but exists as its own component so we can grow filtered-list specific behaviour (sidebar hide affordance, primary action slot, FilterBar/Results sub-regions) without re-litigating SplitPageLayout's API. Built directly on PageLayout (not wrapping SplitPageLayout) to avoid layered indirection. Refs github/core-ux#2156 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6e71d16 commit a1b34c3

5 files changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"id": "filtered_list_layout",
3+
"name": "FilteredListLayout",
4+
"status": "alpha",
5+
"stories": [
6+
{
7+
"id": "components-filteredlistlayout--default"
8+
}
9+
],
10+
"importPath": "@primer/react",
11+
"props": [
12+
{
13+
"name": "className",
14+
"type": "string"
15+
}
16+
],
17+
"subcomponents": [
18+
{
19+
"name": "FilteredListLayout.Header",
20+
"props": []
21+
},
22+
{
23+
"name": "FilteredListLayout.Sidebar",
24+
"props": []
25+
},
26+
{
27+
"name": "FilteredListLayout.Content",
28+
"props": []
29+
},
30+
{
31+
"name": "FilteredListLayout.Footer",
32+
"props": []
33+
}
34+
]
35+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type {Meta, StoryFn} from '@storybook/react-vite'
2+
import {Placeholder} from '../Placeholder'
3+
import {FilteredListLayout} from '../FilteredListLayout'
4+
5+
export default {
6+
title: 'Components/FilteredListLayout',
7+
parameters: {
8+
layout: 'fullscreen',
9+
controls: {expanded: true},
10+
},
11+
} as Meta<typeof FilteredListLayout>
12+
13+
export const Default: StoryFn = () => (
14+
<FilteredListLayout>
15+
<FilteredListLayout.Header>
16+
<Placeholder label="Header (page title + primary action)" height={64} />
17+
</FilteredListLayout.Header>
18+
<FilteredListLayout.Sidebar position="start" aria-label="Sidebar">
19+
<Placeholder label="Sidebar" height={400} />
20+
</FilteredListLayout.Sidebar>
21+
<FilteredListLayout.Content>
22+
<Placeholder label="Filter bar" height={48} />
23+
<Placeholder label="Results" height={552} />
24+
</FilteredListLayout.Content>
25+
</FilteredListLayout>
26+
)
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import type React from 'react'
2+
import type {
3+
PageLayoutContentProps,
4+
PageLayoutFooterProps,
5+
PageLayoutHeaderProps,
6+
PageLayoutPaneProps,
7+
} from '../PageLayout'
8+
import {PageLayout} from '../PageLayout'
9+
10+
// ----------------------------------------------------------------------------
11+
// FilteredListLayout
12+
//
13+
// A page template for the common "sidebar + filter input + filtered results"
14+
// shape used across GitHub (e.g. Issues, Pull requests, Discussions).
15+
//
16+
// Status: alpha. v1 mirrors SplitPageLayout's defaults — full-bleed, dividers
17+
// on, sticky pane — but is its own component so we can grow filtered-list
18+
// specific behaviour (sidebar hide affordance, primary action slot, filter
19+
// bar / results sub-regions) without re-litigating SplitPageLayout's API.
20+
21+
export type FilteredListLayoutProps = {className?: string}
22+
23+
export const Root: React.FC<React.PropsWithChildren<FilteredListLayoutProps>> = props => {
24+
return (
25+
<PageLayout
26+
containerWidth="full"
27+
padding="none"
28+
columnGap="none"
29+
rowGap="none"
30+
_slotsConfig={{
31+
header: Header,
32+
footer: Footer,
33+
}}
34+
{...props}
35+
/>
36+
)
37+
}
38+
39+
Root.displayName = 'FilteredListLayout'
40+
41+
// ----------------------------------------------------------------------------
42+
// FilteredListLayout.Header
43+
44+
export type FilteredListLayoutHeaderProps = PageLayoutHeaderProps
45+
46+
export const Header: React.FC<React.PropsWithChildren<FilteredListLayoutHeaderProps>> = ({
47+
padding = 'normal',
48+
divider = 'line',
49+
...props
50+
}) => {
51+
// eslint-disable-next-line primer-react/direct-slot-children
52+
return <PageLayout.Header padding={padding} divider={divider} {...props} />
53+
}
54+
55+
Header.displayName = 'FilteredListLayout.Header'
56+
57+
// ----------------------------------------------------------------------------
58+
// FilteredListLayout.Sidebar
59+
//
60+
// Wraps PageLayout.Pane with defaults appropriate for a filtered-list sidebar.
61+
// Future: hide affordance at the bottom (per epic #2156 DoD).
62+
63+
export type FilteredListLayoutSidebarProps = PageLayoutPaneProps
64+
65+
export const Sidebar: React.FC<React.PropsWithChildren<FilteredListLayoutSidebarProps>> = ({
66+
position = 'start',
67+
sticky = true,
68+
padding = 'normal',
69+
divider = 'line',
70+
...props
71+
}) => {
72+
return (
73+
<PageLayout.Pane
74+
position={position}
75+
sticky={sticky}
76+
padding={padding}
77+
divider={divider}
78+
{...props}
79+
/>
80+
)
81+
}
82+
83+
Sidebar.displayName = 'FilteredListLayout.Sidebar'
84+
85+
// ----------------------------------------------------------------------------
86+
// FilteredListLayout.Content
87+
//
88+
// Hosts the filter bar and results region. v1 is a single slot; future versions
89+
// may introduce FilterBar and Results sub-components for opinionated layout.
90+
91+
export type FilteredListLayoutContentProps = PageLayoutContentProps
92+
93+
export const Content: React.FC<React.PropsWithChildren<FilteredListLayoutContentProps>> = ({
94+
width = 'xlarge',
95+
padding = 'normal',
96+
...props
97+
}) => {
98+
return <PageLayout.Content width={width} padding={padding} {...props} />
99+
}
100+
101+
Content.displayName = 'FilteredListLayout.Content'
102+
103+
// ----------------------------------------------------------------------------
104+
// FilteredListLayout.Footer
105+
106+
export type FilteredListLayoutFooterProps = PageLayoutFooterProps
107+
108+
export const Footer: React.FC<React.PropsWithChildren<FilteredListLayoutFooterProps>> = ({
109+
padding = 'normal',
110+
divider = 'line',
111+
...props
112+
}) => {
113+
// eslint-disable-next-line primer-react/direct-slot-children
114+
return <PageLayout.Footer padding={padding} divider={divider} {...props} />
115+
}
116+
117+
Footer.displayName = 'FilteredListLayout.Footer'
118+
119+
// ----------------------------------------------------------------------------
120+
// Export
121+
122+
export const FilteredListLayout = Object.assign(Root, {
123+
Header,
124+
Sidebar,
125+
Content,
126+
Footer,
127+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './FilteredListLayout'

packages/react/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ export type {
2424
SplitPageLayoutPaneProps,
2525
SplitPageLayoutFooterProps,
2626
} from './SplitPageLayout'
27+
export {FilteredListLayout} from './FilteredListLayout'
28+
export type {
29+
FilteredListLayoutProps,
30+
FilteredListLayoutHeaderProps,
31+
FilteredListLayoutContentProps,
32+
FilteredListLayoutSidebarProps,
33+
FilteredListLayoutFooterProps,
34+
} from './FilteredListLayout'
2735

2836
// Hooks
2937
export {default as useDetails} from './hooks/useDetails'

0 commit comments

Comments
 (0)