From 3502e22da169e4ec6f21b0594efff7f5d9736818 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Tue, 7 May 2024 18:27:45 +0200 Subject: [PATCH 1/5] wip: leading action slot --- .../TreeView/TreeView.features.stories.tsx | 134 ++++++------------ packages/react/src/TreeView/TreeView.tsx | 59 ++++---- 2 files changed, 72 insertions(+), 121 deletions(-) diff --git a/packages/react/src/TreeView/TreeView.features.stories.tsx b/packages/react/src/TreeView/TreeView.features.stories.tsx index 3600099bf40..0c3d41a5b76 100644 --- a/packages/react/src/TreeView/TreeView.features.stories.tsx +++ b/packages/react/src/TreeView/TreeView.features.stories.tsx @@ -6,6 +6,8 @@ import { FileIcon, GrabberIcon, KebabHorizontalIcon, + IssueClosedIcon, + IssueOpenedIcon, } from '@primer/octicons-react' import type {Meta, Story} from '@storybook/react' import React from 'react' @@ -409,17 +411,10 @@ AsyncSuccess.args = { } export const AsyncWithCount: Story = args => { - const [isLoading, setIsLoading] = React.useState(false) const [asyncItems, setAsyncItems] = React.useState([]) let state: SubTreeState = 'initial' - if (isLoading) { - state = 'loading' - } else if (asyncItems.length > 0) { - state = 'done' - } - return ( ) +export const WithLeadingAction: Story = () => { + // todo: implement fold on click + const dragAction = + + return ( + + + + + + Item 1 + + + + + + Item 2 + + + + + + sub task 1 + + + + + + sub task 2 + + + + + + + + Item 3 + + + ) +} + export default meta diff --git a/packages/react/src/TreeView/TreeView.tsx b/packages/react/src/TreeView/TreeView.tsx index d938e6fac36..cc2eca1612c 100644 --- a/packages/react/src/TreeView/TreeView.tsx +++ b/packages/react/src/TreeView/TreeView.tsx @@ -98,6 +98,10 @@ const UlBox = styled.ul` outline-offset: -2; } } + + &[data-has-leading-action] { + --has-leading-action: 1; + } } .PRIVATE_TreeView-item-container { @@ -105,8 +109,10 @@ const UlBox = styled.ul` --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(--leading-action-width) var(--spacer-width) var(--toggle-width) 1fr; + grid-template-areas: 'leadingAction spacer toggle content'; width: 100%; min-height: 2rem; /* 32px */ font-size: ${get('fontSizes.1')}; @@ -121,10 +127,6 @@ const UlBox = styled.ul` outline: 2px solid transparent; outline-offset: -2px; } - - .PRIVATE_TreeView-item-drag-handle { - visibility: visible; - } } @media (pointer: coarse) { @@ -143,21 +145,7 @@ const UlBox = styled.ul` } &[data-omit-spacer='true'] .PRIVATE_TreeView-item-container { - grid-template-columns: 0 0 1fr; - } - - &[data-drag-and-drop='true'] .PRIVATE_TreeView-item-container { - grid-template-columns: 1.5rem calc(calc(var(--level) - 1) * (var(--toggle-width) / 2)) var(--toggle-width) 1fr; - grid-template-areas: 'drag spacer toggle content'; - } - - .PRIVATE_TreeView-item-drag-handle { - grid-area: drag; - height: 100%; - visibility: hidden; - display: flex; - align-items: center; - justify-content: center; + grid-template-columns: 0 0 0 1fr; } .PRIVATE_TreeView-item[aria-current='true'] > .PRIVATE_TreeView-item-container { @@ -221,6 +209,12 @@ const UlBox = styled.ul` 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%; @@ -267,6 +261,15 @@ const UlBox = styled.ul` border-width: 0; } + .PRIVATE_TreeView-item-toggle { + grid-area: toggle; + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: ${get('colors.fg.muted')}; + } + ${sx} ` @@ -276,7 +279,6 @@ const Root: React.FC = ({ children, flat, className, - dragAndDrop, }) => { const containerRef = React.useRef(null) const mouseDownRef = React.useRef(false) @@ -332,7 +334,6 @@ const Root: React.FC = ({ aria-label={ariaLabel} aria-labelledby={ariaLabelledby} data-omit-spacer={flat} - data-drag-and-drop={dragAndDrop} onMouseDown={onMouseDown} className={className} > @@ -358,7 +359,7 @@ export type TreeViewItemProps = { onExpandedChange?: (expanded: boolean) => void onSelect?: (event: React.MouseEvent | React.KeyboardEvent) => void className?: string - dragHandle?: ReactElement + leadingAction?: ReactElement } const Item = React.forwardRef( @@ -373,7 +374,7 @@ const Item = React.forwardRef( onSelect, children, className, - dragHandle, + leadingAction, }, ref, ) => { @@ -472,6 +473,7 @@ const Item = React.forwardRef( aria-expanded={isSubTreeEmpty ? undefined : isExpanded} aria-current={isCurrentItem ? 'true' : undefined} aria-selected={isFocused ? 'true' : 'false'} + data-has-leading-action={leadingAction ? true : undefined} onKeyDown={handleKeyDown} onFocus={event => { // Scroll the first child into view when the item receives focus @@ -508,15 +510,10 @@ const Item = React.forwardRef( containIntrinsicSize, }} > + {leadingAction ?
{leadingAction}
: null}
- {dragHandle ? ( - // eslint-disable-next-line jsx-a11y/no-static-element-interactions -
setIsExpanded(false)}> - {dragHandle} -
- ) : null} {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 From a9909c71015bb77ca7661c27fd715c8d21f06518 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Tue, 7 May 2024 18:34:33 +0200 Subject: [PATCH 2/5] clean up a little --- packages/react/src/TreeView/TreeView.docs.json | 9 ++------- .../src/TreeView/TreeView.features.stories.tsx | 7 +++++++ packages/react/src/TreeView/TreeView.test.tsx | 2 +- packages/react/src/TreeView/TreeView.tsx | 15 ++------------- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/packages/react/src/TreeView/TreeView.docs.json b/packages/react/src/TreeView/TreeView.docs.json index 90772f2170c..4fa4207aac9 100644 --- a/packages/react/src/TreeView/TreeView.docs.json +++ b/packages/react/src/TreeView/TreeView.docs.json @@ -22,11 +22,6 @@ "name": "flat", "type": "boolean", "description": "Prevents the tree from indenting items. This should only be used when the tree is used to display a flat list of items." - }, - { - "name": "dragAndDrop", - "type": "boolean", - "description": "Updates the css grid of the tree to have space to render the drag handle. This is currently only used for drag and drop in subissues." } ], "subcomponents": [ @@ -79,9 +74,9 @@ "type": "React.Ref" }, { - "name": "dragHandle", + "name": "leadingAction", "type": "ReactElement", - "description": "The trigger button that activates the drag and drop of the TreeView.Item. Consumer also needs to pass `dragAndDrop` prop to TreeView so dragHandle is rendered in the correct position." + "description": "An action that can be rendered at the start of an Item" } ] }, diff --git a/packages/react/src/TreeView/TreeView.features.stories.tsx b/packages/react/src/TreeView/TreeView.features.stories.tsx index 0c3d41a5b76..311465239eb 100644 --- a/packages/react/src/TreeView/TreeView.features.stories.tsx +++ b/packages/react/src/TreeView/TreeView.features.stories.tsx @@ -411,10 +411,17 @@ AsyncSuccess.args = { } export const AsyncWithCount: Story = args => { + const [isLoading, setIsLoading] = React.useState(false) const [asyncItems, setAsyncItems] = React.useState([]) let state: SubTreeState = 'initial' + if (isLoading) { + state = 'loading' + } else if (asyncItems.length > 0) { + state = 'done' + } + return (