diff --git a/.changeset/twenty-dogs-sparkle.md b/.changeset/twenty-dogs-sparkle.md new file mode 100644 index 00000000000..78e2df1e8b9 --- /dev/null +++ b/.changeset/twenty-dogs-sparkle.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Convert ActionList (wrapper) and ActionList.Divider to CSS Modules diff --git a/packages/react/src/ActionList/ActionList.dev.stories.tsx b/packages/react/src/ActionList/ActionList.dev.stories.tsx index ab7de7a1157..35c37cf213f 100644 --- a/packages/react/src/ActionList/ActionList.dev.stories.tsx +++ b/packages/react/src/ActionList/ActionList.dev.stories.tsx @@ -117,6 +117,21 @@ export const GroupHeadingCustomClassname = () => ( ) +export const ListCustomClassname = () => ( + + Copy link + Quote reply + +) + +export const DividerCustomClassname = () => ( + + Edit comment + + Quote reply + +) + export const HeadingCustomClassname = () => ( diff --git a/packages/react/src/ActionList/ActionList.module.css b/packages/react/src/ActionList/ActionList.module.css new file mode 100644 index 00000000000..0f3e563ffa0 --- /dev/null +++ b/packages/react/src/ActionList/ActionList.module.css @@ -0,0 +1,79 @@ +/* stylelint-disable selector-max-specificity, selector-max-compound-selectors */ + +.ActionList { + padding: 0; + margin: 0; + list-style: none; + + ul { + padding: 0; + margin: 0; + list-style: none; + } + + &:where([data-variant='inset']) { + /* change to padding (all) when Item is converted */ + padding-block: var(--base-size-8); + } + + &:where([data-dividers='true']) { + /* place dividers on the wrapper that excludes leading visuals/actions */ + & .ActionListSubContent::before { + position: absolute; + /* stylelint-disable-next-line primer/spacing */ + top: calc(-1 * var(--control-medium-paddingBlock)); + display: block; + width: 100%; + height: 1px; + content: ''; + /* stylelint-disable-next-line primer/colors */ + background: var(--borderColor-muted); + } + + /* if inline description, move pseudo divider to description wrapper */ + & [data-description-variant='inline'] { + &::before { + position: absolute; + /* stylelint-disable-next-line primer/spacing */ + top: calc(-1 * var(--control-medium-paddingBlock)); + display: block; + width: 100%; + height: var(--borderWidth-thin); + content: ''; + /* stylelint-disable-next-line primer/colors */ + background: var(--borderColor-muted); + } + + /* remove the default divider */ + & .ActionListSubContent::before { + content: unset; + } + } + + /* hide if item is first of type with label::before, or is the first item after a sectionDivider */ + .ActionListItem:first-of-type .ActionListSubContent::before, + .Divider + .ActionListItem .ActionListSubContent::before { + visibility: hidden; + } + + /* hide if item is first of type with label::before, or is the first item after a sectionDivider */ + .ActionListItem:first-of-type [data-description-variant='inline']::before, + .Divider + .ActionListItem [data-description-variant='inline']::before { + visibility: hidden; + } + } +} + +.Divider { + display: block; + height: var(--borderWidth-thin); + padding: 0; + /* stylelint-disable-next-line primer/spacing */ + margin-block-start: calc(var(--base-size-8) - var(--borderWidth-thin)); + margin-block-end: var(--base-size-8); + margin-inline: calc(-1 * var(--base-size-8)); + list-style: none; + /* stylelint-disable-next-line primer/colors */ + background: var(--borderColor-muted); + border: 0; +} diff --git a/packages/react/src/ActionList/ActionList.test.tsx b/packages/react/src/ActionList/ActionList.test.tsx index bf1dfdffae1..11ce02d53fc 100644 --- a/packages/react/src/ActionList/ActionList.test.tsx +++ b/packages/react/src/ActionList/ActionList.test.tsx @@ -650,4 +650,57 @@ describe('ActionList', () => { expect(description.parentElement).toHaveAttribute('data-component', 'ActionList.Item--DividerContainer') }) }) + + it('should support a custom `className` on the outermost element', () => { + const Element = () => { + return ( + + Item + + ) + } + const FeatureFlagElement = () => { + return ( + + + + ) + } + expect(HTMLRender().container.querySelector('ul')).toHaveClass('test-class-name') + expect(HTMLRender().container.querySelector('ul')).toHaveClass('test-class-name') + }) + + it('divider should support a custom `className`', () => { + const Element = () => { + return ( + + Item + + + ) + } + const FeatureFlagElement = () => { + return ( + + + + ) + } + expect(HTMLRender().container.querySelector('li[aria-hidden="true"]')).toHaveClass( + 'test-class-name', + ) + expect(HTMLRender().container.querySelector('li[aria-hidden="true"]')).toHaveClass('test-class-name') + }) }) diff --git a/packages/react/src/ActionList/Divider.tsx b/packages/react/src/ActionList/Divider.tsx index dedf06f7689..04e0de3c690 100644 --- a/packages/react/src/ActionList/Divider.tsx +++ b/packages/react/src/ActionList/Divider.tsx @@ -4,13 +4,34 @@ import {get} from '../constants' import type {Theme} from '../ThemeProvider' import type {SxProp} from '../sx' import {merge} from '../sx' +import {clsx} from 'clsx' +import {useFeatureFlag} from '../FeatureFlags' +import classes from './ActionList.module.css' +import {defaultSxProp} from '../utils/defaultSxProp' -export type ActionListDividerProps = SxProp +export type ActionListDividerProps = SxProp & { + className?: string +} /** * Visually separates `Item`s or `Group`s in an `ActionList`. */ -export const Divider: React.FC> = ({sx = {}}) => { +export const Divider: React.FC> = ({sx = defaultSxProp, className}) => { + const enabled = useFeatureFlag('primer_react_css_modules_team') + if (enabled) { + if (sx !== defaultSxProp) { + return ( +