@@ -7,20 +7,27 @@ import React, {
77 useRef ,
88 useState ,
99} from 'react' ;
10-
1110import {
1211 DrawerLayout ,
1312 DisplayMode as DrawerDisplayMode ,
1413 useDrawerToolbarContext ,
1514 type DrawerLayoutProps ,
16- } from '. /drawer' ;
15+ } from '@leafygreen-ui /drawer' ;
1716import { css , cx } from '@leafygreen-ui/emotion' ;
1817import { isEqual } from 'lodash' ;
1918import { rafraf } from '../utils/rafraf' ;
19+ import { BaseFontSize , fontWeights } from '@leafygreen-ui/tokens' ;
20+
21+ type ToolbarData = Required < DrawerLayoutProps > [ 'toolbarData' ] ;
2022
21- type SectionData = Required < DrawerLayoutProps > [ 'toolbarData' ] [ number ] ;
23+ type SectionData = ToolbarData [ number ] ;
2224
2325type DrawerSectionProps = Omit < SectionData , 'content' | 'onClick' > & {
26+ // Title exists in DrawerLayoutProps, but is optional, whereas for us it needs
27+ // to be required (also due to merging of types inside leafygreen, we can't
28+ // convince typescript that our toolbarData is compatible with lg toolbarData
29+ // if that is not explicit)
30+ title : React . ReactNode ;
2431 /**
2532 * If `true` will automatically open the section when first mounted. Default: `false`
2633 */
@@ -163,22 +170,19 @@ const drawerLayoutFixesStyles = css({
163170 } ,
164171
165172 // drawer section
166- '& > div:nth-child(2)' : {
167- marginTop : - 1 , // hiding the top border as we already have one in the place where the Anchor is currently rendered
173+ '& > div:nth-child(2) > div' : {
174+ // hiding the border border as we already have one in the place where the
175+ // Anchor is currently rendered
176+ borderTop : 'none' ,
177+ borderBottom : 'none' ,
168178 } ,
169179
170- // We're stretching the title container to all available width so that we can
171- // layout the controls there better. Doing our best to target the section
172- // title here, leafygreen really doesn't give us anything else to try.
173- //
174- // TODO(ticket): This is obviously a horrible selector and we should make sure
175- // that LG team provides a better one for us to achieve this behavior when
176- // we're removing the vendored version of the drawer
177- '& > div:nth-child(2) > div:nth-child(2) > div:first-child > div:first-child > div:first-child > div:first-child' :
180+ // drawer content > title content
181+ '& > div:nth-child(2) > div:nth-child(2) > div:nth-child(2) > div:first-child > div:first-child > div:first-child' :
178182 {
179- flex : 'none' ,
180- width : 'calc(100% - 28px)' , // disallow going over the title size (100 - close button width)
181- overflow : 'hidden' ,
183+ // fix for the flex parent not allowing flex children to collapse if they
184+ // are overflowing the container
185+ minWidth : 0 ,
182186 } ,
183187} ) ;
184188
@@ -210,13 +214,21 @@ const drawerSectionPortalStyles = css({
210214 height : '100%' ,
211215} ) ;
212216
217+ // Leafygreen dynamically changes styles of the title group based on whether or
218+ // not title is a `string` or a `ReactNode`, we want it to consistently have
219+ // bold title styles no matter what title you provided, so we wrap it in our own
220+ // container
221+ const drawerTitleGroupStyles = css ( {
222+ width : '100%' ,
223+ fontSize : BaseFontSize . Body2 ,
224+ fontWeight : fontWeights . bold ,
225+ } ) ;
226+
213227/**
214228 * DrawerAnchor component will render the drawer in any place it is rendered.
215229 * This component has to wrap any content that Drawer will be shown near
216230 */
217- export const DrawerAnchor : React . FunctionComponent < {
218- displayMode ?: DrawerDisplayMode ;
219- } > = ( { displayMode, children } ) => {
231+ export const DrawerAnchor : React . FunctionComponent = ( { children } ) => {
220232 const actions = useContext ( DrawerActionsContext ) ;
221233 const drawerSectionItems = useContext ( DrawerStateContext ) ;
222234 const prevDrawerSectionItems = useRef < DrawerSectionProps [ ] > ( [ ] ) ;
@@ -239,7 +251,13 @@ export const DrawerAnchor: React.FunctionComponent<{
239251 return drawerSectionItems
240252 . map ( ( data ) => {
241253 return {
254+ hasPadding : false ,
242255 ...data ,
256+ title : (
257+ < div key = { data . id } className = { drawerTitleGroupStyles } >
258+ { data . title }
259+ </ div >
260+ ) ,
243261 content : (
244262 < div
245263 key = { data . id }
@@ -255,7 +273,8 @@ export const DrawerAnchor: React.FunctionComponent<{
255273 } , [ drawerSectionItems ] ) ;
256274 return (
257275 < DrawerLayout
258- displayMode = { displayMode ?? DrawerDisplayMode . Embedded }
276+ displayMode = { DrawerDisplayMode . Embedded }
277+ resizable
259278 toolbarData = { toolbarData }
260279 className = { cx (
261280 drawerLayoutFixesStyles ,
@@ -270,6 +289,15 @@ export const DrawerAnchor: React.FunctionComponent<{
270289 ) ;
271290} ;
272291
292+ function querySectionPortal (
293+ parent : Document | Element | null ,
294+ id ?: string
295+ ) : HTMLElement | null {
296+ return (
297+ parent ?. querySelector ( `[data-drawer-section${ id ? `=${ id } ` : '' } ]` ) ?? null
298+ ) ;
299+ }
300+
273301/**
274302 * DrawerSection allows to declaratively render sections inside the drawer
275303 * independantly from the Drawer itself
@@ -278,7 +306,9 @@ export const DrawerSection: React.FunctionComponent<DrawerSectionProps> = ({
278306 children,
279307 ...props
280308} ) => {
281- const [ portalNode , setPortalNode ] = useState < Element | null > ( null ) ;
309+ const [ portalNode , setPortalNode ] = useState < Element | null > ( ( ) => {
310+ return querySectionPortal ( document , props . id ) ;
311+ } ) ;
282312 const actions = useContext ( DrawerActionsContext ) ;
283313 const prevProps = useRef < DrawerSectionProps > ( ) ;
284314 useEffect ( ( ) => {
@@ -296,14 +326,24 @@ export const DrawerSection: React.FunctionComponent<DrawerSectionProps> = ({
296326 'Can not use DrawerSection without DrawerAnchor being mounted on the page'
297327 ) ;
298328 }
299- setPortalNode (
300- document . querySelector ( `[data-drawer-section="${ props . id } "]` )
301- ) ;
329+ setPortalNode ( querySectionPortal ( drawerEl , props . id ) ) ;
302330 const mutationObserver = new MutationObserver ( ( mutations ) => {
303331 for ( const mutation of mutations ) {
304- for ( const node of Array . from ( mutation . addedNodes ) as HTMLElement [ ] ) {
305- if ( node . dataset && node . dataset . drawerSection === props . id ) {
306- setPortalNode ( node ) ;
332+ if ( mutation . type === 'childList' ) {
333+ for ( const node of Array . from ( mutation . addedNodes ) ) {
334+ // Added node can be either the drawer section portal itself, a
335+ // parent node containing the section (in that case we won't get an
336+ // explicit mutation for the section itself), or something
337+ // completely unrelated, like a text node insert. By searching for
338+ // the section portal from added node parent element we cover all
339+ // these cases in one go
340+ const drawerSectionNode = querySectionPortal (
341+ node . parentElement ,
342+ props . id
343+ ) ;
344+ if ( drawerSectionNode ) {
345+ setPortalNode ( drawerSectionNode ) ;
346+ }
307347 }
308348 }
309349 }
@@ -315,7 +355,7 @@ export const DrawerSection: React.FunctionComponent<DrawerSectionProps> = ({
315355 return ( ) => {
316356 mutationObserver . disconnect ( ) ;
317357 } ;
318- } , [ actions , props . id ] ) ;
358+ } , [ props . id ] ) ;
319359 useEffect ( ( ) => {
320360 return ( ) => {
321361 actions . current . removeToolbarData ( props . id ) ;
@@ -333,7 +373,9 @@ export function useDrawerActions() {
333373 const actions = useContext ( DrawerActionsContext ) ;
334374 const stableActions = useRef ( {
335375 openDrawer : ( id : string ) => {
336- actions . current . openDrawer ( id ) ;
376+ rafraf ( ( ) => {
377+ actions . current . openDrawer ( id ) ;
378+ } ) ;
337379 } ,
338380 closeDrawer : ( ) => {
339381 actions . current . closeDrawer ( ) ;
@@ -352,3 +394,5 @@ export const useDrawerState = () => {
352394 drawerState . length > 0 ,
353395 } ;
354396} ;
397+
398+ export { getLgIds as getDrawerIds } from '@leafygreen-ui/drawer' ;
0 commit comments