Skip to content

Commit 4f65305

Browse files
authored
Merge branch 'main' into hectahertz/selectpanel-content-visibility-perf
2 parents aff0c6a + b69325f commit 4f65305

17 files changed

+875
-122
lines changed

.changeset/gold-snakes-smash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/mcp': minor
3+
---
4+
5+
Adds support for fetching docs via the `/llms.txt` endpoint per-component
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
'@primer/react': minor
3+
---
4+
5+
Add `currentWidth` and `onResizeEnd` props to PageLayout.Pane for controlled resizable width
6+
7+
The `PageLayout.Pane` component now supports controlled width:
8+
9+
- `onResizeEnd` — callback fired when a resize operation ends (pointer release or keyboard key up). Replaces localStorage persistence. Requires `currentWidth`.
10+
- `currentWidth` — sets the current displayed width in pixels (`number | undefined`). Pass `undefined` when the persisted value hasn't loaded yet. Requires `onResizeEnd`.
11+
12+
Both props must be provided together (enforced by TypeScript). `resizable` remains a plain `boolean` prop.
13+
14+
These props are only meaningful when `resizable={true}` — without it, no drag handle renders so `onResizeEnd` never fires.
15+
16+
**New export:**
17+
18+
- `defaultPaneWidth` — Record of preset width values: `{small: 256, medium: 296, large: 320}`
19+
20+
**Example usage:**
21+
22+
```tsx
23+
import {PageLayout, defaultPaneWidth} from '@primer/react'
24+
25+
// Default behavior (unchanged) — localStorage persistence
26+
<PageLayout.Pane resizable />
27+
28+
// Controlled width with custom persistence
29+
const [width, setWidth] = useState(defaultPaneWidth.medium)
30+
<PageLayout.Pane
31+
resizable
32+
currentWidth={width}
33+
onResizeEnd={(newWidth) => {
34+
setWidth(newWidth)
35+
myStorage.save('pane-width', newWidth)
36+
}}
37+
/>
38+
39+
// Async load — pass undefined until value is fetched
40+
<PageLayout.Pane
41+
resizable
42+
currentWidth={savedWidth ?? undefined}
43+
onResizeEnd={handleResizeEnd}
44+
/>
45+
```

.changeset/red-pugs-sing.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
Update PageHeader story to have semantic headings

.changeset/sixty-keys-perform.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
fix: ActionBar overflow menu not closing on select

package-lock.json

Lines changed: 12 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/mcp/src/primer.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ function idToSlug(id: string): string {
2121
return 'dialog'
2222
}
2323

24-
if (id.startsWith('skeleton')) {
25-
return 'skeleton-loaders'
26-
}
27-
2824
return id.replaceAll('_', '-')
2925
}
3026

packages/mcp/src/server.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,27 @@ server.registerTool(
111111
})
112112
if (!match) {
113113
return {
114-
content: [
115-
{
116-
type: 'text',
117-
text: `There is no component named \`${name}\` in the @primer/react package. For a full list of components, use the \`list_components\` tool.`,
118-
},
119-
],
114+
isError: true,
115+
errorMessage: `There is no component named \`${name}\` in the @primer/react package. For a full list of components, use the \`list_components\` tool.`,
116+
content: [],
117+
}
118+
}
119+
120+
const llmsUrl = new URL(`/product/components/${match.slug}/llms.txt`, 'https://primer.style')
121+
const llmsResponse = await fetch(llmsUrl)
122+
if (llmsResponse.ok) {
123+
try {
124+
const llmsText = await llmsResponse.text()
125+
return {
126+
content: [
127+
{
128+
type: 'text',
129+
text: llmsText,
130+
},
131+
],
132+
}
133+
} catch (_: unknown) {
134+
// If there's an error fetching or processing the llms.txt, we fall back to the regular documentation
120135
}
121136
}
122137

