Skip to content
Merged
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
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.
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.
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.
53 changes: 53 additions & 0 deletions e2e/components/BranchName.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ const stories = [
id: 'components-branchname-features--with-branch-icon',
focus: false,
},
{
title: 'With Trailing Action',
id: 'components-branchname-features--with-trailing-action',
focus: false,
},
{
title: 'With Trailing Action Menu',
id: 'components-branchname-features--with-trailing-action-menu',
focus: false,
},
] as const

test.describe('BranchName', () => {
Expand Down Expand Up @@ -46,4 +56,47 @@ test.describe('BranchName', () => {
}
})
}

// Trailing action stories - focus states only in light theme
test.describe('With Trailing Action', () => {
test('focus states @vrt', async ({page}) => {
await visit(page, {
id: 'components-branchname-features--with-trailing-action',
globals: {
colorScheme: 'light',
},
})

// Focus on branch name link
await page.keyboard.press('Tab')
await expect(page).toHaveScreenshot('BranchName.With Trailing Action.light.focus-link.png')

// Focus on trailing action button
await page.keyboard.press('Tab')
await expect(page).toHaveScreenshot('BranchName.With Trailing Action.light.focus-button.png')
})
})

test.describe('With Trailing Action Menu', () => {
test('focus states @vrt', async ({page}) => {
await visit(page, {
id: 'components-branchname-features--with-trailing-action-menu',
globals: {
colorScheme: 'light',
},
})

// Focus on branch name link
await page.keyboard.press('Tab')
await expect(page).toHaveScreenshot('BranchName.With Trailing Action Menu.light.focus-link.png')

// Focus on trailing action button
await page.keyboard.press('Tab')
await expect(page).toHaveScreenshot('BranchName.With Trailing Action Menu.light.focus-button.png')

// Open the menu
await page.keyboard.press('Enter')
await expect(page).toHaveScreenshot('BranchName.With Trailing Action Menu.light.menu-open.png')
})
})
Comment on lines +80 to +101
})
106 changes: 105 additions & 1 deletion packages/react/src/BranchName/BranchName.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import type {Meta} from '@storybook/react-vite'
import React, {useState} from 'react'
import BranchName from './BranchName'
import {Stack} from '../Stack'
import Octicon from '../Octicon'
import {GitBranchIcon} from '@primer/octicons-react'
import {GitBranchIcon, CopyIcon, CheckIcon, TriangleDownIcon} from '@primer/octicons-react'
import {IconButton} from '../Button'
import {Tooltip} from '../TooltipV2'
import {SelectPanel} from '../SelectPanel'
import type {ItemInput} from '../FilteredActionList'
import {clsx} from 'clsx'
import {announce} from '@primer/live-region-element'

import styles from './BranchName.stories.module.css'

