From 1736a6a852e22f5f9afec775f8a5ed6cacd3641a Mon Sep 17 00:00:00 2001 From: Josh Black Date: Wed, 19 Oct 2022 14:23:52 -0500 Subject: [PATCH 1/9] feat(TreeView): add count prop to TreeView.SubTree Co-authored-by: Mike Perrotti --- docs/content/TreeView.mdx | 7 +- src/TreeView/TreeView.stories.tsx | 11 ++- src/TreeView/TreeView.tsx | 117 ++++++++++++++++++++++++++++-- 3 files changed, 125 insertions(+), 10 deletions(-) diff --git a/docs/content/TreeView.mdx b/docs/content/TreeView.mdx index 9d4f116ee1d..b9fa60fb1d7 100644 --- a/docs/content/TreeView.mdx +++ b/docs/content/TreeView.mdx @@ -307,7 +307,12 @@ See [Storybook](https://primer.style/react/storybook?path=/story/components-tree } /> - {/* */} + + ### TreeView.LeadingVisual diff --git a/src/TreeView/TreeView.stories.tsx b/src/TreeView/TreeView.stories.tsx index a153b1dba71..0d28c5a2e76 100644 --- a/src/TreeView/TreeView.stories.tsx +++ b/src/TreeView/TreeView.stories.tsx @@ -438,7 +438,7 @@ export const AsyncSuccess: Story = args => { Directory with async items - + {asyncItems.map(item => ( @@ -456,7 +456,14 @@ export const AsyncSuccess: Story = args => { } AsyncSuccess.args = { - responseTime: 2000 + responseTime: 2000, + count: null +} + +AsyncSuccess.argTypes = { + count: { + type: 'number' + } } async function alwaysFails(responseTime: number) { diff --git a/src/TreeView/TreeView.tsx b/src/TreeView/TreeView.tsx index d2afa327788..da0499d5612 100644 --- a/src/TreeView/TreeView.tsx +++ b/src/TreeView/TreeView.tsx @@ -6,8 +6,9 @@ import { } from '@primer/octicons-react' import {useSSRSafeId} from '@react-aria/ssr' import React from 'react' -import styled from 'styled-components' +import styled, {keyframes} from 'styled-components' import Box from '../Box' +import {get} from '../constants' import {useControllableState} from '../hooks/useControllableState' import useSafeTimeout from '../hooks/useSafeTimeout' import Spinner from '../Spinner' @@ -112,12 +113,15 @@ export type TreeViewItemProps = { expanded?: boolean onExpandedChange?: (expanded: boolean) => void onSelect?: (event: React.MouseEvent | React.KeyboardEvent) => void -} +} & SxProp const {Slots, Slot} = createSlots(['LeadingVisual', 'TrailingVisual']) const Item = React.forwardRef( - ({current: isCurrentItem = false, defaultExpanded = false, expanded, onExpandedChange, onSelect, children}, ref) => { + ( + {current: isCurrentItem = false, defaultExpanded = false, expanded, onExpandedChange, onSelect, children, sx}, + ref + ) => { const itemId = useSSRSafeId() const labelId = useSSRSafeId() const leadingVisualId = useSSRSafeId() @@ -265,7 +269,8 @@ const Item = React.forwardRef( bg: 'accent.fg', borderRadius: 2 } - } + }, + ...sx }} > @@ -401,9 +406,13 @@ export type SubTreeState = 'initial' | 'loading' | 'done' | 'error' export type TreeViewSubTreeProps = { children?: React.ReactNode state?: SubTreeState + /** + * Display a skeleton loading state with the specified count of items + */ + count?: number } -const SubTree: React.FC = ({state, children}) => { +const SubTree: React.FC = ({count, state, children}) => { const {announceUpdate} = React.useContext(RootContext) const {itemId, isExpanded} = React.useContext(ItemContext) const [isLoadingItemVisible, setIsLoadingItemVisible] = React.useState(false) @@ -469,14 +478,108 @@ const SubTree: React.FC = ({state, children}) => { margin: 0 }} > - {isLoadingItemVisible ? : children} + {isLoadingItemVisible ? : children} ) } SubTree.displayName = 'TreeView.SubTree' -const LoadingItem = React.forwardRef((props, ref) => { +const shimmer = keyframes` + from { mask-position: 200%; } + to { mask-position: 0%; } +` + +const SkeletonItem = styled.span` + display: flex; + align-items: center; + column-gap: 0.5rem; + height: 2rem; + + @media (prefers-reduced-motion: no-preference) { + mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%); + mask-size: 200%; + animation: ${shimmer}; + animation-duration: 1s; + animation-iteration-count: infinite; + } + + &::before { + content: ''; + display: block; + width: 16px; + height: 16px; + background-color: ${get('colors.neutral.subtle')}; + border-radius: 3px; + @media (forced-colors: active) { + outline: 1px solid transparent; + outline-offset: -1px; + } + } + + &::after { + content: ''; + display: block; + width: var(--tree-item-loading-width, 67%); + height: 16px; + background-color: ${get('colors.neutral.subtle')}; + border-radius: 3px; + @media (forced-colors: active) { + outline: 1px solid transparent; + outline-offset: -1px; + } + } + + &:nth-of-type(5n + 1) { + --tree-item-loading-width: 67%; + } + + &:nth-of-type(5n + 2) { + --tree-item-loading-width: 47%; + } + + &:nth-of-type(5n + 3) { + --tree-item-loading-width: 73%; + } + + &:nth-of-type(5n + 4) { + --tree-item-loading-width: 64%; + } + + &:nth-of-type(5n + 5) { + --tree-item-loading-width: 50%; + } +` + +type LoadingItemProps = { + count?: number +} + +const LoadingItem = React.forwardRef((props, ref) => { + const {count} = props + + if (count) { + return ( + + {Array.from({length: count}).map((_, i) => { + return + })} + Loading {count} items + + ) + } + return ( From 352b08d203f37a148cdafb720c5ce74065668a2c Mon Sep 17 00:00:00 2001 From: Josh Black Date: Wed, 19 Oct 2022 14:30:08 -0500 Subject: [PATCH 2/9] chore: add changeset --- .changeset/tender-turtles-serve.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tender-turtles-serve.md diff --git a/.changeset/tender-turtles-serve.md b/.changeset/tender-turtles-serve.md new file mode 100644 index 00000000000..a9710f4fde9 --- /dev/null +++ b/.changeset/tender-turtles-serve.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +Add support for a skeleton state with the TreeView.Subtree `count` prop From d8537f2516ff680561d1560c4c7370fcb6b8af62 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Wed, 19 Oct 2022 14:36:53 -0500 Subject: [PATCH 3/9] refactor(TreeView): update px units to rem --- src/TreeView/TreeView.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/TreeView/TreeView.tsx b/src/TreeView/TreeView.tsx index da0499d5612..22334e22d3d 100644 --- a/src/TreeView/TreeView.tsx +++ b/src/TreeView/TreeView.tsx @@ -507,8 +507,8 @@ const SkeletonItem = styled.span` &::before { content: ''; display: block; - width: 16px; - height: 16px; + width: 1rem; + height: 1rem; background-color: ${get('colors.neutral.subtle')}; border-radius: 3px; @media (forced-colors: active) { @@ -521,7 +521,7 @@ const SkeletonItem = styled.span` content: ''; display: block; width: var(--tree-item-loading-width, 67%); - height: 16px; + height: 1rem; background-color: ${get('colors.neutral.subtle')}; border-radius: 3px; @media (forced-colors: active) { From af3807abf8e0749a11ec79900a50464dd0f49d76 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Wed, 19 Oct 2022 16:42:20 -0500 Subject: [PATCH 4/9] Update docs/content/TreeView.mdx Co-authored-by: Cole Bemis --- docs/content/TreeView.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/TreeView.mdx b/docs/content/TreeView.mdx index b9fa60fb1d7..d07cf2db27d 100644 --- a/docs/content/TreeView.mdx +++ b/docs/content/TreeView.mdx @@ -310,7 +310,7 @@ See [Storybook](https://primer.style/react/storybook?path=/story/components-tree From 5c3fd039ba5caa062d2c77012b3c1f30a9cc6b1d Mon Sep 17 00:00:00 2001 From: Josh Black Date: Wed, 19 Oct 2022 16:44:03 -0500 Subject: [PATCH 5/9] Update .changeset/tender-turtles-serve.md Co-authored-by: Cole Bemis --- .changeset/tender-turtles-serve.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tender-turtles-serve.md b/.changeset/tender-turtles-serve.md index a9710f4fde9..311317e2152 100644 --- a/.changeset/tender-turtles-serve.md +++ b/.changeset/tender-turtles-serve.md @@ -2,4 +2,4 @@ '@primer/react': patch --- -Add support for a skeleton state with the TreeView.Subtree `count` prop +TreeView: Add support for a skeleton state with the TreeView.SubTree `count` prop From c0a2f1041df87728c91a1c4b41f17f425cd19fd4 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 20 Oct 2022 09:33:55 -0500 Subject: [PATCH 6/9] refactor(TreeView): update stories and merge sx prop --- src/TreeView/TreeView.stories.tsx | 60 +++++++++++++++++- src/TreeView/TreeView.tsx | 100 +++++++++++++++--------------- 2 files changed, 108 insertions(+), 52 deletions(-) diff --git a/src/TreeView/TreeView.stories.tsx b/src/TreeView/TreeView.stories.tsx index 0d28c5a2e76..cb1e49afb67 100644 --- a/src/TreeView/TreeView.stories.tsx +++ b/src/TreeView/TreeView.stories.tsx @@ -438,7 +438,7 @@ export const AsyncSuccess: Story = args => { Directory with async items - + {asyncItems.map(item => ( @@ -456,11 +456,65 @@ export const AsyncSuccess: Story = args => { } AsyncSuccess.args = { + responseTime: 2000 +} + +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 ( + + + + ) +} + +AsyncWithCount.args = { responseTime: 2000, - count: null + count: 5 } -AsyncSuccess.argTypes = { +AsyncWithCount.argTypes = { count: { type: 'number' } diff --git a/src/TreeView/TreeView.tsx b/src/TreeView/TreeView.tsx index 22334e22d3d..6cc7f135a2b 100644 --- a/src/TreeView/TreeView.tsx +++ b/src/TreeView/TreeView.tsx @@ -13,7 +13,7 @@ import {useControllableState} from '../hooks/useControllableState' import useSafeTimeout from '../hooks/useSafeTimeout' import Spinner from '../Spinner' import StyledOcticon from '../StyledOcticon' -import sx, {SxProp} from '../sx' +import sx, {SxProp, merge} from '../sx' import Text from '../Text' import {Theme} from '../ThemeProvider' import createSlots from '../utils/create-slots' @@ -119,7 +119,7 @@ const {Slots, Slot} = createSlots(['LeadingVisual', 'TrailingVisual']) const Item = React.forwardRef( ( - {current: isCurrentItem = false, defaultExpanded = false, expanded, onExpandedChange, onSelect, children, sx}, + {current: isCurrentItem = false, defaultExpanded = false, expanded, onExpandedChange, onSelect, children, sx = {}}, ref ) => { const itemId = useSSRSafeId() @@ -223,55 +223,57 @@ const Item = React.forwardRef( toggle(event) } }} - sx={{ - '--toggle-width': '1rem', // 16px - position: 'relative', - display: 'grid', - gridTemplateColumns: `calc(${level - 1} * (var(--toggle-width) / 2)) var(--toggle-width) 1fr`, - gridTemplateAreas: `"spacer toggle content"`, - width: '100%', - height: '2rem', // 32px - fontSize: 1, - color: 'fg.default', - borderRadius: 2, - cursor: 'pointer', - '&:hover': { - backgroundColor: 'actionListItem.default.hoverBg', - '@media (forced-colors: active)': { - outline: '2px solid transparent', - outlineOffset: -2 - } - }, - '@media (pointer: coarse)': { - '--toggle-width': '1.5rem', // 24px - height: '2.75rem' // 44px - }, - // WARNING: styled-components v5.2 introduced a bug that changed - // how it expands `&` in CSS selectors. The following selectors - // are unnecessarily specific to work around that styled-components bug. - // Reference issue: https://github.com/styled-components/styled-components/issues/3265 - [`#${itemId}:focus-visible > &:is(div)`]: { - boxShadow: (theme: Theme) => `inset 0 0 0 2px ${theme.colors.accent.emphasis}`, - '@media (forced-colors: active)': { - outline: '2px solid SelectedItem', - outlineOffset: -2 - } - }, - '[role=treeitem][aria-current=true] > &:is(div)': { - bg: 'actionListItem.default.selectedBg', - '&::after': { - position: 'absolute', - top: 'calc(50% - 12px)', - left: -2, - width: '4px', - height: '24px', - content: '""', - bg: 'accent.fg', - borderRadius: 2 + sx={merge.all([ + { + '--toggle-width': '1rem', // 16px + position: 'relative', + display: 'grid', + gridTemplateColumns: `calc(${level - 1} * (var(--toggle-width) / 2)) var(--toggle-width) 1fr`, + gridTemplateAreas: `"spacer toggle content"`, + width: '100%', + height: '2rem', // 32px + fontSize: 1, + color: 'fg.default', + borderRadius: 2, + cursor: 'pointer', + '&:hover': { + backgroundColor: 'actionListItem.default.hoverBg', + '@media (forced-colors: active)': { + outline: '2px solid transparent', + outlineOffset: -2 + } + }, + '@media (pointer: coarse)': { + '--toggle-width': '1.5rem', // 24px + height: '2.75rem' // 44px + }, + // WARNING: styled-components v5.2 introduced a bug that changed + // how it expands `&` in CSS selectors. The following selectors + // are unnecessarily specific to work around that styled-components bug. + // Reference issue: https://github.com/styled-components/styled-components/issues/3265 + [`#${itemId}:focus-visible > &:is(div)`]: { + boxShadow: (theme: Theme) => `inset 0 0 0 2px ${theme.colors.accent.emphasis}`, + '@media (forced-colors: active)': { + outline: '2px solid SelectedItem', + outlineOffset: -2 + } + }, + '[role=treeitem][aria-current=true] > &:is(div)': { + bg: 'actionListItem.default.selectedBg', + '&::after': { + position: 'absolute', + top: 'calc(50% - 12px)', + left: -2, + width: '4px', + height: '24px', + content: '""', + bg: 'accent.fg', + borderRadius: 2 + } } }, - ...sx - }} + sx as SxProp + ])} > From 4f50d9331ddb43fb4da43dd432473bfcd73d7831 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 20 Oct 2022 09:44:39 -0500 Subject: [PATCH 7/9] refactor: update height for items and adjust height for coarse pointers --- src/TreeView/TreeView.stories.tsx | 40 +++++++++++++++++++++++++++++++ src/TreeView/TreeView.tsx | 6 ++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/TreeView/TreeView.stories.tsx b/src/TreeView/TreeView.stories.tsx index cb1e49afb67..33898e128d7 100644 --- a/src/TreeView/TreeView.stories.tsx +++ b/src/TreeView/TreeView.stories.tsx @@ -503,6 +503,46 @@ export const AsyncWithCount: Story = args => { ))} + + + + + src + + + + + + Avatar.tsx + + + + + + Button + + + + + + Button.tsx + + + + + + Button.test.tsx + + + + + + + + ReallyLongFileNameThatShouldBeTruncated.tsx + + + diff --git a/src/TreeView/TreeView.tsx b/src/TreeView/TreeView.tsx index 6cc7f135a2b..86abc3e1b56 100644 --- a/src/TreeView/TreeView.tsx +++ b/src/TreeView/TreeView.tsx @@ -231,7 +231,7 @@ const Item = React.forwardRef( gridTemplateColumns: `calc(${level - 1} * (var(--toggle-width) / 2)) var(--toggle-width) 1fr`, gridTemplateAreas: `"spacer toggle content"`, width: '100%', - height: '2rem', // 32px + minHeight: '2rem', // 32px fontSize: 1, color: 'fg.default', borderRadius: 2, @@ -498,6 +498,10 @@ const SkeletonItem = styled.span` column-gap: 0.5rem; height: 2rem; + @media (pointer: coarse) { + height: 2.75rem; + } + @media (prefers-reduced-motion: no-preference) { mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%); mask-size: 200%; From de6ce08197460fe7408cd41ada453583a45bc01f Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 20 Oct 2022 14:52:27 -0500 Subject: [PATCH 8/9] Update src/TreeView/TreeView.stories.tsx Co-authored-by: Cole Bemis --- src/TreeView/TreeView.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TreeView/TreeView.stories.tsx b/src/TreeView/TreeView.stories.tsx index 33898e128d7..ba9bb1c0a8f 100644 --- a/src/TreeView/TreeView.stories.tsx +++ b/src/TreeView/TreeView.stories.tsx @@ -551,7 +551,7 @@ export const AsyncWithCount: Story = args => { AsyncWithCount.args = { responseTime: 2000, - count: 5 + count: 3 } AsyncWithCount.argTypes = { From 60942bc2ca65316955b6a8080fb42b282999c1d8 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 20 Oct 2022 14:53:16 -0500 Subject: [PATCH 9/9] fix: update coarse pointer styles --- src/TreeView/TreeView.stories.tsx | 2 +- src/TreeView/TreeView.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TreeView/TreeView.stories.tsx b/src/TreeView/TreeView.stories.tsx index 33898e128d7..ba9bb1c0a8f 100644 --- a/src/TreeView/TreeView.stories.tsx +++ b/src/TreeView/TreeView.stories.tsx @@ -551,7 +551,7 @@ export const AsyncWithCount: Story = args => { AsyncWithCount.args = { responseTime: 2000, - count: 5 + count: 3 } AsyncWithCount.argTypes = { diff --git a/src/TreeView/TreeView.tsx b/src/TreeView/TreeView.tsx index 86abc3e1b56..09ba38bc75a 100644 --- a/src/TreeView/TreeView.tsx +++ b/src/TreeView/TreeView.tsx @@ -245,7 +245,7 @@ const Item = React.forwardRef( }, '@media (pointer: coarse)': { '--toggle-width': '1.5rem', // 24px - height: '2.75rem' // 44px + minHeight: '2.75rem' // 44px }, // WARNING: styled-components v5.2 introduced a bug that changed // how it expands `&` in CSS selectors. The following selectors