diff --git a/.changeset/gentle-yaks-shake.md b/.changeset/gentle-yaks-shake.md new file mode 100644 index 00000000000..c84b39ddca5 --- /dev/null +++ b/.changeset/gentle-yaks-shake.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Update `TreeView` component to use CSS Modules diff --git a/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Default-dark-high-contrast-linux.png b/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Default-dark-high-contrast-linux.png index 998d0128eb1..41a3d33be6a 100644 Binary files a/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Default-dark-high-contrast-linux.png and b/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Default-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Empty-Directories-dark-high-contrast-linux.png b/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Empty-Directories-dark-high-contrast-linux.png index a7ddc08bf61..bd1437b8b47 100644 Binary files a/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Empty-Directories-dark-high-contrast-linux.png and b/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Empty-Directories-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Files-Changed-dark-high-contrast-linux.png b/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Files-Changed-dark-high-contrast-linux.png index de83cf8f11b..11fb1f3f63c 100644 Binary files a/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Files-Changed-dark-high-contrast-linux.png and b/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Files-Changed-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Files-dark-high-contrast-linux.png b/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Files-dark-high-contrast-linux.png index 78d0c95b3c6..24a37f28b24 100644 Binary files a/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Files-dark-high-contrast-linux.png and b/.playwright/snapshots/components/TreeView.test.ts-snapshots/TreeView-Files-dark-high-contrast-linux.png differ diff --git a/contributor-docs/migrating-to-css-modules.md b/contributor-docs/migrating-to-css-modules.md index 50ab7c0781f..c72df1d7800 100644 --- a/contributor-docs/migrating-to-css-modules.md +++ b/contributor-docs/migrating-to-css-modules.md @@ -53,7 +53,7 @@ This guide outlines the steps to follow when refactoring Primer React components - Add a feature flag to toggle the `sx` prop for controlled rollout (staff shipping). How it's used will be based on the implementation of the component. For most you'll be able to `useFeatureFlag` and toggle between components. For more complex styled components, you can use the utility `toggleStyledComponent` which will render based on the feature flag string provided. ```jsx - /* When there is an exisiting styled component, use the `toggleStyledComponent` utility. */ + /* When there is an existing styled component, use the `toggleStyledComponent` utility. */ const StyledDiv = toggleStyledComponent( 'primer_react_css_modules_team', 'div', diff --git a/packages/react/src/TreeView/TreeView.module.css b/packages/react/src/TreeView/TreeView.module.css new file mode 100644 index 00000000000..70be89a0310 --- /dev/null +++ b/packages/react/src/TreeView/TreeView.module.css @@ -0,0 +1,260 @@ +.TreeViewRootUlStyles { + padding: 0; + margin: 0; + list-style: none; + + /* + * WARNING: This is a performance optimization. + * + * We define styles for the tree items at the root level of the tree + * to avoid recomputing the styles for each item when the tree updates. + * We're sacrificing maintainability for performance because TreeView + * needs to be performant enough to handle large trees (thousands of items). + * + * This is intended to be a temporary solution until we can improve the + * performance of our styling patterns. + * + * Do NOT copy this pattern without understanding the tradeoffs. + */ + .TreeViewItem { + outline: none; + + &:focus-visible > div, + &.focus-visible > div { + box-shadow: var(--boxShadow-thick) var(--fgColor-accent); + + @media (forced-colors: active) { + outline: 2px solid HighlightText; + outline-offset: -2; + } + } + + &[data-has-leading-action] { + --has-leading-action: 1; + } + } + + .TreeViewItemContainer { + --level: 1; + --toggle-width: 1rem; + --min-item-height: 2rem; + + position: relative; + display: grid; + width: 100%; + font-size: var(--text-body-size-medium); + color: var(--fgColor-default); + cursor: pointer; + border-radius: var(--borderRadius-medium); + grid-template-columns: var(--spacer-width) var(--leading-action-width) var(--toggle-width) 1fr; + grid-template-areas: 'spacer leadingAction toggle content'; + + --leading-action-width: calc(var(--has-leading-action, 0) * 1.5rem); + --spacer-width: calc(calc(var(--level) - 1) * (var(--toggle-width) / 2)); + + &:hover { + background-color: var(--control-transparent-bgColor-hover); + + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: -2px; + } + } + + @media (pointer: coarse) { + --toggle-width: 1.5rem; + --min-item-height: 2.75rem; + } + + &:has(.TreeViewItemSkeleton):hover { + cursor: default; + background-color: transparent; + + @media (forced-colors: active) { + outline: none; + } + } + } + + &:where([data-omit-spacer='true']) .TreeViewItemContainer { + grid-template-columns: 0 0 0 1fr; + } + + .TreeViewItem[aria-current='true'] > .TreeViewItemContainer { + background-color: var(--control-transparent-bgColor-selected); + + /* Current item indicator */ + /* stylelint-disable-next-line selector-max-specificity */ + &::after { + position: absolute; + top: calc(50% - var(--base-size-12)); + left: calc(-1 * var(--base-size-8)); + width: 0.25rem; + height: 1.5rem; + content: ''; + + /* + * Use fgColor accent for consistency across all themes. Using the "correct" variable, + * --bgColor-accent-emphasis, causes vrt failures for dark high contrast mode + */ + /* stylelint-disable-next-line primer/colors */ + background-color: var(--fgColor-accent); + border-radius: var(--borderRadius-medium); + + @media (forced-colors: active) { + background-color: HighlightText; + } + } + } + + .TreeViewItemToggle { + display: flex; + height: 100%; + + /* The toggle should appear vertically centered for single-line items, but remain at the top for items that wrap + across more lines. */ + /* stylelint-disable-next-line primer/spacing */ + padding-top: calc(var(--min-item-height) / 2 - var(--base-size-12) / 2); + color: var(--fgColor-muted); + grid-area: toggle; + justify-content: center; + align-items: flex-start; + } + + .TreeViewItemToggleHover:hover { + background-color: var(--control-transparent-bgColor-hover); + } + + .TreeViewItemToggleEnd { + border-top-left-radius: var(--borderRadius-medium); + border-bottom-left-radius: var(--borderRadius-medium); + } + + .TreeViewItemContent { + display: flex; + height: 100%; + padding: 0 var(--base-size-8); + + /* The dynamic top and bottom padding to maintain the minimum item height for single line items */ + /* stylelint-disable-next-line primer/spacing */ + padding-top: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2); + /* stylelint-disable-next-line primer/spacing */ + padding-bottom: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2); + line-height: var(--custom-line-height, var(--text-body-lineHeight-medium, 1.4285)); + grid-area: content; + gap: var(--stack-gap-condensed); + } + + .TreeViewItemContentText { + flex: 1 1 auto; + width: 0; + } + + &:where([data-truncate-text='true']) .TreeViewItemContentText { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &:where([data-truncate-text='false']) .TreeViewItemContentText { + word-break: break-word; + } + + .TreeViewItemVisual { + display: flex; + + /* The visual icons should appear vertically centered for single-line items, but remain at the top for items that wrap + across more lines. */ + height: var(--custom-line-height, 1.3rem); + color: var(--fgColor-muted); + align-items: center; + } + + .TreeViewItemLeadingAction { + display: flex; + color: var(--fgColor-muted); + grid-area: leadingAction; + } + + .TreeViewItemLevelLine { + width: 100%; + height: 100%; + + /* + * On devices without hover, the nesting indicator lines + * appear at all times. + */ + border-color: var(--borderColor-muted); + border-right: var(--borderWidth-thin) solid; + } + + /* + * On devices with :hover support, the nesting indicator lines + * fade in when the user mouses over the entire component, + * or when there's focus inside the component. This makes + * sure the component remains simple when not in use. + */ + @media (hover: hover) { + .TreeViewItemLevelLine { + border-color: transparent; + } + + &:hover .TreeViewItemLevelLine, + &:focus-within .TreeViewItemLevelLine { + border-color: var(--borderColor-muted); + } + } + + .TreeViewDirectoryIcon { + display: grid; + color: var(--treeViewItem-leadingVisual-iconColor-rest); + } + + .TreeViewVisuallyHidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + /* stylelint-disable-next-line primer/spacing */ + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } +} + +.TreeViewSkeletonItemContainerStyle { + display: flex; + align-items: center; + column-gap: 0.5rem; + height: 2rem; + + @media (pointer: coarse) { + height: 2.75rem; + } + + &: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%; + } +} + +.TreeItemSkeletonTextStyles { + width: var(--tree-item-loading-width, 67%); +} diff --git a/packages/react/src/TreeView/TreeView.test.tsx b/packages/react/src/TreeView/TreeView.test.tsx index eb2329f9b22..34dc96e5df9 100644 --- a/packages/react/src/TreeView/TreeView.test.tsx +++ b/packages/react/src/TreeView/TreeView.test.tsx @@ -4,6 +4,7 @@ import React from 'react' import {ThemeProvider} from '../ThemeProvider' import type {SubTreeState} from './TreeView' import {TreeView} from './TreeView' +import {FeatureFlags} from '../FeatureFlags' jest.useFakeTimers() @@ -1364,7 +1365,7 @@ describe('State', () => { }) }) -describe('Asyncronous loading', () => { +describe('Asynchronous loading', () => { it('updates aria live region when loading is done', () => { function TestTree() { const [state, setState] = React.useState('initial') @@ -1640,3 +1641,32 @@ describe('Asyncronous loading', () => { expect(getByRole('treeitem', {name: 'empty child'})).toHaveAttribute('aria-expanded') }) }) + +describe('CSS Module Migration', () => { + it('should support `className` on the outermost element', () => { + const TreeViewTestComponent = () => ( + + Item 1 + Item 2 + Item 3 + + ) + const FeatureFlagElement = () => { + return ( + + + + ) + } + + // Testing on the second child element because the first child element is visually hidden + expect(render().container.children[1]).toHaveClass('test-class-name') + expect(render().container.children[1]).toHaveClass('test-class-name') + }) +}) diff --git a/packages/react/src/TreeView/TreeView.tsx b/packages/react/src/TreeView/TreeView.tsx index fdf262d36ce..7cec5d002c3 100644 --- a/packages/react/src/TreeView/TreeView.tsx +++ b/packages/react/src/TreeView/TreeView.tsx @@ -7,6 +7,7 @@ import { import {clsx} from 'clsx' import React, {useCallback, useEffect} from 'react' import styled from 'styled-components' +import classes from './TreeView.module.css' import {ConfirmationDialog} from '../ConfirmationDialog/ConfirmationDialog' import Spinner from '../Spinner' import Text from '../Text' @@ -23,6 +24,8 @@ import {getFirstChildElement, useRovingTabIndex} from './useRovingTabIndex' import {useTypeahead} from './useTypeahead' import {SkeletonAvatar} from '../experimental/Skeleton/SkeletonAvatar' import {SkeletonText} from '../experimental/Skeleton/SkeletonText' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' +import {useFeatureFlag} from '../FeatureFlags' // ---------------------------------------------------------------------------- // Context @@ -68,221 +71,226 @@ export type TreeViewProps = { flat?: boolean truncate?: boolean className?: string + style?: React.CSSProperties } /* Size of toggle icon in pixels. */ const TOGGLE_ICON_SIZE = 12 -const UlBox = styled.ul` - list-style: none; - padding: 0; - margin: 0; - - /* - * WARNING: This is a performance optimization. - * - * We define styles for the tree items at the root level of the tree - * to avoid recomputing the styles for each item when the tree updates. - * We're sacraficing maintainability for performance because TreeView - * needs to be performant enough to handle large trees (thousands of items). - * - * This is intended to be a temporary solution until we can improve the - * performance of our styling patterns. - * - * Do NOT copy this pattern without understanding the tradeoffs. - * Do NOT reference PRIVATE_* classnames outside of this file. - */ - .PRIVATE_TreeView-item { - outline: none; - - &:focus-visible > div, - &.focus-visible > div { - box-shadow: inset 0 0 0 2px ${get(`colors.accent.fg`)}; - @media (forced-colors: active) { - outline: 2px solid HighlightText; - outline-offset: -2; - } - } - &[data-has-leading-action] { - --has-leading-action: 1; - } - } +const UlBox = toggleStyledComponent( + 'primer_react_css_modules_team', + 'ul', + styled.ul` + list-style: none; + padding: 0; + margin: 0; - .PRIVATE_TreeView-item-container { - --level: 1; /* default level */ - --toggle-width: 1rem; /* 16px */ - --min-item-height: 2rem; /* 32px */ - position: relative; - display: grid; - --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%; - font-size: ${get('fontSizes.1')}; - color: ${get('colors.fg.default')}; - border-radius: ${get('radii.2')}; - cursor: pointer; - - &:hover { - background-color: ${get('colors.actionListItem.default.hoverBg')}; - - @media (forced-colors: active) { - outline: 2px solid transparent; - outline-offset: -2px; + /* + * WARNING: This is a performance optimization. + * + * We define styles for the tree items at the root level of the tree + * to avoid recomputing the styles for each item when the tree updates. + * We're sacraficing maintainability for performance because TreeView + * needs to be performant enough to handle large trees (thousands of items). + * + * This is intended to be a temporary solution until we can improve the + * performance of our styling patterns. + * + * Do NOT copy this pattern without understanding the tradeoffs. + * Do NOT reference PRIVATE_* classnames outside of this file. + */ + .PRIVATE_TreeView-item { + outline: none; + + &:focus-visible > div, + &.focus-visible > div { + box-shadow: inset 0 0 0 2px ${get(`colors.accent.fg`)}; + @media (forced-colors: active) { + outline: 2px solid HighlightText; + outline-offset: -2; + } + } + &[data-has-leading-action] { + --has-leading-action: 1; } } - @media (pointer: coarse) { - --toggle-width: 1.5rem; /* 24px */ - --min-item-height: 2.75rem; /* 44px */ - } + .PRIVATE_TreeView-item-container { + --level: 1; /* default level */ + --toggle-width: 1rem; /* 16px */ + --min-item-height: 2rem; /* 32px */ + position: relative; + display: grid; + --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%; + font-size: ${get('fontSizes.1')}; + color: ${get('colors.fg.default')}; + border-radius: ${get('radii.2')}; + cursor: pointer; - &:has(.PRIVATE_TreeView-item-skeleton):hover { - background-color: transparent; - cursor: default; + &:hover { + background-color: ${get('colors.actionListItem.default.hoverBg')}; - @media (forced-colors: active) { - outline: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: -2px; + } } - } - } - &[data-omit-spacer='true'] .PRIVATE_TreeView-item-container { - grid-template-columns: 0 0 0 1fr; - } + @media (pointer: coarse) { + --toggle-width: 1.5rem; /* 24px */ + --min-item-height: 2.75rem; /* 44px */ + } - .PRIVATE_TreeView-item[aria-current='true'] > .PRIVATE_TreeView-item-container { - background-color: ${get('colors.actionListItem.default.selectedBg')}; + &:has(.PRIVATE_TreeView-item-skeleton):hover { + background-color: transparent; + cursor: default; - /* Current item indicator */ - &::after { - content: ''; - position: absolute; - top: calc(50% - 0.75rem); /* 50% - 12px */ - left: -${get('space.2')}; - width: 0.25rem; /* 4px */ - height: 1.5rem; /* 24px */ - background-color: ${get('colors.accent.fg')}; - border-radius: ${get('radii.2')}; + @media (forced-colors: active) { + outline: none; + } + } + } + + &[data-omit-spacer='true'] .PRIVATE_TreeView-item-container { + grid-template-columns: 0 0 0 1fr; + } - @media (forced-colors: active) { - background-color: HighlightText; + .PRIVATE_TreeView-item[aria-current='true'] > .PRIVATE_TreeView-item-container { + background-color: ${get('colors.actionListItem.default.selectedBg')}; + + /* Current item indicator */ + &::after { + content: ''; + position: absolute; + top: calc(50% - 0.75rem); /* 50% - 12px */ + left: -${get('space.2')}; + width: 0.25rem; /* 4px */ + height: 1.5rem; /* 24px */ + background-color: ${get('colors.accent.fg')}; + border-radius: ${get('radii.2')}; + + @media (forced-colors: active) { + background-color: HighlightText; + } } } - } - .PRIVATE_TreeView-item-toggle { - grid-area: toggle; - display: flex; - justify-content: center; - align-items: flex-start; - /* The toggle should appear vertically centered for single-line items, but remain at the top for items that wrap + .PRIVATE_TreeView-item-toggle { + grid-area: toggle; + display: flex; + justify-content: center; + align-items: flex-start; + /* The toggle should appear vertically centered for single-line items, but remain at the top for items that wrap across more lines. */ - padding-top: calc(var(--min-item-height) / 2 - ${TOGGLE_ICON_SIZE}px / 2); - height: 100%; - color: ${get('colors.fg.muted')}; - } + padding-top: calc(var(--min-item-height) / 2 - ${TOGGLE_ICON_SIZE}px / 2); + height: 100%; + color: ${get('colors.fg.muted')}; + } - .PRIVATE_TreeView-item-toggle--hover:hover { - background-color: ${get('colors.treeViewItem.chevron.hoverBg')}; - } + .PRIVATE_TreeView-item-toggle--hover:hover { + background-color: ${get('colors.treeViewItem.chevron.hoverBg')}; + } - .PRIVATE_TreeView-item-toggle--end { - border-top-left-radius: ${get('radii.2')}; - border-bottom-left-radius: ${get('radii.2')}; - } + .PRIVATE_TreeView-item-toggle--end { + border-top-left-radius: ${get('radii.2')}; + border-bottom-left-radius: ${get('radii.2')}; + } - .PRIVATE_TreeView-item-content { - grid-area: content; - display: flex; - height: 100%; - padding: 0 ${get('space.2')}; - gap: ${get('space.2')}; - line-height: var(--custom-line-height, var(--text-body-lineHeight-medium, 1.4285)); - /* The dynamic top and bottom padding to maintain the minimum item height for single line items */ - padding-top: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2); - padding-bottom: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2); - } + .PRIVATE_TreeView-item-content { + grid-area: content; + display: flex; + height: 100%; + padding: 0 ${get('space.2')}; + gap: ${get('space.2')}; + line-height: var(--custom-line-height, var(--text-body-lineHeight-medium, 1.4285)); + /* The dynamic top and bottom padding to maintain the minimum item height for single line items */ + padding-top: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2); + padding-bottom: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2); + } - .PRIVATE_TreeView-item-content-text { - flex: 1 1 auto; - width: 0; - } + .PRIVATE_TreeView-item-content-text { + flex: 1 1 auto; + width: 0; + } - &[data-truncate-text='true'] .PRIVATE_TreeView-item-content-text { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } + &[data-truncate-text='true'] .PRIVATE_TreeView-item-content-text { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } - &[data-truncate-text='false'] .PRIVATE_TreeView-item-content-text { - word-break: break-word; - } + &[data-truncate-text='false'] .PRIVATE_TreeView-item-content-text { + word-break: break-word; + } - .PRIVATE_TreeView-item-visual { - display: flex; - align-items: center; - color: ${get('colors.fg.muted')}; - /* The visual icons should appear vertically centered for single-line items, but remain at the top for items that wrap + .PRIVATE_TreeView-item-visual { + display: flex; + align-items: center; + color: ${get('colors.fg.muted')}; + /* The visual icons should appear vertically centered for single-line items, but remain at the top for items that wrap across more lines. */ - height: var(--custom-line-height, 1.3rem); - } + height: var(--custom-line-height, 1.3rem); + } - .PRIVATE_TreeView-item-leading-action { - display: flex; - color: ${get('colors.fg.muted')}; - grid-area: leadingAction; - } + .PRIVATE_TreeView-item-leading-action { + display: flex; + color: ${get('colors.fg.muted')}; + grid-area: leadingAction; + } - .PRIVATE_TreeView-item-level-line { - width: 100%; - height: 100%; - border-right: 1px solid; + .PRIVATE_TreeView-item-level-line { + width: 100%; + height: 100%; + border-right: 1px solid; + + /* + * On devices without hover, the nesting indicator lines + * appear at all times. + */ + border-color: ${get('colors.border.subtle')}; + } /* - * On devices without hover, the nesting indicator lines - * appear at all times. + * On devices with :hover support, the nesting indicator lines + * fade in when the user mouses over the entire component, + * or when there's focus inside the component. This makes + * sure the component remains simple when not in use. */ - border-color: ${get('colors.border.subtle')}; - } + @media (hover: hover) { + .PRIVATE_TreeView-item-level-line { + border-color: transparent; + } - /* - * On devices with :hover support, the nesting indicator lines - * fade in when the user mouses over the entire component, - * or when there's focus inside the component. This makes - * sure the component remains simple when not in use. - */ - @media (hover: hover) { - .PRIVATE_TreeView-item-level-line { - border-color: transparent; + &:hover .PRIVATE_TreeView-item-level-line, + &:focus-within .PRIVATE_TreeView-item-level-line { + border-color: ${get('colors.border.subtle')}; + } } - &:hover .PRIVATE_TreeView-item-level-line, - &:focus-within .PRIVATE_TreeView-item-level-line { - border-color: ${get('colors.border.subtle')}; + .PRIVATE_TreeView-directory-icon { + display: grid; + color: ${get('colors.treeViewItem.directory.fill')}; } - } - .PRIVATE_TreeView-directory-icon { - display: grid; - color: ${get('colors.treeViewItem.directory.fill')}; - } - - .PRIVATE_VisuallyHidden { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border-width: 0; - } + .PRIVATE_VisuallyHidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } - ${sx} -` + ${sx} + `, +) const Root: React.FC = ({ 'aria-label': ariaLabel, @@ -291,6 +299,7 @@ const Root: React.FC = ({ flat, truncate = true, className, + style, }) => { const containerRef = React.useRef(null) const mouseDownRef = React.useRef(false) @@ -329,6 +338,8 @@ const Root: React.FC = ({ expandedStateCache.current = new Map() } + const cssModulesEnabled = useFeatureFlag('primer_react_css_modules_team') + return ( = ({ data-omit-spacer={flat} data-truncate-text={truncate || false} onMouseDown={onMouseDown} - className={className} + className={clsx(className, {[classes.TreeViewRootUlStyles]: cssModulesEnabled})} + style={style} > {children} @@ -472,6 +484,8 @@ const Item = React.forwardRef( slots.trailingVisual ? trailingVisualId : null, ].filter(Boolean) + const cssModulesEnabled = useFeatureFlag('primer_react_css_modules_team') + return ( ( > {/* @ts-ignore Box doesn't have type support for `ref` used in combination with `as` */}
  • } tabIndex={0} id={itemId} @@ -528,7 +542,9 @@ const Item = React.forwardRef( }} >
    ( 'PRIVATE_TreeView-item-toggle', onSelect && 'PRIVATE_TreeView-item-toggle--hover', level === 1 && 'PRIVATE_TreeView-item-toggle--end', + { + [classes.TreeViewItemToggle]: cssModulesEnabled, + [classes.TreeViewItemToggleHover]: cssModulesEnabled, + [classes.TreeViewItemToggleEnd]: cssModulesEnabled, + }, )} onClick={event => { if (onSelect) { @@ -565,9 +586,20 @@ const Item = React.forwardRef( )}
    ) : null} -
    +
    {slots.leadingVisual} - {childrenWithoutSubTree} + + {childrenWithoutSubTree} + {slots.trailingVisual}
    @@ -580,10 +612,16 @@ const Item = React.forwardRef( /** Lines to indicate the depth of an item in a TreeView */ const LevelIndicatorLines: React.FC<{level: number}> = ({level}) => { + const cssModulesEnabled = useFeatureFlag('primer_react_css_modules_team') return (
    {Array.from({length: level - 1}).map((_, index) => ( -
    +
    ))}
    ) @@ -728,46 +766,65 @@ function usePreviousValue(value: T): T { return ref.current } -const StyledSkeletonItemContainer = styled.span.attrs({className: 'PRIVATE_TreeView-item-skeleton'})` - display: flex; - align-items: center; - column-gap: 0.5rem; - height: 2rem; +const StyledSkeletonItemContainer = toggleStyledComponent( + 'primer_react_css_modules_team', + 'span', + styled.span.attrs({ + className: 'PRIVATE_TreeView-item-skeleton', + })` + display: flex; + align-items: center; + column-gap: 0.5rem; + height: 2rem; - @media (pointer: coarse) { - height: 2.75rem; - } + @media (pointer: coarse) { + height: 2.75rem; + } - &:nth-of-type(5n + 1) { - --tree-item-loading-width: 67%; - } + &:nth-of-type(5n + 1) { + --tree-item-loading-width: 67%; + } - &:nth-of-type(5n + 2) { - --tree-item-loading-width: 47%; - } + &:nth-of-type(5n + 2) { + --tree-item-loading-width: 47%; + } - &:nth-of-type(5n + 3) { - --tree-item-loading-width: 73%; - } + &:nth-of-type(5n + 3) { + --tree-item-loading-width: 73%; + } - &:nth-of-type(5n + 4) { - --tree-item-loading-width: 64%; - } + &:nth-of-type(5n + 4) { + --tree-item-loading-width: 64%; + } - &:nth-of-type(5n + 5) { - --tree-item-loading-width: 50%; - } -` + &:nth-of-type(5n + 5) { + --tree-item-loading-width: 50%; + } + `, +) -const StyledSkeletonText = styled(SkeletonText)` - width: var(--tree-item-loading-width, 67%); -` +const StyledSkeletonText = toggleStyledComponent( + 'primer_react_css_modules_team', + SkeletonText, + styled(SkeletonText)` + width: var(--tree-item-loading-width, 67%); + `, +) const SkeletonItem = () => { + const cssModulesEnabled = useFeatureFlag('primer_react_css_modules_team') return ( - + - + ) } @@ -778,6 +835,7 @@ type LoadingItemProps = { const LoadingItem = React.forwardRef(({count}, ref) => { const itemId = useId() + const cssModulesEnabled = useFeatureFlag('primer_react_css_modules_team') if (count) { return ( @@ -785,7 +843,9 @@ const LoadingItem = React.forwardRef(({count}, re {Array.from({length: count}).map((_, i) => { return })} -
    Loading {count} items
    +
    + Loading {count} items +
    ) } @@ -795,7 +855,7 @@ const LoadingItem = React.forwardRef(({count}, re - Loading... + Loading... ) }) @@ -803,7 +863,7 @@ const LoadingItem = React.forwardRef(({count}, re const EmptyItem = React.forwardRef((props, ref) => { return ( - No items found + No items found ) }) @@ -837,14 +897,22 @@ export type TreeViewVisualProps = { } const LeadingVisual: React.FC = props => { + const cssModulesEnabled = useFeatureFlag('primer_react_css_modules_team') const {isExpanded, leadingVisualId} = React.useContext(ItemContext) const children = typeof props.children === 'function' ? props.children({isExpanded}) : props.children return ( <> -
    +
    {props.label}
    -
    +
    {children}
    @@ -854,14 +922,22 @@ const LeadingVisual: React.FC = props => { LeadingVisual.displayName = 'TreeView.LeadingVisual' const TrailingVisual: React.FC = props => { + const cssModulesEnabled = useFeatureFlag('primer_react_css_modules_team') const {isExpanded, trailingVisualId} = React.useContext(ItemContext) const children = typeof props.children === 'function' ? props.children({isExpanded}) : props.children return ( <> -
    +
    {props.label}
    -
    +
    {children}
    @@ -874,14 +950,23 @@ TrailingVisual.displayName = 'TreeView.TrailingVisual' // TreeView.LeadingAction const LeadingAction: React.FC = props => { + const cssModulesEnabled = useFeatureFlag('primer_react_css_modules_team') const {isExpanded} = React.useContext(ItemContext) const children = typeof props.children === 'function' ? props.children({isExpanded}) : props.children return ( <> -
    +
    {props.label}
    -
    +
    {children}
    @@ -893,10 +978,11 @@ LeadingAction.displayName = 'TreeView.LeadingAction' // TreeView.DirectoryIcon const DirectoryIcon = () => { + const cssModulesEnabled = useFeatureFlag('primer_react_css_modules_team') const {isExpanded} = React.useContext(ItemContext) const Icon = isExpanded ? FileDirectoryOpenFillIcon : FileDirectoryFillIcon return ( -
    +
    ) diff --git a/packages/react/src/legacy-theme/ts/color-schemes.ts b/packages/react/src/legacy-theme/ts/color-schemes.ts index 61b885caa3d..3327c9e8444 100644 --- a/packages/react/src/legacy-theme/ts/color-schemes.ts +++ b/packages/react/src/legacy-theme/ts/color-schemes.ts @@ -385,7 +385,7 @@ const colors = { "hoverBg": "var(--control-transparent-bgColor-hover, var(--color-tree-view-item-chevron-hover-bg, rgba(208,215,222,0.32)))" }, "directory": { - "fill": "var(--treeViewItem-leadingVisual-bgColor-rest, var(--color-tree-view-item-chevron-directory-fill, #54aeff))" + "fill": "var(--treeViewItem-leadingVisual-iconColor-rest, var(--color-tree-view-item-chevron-directory-fill, #54aeff))" } }, "fg": { @@ -909,7 +909,7 @@ const colors = { "hoverBg": "var(--control-transparent-bgColor-hover, var(--color-tree-view-item-chevron-hover-bg, #ced5dc))" }, "directory": { - "fill": "var(--treeViewItem-leadingVisual-bgColor-rest, var(--color-tree-view-item-chevron-directory-fill, #368cf9))" + "fill": "var(--treeViewItem-leadingVisual-iconColor-rest, var(--color-tree-view-item-chevron-directory-fill, #368cf9))" } }, "fg": { @@ -1433,7 +1433,7 @@ const colors = { "hoverBg": "var(--control-transparent-bgColor-hover, var(--color-tree-view-item-chevron-hover-bg, rgba(208,215,222,0.32)))" }, "directory": { - "fill": "var(--treeViewItem-leadingVisual-bgColor-rest, var(--color-tree-view-item-chevron-directory-fill, #54aeff))" + "fill": "var(--treeViewItem-leadingVisual-iconColor-rest, var(--color-tree-view-item-chevron-directory-fill, #54aeff))" } }, "fg": { @@ -1957,7 +1957,7 @@ const colors = { "hoverBg": "var(--control-transparent-bgColor-hover, var(--color-tree-view-item-chevron-hover-bg, rgba(208,215,222,0.32)))" }, "directory": { - "fill": "var(--treeViewItem-leadingVisual-bgColor-rest, var(--color-tree-view-item-chevron-directory-fill, #54aeff))" + "fill": "var(--treeViewItem-leadingVisual-iconColor-rest, var(--color-tree-view-item-chevron-directory-fill, #54aeff))" } }, "fg": { @@ -2480,7 +2480,7 @@ const colors = { "hoverBg": "var(--control-transparent-bgColor-hover, var(--color-tree-view-item-chevron-hover-bg, rgba(177,186,196,0.12)))" }, "directory": { - "fill": "var(--treeViewItem-leadingVisual-bgColor-rest, var(--color-tree-view-item-directory-fill, #848d97))" + "fill": "var(--treeViewItem-leadingVisual-iconColor-rest, var(--color-tree-view-item-directory-fill, #848d97))" } }, "fg": { @@ -3006,7 +3006,7 @@ const colors = { "hoverBg": "var(--control-transparent-bgColor-hover, var(--color-tree-view-item-chevron-hover-bg, rgba(144,157,171,0.12)))" }, "directory": { - "fill": "var(--treeViewItem-leadingVisual-bgColor-rest, var(--color-tree-view-item-directory-fill, #768390))" + "fill": "var(--treeViewItem-leadingVisual-iconColor-rest, var(--color-tree-view-item-directory-fill, #768390))" } }, "fg": { @@ -3532,7 +3532,7 @@ const colors = { "hoverBg": "var(--control-transparent-bgColor-hover, var(--color-tree-view-item-chevron-hover-bg, #525964))" }, "directory": { - "fill": "var(--treeViewItem-leadingVisual-bgColor-rest, var(--color-tree-view-item-directory-fill, #f0f3f6))" + "fill": "var(--treeViewItem-leadingVisual-iconColor-rest, var(--color-tree-view-item-directory-fill, #f0f3f6))" } }, "fg": { @@ -4058,7 +4058,7 @@ const colors = { "hoverBg": "var(--control-transparent-bgColor-hover, var(--color-tree-view-item-chevron-hover-bg, rgba(177,186,196,0.12)))" }, "directory": { - "fill": "var(--treeViewItem-leadingVisual-bgColor-rest, var(--color-tree-view-item-directory-fill, #8b949e))" + "fill": "var(--treeViewItem-leadingVisual-iconColor-rest, var(--color-tree-view-item-directory-fill, #8b949e))" } }, "fg": { @@ -4584,7 +4584,7 @@ const colors = { "hoverBg": "var(--control-transparent-bgColor-hover, var(--color-tree-view-item-chevron-hover-bg, rgba(177,186,196,0.12)))" }, "directory": { - "fill": "var(--treeViewItem-leadingVisual-bgColor-rest, var(--color-tree-view-item-directory-fill, #8b949e))" + "fill": "var(--treeViewItem-leadingVisual-iconColor-rest, var(--color-tree-view-item-directory-fill, #8b949e))" } }, "fg": {