export default {
title: 'Components/BranchName/Features',
Expand All @@ -28,3 +37,98 @@ export const LinkWithoutHref = () => (
)

export const NoProps = () => <BranchName>branch_name_no_props</BranchName>

export const WithTrailingAction = ({
branchName = 'fix/anchored-overlay-outside-top-autogrow',
repo = 'primer/react',
}: {
branchName: string
repo: string
}) => {
const [copied, setCopied] = React.useState(false)

const handleCopy = () => {
setCopied(true)
void navigator.clipboard.writeText(branchName)
announce('Copied!')
setTimeout(() => setCopied(false), 2000)
}
Comment on lines +48 to +55

const tooltipText = copied ? 'Copied!' : 'Copy branch name to clipboard'

return (
<span className={styles.BranchNameWithTrailingAction}>
<Tooltip text={`${repo}:${branchName}`}>
<BranchName href={`https://github.com/${repo}/tree/${branchName}`} className={styles.BranchNameTransparent}>
{branchName}
</BranchName>
</Tooltip>
<Tooltip text={tooltipText} aria-hidden>
<IconButton
icon={copied ? CheckIcon : CopyIcon}
aria-label="Copy branch name to clipboard"
variant="invisible"
size="small"
onClick={handleCopy}
className={clsx(styles.TrailingActionButton, copied && styles.TrailingActionButtonCopied)}
/>
</Tooltip>
</span>
)
}

export const WithTrailingActionMenu = ({repo = 'primer/react'}: {repo: string}) => {
const branches: ItemInput[] = [
{text: 'main', id: 'main'},
{text: 'mp/test-combobox-unrendered-listbox', id: 'mp/test-combobox-unrendered-listbox'},
{text: 'mp/rwd-stack-wrap-demo', id: 'mp/rwd-stack-wrap-demo'},
{text: 'mp/rm-box-and-sx-from-underlinenav', id: 'mp/rm-box-and-sx-from-underlinenav'},
{text: 'mp/rm-box-and-sx-from-pageheader', id: 'mp/rm-box-and-sx-from-pageheader'},
{text: 'mp/prototype-datatable-API', id: 'mp/prototype-datatable-API'},
{text: 'mp/fake-for-screenshot', id: 'mp/fake-for-screenshot'},
{text: 'mp/add-statelabel-stories', id: 'mp/add-statelabel-stories'},
{text: 'mo/2156-filtered-list-layout', id: 'mo/2156-filtered-list-layout'},
{text: 'mitigate-hydration-mismatches', id: 'mitigate-hydration-mismatches'},
]

const [selected, setSelected] = useState<ItemInput>(branches[0])
const [filter, setFilter] = useState('')
const [open, setOpen] = useState(false)

const filteredBranches = branches.filter(branch => branch.text?.toLowerCase().includes(filter.toLowerCase()))

const handleSelectedChange = (newSelected: ItemInput | undefined) => {
if (newSelected) {
setSelected(newSelected)
}
}

return (
<span className={styles.BranchNameWithTrailingAction}>
<BranchName href={`https://github.com/${repo}/tree/${selected.text}`} className={styles.BranchNameTransparent}>
{selected.text}
</BranchName>
<SelectPanel
renderAnchor={anchorProps => (
<IconButton
icon={TriangleDownIcon}
aria-label="Change base branch"
variant="invisible"
size="small"
className={styles.TrailingActionButton}
{...anchorProps}
/>
)}
Comment on lines +112 to +121
placeholder="Find a branch..."
open={open}
onOpenChange={setOpen}
items={filteredBranches}
selected={selected}
onSelectedChange={handleSelectedChange}
onFilterChange={setFilter}
title="Change base branch"
overlayProps={{width: 'medium'}}
/>
</span>
)
}
56 changes: 56 additions & 0 deletions packages/react/src/BranchName/BranchName.stories.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* Story-only styles for demonstrating trailing action pattern */

.BranchNameWithTrailingAction {
display: inline-flex;
/* Let items stretch to fill container height */
align-items: stretch;
/* Create room for pseudo-border divider */
gap: var(--borderWidth-default);
flex-wrap: nowrap;
/* Moving the background to the container allows stacking the button hover background over it */
background-color: var(--bgColor-accent-muted);
border-radius: var(--borderRadius-medium);
}

.BranchNameTransparent {
background-color: transparent;
}

.TrailingActionButton {
/* Make the size auto calculated based on icon size and padding */
height: auto;
width: auto;
padding: var(--base-size-2) var(--base-size-6);
position: relative;
/* Center content when button stretches to fill container */
align-items: center;

&:not(:disabled, [aria-disabled='true']) {
--button-invisible-iconColor-rest: var(--fgColor-link);
}

&.TrailingActionButtonCopied:not(:disabled, [aria-disabled='true']) {
--button-invisible-iconColor-rest: var(--fgColor-success);
}

[data-component='Octicon'] {
/* Set icon size to text size to exactly match the branch name component */
height: var(--text-body-size-small);
width: var(--text-body-size-small);
}

/* Dividing line between button and name. Using ::before on button allows for multiple buttons (with a line between each) */
&::before {
content: '';
position: absolute;
width: var(--borderWidth-default);
/* Position _between_ items (offsetting by width) to avoid overlapping with the real button border on hover/focus */
inset: var(--base-size-4) auto var(--base-size-4) calc(-1 * var(--borderWidth-default));
/* stylelint-disable-next-line primer/colors */
background-color: var(--borderColor-accent-muted);
}
}

.TrailingActionButtonCopied {
/* Class exists for JS toggling - styles handled via compound selector above */
}
Loading