Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/many-suns-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

Replace `ActionBar` overflow calculations with CSS wrapping approach to improve performance and stability
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions e2e/components/drafts/ActionBar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ test.describe('ActionBar', () => {
},
})
const toolbarButtonSelector = `button[data-component="IconButton"]`
await expect(page.locator(toolbarButtonSelector)).toHaveCount(10)
await page.setViewportSize({width: viewports['primer.breakpoint.xs'], height: 768})
await expect(page.locator(toolbarButtonSelector).filter({visible: true})).toHaveCount(10)
await page.setViewportSize({width: viewports['primer.breakpoint.xs'] - 64, height: 768})
await page.getByLabel('Task List').waitFor({
state: 'hidden',
})
await expect(page.locator(toolbarButtonSelector)).toHaveCount(8)
await expect(page.locator(toolbarButtonSelector).filter({visible: true})).toHaveCount(8)
const moreButtonSelector = page.getByLabel('More Comment box toolbar items')
await moreButtonSelector.click()
await expect(page.locator('ul[role="menu"] [role="menuitem"]')).toHaveCount(3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,32 @@

.CommentBoxHeader {
display: flex;
flex-direction: row;
gap: var(--base-size-16);
background-color: var(--bgColor-muted);
border-top-left-radius: var(--borderRadius-medium);
border-top-right-radius: var(--borderRadius-medium);
justify-content: space-between;
}

.CommentBoxHeaderLeft {
width: 100%;
min-width: 0;
.CommentBoxHeaderViewSwitch {
flex-basis: 0;
display: flex;
height: 37px;
}

.CommentBoxHeaderRight {
display: flex;
gap: var(--base-size-8);
.CommentBoxHeaderToolbar {
flex: 1;
justify-content: flex-end;
align-items: center;
min-width: 0;
display: flex;
padding-right: var(--base-size-4);

.CommentBoxHeaderActionBar {
flex: 1;
justify-content: flex-end;
padding-inline: 0;
overflow: hidden;
}
}

.DialogContent {
Expand Down Expand Up @@ -69,3 +78,7 @@
.MultipleActionBarsSection {
padding: var(--base-size-16);
}

.OverflowHidden {
overflow: hidden;
}
21 changes: 7 additions & 14 deletions packages/react/src/ActionBar/ActionBar.examples.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,6 @@ export const WithGroups = () => (
</ActionBar>
)

export const TextLabels = () => (
<ActionBar aria-label="Toolbar">
<Button>Edit</Button>
<Button>Duplicate</Button>
<Button>Export to CSV</Button>
</ActionBar>
)

export const SmallActionBar = () => (
<ActionBar size="small" aria-label="Toolbar">
<ActionBar.IconButton icon={BoldIcon} aria-label="Bold"></ActionBar.IconButton>
Expand Down Expand Up @@ -128,8 +120,13 @@ export const CommentBox = (props: CommentBoxProps) => {
return (
<div className={classes.CommentBoxContainer}>
<header className={classes.CommentBoxHeader}>
<div className={classes.CommentBoxHeaderLeft}>
<ActionBar aria-label={toolBarLabel}>
<div className={classes.CommentBoxHeaderViewSwitch}>
<Button variant="invisible">Write</Button>
<Button variant="invisible">Preview</Button>
</div>

<div className={classes.CommentBoxHeaderToolbar}>
<ActionBar aria-label={toolBarLabel} className={classes.CommentBoxHeaderActionBar} gap="none">
<ActionBar.IconButton icon={HeadingIcon} aria-label="Heading"></ActionBar.IconButton>
<ActionBar.IconButton icon={BoldIcon} aria-label="Bold"></ActionBar.IconButton>
<ActionBar.IconButton icon={ItalicIcon} aria-label="Italic"></ActionBar.IconButton>
Expand All @@ -148,10 +145,6 @@ export const CommentBox = (props: CommentBoxProps) => {
></ActionBar.IconButton>
</ActionBar>
</div>
<div className={classes.CommentBoxHeaderRight}>
<Button variant="invisible">Write</Button>
<Button variant="invisible">Preview</Button>
</div>
</header>
<Textarea value={value} onChange={e => setValue(e.target.value)} id="markdowninput" aria-label="Markdown value" />
<Dialog aria-labelledby="header" returnFocusRef={buttonRef} isOpen={isOpen} onDismiss={() => setIsOpen(false)}>
Expand Down
57 changes: 55 additions & 2 deletions packages/react/src/ActionBar/ActionBar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,35 @@
/* wonder why this is here */
/* stylelint-disable-next-line primer/spacing */
margin-bottom: -1px;
white-space: nowrap;
list-style: none;
align-items: center;
align-items: flex-start;
gap: var(--actionbar-gap, var(--stack-gap-condensed));
overflow: hidden;
/* Explicit height is required to clip wrapped items */
height: var(--actionbar-height, var(--control-small-size));

/* Scroll-based animations have no effect unless the container is scrollable (has overflow, even with overflow:hidden)
so we can use them to detect overflow. It would be cleaner to use scroll-state container queries for this, but
browser support for scroll-driven animations is slightly better. */
animation: detect-overflow linear;
animation-timeline: scroll(self block);

/* After initial render, JS is used to control visibility which provides progressive enhancement for unsupported browsers */
&[data-has-overflow='true'] {
--morebutton-display: flex;
}

&:where([data-size='small']) {
--actionbar-height: var(--control-small-size);
}

&:where([data-size='medium']) {
--actionbar-height: var(--control-medium-size);
}

&:where([data-size='large']) {
--actionbar-height: var(--control-large-size);
}

/* Gap scale (mirrors Stack) */
&:where([data-gap='none']) {
Expand All @@ -23,6 +48,20 @@
&:where([data-gap='condensed']) {
--actionbar-gap: var(--stack-gap-condensed);
}

& [data-overflowing] {
/* Hide overflowing items. Even though they are clipped by `overflow: hidden`, setting `visibility: hidden` ensures
they can't accidentally be shown and also hides them from screen readers / keyboard nav. `!important` prevents
consumers from unintentionally overriding this and breaking accessibility. */
visibility: hidden !important;
}
}

@keyframes detect-overflow {
0%,
100% {
--morebutton-display: flex;
}
}

.Nav {
Expand All @@ -44,10 +83,24 @@
content: '';
/* stylelint-disable-next-line primer/colors */
background: var(--borderColor-muted);
/* stylelint-disable-next-line primer/spacing */
margin-top: calc((var(--actionbar-height) - var(--base-size-20)) / 2);
}
}

.Group {
display: flex;
gap: inherit;
}

.OverflowContainer {
display: flex;
flex-wrap: wrap;
gap: inherit;
justify-content: flex-end;
overflow: hidden;
}

.MoreButton {
display: var(--morebutton-display, none);
}
5 changes: 3 additions & 2 deletions packages/react/src/ActionBar/ActionBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,14 @@ describe('ActionBar Registry System', () => {
render(
<div style={{width: 0, overflow: 'hidden'}}>
<ActionBar aria-label="Zero width">
<ActionBar.IconButton icon={BoldIcon} aria-label="Zero width button" />
<ActionBar.IconButton icon={BoldIcon} aria-label="Zero width button" data-testid="zero-width-button" />
</ActionBar>
</div>,
)

// Component should still render even with zero width
expect(screen.getByRole('button', {name: 'Zero width button'})).toBeInTheDocument()
// Button is unlabeled because the label is hidden, so we select by test id instead
expect(screen.getByTestId('zero-width-button')).toBeInTheDocument()
})

it('should clean up registry on unmount', async () => {
Expand Down
Loading
Loading