11import { CheckIcon , IconProps } from '@primer/octicons-react'
2- import React , { useCallback } from 'react'
2+ import React , { useCallback , useMemo } from 'react'
33import { get } from '../constants'
44import sx , { SxProp } from '../sx'
55import Truncate from '../Truncate'
@@ -13,6 +13,7 @@ import {
1313 activeDescendantActivatedIndirectly ,
1414 isActiveDescendantAttribute
1515} from '../behaviors/focusZone'
16+ import { uniqueId } from '../utils/uniqueId'
1617
1718/**
1819 * These colors are not yet in our default theme. Need to remove this once they are added.
@@ -153,11 +154,21 @@ const getItemVariant = (variant = 'default', disabled?: boolean) => {
153154 }
154155}
155156
156- const StyledItemContent = styled . div `
157+ const DividedContent = styled . div `
157158 display: flex;
158159 min-width: 0;
159- flex-grow: 1;
160+
161+ /* Required for dividers */
160162 position: relative;
163+ flex-grow: 1;
164+ `
165+
166+ const MainContent = styled . div `
167+ align-items: baseline;
168+ display: flex;
169+ min-width: 0;
170+ flex-direction: var(--main-content-flex-direction);
171+ flex-grow: 1;
161172`
162173
163174const StyledItem = styled . div <
@@ -193,7 +204,7 @@ const StyledItem = styled.div<
193204 :not(:first-of-type):not(${ StyledDivider } + &):not(${ StyledHeader } + &) {
194205 margin-top: ${ ( { showDivider} ) => ( showDivider ? `1px` : '0' ) } ;
195206
196- ${ StyledItemContent } ::before {
207+ ${ DividedContent } ::before {
197208 content: ' ';
198209 display: block;
199210 position: absolute;
@@ -207,23 +218,23 @@ const StyledItem = styled.div<
207218
208219 // Item dividers should not be visible:
209220 // - above Hovered
210- &:hover ${ StyledItemContent } ::before,
221+ &:hover ${ DividedContent } ::before,
211222 // - below Hovered
212223 // '*' instead of '&' because '&' maps to separate class names depending on 'variant'
213- :hover + * ${ StyledItemContent } ::before {
224+ :hover + * ${ DividedContent } ::before {
214225 // allow override in case another item in the list is active/focused
215226 border-color: var(--item-hover-divider-border-color-override, transparent) !important;
216227 }
217228
218229 // - above Focused
219- &:focus ${ StyledItemContent } ::before,
230+ &:focus ${ DividedContent } ::before,
220231 // - below Focused
221232 // '*' instead of '&' because '&' maps to separate class names depending on 'variant'
222- :focus + * ${ StyledItemContent } ::before,
233+ :focus + * ${ DividedContent } ::before,
223234 // - above Active Descendent
224- &[${ isActiveDescendantAttribute } ] ${ StyledItemContent } ::before,
235+ &[${ isActiveDescendantAttribute } ] ${ DividedContent } ::before,
225236 // - below Active Descendent
226- [${ isActiveDescendantAttribute } ] + & ${ StyledItemContent } ::before {
237+ [${ isActiveDescendantAttribute } ] + & ${ DividedContent } ::before {
227238 // '!important' because all the ':not's above give higher specificity
228239 border-color: transparent !important;
229240 }
@@ -248,27 +259,16 @@ const StyledItem = styled.div<
248259 ${ sx }
249260`
250261
251- export const TextContainer = styled . div < {
262+ export const TextContainer = styled . span < {
252263 dangerouslySetInnerHtml ?: React . DOMAttributes < HTMLDivElement > [ 'dangerouslySetInnerHTML' ]
253- descriptionVariant : ItemProps [ 'descriptionVariant' ]
254- } > `
255- display: flex;
256- min-width: 0;
257- flex-grow: 1;
258- flex-direction: ${ ( { descriptionVariant} ) => ( descriptionVariant === 'inline' ? 'row' : 'column' ) } ;
259- align-items: baseline;
260- `
264+ } > ``
261265
262266const BaseVisualContainer = styled . div < { variant ?: ItemProps [ 'variant' ] ; disabled ?: boolean } > `
263267 // Match visual height to adjacent text line height.
264268 // TODO: When rem-based spacing on a 4px scale lands, replace
265269 // hardcoded '20px' with '${ get ( 'space.s20' ) } '.
266270 height: 20px;
267271 width: ${ get ( 'space.3' ) } ;
268- flex-shrink: 0;
269- display: flex;
270- flex-direction: column;
271- justify-content: center;
272272 margin-right: ${ get ( 'space.2' ) } ;
273273`
274274
@@ -279,30 +279,33 @@ const ColoredVisualContainer = styled(BaseVisualContainer)`
279279 }
280280`
281281
282- const LeadingVisualContainer = styled ( ColoredVisualContainer ) ``
282+ const LeadingVisualContainer = styled ( ColoredVisualContainer ) `
283+ flex-shrink: 0;
284+ display: flex;
285+ flex-direction: column;
286+ justify-content: center;
287+ `
283288
284- const TrailingVisualContainer = styled ( ColoredVisualContainer ) `
289+ const TrailingContent = styled ( ColoredVisualContainer ) `
285290 color: ${ ( { variant, disabled} ) => getItemVariant ( variant , disabled ) . annotationColor } };
286291 margin-left: ${ get ( 'space.2' ) } ;
287292 margin-right: 0;
293+ width: auto;
288294 div:nth-child(2) {
289295 margin-left: ${ get ( 'space.2' ) } ;
290296 }
291- display: flex;
292- flex-direction: row;
293- justify-content: flex-end;
294297`
295298
296- const DescriptionContainer = styled . span < { descriptionVariant : ItemProps [ 'descriptionVariant' ] } > `
299+ const DescriptionContainer = styled . span `
297300 color: ${ get ( 'colors.text.secondary' ) } ;
298301 font-size: ${ get ( 'fontSizes.0' ) } ;
299302 // TODO: When rem-based spacing on a 4px scale lands, replace
300303 // hardcoded '16px' with '${ get ( 'lh-12' ) } '.
301304 line-height: 16px;
302- margin-left: ${ ( { descriptionVariant } ) => ( descriptionVariant === 'inline' ? get ( 'space.2' ) : 0 ) } ;
305+ margin-left: var(--description-container-margin-left) ;
303306 min-width: 0;
304307 flex-grow: 1;
305- flex-basis: ${ ( { descriptionVariant } ) => ( descriptionVariant === 'inline' ? 0 : 'auto' ) } ;
308+ flex-basis: var(--description-container-flex-basis) ;
306309`
307310
308311const MultiSelectInput = styled . input `
@@ -333,6 +336,9 @@ export function Item(itemProps: Partial<ItemProps> & {item?: ItemInput}): JSX.El
333336 ...props
334337 } = itemProps
335338
339+ const labelId = useMemo ( ( ) => uniqueId ( ) , [ ] )
340+ const descriptionId = useMemo ( ( ) => uniqueId ( ) , [ ] )
341+
336342 const keyPressHandler = useCallback (
337343 event => {
338344 if ( disabled ) {
@@ -377,6 +383,8 @@ export function Item(itemProps: Partial<ItemProps> & {item?: ItemInput}): JSX.El
377383 variant = { variant }
378384 showDivider = { showDivider }
379385 aria-selected = { selected }
386+ aria-labelledby = { text ? labelId : undefined }
387+ aria-describedby = { description ? descriptionId : undefined }
380388 { ...props }
381389 data-id = { id }
382390 onKeyPress = { keyPressHandler }
@@ -412,35 +420,41 @@ export function Item(itemProps: Partial<ItemProps> & {item?: ItemInput}): JSX.El
412420 < LeadingVisual />
413421 </ LeadingVisualContainer >
414422 ) }
415- < StyledItemContent >
416- { children }
417- { ( text || description ) && (
418- < TextContainer descriptionVariant = { descriptionVariant } >
419- { text && < div > { text } </ div > }
420- { description && (
421- < DescriptionContainer descriptionVariant = { descriptionVariant } >
422- { descriptionVariant === 'block' ? (
423- description
424- ) : (
425- < Truncate title = { description } inline = { true } maxWidth = "100%" >
426- { description }
427- </ Truncate >
428- ) }
429- </ DescriptionContainer >
430- ) }
431- </ TextContainer >
432- ) }
433- { ( TrailingIcon || trailingText ) && (
434- < TrailingVisualContainer variant = { variant } disabled = { disabled } >
435- { trailingText && < div > { trailingText } </ div > }
436- { TrailingIcon && (
437- < div >
438- < TrailingIcon />
439- </ div >
440- ) }
441- </ TrailingVisualContainer >
442- ) }
443- </ StyledItemContent >
423+ < DividedContent >
424+ < MainContent
425+ style = {
426+ { '--main-content-flex-direction' : descriptionVariant === 'inline' ? 'row' : 'column' } as React . CSSProperties
427+ }
428+ >
429+ { children }
430+ { text ? < TextContainer id = { labelId } > { text } </ TextContainer > : null }
431+ { description ? (
432+ < DescriptionContainer
433+ id = { descriptionId }
434+ style = {
435+ {
436+ '--description-container-margin-left' : descriptionVariant === 'inline' ? get ( 'space.2' ) ( theme ) : 0 ,
437+ '--description-container-flex-basis' : descriptionVariant === 'inline' ? 0 : 'auto'
438+ } as React . CSSProperties
439+ }
440+ >
441+ { descriptionVariant === 'block' ? (
442+ description
443+ ) : (
444+ < Truncate title = { description } inline = { true } maxWidth = "100%" >
445+ { description }
446+ </ Truncate >
447+ ) }
448+ </ DescriptionContainer >
449+ ) : null }
450+ </ MainContent >
451+ { TrailingIcon || trailingText ? (
452+ < TrailingContent variant = { variant } disabled = { disabled } >
453+ { trailingText }
454+ { TrailingIcon && < TrailingIcon /> }
455+ </ TrailingContent >
456+ ) : null }
457+ </ DividedContent >
444458 </ StyledItem >
445459 )
446460}
0 commit comments