Skip to content

Commit

Permalink
next (#26)
Browse files Browse the repository at this point in the history
* feat: 🎸 make item and itemContent props customizable  (#25)

* fix: 🐛 fix type inference for props

Fix the broken type inference caused by using forwardRef. See:
https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/forward_and_create_ref/#generic-forwardrefs

* feat: 🎸 make item and itemContent props customizable

Add getItemProps and getItemContentProps to allow customizing the props
of the wrapper item and the item content

✅ Closes: #21

* refactor: clean code

---------

Co-authored-by: blaiseludvig <[email protected]>
  • Loading branch information
vingeraycn and blaiseludvig authored Nov 27, 2023
1 parent 8c77b05 commit 9288ad9
Show file tree
Hide file tree
Showing 4 changed files with 1,423 additions and 1,584 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"postversion": "git push && git push --tags"
},
"dependencies": {
"react-use": "^17.4.0"
"react-use": "^17.4.0",
"type-fest": "^4.3.2"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.0.0",
Expand Down
68 changes: 61 additions & 7 deletions src/DraggableGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import clsx from 'clsx'
import { forEach, get, lowerFirst, merge, omit, pick } from 'lodash-es'
import Grid, { GridEvents, GridOptions } from 'muuri'
import {
ComponentPropsWithoutRef,
ForwardedRef,
HTMLAttributes,
ReactNode,
Expand All @@ -13,6 +14,7 @@ import {
useRef,
} from 'react'
import { useDeepCompareEffect } from 'react-use'
import { LiteralUnion } from 'type-fest'
import GridController from './GridController'
import {
DRAGGABLE_GRID_EVENT_HANDLER_KEY_LIST,
Expand All @@ -30,6 +32,18 @@ export type DraggableGridHandle = {
container: HTMLDivElement | null
}

// omit key as it is assigned internally
export type GetItemProps<T> = (
itemData: T,
index: number,
) => Omit<ComponentPropsWithoutRef<'div'>, 'key'>

// omit key as it is assigned internally
export type GetItemContentProps<T> = (
itemData: T,
index: number,
) => Omit<ComponentPropsWithoutRef<'div'>, 'key'>

const DEFAULT_GRID_OPTIONS: GridOptions = {
dragEnabled: true,
items: '.ruuri-draggable-item',
Expand Down Expand Up @@ -63,7 +77,7 @@ function unbindEvents(grid: Grid, handlers: GridEventHandler) {
})
}

export interface DraggableGridProps<T = any>
export interface DraggableGridProps<T>
extends Omit<HTMLAttributes<HTMLDivElement>, keyof GridEventHandler | 'children'>,
GridOptions,
GridEventHandler {
Expand All @@ -73,13 +87,31 @@ export interface DraggableGridProps<T = any>
*/
data: T[]

/**
* merges returned props into the wrapper item's props
*
* @param itemData item of the data source
* @param index index of the current item
*/
getItemProps?: GetItemProps<T>

/**
* merges returned props into the item content's props
*
* @param itemData item of the data source
* @param index index of the current item
*/
getItemContentProps?: GetItemContentProps<T>

// Using the LiteralUnion type gives us autocomplete for the
// first depth of keys
/**
* Unique key for every data item in the grid.
* It supports lodash-like properties path names, such as 'content.id', the uni key must be string type.
*
* @default 'id'
*/
uniKey?: string
uniKey?: LiteralUnion<keyof T, string>

/**
* grid item renderer
Expand All @@ -90,8 +122,15 @@ export interface DraggableGridProps<T = any>
renderItem?: (data: T) => ReactNode
}

function DraggableGrid(
{ data, renderItem, uniKey = 'id', ...props }: DraggableGridProps,
function DraggableGrid<T>(
{
data,
getItemProps,
getItemContentProps,
renderItem,
uniKey = 'id',
...props
}: DraggableGridProps<T>,
ref: ForwardedRef<DraggableGridHandle>,
) {
const rootRef = useRef<HTMLDivElement | null>(null)
Expand Down Expand Up @@ -154,11 +193,26 @@ function DraggableGrid(
{...(omit(props, DRAGGABLE_GRID_PROP_KEY_LIST) as unknown as HTMLAttributes<HTMLDivElement>)}
className={clsx('ruuri-draggable-grid', props.className)}
>
{data.map((item) => {
{data.map((item, index) => {
const id = get(item, uniKey)

if (!(typeof id === 'string' || typeof id === 'number')) {
throw new TypeError(
`The value of the ${String(uniKey)} property must be a string or number`,
)
}

const itemProps = getItemProps?.(item, index)
const itemClassName = clsx('ruuri-draggable-item draggable-item', itemProps?.className)

const itemContentProps = getItemContentProps?.(item, index)
const itemContentClassName = clsx('draggable-item-content', itemContentProps?.className)

return (
<div className="ruuri-draggable-item draggable-item" data-ruuri-id={id} key={id}>
<div className="draggable-item-content">{renderItem?.(item)}</div>
<div {...itemProps} className={itemClassName} data-ruuri-id={id} key={id}>
<div {...itemContentProps} className={itemContentClassName}>
{renderItem?.(item)}
</div>
</div>
)
})}
Expand Down
10 changes: 10 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'react'

// Redecalare forwardRef to restore type inference on the forwarded component
// see: https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/forward_and_create_ref/#generic-forwardrefs
declare module 'react' {
// eslint-disable-next-line @typescript-eslint/ban-types
function forwardRef<T, P = {}>(
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null,
): (props: P & React.RefAttributes<T>) => React.ReactElement | null
}
Loading

0 comments on commit 9288ad9

Please sign in to comment.