Skip to content

Commit

Permalink
misc: start migration of Combobox to Tailwind (#1942)
Browse files Browse the repository at this point in the history
  • Loading branch information
ansmonjol authored Dec 26, 2024
1 parent e88ace7 commit efa35ac
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 125 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
"react-ace": "^11.0.1",
"react-dom": "18.2.0",
"react-router-dom": "6.15.0",
"react-window": "1.8.9",
"react-window": "1.8.11",
"recharts": "^2.9.0",
"sanitize-html": "2.12.1",
"styled-components": "^6.1.13",
Expand Down
29 changes: 7 additions & 22 deletions src/components/form/ComboBox/ComboBox.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Autocomplete, createFilterOptions } from '@mui/material'
import _sortBy from 'lodash/sortBy'
import { useEffect, useMemo, useRef } from 'react'
import styled from 'styled-components'

import { Skeleton } from '~/components/designSystem'
import { useInternationalization } from '~/hooks/core/useInternationalization'
import { useDebouncedSearch } from '~/hooks/useDebouncedSearch'
import { theme } from '~/styles'

import { ComboBoxInput } from './ComboBoxInput'
import { ComboBoxItem } from './ComboBoxItem'
Expand Down Expand Up @@ -132,14 +130,17 @@ export const ComboBox = ({
value={value || null}
loading={isLoading}
loadingText={
<LoadingIemsWrapper>
<div className="my-4 flex flex-col gap-8">
{[1, 2, 3].map((i) => (
<LoadingItem key={`combobox-loading-item-${i}`}>
<div
className="mx-2 flex items-center justify-between px-4"
key={`combobox-loading-item-${i}`}
>
<Skeleton variant="circular" size="small" className="mr-4" />
<Skeleton variant="text" />
</LoadingItem>
</div>
))}
</LoadingIemsWrapper>
</div>
}
noOptionsText={emptyText ?? translate('text_623b3acb8ee4e000ba87d082')}
selectOnFocus={allowAddValue}
Expand Down Expand Up @@ -213,19 +214,3 @@ export const ComboBox = ({
/>
)
}

const LoadingIemsWrapper = styled.div`
margin: ${theme.spacing(4)} 0;
display: flex;
flex-direction: column;
gap: ${theme.spacing(8)};
`

const LoadingItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
margin: 0 ${theme.spacing(2)};
padding: 0 ${theme.spacing(4)};
`
14 changes: 3 additions & 11 deletions src/components/form/ComboBox/ComboBoxInput.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { InputAdornment } from '@mui/material'
import _omit from 'lodash/omit'
import styled from 'styled-components'

import { Button, Typography } from '~/components/designSystem'
import { theme } from '~/styles'
import { tw } from '~/styles/utils'

import { ComboBoxInputProps } from './types'
Expand Down Expand Up @@ -84,10 +82,10 @@ export const ComboBoxInput = ({
),
startAdornment: startAdornmentValue && (
<InputAdornment position="start">
<StartAdornmentTypography noWrap variant="body" color="grey700">
<span>{startAdornmentValue}</span>
<Typography noWrap variant="body" color="grey700">
<span className="mr-2">{startAdornmentValue}</span>
<span></span>
</StartAdornmentTypography>
</Typography>
</InputAdornment>
),
}}
Expand All @@ -96,9 +94,3 @@ export const ComboBoxInput = ({
/>
)
}

const StartAdornmentTypography = styled(Typography)`
> span:first-child {
margin-right: ${theme.spacing(2)};
}
`
6 changes: 1 addition & 5 deletions src/components/form/ComboBox/ComboBoxItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const ComboBoxItem = ({
>
{customValue ? (
<>
<AddCustomValueIcon color="dark" name="plus" />
<Icon className="mr-4" color="dark" name="plus" />
<Typography variant="body" noWrap>
{labelNode ?? label}
</Typography>
Expand Down Expand Up @@ -99,10 +99,6 @@ const ItemWrapper = styled.div`
}
`

const AddCustomValueIcon = styled(Icon)`
margin-right: ${theme.spacing(4)};
`

export const Item = styled.div<{ $virtualized?: boolean }>`
display: flex;
align-items: center;
Expand Down
5 changes: 0 additions & 5 deletions src/components/form/ComboBox/ComboBoxPopperFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,6 @@ const StyledPopper = styled(Popper)<{
.MuiAutocomplete-paper {
border: 1px solid ${theme.palette.grey[200]};
padding: ${theme.spacing(2)} 0;
box-sizing: content-box;
}
> *.combobox-popper--grouped .MuiAutocomplete-paper {
padding: 0 0 ${theme.spacing(2)} 0;
}
`
24 changes: 14 additions & 10 deletions src/components/form/ComboBox/ComboBoxVirtualizedList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ReactElement, useEffect, useRef } from 'react'
import { VariableSizeList } from 'react-window'

import { tw } from '~/styles/utils'

import { ITEM_HEIGHT } from './ComboBoxItem'
import { ComboBoxProps } from './types'

Expand All @@ -25,19 +27,18 @@ type ComboBoxVirtualizedListProps = {
export const ComboBoxVirtualizedList = (props: ComboBoxVirtualizedListProps) => {
const { elements, value } = props

const hasDescription = elements.some(
(el) => (el.props?.children?.props?.option?.description as string)?.length > 0,
)

const itemCount = elements?.length
const elementHeight = hasDescription ? ITEM_HEIGHT + 4 : ITEM_HEIGHT

const getHeight = () => {
const hasAnyGroupHeader = elements.some((el) => (el.key as string).includes(GROUP_ITEM_KEY))

// recommended perf best practice
if (itemCount > 5) {
return 5 * (elementHeight + 4) - 4 // Last item does not have 4px margin-bottom
return 5 * (ITEM_HEIGHT + 4) + 4 // Add 4px for margins
} else if (itemCount <= 2 && hasAnyGroupHeader) {
return itemCount * (ITEM_HEIGHT + 2) // Add 2px for margins
}
return itemCount * (elementHeight + 4) - 4 // Last item does not have 4px margin-bottom
return itemCount * (ITEM_HEIGHT + 4) + 4 // Add 4px for margins
}

// reset the `VariableSizeList` cache if data gets updated
Expand All @@ -63,17 +64,20 @@ export const ComboBoxVirtualizedList = (props: ComboBoxVirtualizedListProps) =>

return (
<VariableSizeList
className={tw({
'mb-1': itemCount > 1,
})}
itemData={elements}
height={getHeight()}
width="100%"
ref={gridRef}
innerElementType="div"
itemSize={(index) => {
return index === itemCount - 1
? elementHeight
? ITEM_HEIGHT
: ((elements[index].key as string) || '').includes(GROUP_ITEM_KEY)
? GROUP_HEADER_HEIGHT + (index === 0 ? 8 : 12)
: elementHeight + 4
? GROUP_HEADER_HEIGHT + (index === 0 ? 2 : 6)
: ITEM_HEIGHT + 8
}}
overscanCount={5}
itemCount={itemCount}
Expand Down
125 changes: 62 additions & 63 deletions src/components/form/ComboBox/ComboboxList.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import _groupBy from 'lodash/groupBy'
import { Children, ForwardedRef, forwardRef, ReactElement, ReactNode, useMemo } from 'react'
import styled, { css } from 'styled-components'
import {
Children,
ForwardedRef,
forwardRef,
PropsWithChildren,
ReactElement,
ReactNode,
useMemo,
} from 'react'

import { Typography } from '~/components/designSystem'
import { theme } from '~/styles'
import { tw } from '~/styles/utils'

import {
ComboBoxVirtualizedList,
GROUP_HEADER_HEIGHT,
GROUP_ITEM_KEY,
} from './ComboBoxVirtualizedList'
import { ComboBoxVirtualizedList, GROUP_ITEM_KEY } from './ComboBoxVirtualizedList'
import { ComboBoxData, ComboBoxProps } from './types'

const randomKey = Math.round(Math.random() * 100000)
Expand All @@ -19,6 +22,27 @@ interface ComboBoxVirtualizedListProps
children: ReactNode
}

const ComboboxListItem = ({
children,
virtualized,
className,
...propsToForward
}: { className?: string } & PropsWithChildren & ComboBoxVirtualizedListProps) => (
<div
className={tw(
'my-1',
{
'not-last:mx-2': virtualized,
'not-last:mx-0': !virtualized,
},
className,
)}
{...propsToForward}
>
{children}
</div>
)

export const ComboboxList = forwardRef(
(
{
Expand All @@ -36,9 +60,9 @@ export const ComboboxList = forwardRef(
if (!isGrouped) {
return Children.toArray(
(children as ReactElement[]).map((item, i) => (
<Item key={`combobox-list-item-${randomKey}-${i}`} {...propsToForward}>
<ComboboxListItem key={`combobox-list-item-${randomKey}-${i}`} {...propsToForward}>
{item}
</Item>
</ComboboxListItem>
)),
)
}
Expand All @@ -60,82 +84,57 @@ export const ComboboxList = forwardRef(
...acc,
isGrouped
? [
// If renderGroupHeader is provided, render the html, otherewise simply render the key
<GroupHeader
// If renderGroupHeader is provided, render the html, otherwise simply render the items
<div
key={`${GROUP_ITEM_KEY}-${key}`}
$isFirst={i === 0}
$virtualized={virtualized}
data-type={GROUP_ITEM_KEY}
className={tw(
'mx-0 my-1 flex h-11 w-[inherit] items-center bg-grey-100 px-6 py-0 shadow-[0px_-1px_0px_0px_#D9DEE7_inset,0px_-1px_0px_0px_#D9DEE7]',
{
'!mt-0': i === 0,
'mb-1': virtualized,
'sticky top-0 z-toast': !virtualized,
},
)}
>
{(!!renderGroupHeader && (renderGroupHeader[key] as ReactNode)) || (
<Typography noWrap>{key}</Typography>
)}
</GroupHeader>,
</div>,
]
: [],
...(groupedBy[key] as ReactElement[]).map((item, j) => (
<Item key={`combobox-list-item-${randomKey}-${i}-${j}`} {...propsToForward}>
<ComboboxListItem
key={`combobox-list-item-${randomKey}-${i}-${j}`}
className={tw({
'mt-1': i === 0 && !isGrouped,
})}
{...propsToForward}
>
{item}
</Item>
</ComboboxListItem>
)),
]
}, []),
)
}, [isGrouped, renderGroupHeader, children, propsToForward, virtualized])

return (
<Container ref={ref} role="listbox" $virtualized={virtualized}>
<div
className={tw('relative max-h-[inherit] overflow-auto pb-0', {
'overflow-hidden': virtualized,
})}
ref={ref}
role="listbox"
>
{virtualized ? (
<ComboBoxVirtualizedList value={value} elements={htmlItems as ReactElement[]} />
) : (
htmlItems
)}
</Container>
</div>
)
},
)

ComboboxList.displayName = 'ComboboxList'

const Item = styled.div``

const Container = styled.div<{ $virtualized?: boolean }>`
max-height: inherit;
position: relative;
padding-bottom: 0;
box-sizing: border-box;
overflow: ${({ $virtualized }) => ($virtualized ? 'hidden' : 'auto')};
.MuiAutocomplete-listbox {
max-height: inherit;
}
${Item}:not(:last-child) {
margin: ${({ $virtualized }) =>
$virtualized ? `0 ${theme.spacing(2)}` : `0 0 ${theme.spacing(1)}`};
}
`

const GroupHeader = styled.div<{ $isFirst?: boolean; $virtualized?: boolean }>`
height: ${GROUP_HEADER_HEIGHT}px;
display: flex;
width: inherit;
align-items: center;
padding: 0 ${theme.spacing(6)};
background-color: ${theme.palette.grey[100]};
box-sizing: border-box;
box-shadow:
${theme.shadows[7]},
0px -1px 0px 0px ${theme.palette.divider};
${({ $virtualized, $isFirst }) =>
!$virtualized
? css`
z-index: ${theme.zIndex.dialog + 2};
position: sticky;
top: 0;
margin: ${$isFirst ? 0 : theme.spacing(2)} 0 ${theme.spacing(2)};
`
: css`
margin: ${$isFirst ? 0 : theme.spacing(1)} 0 ${theme.spacing(2)};
`};
`
4 changes: 0 additions & 4 deletions src/components/form/MultipleComboBox/MultipleComboBoxList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@ const Container = styled.div<{ $virtualized?: boolean }>`
box-sizing: border-box;
overflow: ${({ $virtualized }) => ($virtualized ? 'hidden' : 'auto')};
.MuiAutocomplete-listbox {
max-height: inherit;
}
${Item}:not(:last-child) {
margin: ${({ $virtualized }) =>
$virtualized ? `0 ${theme.spacing(2)}` : `0 0 ${theme.spacing(1)}`};
Expand Down
1 change: 1 addition & 0 deletions src/styles/muiTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ export const theme = createTheme({
display: 'flex',
flexDirection: 'column',
gap: '4px',
maxHeight: 'inherit',
padding: 0,
},
root: {
Expand Down
Loading

0 comments on commit efa35ac

Please sign in to comment.