1
- import { Chip , darken , Grid , Stack , Tooltip } from '@mui/material' ;
1
+ import { Box , Chip , ChipProps , darken , Stack , Tooltip } from '@mui/material' ;
2
2
import React from 'react' ;
3
3
import ReactDOM from 'react-dom' ;
4
4
import { MemoryRouter } from 'react-router-dom' ;
5
5
6
- const defaultChipRender = ( label , key ) => (
7
- < Chip sx = { { backgroundColor : ( t ) => t . palette . secondary . main } } label = { label } key = { key } />
6
+ const defaultChipRender = ( label , key , size = 'small' ) => (
7
+ < Chip
8
+ sx = { { backgroundColor : ( t ) => t . palette . secondary . main } }
9
+ label = { label }
10
+ size = { size as ChipProps [ 'size' ] }
11
+ key = { key }
12
+ />
13
+ ) ;
14
+
15
+ const defaultTooltipRender = ( chipsToHide ) => (
16
+ < Box sx = { { py : ( t ) => t . typography . pxToRem ( 6 ) , px : ( t ) => t . typography . pxToRem ( 10 ) } } >
17
+ < Stack spacing = { 1.5 } flexWrap = "wrap" sx = { { fontWeight : 400 } } >
18
+ { chipsToHide . map ( ( chip ) => defaultChipRender ( chip , chip ) ) }
19
+ </ Stack >
20
+ </ Box >
8
21
) ;
9
22
10
23
const stackGap = 1 ;
@@ -13,52 +26,98 @@ const themeGapMultiplier = 8;
13
26
export const ResponsiveChips = ( {
14
27
chipsData,
15
28
renderChip = defaultChipRender ,
29
+ renderTooltip = defaultTooltipRender ,
16
30
} : {
17
31
chipsData : string [ ] ;
18
- renderChip : ( chip : string , key : string ) => React . ReactElement ;
32
+ renderChip : ( chip : string , key : string , size ?: string ) => React . ReactElement ;
33
+ renderTooltip : ( chipsToHide : string [ ] , chipsToShow : string [ ] ) => React . ReactElement ;
19
34
} ) => {
20
35
const containerRef = React . useRef ( null ) ;
21
36
const showMoreButtonRef = React . useRef ( null ) ;
22
37
23
- const ghostContainerRef = React . useRef ( null ) ;
24
-
25
38
const [ data , setData ] = React . useState ( {
26
39
chipsToShow : [ ] ,
27
40
chipsToHide : [ ] ,
28
41
occupiedWidth : 0 ,
29
42
} ) ;
30
43
31
44
const calculateToFit = React . useCallback ( ( ) => {
32
- const freeSpaceWidth = containerRef . current ? containerRef . current . offsetWidth : 0 ;
33
- const buttonWidth = showMoreButtonRef . current ? showMoreButtonRef . current . offsetWidth : 0 ;
34
- const availableWidth = freeSpaceWidth - buttonWidth ;
45
+ const freeSpaceWidth = containerRef . current
46
+ ? containerRef . current . getBoundingClientRect ( ) . width
47
+ : 0 ;
48
+
49
+ const buttonWidth = showMoreButtonRef . current
50
+ ? showMoreButtonRef . current . getBoundingClientRect ( ) . width
51
+ : 0 ;
52
+
53
+ const margin = stackGap * themeGapMultiplier ;
54
+ const availableWidth = freeSpaceWidth - buttonWidth - margin ;
35
55
36
56
let accumulatingWidth = 0 ;
37
- const chipsToShow = [ ] ;
38
- const chipsToHide = [ ] ;
57
+ const chipsToShow : string [ ] = [ ] ;
58
+ const chipsToHide : string [ ] = [ ] ;
39
59
let overflowStarted = false ;
40
60
41
- chipsData . forEach ( ( chip ) => {
42
- const ChipElement = < MemoryRouter > { renderChip ( chip , chip ) } </ MemoryRouter > ; //TODO: find a way to get rid of temporary router wrapper
43
- ReactDOM . render ( ChipElement , ghostContainerRef . current ) ;
61
+ const shadowHost = document . createElement ( 'div' ) ;
62
+ document . body . appendChild ( shadowHost ) ;
63
+ const shadowRoot = shadowHost . attachShadow ( { mode : 'open' } ) ;
64
+
65
+ const chipRenderingContainer = document . createElement ( 'div' ) ;
66
+ shadowRoot . appendChild ( chipRenderingContainer ) ;
67
+
68
+ shadowHost . style . cssText = `
69
+ visibility: hidden;
70
+ position: absolute;
71
+ top: 0;
72
+ left: 0;
73
+ width: auto;
74
+ height: auto;
75
+ pointer-events: none;
76
+ ` ;
44
77
45
- const renderedElement = ghostContainerRef . current . firstChild ;
46
- if ( renderedElement instanceof HTMLElement ) {
47
- const chipWidth = renderedElement . offsetWidth + stackGap * themeGapMultiplier ;
78
+ ReactDOM . render (
79
+ < MemoryRouter >
80
+ { chipsData . map ( ( chip , index ) => (
81
+ < div key = { index } id = { `chip-${ index } ` } >
82
+ { renderChip ( chip , chip ) }
83
+ </ div >
84
+ ) ) }
85
+ </ MemoryRouter > ,
86
+ chipRenderingContainer
87
+ ) ;
48
88
49
- if ( ! overflowStarted && accumulatingWidth + chipWidth <= availableWidth ) {
89
+ chipsData . forEach ( ( chip , index ) => {
90
+ const chipElement = chipRenderingContainer . querySelector ( `#chip-${ index } ` ) ;
91
+ if ( chipElement instanceof HTMLElement ) {
92
+ const chipWidth = chipElement . offsetWidth + stackGap * themeGapMultiplier ;
93
+
94
+ const remainingSpace = availableWidth - accumulatingWidth ;
95
+
96
+ if (
97
+ ! overflowStarted &&
98
+ ( accumulatingWidth + chipWidth <= availableWidth || remainingSpace > chipWidth - margin )
99
+ ) {
50
100
accumulatingWidth += chipWidth ;
51
101
chipsToShow . push ( chip ) ;
52
102
} else {
53
103
overflowStarted = true ;
54
104
chipsToHide . push ( chip ) ;
55
105
}
56
106
}
57
-
58
- ReactDOM . unmountComponentAtNode ( ghostContainerRef . current ) ;
59
107
} ) ;
60
108
61
- setData ( { chipsToShow, chipsToHide, occupiedWidth : accumulatingWidth } ) ;
109
+ ReactDOM . unmountComponentAtNode ( chipRenderingContainer ) ;
110
+ document . body . removeChild ( shadowHost ) ;
111
+
112
+ setData ( ( prevData ) => {
113
+ if (
114
+ JSON . stringify ( prevData . chipsToShow ) !== JSON . stringify ( chipsToShow ) ||
115
+ JSON . stringify ( prevData . chipsToHide ) !== JSON . stringify ( chipsToHide )
116
+ ) {
117
+ return { chipsToShow, chipsToHide, occupiedWidth : accumulatingWidth } ;
118
+ }
119
+ return prevData ;
120
+ } ) ;
62
121
} , [ chipsData , renderChip ] ) ;
63
122
64
123
React . useEffect ( ( ) => {
@@ -74,24 +133,12 @@ export const ResponsiveChips = ({
74
133
75
134
return (
76
135
< >
77
- < div
78
- ref = { ghostContainerRef }
79
- style = { { visibility : 'hidden' , position : 'absolute' , zIndex : - 1 , pointerEvents : 'none' } }
80
- />
81
136
< div ref = { containerRef } style = { { width : '100%' } } >
82
137
< Stack direction = "row" spacing = { stackGap } alignItems = "center" >
83
138
{ data . chipsToShow . map ( ( chip ) => renderChip ( chip , chip ) ) }
84
139
{ data . chipsToHide . length > 0 && (
85
140
< >
86
- < Tooltip
87
- title = {
88
- < Grid container spacing = { 2 } flexWrap = "wrap" sx = { { fontWeight : 400 } } >
89
- { data . chipsToHide . map ( ( chip ) => (
90
- < Grid item > { renderChip ( chip , chip ) } </ Grid >
91
- ) ) }
92
- </ Grid >
93
- }
94
- >
141
+ < Tooltip title = { renderTooltip ( data . chipsToHide , data . chipsToShow ) } >
95
142
< Chip
96
143
ref = { showMoreButtonRef }
97
144
sx = { {
0 commit comments