2
2
// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
3
3
//
4
4
// SPDX-License-Identifier: Apache-2.0
5
- import { memo , useMemo } from 'react' ;
5
+ import MuiBox from '@mui/material/Box' ;
6
+ import MuiChip from '@mui/material/Chip' ;
7
+ import MuiTooltip from '@mui/material/Tooltip' ;
8
+ import MuiTypography from '@mui/material/Typography' ;
9
+ import { SxProps } from '@mui/system' ;
10
+ import { memo , useEffect , useMemo , useRef } from 'react' ;
6
11
7
- import { PackageInfo } from '../../../shared/shared-types' ;
12
+ import { Criticality , PackageInfo } from '../../../shared/shared-types' ;
13
+ import { text } from '../../../shared/text' ;
14
+ import { OpossumColors } from '../../shared-styles' ;
8
15
import { getCardLabels } from '../../util/get-card-labels' ;
16
+ import { maybePluralize } from '../../util/maybe-pluralize' ;
9
17
import { Checkbox } from '../Checkbox/Checkbox' ;
10
- import { ListCard , ListCardConfig } from '../ListCard/ListCard' ;
11
18
import { getRightIcons } from './PackageCard.util' ;
12
19
13
- interface PackageCardProps {
20
+ export const PACKAGE_CARD_HEIGHT = 40 ;
21
+
22
+ const hoveredSelectedBackgroundColor = OpossumColors . middleBlue ;
23
+ const hoveredBackgroundColor = OpossumColors . lightestBlueOnHover ;
24
+
25
+ const classes = {
26
+ root : {
27
+ flex : 1 ,
28
+ display : 'flex' ,
29
+ alignItems : 'center' ,
30
+ height : PACKAGE_CARD_HEIGHT ,
31
+ padding : '0 4px' ,
32
+ gap : '4px' ,
33
+ '&:focus' : {
34
+ background : hoveredBackgroundColor ,
35
+ outline : 'none' ,
36
+ } ,
37
+ } ,
38
+ innerRoot : {
39
+ flex : 1 ,
40
+ display : 'flex' ,
41
+ alignItems : 'center' ,
42
+ height : PACKAGE_CARD_HEIGHT ,
43
+ overflow : 'hidden' ,
44
+ gap : '8px' ,
45
+ } ,
46
+ hover : {
47
+ '&:hover' : {
48
+ cursor : 'pointer' ,
49
+ background : hoveredBackgroundColor ,
50
+ } ,
51
+ } ,
52
+ selected : {
53
+ background : OpossumColors . middleBlue ,
54
+ '&:hover' : {
55
+ background : hoveredSelectedBackgroundColor ,
56
+ } ,
57
+ '&:focus' : {
58
+ background : hoveredSelectedBackgroundColor ,
59
+ outline : 'none' ,
60
+ } ,
61
+ } ,
62
+ resolved : {
63
+ opacity : 0.5 ,
64
+ backgroundColor : 'white' ,
65
+ } ,
66
+ iconColumn : {
67
+ display : 'grid' ,
68
+ gridTemplateRows : '1fr 1fr' ,
69
+ gridAutoFlow : 'column' ,
70
+ direction : 'rtl' ,
71
+ } ,
72
+ textLines : {
73
+ flex : 1 ,
74
+ overflow : 'hidden' ,
75
+ } ,
76
+ textLine : {
77
+ userSelect : 'none' ,
78
+ textOverflow : 'ellipsis' ,
79
+ overflow : 'hidden' ,
80
+ whiteSpace : 'nowrap' ,
81
+ } ,
82
+ } satisfies SxProps ;
83
+
84
+ export interface PackageCardConfig {
85
+ criticality ?: Criticality ;
86
+ excludeFromNotice ?: boolean ;
87
+ firstParty ?: boolean ;
88
+ followUp ?: boolean ;
89
+ incomplete ?: boolean ;
90
+ needsReview ?: boolean ;
91
+ preSelected ?: boolean ;
92
+ preferred ?: boolean ;
93
+ resolved ?: boolean ;
94
+ selected ?: boolean ;
95
+ focused ?: boolean ;
96
+ wasPreferred ?: boolean ;
97
+ }
98
+
99
+ export interface PackageCardProps {
14
100
packageInfo : PackageInfo ;
15
- cardConfig ?: ListCardConfig ;
101
+ cardConfig ?: PackageCardConfig ;
16
102
onClick ?( ) : void ;
17
103
checkbox ?: {
18
104
checked : boolean ;
@@ -23,11 +109,12 @@ interface PackageCardProps {
23
109
24
110
export const PackageCard = memo (
25
111
( { packageInfo, cardConfig, checkbox, onClick } : PackageCardProps ) => {
112
+ const ref = useRef < HTMLDivElement > ( null ) ;
26
113
const packageLabels = useMemo (
27
114
( ) => getCardLabels ( packageInfo ) ,
28
115
[ packageInfo ] ,
29
116
) ;
30
- const listCardConfig = useMemo < ListCardConfig > (
117
+ const effectiveCardConfig = useMemo < PackageCardConfig > (
31
118
( ) => ( {
32
119
criticality : packageInfo . criticality ,
33
120
excludeFromNotice : packageInfo . excludeFromNotice ,
@@ -42,29 +129,77 @@ export const PackageCard = memo(
42
129
[ cardConfig , packageInfo ] ,
43
130
) ;
44
131
const rightIcons = useMemo (
45
- ( ) => getRightIcons ( listCardConfig ) ,
46
- [ listCardConfig ] ,
132
+ ( ) => getRightIcons ( effectiveCardConfig ) ,
133
+ [ effectiveCardConfig ] ,
47
134
) ;
48
135
136
+ useEffect ( ( ) => {
137
+ if ( effectiveCardConfig . focused ) {
138
+ ref . current ?. focus ( ) ;
139
+ }
140
+ } , [ effectiveCardConfig . focused ] ) ;
141
+
49
142
return (
50
- < ListCard
51
- text = { packageLabels [ 0 ] }
52
- secondLineText = { packageLabels [ 1 ] }
53
- cardConfig = { listCardConfig }
54
- count = { packageInfo . count }
55
- rightIcons = { rightIcons }
56
- onClick = { onClick }
57
- leftElement = {
58
- checkbox && (
59
- < Checkbox
60
- checked = { checkbox . checked }
61
- disabled = { checkbox . disabled }
62
- onChange = { checkbox ?. onChange }
63
- disableRipple
64
- />
65
- )
66
- }
67
- />
143
+ < MuiBox
144
+ ref = { ref }
145
+ aria-label = { `package card ${ packageLabels [ 0 ] } ` }
146
+ tabIndex = { 0 }
147
+ onKeyDown = { ( event ) => {
148
+ if ( [ 'Enter' , 'Space' ] . includes ( event . code ) ) {
149
+ event . preventDefault ( ) ;
150
+ onClick ?.( ) ;
151
+ }
152
+ } }
153
+ sx = { {
154
+ ...classes . root ,
155
+ ...( onClick && classes . hover ) ,
156
+ ...( cardConfig ?. resolved && classes . resolved ) ,
157
+ ...( cardConfig ?. selected && classes . selected ) ,
158
+ } }
159
+ >
160
+ { checkbox && (
161
+ < Checkbox
162
+ checked = { checkbox . checked }
163
+ disabled = { checkbox . disabled }
164
+ onChange = { checkbox . onChange }
165
+ disableRipple
166
+ />
167
+ ) }
168
+ < MuiBox sx = { classes . innerRoot } onClick = { onClick } >
169
+ { packageInfo . count && (
170
+ < MuiTooltip
171
+ title = { maybePluralize (
172
+ packageInfo . count ,
173
+ text . attributionColumn . occurrence ,
174
+ {
175
+ showOne : true ,
176
+ } ,
177
+ ) }
178
+ enterDelay = { 500 }
179
+ >
180
+ < MuiChip
181
+ sx = { { minWidth : '24px' , userSelect : 'none' } }
182
+ label = { new Intl . NumberFormat ( 'en-US' , {
183
+ notation : 'compact' ,
184
+ compactDisplay : 'short' ,
185
+ } ) . format ( packageInfo . count ) }
186
+ size = { 'small' }
187
+ />
188
+ </ MuiTooltip >
189
+ ) }
190
+ < MuiBox sx = { classes . textLines } >
191
+ < MuiTypography sx = { classes . textLine } >
192
+ { packageLabels [ 0 ] }
193
+ </ MuiTypography >
194
+ { ! ! packageLabels [ 1 ] && (
195
+ < MuiTypography sx = { classes . textLine } >
196
+ { packageLabels [ 1 ] }
197
+ </ MuiTypography >
198
+ ) }
199
+ </ MuiBox >
200
+ < MuiBox sx = { classes . iconColumn } > { rightIcons } </ MuiBox >
201
+ </ MuiBox >
202
+ </ MuiBox >
68
203
) ;
69
204
} ,
70
205
) ;
0 commit comments