@@ -691,13 +706,7 @@ server.registerTool(
691706
inputSchema: {
692707
surroundingText: z.string().describe('Text surrounding the image, relevant to the image.'),
693708
alt: z.string().describe('The alt text of the image being evaluated'),
694-
image: z
695-
.union([
696-
z.instanceof(File).describe('The image src file being evaluated'),
697-
z.url().describe('The URL of the image src being evaluated'),
698-
z.string().describe('The file path of the image src being evaluated'),
699-
])
700-
.describe('The image file, file path, or URL being evaluated'),
709+
image: z.string().describe('The image URL or file path being evaluated'),
701710
},
702711
},
703712
async ({surroundingText, alt, image}) => {

packages/react/src/ActionBar/ActionBar.stories.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ export const Default = () => (
6767
<ActionBar.IconButton icon={QuoteIcon} aria-label="Insert Quote"></ActionBar.IconButton>
6868
<ActionBar.IconButton icon={ListUnorderedIcon} aria-label="Unordered List"></ActionBar.IconButton>
6969
<ActionBar.IconButton icon={ListOrderedIcon} aria-label="Ordered List"></ActionBar.IconButton>
70-
<ActionBar.IconButton icon={TasklistIcon} aria-label="Task List"></ActionBar.IconButton>
70+
<ActionBar.IconButton
71+
icon={TasklistIcon}
72+
aria-label="Task List"
73+
onClick={() => alert('Task List clicked')}
74+
></ActionBar.IconButton>
7175
</ActionBar>
7276
)
7377

packages/react/src/ActionBar/ActionBar.tsx

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ import React, {useState, useCallback, useRef, forwardRef, useId} from 'react'
33
import {KebabHorizontalIcon} from '@primer/octicons-react'
44
import {ActionList, type ActionListItemProps} from '../ActionList'
55
import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect'
6-
import {useOnEscapePress} from '../hooks/useOnEscapePress'
76
import type {ResizeObserverEntry} from '../hooks/useResizeObserver'
87
import {useResizeObserver} from '../hooks/useResizeObserver'
98

10-
import {useOnOutsideClick} from '../hooks/useOnOutsideClick'
119
import type {IconButtonProps} from '../Button'
1210
import {IconButton} from '../Button'
1311
import {ActionMenu} from '../ActionMenu'
@@ -322,8 +320,6 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
322320
if (!Number.isNaN(parsed)) setComputedGap(parsed)
323321
}, [gap])
324322
const moreMenuRef = useRef<HTMLLIElement>(null)
325-
const moreMenuBtnRef = useRef<HTMLButtonElement>(null)
326-
const containerRef = React.useRef<HTMLUListElement>(null)
327323

328324
useResizeObserver((resizeObserverEntries: ResizeObserverEntry[]) => {
329325
const navWidth = resizeObserverEntries[0].contentRect.width
@@ -343,29 +339,6 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
343339
[menuItemIds],
344340
)
345341

346-
const [isWidgetOpen, setIsWidgetOpen] = useState(false)
347-
348-
const closeOverlay = React.useCallback(() => {
349-
setIsWidgetOpen(false)
350-
}, [setIsWidgetOpen])
351-
352-
const focusOnMoreMenuBtn = React.useCallback(() => {
353-
moreMenuBtnRef.current?.focus()
354-
}, [])
355-
356-
useOnEscapePress(
357-
(event: KeyboardEvent) => {
358-
if (isWidgetOpen) {
359-
event.preventDefault()
360-
closeOverlay()
361-
focusOnMoreMenuBtn()
362-
}
363-
},
364-
[isWidgetOpen],
365-
)
366-
367-
useOnOutsideClick({onClickOutside: closeOverlay, containerRef, ignoreClickRefs: [moreMenuBtnRef]})
368-
369342
useFocusZone({
370343
containerRef: listRef,
371344
bindKeys: FocusKeys.ArrowHorizontal | FocusKeys.HomeAndEnd,
@@ -415,11 +388,8 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
415388
return (
416389
<ActionList.Item
417390
key={label}
418-
// eslint-disable-next-line primer-react/prefer-action-list-item-onselect
419-
onClick={(event: React.MouseEvent<HTMLLIElement, MouseEvent>) => {
420-
closeOverlay()
421-
focusOnMoreMenuBtn()
422-
typeof onClick === 'function' && onClick(event)
391+
onSelect={event => {
392+
typeof onClick === 'function' && onClick(event as React.MouseEvent<HTMLElement>)
423393
}}
424394
disabled={disabled}
425395
>
@@ -469,8 +439,6 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
469439
<ActionList.Item
470440
key={key}
471441
onSelect={event => {
472-
closeOverlay()
473-
focusOnMoreMenuBtn()
474442
typeof onClick === 'function' && onClick(event as React.MouseEvent<HTMLElement>)
475443
}}
476444
disabled={disabled}

packages/react/src/PageHeader/PageHeader.examples.stories.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,9 @@ export const WithPageLayout = () => {
362362
<PageLayout.Pane>
363363
<div className={classes.PaneStack}>
364364
<div>
365-
<Text className={classes.PaneSectionHeading}>Assignees</Text>
365+
<Heading as="h2" className={classes.PaneSectionHeading}>
366+
Assignees
367+
</Heading>
366368
<Text className={classes.PaneMetaTextWithButton}>
367369
No one —
368370
<Button
@@ -378,7 +380,9 @@ export const WithPageLayout = () => {
378380
</div>
379381
<div className={classes.PaneSeparator} role="separator"></div>
380382
<div>
381-
<Text className={classes.PaneSectionHeading}>Labels</Text>
383+
<Heading as="h2" className={classes.PaneSectionHeading}>
384+
Labels
385+
</Heading>
382386
<Text className={classes.PaneMetaText}>None yet</Text>
383387
</div>
384388
</div>

0 commit comments

Comments
 (0)