diff --git a/.changeset/thick-teams-taste.md b/.changeset/thick-teams-taste.md new file mode 100644 index 00000000000..1e528018f9f --- /dev/null +++ b/.changeset/thick-teams-taste.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +Remove the CSS modules feature flag from the TreeView component diff --git a/packages/react/src/TreeView/TreeView.test.tsx b/packages/react/src/TreeView/TreeView.test.tsx index 473692e3f98..cb7399ceb84 100644 --- a/packages/react/src/TreeView/TreeView.test.tsx +++ b/packages/react/src/TreeView/TreeView.test.tsx @@ -4,7 +4,6 @@ import React from 'react' import {ThemeProvider} from '../ThemeProvider' import type {SubTreeState} from './TreeView' import {TreeView} from './TreeView' -import {FeatureFlags} from '../FeatureFlags' jest.useFakeTimers() @@ -1651,20 +1650,8 @@ describe('CSS Module Migration', () => { 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 95e6e11bb20..ce07a66683d 100644 --- a/packages/react/src/TreeView/TreeView.tsx +++ b/packages/react/src/TreeView/TreeView.tsx @@ -6,28 +6,20 @@ import { } from '@primer/octicons-react' 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' import VisuallyHidden from '../_VisuallyHidden' -import {get} from '../constants' import {useControllableState} from '../hooks/useControllableState' import {useId} from '../hooks/useId' import useSafeTimeout from '../hooks/useSafeTimeout' import {useSlots} from '../hooks/useSlots' -import type {SxProp} from '../sx' -import sx from '../sx' import {getAccessibleName} from './shared' 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' - -const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_ga' // ---------------------------------------------------------------------------- // Context @@ -79,225 +71,6 @@ export type TreeViewProps = { /* Size of toggle icon in pixels. */ const TOGGLE_ICON_SIZE = 12 -const UlBox = toggleStyledComponent( - CSS_MODULES_FEATURE_FLAG, - 'ul', - 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; - } - } - - .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; - } - } - - @media (pointer: coarse) { - --toggle-width: 1.5rem; /* 24px */ - --min-item-height: 2.75rem; /* 44px */ - } - - &:has(.PRIVATE_TreeView-item-skeleton):hover { - background-color: transparent; - cursor: default; - - @media (forced-colors: active) { - outline: none; - } - } - } - - &[data-omit-spacer='true'] .PRIVATE_TreeView-item-container { - grid-template-columns: 0 0 0 1fr; - } - - .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 - across more lines. */ - 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--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-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='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 - across more lines. */ - height: var(--custom-line-height, 1.3rem); - } - - .PRIVATE_TreeView-item-leading-action { - display: flex; - color: ${get('colors.fg.muted')}; - grid-area: leadingAction; - - & > button { - flex-shrink: 1; - } - } - - .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 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')}; - } - } - - .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; - } - - ${sx} - `, -) - const Root: React.FC = ({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, @@ -344,8 +117,6 @@ const Root: React.FC = ({ expandedStateCache.current = new Map() } - const cssModulesEnabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) - return ( = ({ {ariaLiveMessage} - = ({ data-omit-spacer={flat} data-truncate-text={truncate || false} onMouseDown={onMouseDown} - className={clsx(className, {[classes.TreeViewRootUlStyles]: cssModulesEnabled})} + className={clsx(className, classes.TreeViewRootUlStyles)} style={style} > {children} - + ) @@ -490,8 +261,6 @@ const Item = React.forwardRef( slots.trailingVisual ? trailingVisualId : null, ].filter(Boolean) - const cssModulesEnabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) - return ( ( > {/* @ts-ignore Box doesn't have type support for `ref` used in combination with `as` */}
  • } tabIndex={0} id={itemId} @@ -548,9 +317,7 @@ 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, - }, + classes.TreeViewItemToggle, + classes.TreeViewItemToggleHover, + classes.TreeViewItemToggleEnd, )} onClick={event => { if (onSelect) { @@ -592,18 +357,9 @@ const Item = React.forwardRef( )}
    ) : null} -
    +
    {slots.leadingVisual} - + {childrenWithoutSubTree} {slots.trailingVisual} @@ -618,16 +374,10 @@ 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(CSS_MODULES_FEATURE_FLAG) return (
    {Array.from({length: level - 1}).map((_, index) => ( -
    +
    ))}
    ) @@ -777,66 +527,18 @@ function usePreviousValue(value: T): T { return ref.current } -const StyledSkeletonItemContainer = toggleStyledComponent( - CSS_MODULES_FEATURE_FLAG, - '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; - } - - &: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%; - } - `, -) - -const StyledSkeletonText = toggleStyledComponent( - CSS_MODULES_FEATURE_FLAG, - SkeletonText, - styled(SkeletonText)` - width: var(--tree-item-loading-width, 67%); - `, -) - const SkeletonItem = () => { - const cssModulesEnabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) return ( - - - + + ) } @@ -846,7 +548,6 @@ type LoadingItemProps = { const LoadingItem = React.forwardRef(({count}, ref) => { const itemId = useId() - const cssModulesEnabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) if (count) { return ( @@ -854,9 +555,7 @@ const LoadingItem = React.forwardRef(({count}, re {Array.from({length: count}).map((_, i) => { return })} -
    - Loading {count} items -
    +
    Loading {count} items
    ) } @@ -908,22 +607,18 @@ export type TreeViewVisualProps = { } const LeadingVisual: React.FC = props => { - const cssModulesEnabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) const {isExpanded, leadingVisualId} = React.useContext(ItemContext) const children = typeof props.children === 'function' ? props.children({isExpanded}) : props.children return ( <>
    {props.label}
    -
    +
    {children}
    @@ -933,22 +628,18 @@ const LeadingVisual: React.FC = props => { LeadingVisual.displayName = 'TreeView.LeadingVisual' const TrailingVisual: React.FC = props => { - const cssModulesEnabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) const {isExpanded, trailingVisualId} = React.useContext(ItemContext) const children = typeof props.children === 'function' ? props.children({isExpanded}) : props.children return ( <>
    {props.label}
    -
    +
    {children}
    @@ -961,21 +652,15 @@ TrailingVisual.displayName = 'TreeView.TrailingVisual' // TreeView.LeadingAction const LeadingAction: React.FC = props => { - const cssModulesEnabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) const {isExpanded} = React.useContext(ItemContext) const children = typeof props.children === 'function' ? props.children({isExpanded}) : props.children return ( <> -
    +
    {props.label}
    {children} @@ -989,11 +674,10 @@ LeadingAction.displayName = 'TreeView.LeadingAction' // TreeView.DirectoryIcon const DirectoryIcon = () => { - const cssModulesEnabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) const {isExpanded} = React.useContext(ItemContext) const Icon = isExpanded ? FileDirectoryOpenFillIcon : FileDirectoryFillIcon return ( -
    +
    )