Skip to content

Commit

Permalink
Merge branch 'main' into bump-eslint
Browse files Browse the repository at this point in the history
  • Loading branch information
broccolinisoup committed May 16, 2024
2 parents 3bf9279 + d4e234f commit 286750b
Show file tree
Hide file tree
Showing 17 changed files with 223 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/calm-insects-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

Update NavList to use the new ActionList.GroupHeading API and Add an "as" prop to specify the heading level as default h3. (No changes expected in the rendered HTML)
5 changes: 5 additions & 0 deletions .changeset/tame-nails-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

TreeView: Add support for `TreeView.LeadingAction`
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.
33 changes: 33 additions & 0 deletions e2e/components/TreeView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,37 @@ test.describe('TreeView', () => {
})
}
})

test.describe('Leading Action', () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: 'components-treeview-features--leading-action',
globals: {
colorScheme: theme,
},
})

expect(await page.screenshot()).toMatchSnapshot(`TreeView.Leading Action.${theme}.png`)
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: 'components-treeview-features--leading-action',
globals: {
colorScheme: theme,
},
})
await expect(page).toHaveNoViolations({
rules: {
'color-contrast': {
enabled: theme !== 'dark_dimmed',
},
},
})
})
})
}
})
})
1 change: 1 addition & 0 deletions packages/react/src/NavList/NavList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ const Group: React.FC<NavListGroupProps> = ({title, children, sx: sxProp = defau
{/* Hide divider if the group is the first item in the list */}
<ActionList.Divider sx={{'&:first-child': {display: 'none'}}} />
<ActionList.Group {...props} sx={sxProp}>
{/* Setting up the default value for the heading level. TODO: API update to give flexibility to NavList.Group title's heading level */}
<ActionList.GroupHeading as="h3">{title}</ActionList.GroupHeading>
{children}
</ActionList.Group>
Expand Down
10 changes: 10 additions & 0 deletions packages/react/src/TreeView/TreeView.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@
}
]
},
{
"name": "TreeView.LeadingAction",
"props": [
{
"name": "children",
"required": true,
"type": "React.ReactNode"
}
]
},
{
"name": "TreeView.DirectoryIcon",
"props": []
Expand Down
76 changes: 76 additions & 0 deletions packages/react/src/TreeView/TreeView.examples.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {GrabberIcon} from '@primer/octicons-react'
import type {Meta, Story} from '@storybook/react'
import React from 'react'
import Box from '../Box'
import {TreeView} from './TreeView'
import {IconButton} from '../Button'

const meta: Meta = {
title: 'Components/TreeView/Examples',
component: TreeView,
decorators: [
Story => {
return (
// Prevent TreeView from expanding to the full width of the screen
<Box sx={{maxWidth: 400}}>
<Story />
</Box>
)
},
],
}

export const DraggableListItem: Story = () => {
return (
<Box
sx={{
// using Box for css, this could be in a css file as well
'.treeview-item': {
'.treeview-leading-action': {visibility: 'hidden'},
'&:hover, &:focus': {
'.treeview-leading-action': {visibility: 'visible'},
},
},
}}
>
<TreeView aria-label="Issues">
<ControlledDraggableItem id="item-1">Item 1</ControlledDraggableItem>
<ControlledDraggableItem id="item-2">
Item 2
<TreeView.SubTree>
<TreeView.Item id="item-2-sub-task-1">sub task 1</TreeView.Item>
<TreeView.Item id="item-2-sub-task-2">sub task 2</TreeView.Item>
</TreeView.SubTree>
</ControlledDraggableItem>
<ControlledDraggableItem id="item-3">Item 3</ControlledDraggableItem>
</TreeView>
</Box>
)
}

const ControlledDraggableItem: React.FC<{id: string; children: React.ReactNode}> = ({id, children}) => {
const [expanded, setExpanded] = React.useState(false)

return (
<>
<TreeView.Item id={id} className="treeview-item" expanded={expanded} onExpandedChange={setExpanded}>
<TreeView.LeadingAction>
<IconButton
icon={GrabberIcon}
variant="invisible"
aria-label="Reorder item"
className="treeview-leading-action"
draggable="true"
onDragStart={() => {
setExpanded(false)
// other drag logic to follow
}}
/>
</TreeView.LeadingAction>
{children}
</TreeView.Item>
</>
)
}

export default meta
51 changes: 51 additions & 0 deletions packages/react/src/TreeView/TreeView.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {
DiffRemovedIcon,
DiffRenamedIcon,
FileIcon,
GrabberIcon,
KebabHorizontalIcon,
IssueClosedIcon,
IssueOpenedIcon,
} from '@primer/octicons-react'
import type {Meta, Story} from '@storybook/react'
import React from 'react'
Expand Down Expand Up @@ -989,4 +992,52 @@ export const WithoutIndentation: Story = () => (
</nav>
)

export const LeadingAction: Story = () => {
return (
<TreeView aria-label="Issues">
<TreeView.Item id="item-0">
<TreeView.LeadingAction>
<IconButton icon={GrabberIcon} aria-label="Reorder item 1" variant="invisible" />
</TreeView.LeadingAction>
<TreeView.LeadingVisual>
<Octicon icon={IssueClosedIcon} sx={{color: 'done.fg'}} />
</TreeView.LeadingVisual>
Item 1
</TreeView.Item>
<TreeView.Item id="item-2">
<TreeView.LeadingAction>
<IconButton icon={GrabberIcon} aria-label="Reorder item 2" variant="invisible" />
</TreeView.LeadingAction>
<TreeView.LeadingVisual>
<Octicon icon={IssueOpenedIcon} sx={{color: 'open.fg'}} />
</TreeView.LeadingVisual>
Item 2
<TreeView.SubTree>
<TreeView.Item id="item-2-sub-task-1">
<TreeView.LeadingVisual>
<Octicon icon={IssueOpenedIcon} sx={{color: 'open.fg'}} />
</TreeView.LeadingVisual>
sub task 1
</TreeView.Item>
<TreeView.Item id="item-2-sub-task-2">
<TreeView.LeadingVisual>
<Octicon icon={IssueOpenedIcon} sx={{color: 'open.fg'}} />
</TreeView.LeadingVisual>
sub task 2
</TreeView.Item>
</TreeView.SubTree>
</TreeView.Item>
<TreeView.Item id="item-3">
<TreeView.LeadingAction>
<IconButton icon={GrabberIcon} aria-label="Reorder item 3" variant="invisible" />
</TreeView.LeadingAction>
<TreeView.LeadingVisual>
<Octicon icon={IssueOpenedIcon} sx={{color: 'open.fg'}} />
</TreeView.LeadingVisual>
Item 3
</TreeView.Item>
</TreeView>
)
}

export default meta
46 changes: 42 additions & 4 deletions packages/react/src/TreeView/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,20 @@ const UlBox = styled.ul<SxProp>`
outline-offset: -2;
}
}
&[data-has-leading-action] {
--has-leading-action: 1;
}
}
.PRIVATE_TreeView-item-container {
--level: 1; /* default level */
--toggle-width: 1rem; /* 16px */
position: relative;
display: grid;
grid-template-columns: calc(calc(var(--level) - 1) * (var(--toggle-width) / 2)) var(--toggle-width) 1fr;
grid-template-areas: 'spacer toggle content';
--leading-action-width: calc(var(--has-leading-action, 0) * 1.5rem);
--spacer-width: calc(calc(var(--level) - 1) * (var(--toggle-width) / 2));
grid-template-columns: var(--spacer-width) var(--leading-action-width) var(--toggle-width) 1fr;
grid-template-areas: 'spacer leadingAction toggle content';
width: 100%;
min-height: 2rem; /* 32px */
font-size: ${get('fontSizes.1')};
Expand Down Expand Up @@ -138,7 +143,7 @@ const UlBox = styled.ul<SxProp>`
}
&[data-omit-spacer='true'] .PRIVATE_TreeView-item-container {
grid-template-columns: 0 0 1fr;
grid-template-columns: 0 0 0 1fr;
}
.PRIVATE_TreeView-item[aria-current='true'] > .PRIVATE_TreeView-item-container {
Expand Down Expand Up @@ -202,6 +207,12 @@ const UlBox = styled.ul<SxProp>`
color: ${get('colors.fg.muted')};
}
.PRIVATE_TreeView-item-leading-action {
display: flex;
color: ${get('colors.fg.muted')};
grid-area: leadingAction;
}
.PRIVATE_TreeView-item-level-line {
width: 100%;
height: 100%;
Expand Down Expand Up @@ -354,11 +365,16 @@ const Item = React.forwardRef<HTMLElement, TreeViewItemProps>(
},
ref,
) => {
const [slots, rest] = useSlots(children, {leadingVisual: LeadingVisual, trailingVisual: TrailingVisual})
const [slots, rest] = useSlots(children, {
leadingAction: LeadingAction,
leadingVisual: LeadingVisual,
trailingVisual: TrailingVisual,
})
const {expandedStateCache} = React.useContext(RootContext)
const labelId = useId()
const leadingVisualId = useId()
const trailingVisualId = useId()

const [isExpanded, setIsExpanded] = useControllableState({
name: itemId,
// If the item was previously mounted, it's expanded state might be cached.
Expand Down Expand Up @@ -449,6 +465,7 @@ const Item = React.forwardRef<HTMLElement, TreeViewItemProps>(
aria-expanded={isSubTreeEmpty ? undefined : isExpanded}
aria-current={isCurrentItem ? 'true' : undefined}
aria-selected={isFocused ? 'true' : 'false'}
data-has-leading-action={slots.leadingAction ? true : undefined}
onKeyDown={handleKeyDown}
onFocus={event => {
// Scroll the first child into view when the item receives focus
Expand Down Expand Up @@ -488,6 +505,7 @@ const Item = React.forwardRef<HTMLElement, TreeViewItemProps>(
<div style={{gridArea: 'spacer', display: 'flex'}}>
<LevelIndicatorLines level={level} />
</div>
{slots.leadingAction}
{hasSubTree ? (
// This lint rule is disabled due to the guidelines in the `TreeView` api docs.
// https://github.com/github/primer/blob/main/apis/tree-view-api.md#the-expandcollapse-chevron-toggle
Expand Down Expand Up @@ -829,6 +847,25 @@ const TrailingVisual: React.FC<TreeViewVisualProps> = props => {

TrailingVisual.displayName = 'TreeView.TrailingVisual'

// ----------------------------------------------------------------------------
// TreeView.LeadingAction

const LeadingAction: React.FC<TreeViewVisualProps> = props => {
const {isExpanded} = React.useContext(ItemContext)
const children = typeof props.children === 'function' ? props.children({isExpanded}) : props.children
return (
<>
<div className="PRIVATE_VisuallyHidden" aria-hidden={true}>
{props.label}
</div>
<div className="PRIVATE_TreeView-item-leading-action" aria-hidden={true}>
{children}
</div>
</>
)
}

LeadingAction.displayName = 'TreeView.LeadingAction'
// ----------------------------------------------------------------------------
// TreeView.DirectoryIcon

Expand Down Expand Up @@ -898,6 +935,7 @@ ErrorDialog.displayName = 'TreeView.ErrorDialog'
export const TreeView = Object.assign(Root, {
Item,
SubTree,
LeadingAction,
LeadingVisual,
TrailingVisual,
DirectoryIcon,
Expand Down

0 comments on commit 286750b

Please sign in to comment.