Skip to content

Commit

Permalink
feat(list): use list context on Table component
Browse files Browse the repository at this point in the history
Signed-off-by: Alexandre Philibeaux <[email protected]>
  • Loading branch information
philibea committed Dec 26, 2024
1 parent a182298 commit 3eaf269
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 405 deletions.
2 changes: 1 addition & 1 deletion packages/ui/src/components/Checkbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
const uniqId = useId()
const localId = id ?? uniqId

const isDisabled = disabled || progress ? true : false
const isDisabled = disabled || progress
/**
* note: checked should be true | undefined as it's seems to break e2e playwright test actually
* as the checked attribue is added to html, maybe related to playwright toCheck code || emotion should not add this attributes when it's false or react 19 related ?
Expand Down
73 changes: 49 additions & 24 deletions packages/ui/src/components/List/ListContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type ListContextValue = {
registerSelectableRow: (rowId: string) => () => void
allRowSelectValue: ComponentProps<typeof Checkbox>['checked']
checkboxAllHandler: (event: ChangeEvent<HTMLInputElement>) => void
subscribeHandler: () => void
columns: ColumnProps[]
inRange: string[]
refList: RefObject<HTMLInputElement[]>
Expand Down Expand Up @@ -194,6 +195,7 @@ export const ListProvider = ({
)

const [lastCheckedIndex, setLastCheckedIndex] = useState<null | number>(null)
const [isShiftEvent, setIsShiftEvent] = useState(false)
const [inRange, setInRange] = useState<string[]>([])

const checkboxAllHandler: ListContextValue['checkboxAllHandler'] =
Expand All @@ -210,20 +212,18 @@ export const ListProvider = ({
}
}, [allRowSelectValue, unselectAll, selectAll])

useEffect(() => {
const subscribeHandler = useCallback(() => {
const handlers: (() => void)[] = []
if (refList.current) {
const handleClick = (index: number, isShiftPressed: boolean) => {
if (index !== 0) {
setLastCheckedIndex(index)
// Only handle shift click event
// Only handle shift click event, onChangeHandler will controle naturals events
if (isShiftPressed && lastCheckedIndex !== null) {
setIsShiftEvent(true)
const start = Math.min(lastCheckedIndex, index)
const end = Math.max(lastCheckedIndex, index)

const newSelectedRowIds = {
...selectedRowIds,
}
const newSelectedRowIds = structuredClone(selectedRowIds)

for (let i = start; i <= end; i += 1) {
const checkbox = refList.current[i]
Expand All @@ -236,7 +236,10 @@ export const ListProvider = ({
}
}
}

setSelectedRowIds(newSelectedRowIds)
setInRange([])

if (onSelectedChange) {
onSelectedChange(
Object.keys(newSelectedRowIds).filter(
Expand All @@ -246,60 +249,77 @@ export const ListProvider = ({
}
}
} else setLastCheckedIndex(null)

// clean up
setIsShiftEvent(false)
}

const handleHover = (
index: number,
isShiftPressed: boolean,
leaving: boolean,
) => {
const newRange: string[] = []

if (isShiftPressed && lastCheckedIndex !== null) {
const start = Math.min(lastCheckedIndex, index)
const end = Math.max(lastCheckedIndex, index)
const end = Math.max(lastCheckedIndex, index + 1)

const newRange: string[] = []

for (let i = start; i < end; i += 1) {
const checkbox = refList.current[i]
if (!checkbox.disabled && !leaving) {
newRange.push(checkbox.value)
}
}
setInRange([...new Set(newRange)])
}

if (!lastCheckedIndex) {
if (index < refList.current.length && index > 0) {
setLastCheckedIndex(index)
}
}
setInRange(newRange)
}

const handleOnChange = (index: number, checked: boolean) => {
const checkbox = refList.current[index]
const checkboxValue = checkbox.value
if (checked) selectRow(checkboxValue)
else unselectRow(checkboxValue)
const handleOnChange = (index: number) => {
// if it's shiftEvent it's control by clickEvent
if (!isShiftEvent) {
const checkbox = refList.current[index]
if (checkbox.checked) {
unselectRow(checkbox.value)
} else {
selectRow(checkbox.value)
}
setLastCheckedIndex(index)
setInRange([])
}
}

refList.current.forEach((checkbox, index) => {
const clickHandler = (event: MouseEvent) =>
const clickHandler = (event: MouseEvent) => {
handleClick(index, event.shiftKey)
}

const hoverEnteringHandler = (event: MouseEvent) =>
const mouseEnterHandler = (event: MouseEvent) =>
handleHover(index, event.shiftKey, false)

const hoverLeavingHandler = (event: MouseEvent) =>
const mouseOutHandler = (event: MouseEvent) =>
handleHover(index, event.shiftKey, true)

const changeHandler = (event: Event) => {
handleOnChange(index, (event.target as HTMLInputElement).checked)
const changeHandler = () => {
handleOnChange(index)
}

checkbox.addEventListener('click', clickHandler)
checkbox.addEventListener('change', changeHandler)
checkbox.addEventListener('mousemove', hoverEnteringHandler)
checkbox.addEventListener('mouseout', hoverLeavingHandler)
checkbox.addEventListener('mouseenter', mouseEnterHandler)
checkbox.addEventListener('mouseout', mouseOutHandler)

handlers.push(() => {
checkbox.removeEventListener('click', clickHandler)
checkbox.removeEventListener('change', changeHandler)
checkbox.removeEventListener('mousemove', hoverEnteringHandler)
checkbox.removeEventListener('mouseout', hoverLeavingHandler)
checkbox.removeEventListener('mouseenter', mouseEnterHandler)
checkbox.removeEventListener('mouseout', mouseOutHandler)
})
})
}
Expand All @@ -313,12 +333,16 @@ export const ListProvider = ({
selectedRowIds,
unselectRow,
selectRow,
isShiftEvent,
])

useEffect(subscribeHandler, [subscribeHandler])

const value = useMemo<ListContextValue>(
() => ({
allRowSelectValue,
checkboxAllHandler,
subscribeHandler,
collapseRow,
columns,
expandButton,
Expand All @@ -338,6 +362,7 @@ export const ListProvider = ({
[
allRowSelectValue,
checkboxAllHandler,
subscribeHandler,
collapseRow,
columns,
expandButton,
Expand Down
13 changes: 1 addition & 12 deletions packages/ui/src/components/List/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,6 @@ export const Row = forwardRef<HTMLTableRowElement, RowProps>(
collapseRow,
registerSelectableRow,
selectedRowIds,
selectRow,
unselectRow,
expandButton,
refList,
inRange,
Expand All @@ -230,14 +228,6 @@ export const Row = forwardRef<HTMLTableRowElement, RowProps>(

const checkboxRef = useRef<HTMLInputElement>(null)

const onCheckboxHandler = () => {
if (selectedRowIds[id]) {
unselectRow(id)
} else {
selectRow(id)
}
}

const isSelectDisabled =
disabled || (selectDisabled !== undefined && selectDisabled !== false)

Expand Down Expand Up @@ -332,9 +322,8 @@ export const Row = forwardRef<HTMLTableRowElement, RowProps>(
checked={selectedRowIds[id]}
value={id}
ref={checkboxRef}
onChange={onCheckboxHandler}
disabled={isSelectDisabled}
inRange={inRange.includes(id)}
inRange={inRange?.includes(id)}
/>
</Tooltip>
</StyledCheckboxContainer>
Expand Down
9 changes: 5 additions & 4 deletions packages/ui/src/components/List/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import styled from '@emotion/styled'
import type { Dispatch, ForwardedRef, ReactNode, SetStateAction } from 'react'
import type { Dispatch, ReactNode, SetStateAction } from 'react'
import { forwardRef } from 'react'
import { Cell } from './Cell'
import { HeaderCell } from './HeaderCell'
Expand All @@ -25,6 +25,7 @@ const StyledTable = styled.table`
position: relative;
`

// TODO: Get type optional type from omit values of ListContext
type ListProps = {
expandable?: boolean
selectable?: boolean
Expand All @@ -44,7 +45,7 @@ type ListProps = {
onSelectedChange?: Dispatch<SetStateAction<string[]>>
}

const BaseList = forwardRef(
const BaseList = forwardRef<HTMLTableElement, ListProps>(
(
{
expandable = false,
Expand All @@ -54,8 +55,8 @@ const BaseList = forwardRef(
loading,
autoCollapse = false,
onSelectedChange,
}: ListProps,
ref: ForwardedRef<HTMLTableElement>,
},
ref,
) => (
<ListProvider
selectable={selectable}
Expand Down
7 changes: 6 additions & 1 deletion packages/ui/src/components/Table/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export const Row = ({
inRange,
columns,
} = useTableContext()

const checkboxRowRef = useRef<HTMLInputElement>(null)

const hasExpandable = !!expandable
Expand Down Expand Up @@ -160,6 +161,10 @@ export const Row = ({
if (refAtEffectStart && current && !refAtEffectStart.includes(current)) {
refList.current.push(current)
}

return () => {
// TODO should add a clean up function to remove the ref of the currentList maybe find an other way of getting the checkbox refList
}
}, [refList])

const theme = useTheme()
Expand Down Expand Up @@ -194,7 +199,7 @@ export const Row = ({
value={id}
disabled={selectDisabled !== undefined}
ref={checkboxRowRef}
inRange={inRange.includes(id)}
inRange={inRange?.includes(id)}
/>
</Tooltip>
</StyledCheckboxContainer>
Expand Down
Loading

0 comments on commit 3eaf269

Please sign in to comment.