Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/lemon-berries-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@primer/react": patch
---

`ActionList` and `NavList` are now SSR-compatible.

Warning: In this new implementation, `ActionList.LeadingVisual`, `ActionList.TrailingVisual,` and `ActionList.Description` must be direct children of `ActionList`. The same applies to `NavList`.
48 changes: 22 additions & 26 deletions src/ActionList/Description.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react'
import Box from '../Box'
import {SxProp, merge} from '../sx'
import Truncate from '../Truncate'
import {Slot, ItemContext} from './shared'
import {SxProp, merge} from '../sx'
import {ItemContext} from './shared'

export type ActionListDescriptionProps = {
/**
Expand All @@ -28,29 +28,25 @@ export const Description: React.FC<React.PropsWithChildren<ActionListDescription
marginLeft: variant === 'block' ? 0 : 2,
}

return (
<Slot name={variant === 'block' ? 'BlockDescription' : 'InlineDescription'}>
{({blockDescriptionId, inlineDescriptionId, disabled}: ItemContext) =>
variant === 'block' ? (
<Box
as="span"
sx={merge({...styles, color: disabled ? 'fg.disabled' : 'fg.muted'}, sx as SxProp)}
id={blockDescriptionId}
>
{props.children}
</Box>
) : (
<Truncate
id={inlineDescriptionId}
sx={merge({...styles, color: disabled ? 'fg.disabled' : 'fg.muted'}, sx as SxProp)}
title={props.children as string}
inline={true}
maxWidth="100%"
>
{props.children}
</Truncate>
)
}
</Slot>
const {blockDescriptionId, inlineDescriptionId, disabled} = React.useContext(ItemContext)

return variant === 'block' ? (
<Box
as="span"
sx={merge({...styles, color: disabled ? 'fg.disabled' : 'fg.muted'}, sx as SxProp)}
id={blockDescriptionId}
>
{props.children}
</Box>
) : (
<Truncate
id={inlineDescriptionId}
sx={merge({...styles, color: disabled ? 'fg.disabled' : 'fg.muted'}, sx as SxProp)}
title={props.children as string}
inline={true}
maxWidth="100%"
>
{props.children}
</Truncate>
)
}
121 changes: 60 additions & 61 deletions src/ActionList/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import React from 'react'
import styled from 'styled-components'
import Box, {BoxProps} from '../Box'
import {useId} from '../hooks/useId'
import {useSlots} from '../hooks/useSlots'
import sx, {BetterSystemStyleObject, merge, SxProp} from '../sx'
import {useTheme} from '../ThemeProvider'
import {useId} from '../hooks/useId'
import {defaultSxProp} from '../utils/defaultSxProp'
import {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {ActionListContainerContext} from './ActionListContainerContext'
import {Description} from './Description'
import {ActionListGroupProps, GroupContext} from './Group'
import {ActionListProps, ListContext} from './List'
import {Selection} from './Selection'
import {ActionListItemProps, Slots, TEXT_ROW_HEIGHT, getVariantStyles} from './shared'
import {defaultSxProp} from '../utils/defaultSxProp'
import {ActionListItemProps, getVariantStyles, ItemContext, TEXT_ROW_HEIGHT} from './shared'
import {LeadingVisual, TrailingVisual} from './Visuals'

const LiBox = styled.li<SxProp>(sx)

Expand All @@ -30,6 +33,11 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
},
forwardedRef,
): JSX.Element => {
const [slots, childrenWithoutSlots] = useSlots(props.children, {
leadingVisual: LeadingVisual,
trailingVisual: TrailingVisual,
description: Description,
})
const {variant: listVariant, showDividers, selectionVariant: listSelectionVariant} = React.useContext(ListContext)
const {selectionVariant: groupSelectionVariant} = React.useContext(GroupContext)
const {container, afterSelect, selectionAttribute} = React.useContext(ActionListContainerContext)
Expand Down Expand Up @@ -169,66 +177,57 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(

const ItemWrapper = _PrivateItemWrapper || React.Fragment

const menuItemProps = {
onClick: clickHandler,
onKeyPress: keyPressHandler,
'aria-disabled': disabled ? true : undefined,
tabIndex: disabled ? undefined : 0,
'aria-labelledby': `${labelId} ${
slots.description && slots.description.props.variant !== 'block' ? inlineDescriptionId : ''
}`,
'aria-describedby': slots.description?.props.variant === 'block' ? blockDescriptionId : undefined,
...(selectionAttribute && {[selectionAttribute]: selected}),
role: role || itemRole,
}

const containerProps = _PrivateItemWrapper ? {role: role || itemRole ? 'none' : undefined} : menuItemProps

const wrapperProps = _PrivateItemWrapper ? menuItemProps : {}

return (
<Slots context={{variant, disabled, inlineDescriptionId, blockDescriptionId}}>
{slots => {
const menuItemProps = {
onClick: clickHandler,
onKeyPress: keyPressHandler,
'aria-disabled': disabled ? true : undefined,
tabIndex: disabled ? undefined : 0,
'aria-labelledby': `${labelId} ${slots.InlineDescription ? inlineDescriptionId : ''}`,
'aria-describedby': slots.BlockDescription ? blockDescriptionId : undefined,
...(selectionAttribute && {[selectionAttribute]: selected}),
role: role || itemRole,
}
const containerProps = _PrivateItemWrapper
? {
role: role || itemRole ? 'none' : undefined,
}
: menuItemProps
const wrapperProps = _PrivateItemWrapper ? menuItemProps : {}

return (
<LiBox
ref={forwardedRef}
sx={merge<BetterSystemStyleObject>(styles, sxProp)}
{...containerProps}
{...props}
<ItemContext.Provider value={{variant, disabled, inlineDescriptionId, blockDescriptionId}}>
<LiBox ref={forwardedRef} sx={merge<BetterSystemStyleObject>(styles, sxProp)} {...containerProps} {...props}>
<ItemWrapper {...wrapperProps}>
<Selection selected={selected} />
{slots.leadingVisual}
<Box
data-component="ActionList.Item--DividerContainer"
sx={{display: 'flex', flexDirection: 'column', flexGrow: 1, minWidth: 0}}
>
<ItemWrapper {...wrapperProps}>
<Selection selected={selected} />
{slots.LeadingVisual}
<Box
data-component="ActionList.Item--DividerContainer"
sx={{display: 'flex', flexDirection: 'column', flexGrow: 1, minWidth: 0}}
<ConditionalBox if={Boolean(slots.trailingVisual)} sx={{display: 'flex', flexGrow: 1}}>
<ConditionalBox
if={!!slots.description && slots.description.props.variant !== 'block'}
sx={{display: 'flex', flexGrow: 1, alignItems: 'baseline', minWidth: 0}}
>
<ConditionalBox if={Boolean(slots.TrailingVisual)} sx={{display: 'flex', flexGrow: 1}}>
<ConditionalBox
if={Boolean(slots.InlineDescription)}
sx={{display: 'flex', flexGrow: 1, alignItems: 'baseline', minWidth: 0}}
>
<Box
as="span"
id={labelId}
sx={{
flexGrow: slots.InlineDescription ? 0 : 1,
fontWeight: slots.InlineDescription ? 'bold' : 'normal',
}}
>
{props.children}
</Box>
{slots.InlineDescription}
</ConditionalBox>
{slots.TrailingVisual}
</ConditionalBox>
{slots.BlockDescription}
</Box>
</ItemWrapper>
</LiBox>
)
}}
</Slots>
<Box
as="span"
id={labelId}
sx={{
flexGrow: slots.description && slots.description.props.variant !== 'block' ? 0 : 1,
fontWeight: slots.description && slots.description.props.variant !== 'block' ? 'bold' : 'normal',
}}
>
{childrenWithoutSlots}
</Box>
{slots.description?.props.variant !== 'block' ? slots.description : null}
</ConditionalBox>
{slots.trailingVisual}
</ConditionalBox>
{slots.description?.props.variant === 'block' ? slots.description : null}
</Box>
</ItemWrapper>
</LiBox>
</ItemContext.Provider>
)
},
) as PolymorphicForwardRefComponent<'li', ActionListItemProps>
Expand Down
66 changes: 30 additions & 36 deletions src/ActionList/Visuals.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react'
import Box from '../Box'
import {SxProp, merge} from '../sx'
import {get} from '../constants'
import {getVariantStyles, Slot, ItemContext, TEXT_ROW_HEIGHT} from './shared'
import {SxProp, merge} from '../sx'
import {ItemContext, TEXT_ROW_HEIGHT, getVariantStyles} from './shared'

type VisualProps = SxProp & React.HTMLAttributes<HTMLSpanElement>

Expand Down Expand Up @@ -30,48 +30,42 @@ export const LeadingVisualContainer: React.FC<React.PropsWithChildren<VisualProp

export type ActionListLeadingVisualProps = VisualProps
export const LeadingVisual: React.FC<React.PropsWithChildren<VisualProps>> = ({sx = {}, ...props}) => {
const {variant, disabled} = React.useContext(ItemContext)
return (
<Slot name="LeadingVisual">
{({variant, disabled}: ItemContext) => (
<LeadingVisualContainer
sx={merge(
{
color: getVariantStyles(variant, disabled).iconColor,
svg: {fontSize: 0},
},
sx as SxProp,
)}
{...props}
>
{props.children}
</LeadingVisualContainer>
<LeadingVisualContainer
sx={merge(
{
color: getVariantStyles(variant, disabled).iconColor,
svg: {fontSize: 0},
},
sx as SxProp,
)}
</Slot>
{...props}
>
{props.children}
</LeadingVisualContainer>
)
}

export type ActionListTrailingVisualProps = VisualProps
export const TrailingVisual: React.FC<React.PropsWithChildren<VisualProps>> = ({sx = {}, ...props}) => {
const {variant, disabled} = React.useContext(ItemContext)
return (
<Slot name="TrailingVisual">
{({variant, disabled}: ItemContext) => (
<Box
as="span"
sx={merge(
{
height: '20px', // match height of text row
flexShrink: 0,
color: getVariantStyles(variant, disabled).annotationColor,
marginLeft: 2,
fontWeight: 'initial',
},
sx as SxProp,
)}
{...props}
>
{props.children}
</Box>
<Box
as="span"
sx={merge(
{
height: '20px', // match height of text row
flexShrink: 0,
color: getVariantStyles(variant, disabled).annotationColor,
marginLeft: 2,
fontWeight: 'initial',
},
sx as SxProp,
)}
</Slot>
{...props}
>
{props.children}
</Box>
)
}
9 changes: 4 additions & 5 deletions src/ActionList/shared.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react'
import {SxProp} from '../sx'
import createSlots from '../utils/create-slots'
import {AriaRole} from '../utils/types'

export type ActionListItemProps = {
Expand Down Expand Up @@ -56,10 +55,12 @@ type MenuItemProps = {
}

export type ItemContext = Pick<ActionListItemProps, 'variant' | 'disabled'> & {
inlineDescriptionId: string
blockDescriptionId: string
inlineDescriptionId?: string
blockDescriptionId?: string
}

export const ItemContext = React.createContext<ItemContext>({})

export const getVariantStyles = (
variant: ActionListItemProps['variant'],
disabled: ActionListItemProps['disabled'],
Expand Down Expand Up @@ -90,6 +91,4 @@ export const getVariantStyles = (
}
}

export const {Slots, Slot} = createSlots(['LeadingVisual', 'InlineDescription', 'BlockDescription', 'TrailingVisual'])

export const TEXT_ROW_HEIGHT = '20px' // custom value off the scale
12 changes: 6 additions & 6 deletions src/NavList/__snapshots__/NavList.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1278,12 +1278,6 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t
list-style: none;
}

.c9 {
padding: 0;
margin: 0;
display: none;
}

.c5 {
display: -webkit-box;
display: -webkit-flex;
Expand All @@ -1295,6 +1289,12 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t
flex-grow: 1;
}

.c9 {
padding: 0;
margin: 0;
display: none;
}

.c7 {
height: 20px;
-webkit-flex-shrink: 0;
Expand Down