diff --git a/packages/react/.storybook/story-config.ts b/packages/react/.storybook/story-config.ts index 6cf57714..cfb0be8f 100644 --- a/packages/react/.storybook/story-config.ts +++ b/packages/react/.storybook/story-config.ts @@ -69,6 +69,7 @@ export type Stories = | 'DataGrid' | 'Dialog' | 'Divider' + | 'DnD' | 'Drawer' | 'Fab' | 'Footer' @@ -246,6 +247,9 @@ const StoryConfig: StorybookConfig = { Divider: { hierarchy: `${StorybookCategories.DataDisplay}/Divider`, }, + DnD: { + hierarchy: `${StorybookCategories.Utils}/Drag & Drop`, + }, Drawer: { hierarchy: `${StorybookCategories.Navigation}/Drawer`, }, diff --git a/packages/react/src/components/dnd/DnD.stories.mdx b/packages/react/src/components/dnd/DnD.stories.mdx new file mode 100644 index 00000000..b307356d --- /dev/null +++ b/packages/react/src/components/dnd/DnD.stories.mdx @@ -0,0 +1,282 @@ +import {ArgsTable, Source, Story, Canvas, Meta} from '@storybook/addon-docs'; +import {useArgs} from '@storybook/client-api'; +import dedent from 'ts-dedent'; +import DnDProvider from './DnDProvider.tsx'; +import DraggableNode from './DraggableNode.tsx'; +import DroppableContainer from './DroppableContainer.tsx'; +import StoryConfig from '../../../.storybook/story-config.ts'; +import Avatar from '../Avatar/Avatar.tsx'; +import Box from '../Box/Box.tsx'; +import Card from '../Card/Card.tsx'; +import Paper from '../Paper/Paper.tsx'; +import Stack from '../Stack/Stack.tsx'; +import Typography from '../Typography/Typography.tsx'; + +export const meta = { + component: DnDProvider, + title: StoryConfig.DnD.hierarchy, +}; + + + +export const Team1Initial = [ + { + avatar: 'https://avatar.vercel.sh/johndoe', + designation: 'Engineering Manager', + id: '12349823nsd9234', + name: 'John Doe', + team: 'team1', + }, + { + avatar: 'https://avatar.vercel.sh/janesmith', + designation: 'Technical Lead', + id: '12349823nsd9237', + name: 'Jane Smith', + team: 'team1', + }, + { + avatar: 'https://avatar.vercel.sh/alicejohnson', + designation: 'Associate Software Engineer', + id: '12349823nsd9238', + name: 'Alice Johnson', + team: 'team1', + }, + { + avatar: 'https://avatar.vercel.sh/bobbrown', + designation: 'Engineering Intern', + id: '12349823nsd9239', + name: 'Bob Brown', + team: 'team1', + }, + { + avatar: 'https://avatar.vercel.sh/charliewhite', + designation: 'Project Manager', + id: '12349823nsd9240', + name: 'Charlie White', + team: 'team1', + }, + { + avatar: 'https://avatar.vercel.sh/davidblack', + designation: 'Software Engineer', + id: '12349823nsd9241', + name: 'David Black', + team: 'team1', + }, +]; + +export const Team2Initial = [ + { + avatar: 'https://avatar.vercel.sh/evegreen', + designation: 'Senior Software Engineer', + id: '12349823nsd9242', + name: 'Eve Green', + team: 'team2', + }, + { + avatar: 'https://avatar.vercel.sh/frankred', + designation: 'Technical Engineer', + id: '12349823nsd9243', + name: 'Frank Red', + team: 'team2', + }, + { + avatar: 'https://avatar.vercel.sh/graceblue', + designation: 'DevOps Engineer', + id: '12349823nsd9244', + name: 'Grace Blue', + team: 'team2', + }, +]; + +export const Team3Initial = [ + { + avatar: 'https://avatar.vercel.sh/harryyellow', + designation: 'Software Engineer (Frontend)', + id: '12349823nsd9245', + name: 'Harry Yellow', + team: 'team3', + }, + { + avatar: 'https://avatar.vercel.sh/ireneorange', + designation: 'Product Manager', + id: '12349823nsd9246', + name: 'Irene Orange', + team: 'team3', + }, + { + avatar: 'https://avatar.vercel.sh/jackpurple', + designation: 'Senior Software Engineer', + id: '12349823nsd9247', + name: 'Jack Purple', + team: 'team3', + }, +]; + +export const Lanes = [ + { + id: 'team1', + label: 'Team 1', + nodes: Team1Initial, + }, + { + id: 'team2', + label: 'Team 2', + nodes: Team2Initial, + }, + { + id: 'team3', + label: 'Team 3', + nodes: Team3Initial, + }, +]; + +export const Template = () => { + const [runtimeArgs, updateArgs] = useArgs(); + const {lanes} = runtimeArgs; + const handleDragOver = e => { + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + }; + const handleDrop = (e, laneIndex, laneId) => { + e.preventDefault(); + const droppedUser = JSON.parse(e.dataTransfer.getData('application/json')); + const updatedLanes = JSON.parse(JSON.stringify(lanes)); + const droppedUserExistingTeamLaneIndex = updatedLanes.findIndex(lane => lane.id === droppedUser.team); + // Remove the user from their current team's nodes. + if (droppedUserExistingTeamLaneIndex !== -1) { + updatedLanes[droppedUserExistingTeamLaneIndex].nodes = updatedLanes[ + droppedUserExistingTeamLaneIndex + ].nodes.filter(node => node.id !== droppedUser.id); + } + // Add the user to the new team's nodes. + updatedLanes[laneIndex].nodes = [...updatedLanes[laneIndex].nodes, {...droppedUser, team: laneId}]; + updateArgs({...runtimeArgs, lanes: updatedLanes}); + }; + return ( + + + {lanes && + lanes.map((lane, laneIndex) => ( + + {lane.label} + null} + onDrop={e => handleDrop(e, laneIndex, lane.id)} + onDragOver={handleDragOver} + sx={{height: '100%'}} + > + {({nodes, getDragItemProps}) => + nodes.map((node, nodeIndex) => ( + + + + + + {node.name} + {node.designation} + + + + + )) + } + + + ))} + + + ); +}; + +# Drag & Drop + +- [Overview](#overview) +- [Props](#props) +- [Usage](#usage) + +## Overview + +Use the `DnDProvider`, `DraggableNode`, and `DroppableContainer` components to create drag-and-drop interfaces. + +#### DnDProvider + +The `DnDProvider` component is a context provider that wraps the draggable and droppable components. + +#### DraggableNode + +The `DraggableNode` component wraps the draggable content. + +#### DroppableContainer + +The `DroppableContainer` component wraps the droppable content. + + + + {Template.bind({})} + + + +## Props + + + +## Usage + +Wrap the `DnDProvider` component around the `DraggableNode` and `DroppableContainer` components in your components as +follows. + + { + // Handle order change + };\n + const handleDrop = e => { + e.preventDefault(); + const droppedData = JSON.parse(e.dataTransfer.getData('application/json')); + // Handle droppedData if needed. + };\n + const handleDragOver = () => { + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + // Anything else + };\n + return ( + + + {({nodes, getDragItemProps}) => + nodes.map((node, nodeIndex) => ( + + + {/* Content */} + + + )) + } + + + ); +}`} +/> diff --git a/packages/react/src/components/dnd/DnDContext.tsx b/packages/react/src/components/dnd/DnDContext.tsx new file mode 100644 index 00000000..f4ae4ade --- /dev/null +++ b/packages/react/src/components/dnd/DnDContext.tsx @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import {Context, createContext} from 'react'; + +/** + * Props interface of {@link DnDContext} + */ +export type DnDContextProps = { + /** + * Utility function to generate a unique component ID. + */ + generateComponentId: (prefix?: string) => string; + /** + * Node object. + */ + node: any | null; + /** + * Setter for the node object. + * @param node - Node object. + */ + setNode: (node: any) => void; +}; + +/** + * Context object for managing the Drag & Drop context. + * + * Demos: + * + * - [Drag & Drop (Oxygen UI)](https://wso2.github.io/oxygen-ui/react/?path=/docs/navigation-drag-and-drop--overview) + * + * API: + * + * - [DnDContext API (Oxygen UI)](// TODO: TBD) + * + * @remarks + * - ✨ This is a custom context that is not available in the Material-UI library. + * + * @param props - The props for the DnDContext component. + * @returns The rendered DnDContext component. + */ +const DnDContext: Context = createContext({ + generateComponentId: () => '', + node: null, + setNode: () => {}, +}); + +DnDContext.displayName = 'DnDContext'; + +export default DnDContext; diff --git a/packages/react/src/components/dnd/DnDProvider.tsx b/packages/react/src/components/dnd/DnDProvider.tsx new file mode 100644 index 00000000..05fb5e55 --- /dev/null +++ b/packages/react/src/components/dnd/DnDProvider.tsx @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import {PropsWithChildren, ReactElement, useMemo, useState} from 'react'; +import DnDContext, {DnDContextProps} from './DnDContext'; + +/** + * Props interface of {@link DnDProvider} + */ +export type DnDProviderProps = unknown; + +/** + * This component provides Drag & Drop context to its children. + * + * Demos: + * + * - [Drag & Drop (Oxygen UI)](https://wso2.github.io/oxygen-ui/react/?path=/docs/navigation-drag-and-drop--overview) + * + * API: + * + * - [DnDProvider API (Oxygen UI)](// TODO: TBD) + * + * @remarks + * - ✨ This is a custom provider that is not available in the Material-UI library. + * + * @param props - The props for the DnDProvider component. + * @returns The rendered DnDProvider component. + */ +const DnDProvider = ({children}: PropsWithChildren): ReactElement => { + const [node, setNode] = useState(null); + + /** + * Generates a unique component ID for the node. + * @returns Unique component ID. + */ + const generateComponentId = (prefix: string = 'Node'): string => + `dnd-${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`; + + const contextValue: DnDContextProps = useMemo(() => ({generateComponentId, node, setNode}), [node]); + + return {children}; +}; + +export default DnDProvider; diff --git a/packages/react/src/components/dnd/DraggableNode.tsx b/packages/react/src/components/dnd/DraggableNode.tsx new file mode 100644 index 00000000..f90b7e2e --- /dev/null +++ b/packages/react/src/components/dnd/DraggableNode.tsx @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import type {OverridableComponent} from '@mui/material/OverridableComponent'; +import clsx from 'clsx'; +import {DragEvent, forwardRef, ElementType, Ref, ReactElement} from 'react'; +import useDnD from './useDnD'; +import Box from '../Box'; +import type {BoxProps, BoxTypeMap} from '../Box'; +import './draggable-node.scss'; + +/** + * Props interface of {@link DraggableNode} + */ +export type DraggableNodeProps = BoxProps & { + /** + * The node that is being dragged. + */ + node: any; +}; + +/** + * DraggableNode component can be used to make any node draggable within a drag-and-drop context. + * + * Demos: + * + * - [Drag & Drop (Oxygen UI)](https://wso2.github.io/oxygen-ui/react/?path=/docs/navigation-drag-and-drop--overview) + * + * API: + * + * - inherits [Box API](https://mui.com/material-ui/api/box/) + * + * @remarks + * - ✨ This is a custom component that is not available in the Material-UI library. + * - ✔️ Props of the [Box](https://mui.com/material-ui/api/box/) component are also available. + * - ✅ `component` prop is supported. + * - ✅ The `ref` is forwarded to the root element. + * + * @template C - The type of the component. + * @param props - The props for the DraggableNode component. + * @param ref - The ref to be forwarded to the Box component. + * @returns The rendered DraggableNode component. + */ +const DraggableNode: OverridableComponent> = forwardRef( + ( + {className, children, node, ...rest}: DraggableNodeProps, + ref: Ref, + ): ReactElement => { + const {setNode} = useDnD(); + + const onDragStart = (event: DragEvent, draggingNode: any): void => { + setNode(draggingNode); + + const {dataTransfer} = event; + dataTransfer.effectAllowed = 'move'; + dataTransfer.setData('application/json', JSON.stringify(draggingNode)); + }; + + return ( + onDragStart(event, node)} + className={clsx('OxygenDraggableNode-root', className)} + {...rest} + > + {children} + + ); + }, +) as OverridableComponent>; + +export default DraggableNode; diff --git a/packages/react/src/components/dnd/DroppableContainer.tsx b/packages/react/src/components/dnd/DroppableContainer.tsx new file mode 100644 index 00000000..cf7075a9 --- /dev/null +++ b/packages/react/src/components/dnd/DroppableContainer.tsx @@ -0,0 +1,220 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import {OverridableComponent} from '@mui/material/OverridableComponent'; +import { + ElementType, + forwardRef, + MutableRefObject, + ReactElement, + ReactNode, + Ref, + useEffect, + useRef, + useState, +} from 'react'; +import Box from '../Box'; +import type {BoxProps, BoxTypeMap} from '../Box'; +import './droppable-container.scss'; + +type DroppableNode = any; + +/** + * Props interface of {@link DroppableContainer} + */ +export type DroppableContainerProps = BoxProps & { + /** + * Function to render the children of the droppable container. + * + * @param props - The props to be passed to the children. + * @returns The rendered children. + */ + children: (props: { + dragHandlers: DragHandlers; + getDragItemProps: GetDragItemProps; + nodes: DroppableNode[]; + }) => ReactNode; + /** + * The nodes to be rendered inside the droppable container. + */ + nodes: DroppableNode[]; + /** + * Callback to handle the order change of nodes. + * + * @param newOrder - The new order of nodes. + */ + onOrderChange?: (newOrder: DroppableNode[]) => void; +}; + +/** + * Interface for the props of a draggable item. + */ +export interface DragItemProps { + /** + * The class name to be applied to the draggable item. + */ + className?: string; + /** + * Indicates whether the item is draggable. + */ + draggable: boolean; + /** + * Function to handle the drag end event. + */ + onDragEnd: () => void; + /** + * Function to handle the drag enter event. + */ + onDragEnter: () => void; + /** + * Function to handle the drag start event. + */ + onDragStart: () => void; +} + +/** + * Type for the function to get the props of a draggable item. + * + * @param index - The index of the draggable item. + * @returns The props of the draggable item. + */ +export type GetDragItemProps = (index: number) => DragItemProps; + +/** + * Interface for the drag handlers. + */ +export interface DragHandlers { + /** + * Function to handle the drag end event. + */ + onDragEnd: () => void; + /** + * Function to handle the drag enter event. + * + * @param index - The index of the item being dragged over. + */ + onDragEnter: (index: number) => void; + /** + * Function to handle the drag start event. + * + * @param index - The index of the item being dragged. + */ + onDragStart: (index: number) => void; +} + +/** + * DroppableContainer component can be used to handle the re-ordering of nodes within a drag-and-drop context. + * + * Demos: + * + * - [Drag & Drop (Oxygen UI)](https://wso2.github.io/oxygen-ui/react/?path=/docs/navigation-drag-and-drop--overview) + * + * API: + * + * - inherits [Box API](https://mui.com/material-ui/api/box/) + * + * @remarks + * - ✨ This is a custom component that is not available in the Material-UI library. + * - ✔️ Props of the [Box](https://mui.com/material-ui/api/box/) component are also available. + * - ✅ `component` prop is supported. + * - ✅ The `ref` is forwarded to the root element. + * + * @template C - The type of the component. + * @param props - The props for the DroppableContainer component. + * @param ref - The ref to be forwarded to the Box component. + * @returns The rendered DroppableContainer component. + */ +const DroppableContainer: OverridableComponent> = forwardRef( + ( + {nodes, onOrderChange, children, ...rest}: DroppableContainerProps, + ref: Ref, + ): ReactElement => { + const [orderedNodes, setOrderedNodes] = useState([]); + const dragNodeIndex: MutableRefObject = useRef(null); + const dragOverNodeIndex: MutableRefObject = useRef(null); + + /** + * Takes a deep copy of the nodes and sets them as the ordered nodes. + */ + useEffect(() => setOrderedNodes(JSON.parse(JSON.stringify(nodes))), [nodes]); + + /** + * Handles the drag start event. + * + * @param index - The index of the item being dragged. + */ + const handleDragStart = (index: number): void => { + dragNodeIndex.current = index; + }; + + /** + * Handles the drag enter event. + * + * @param index - The index of the item being dragged over. + */ + const handleDragEnter = (index: number): void => { + dragOverNodeIndex.current = index; + }; + + /** + * Handles the drag end event. + */ + const handleDragEnd = (): void => { + if ( + dragNodeIndex.current !== null && + dragOverNodeIndex.current !== null && + dragNodeIndex.current !== dragOverNodeIndex.current + ) { + const updatedNodes: DroppableNode[] = [...orderedNodes]; + const [draggedItem] = updatedNodes.splice(dragNodeIndex.current, 1); + + updatedNodes.splice(dragOverNodeIndex.current, 0, draggedItem); + + setOrderedNodes(updatedNodes); + if (onOrderChange) { + onOrderChange(updatedNodes); + } + } + + dragNodeIndex.current = null; + dragOverNodeIndex.current = null; + }; + + const dragHandlers: DragHandlers = { + onDragEnd: handleDragEnd, + onDragEnter: handleDragEnter, + onDragStart: handleDragStart, + }; + + const getDragItemProps = (index: number): DragItemProps => ({ + className: 'OxygenDraggableItem-root', + draggable: true, + onDragEnd: handleDragEnd, + onDragEnter: () => handleDragEnter(index), + onDragStart: () => handleDragStart(index), + }); + + return ( + + {children({dragHandlers, getDragItemProps, nodes: orderedNodes})} + + ); + }, +) as OverridableComponent>; + +export default DroppableContainer; diff --git a/packages/react/src/components/dnd/draggable-node.scss b/packages/react/src/components/dnd/draggable-node.scss new file mode 100644 index 00000000..586caf30 --- /dev/null +++ b/packages/react/src/components/dnd/draggable-node.scss @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.OxygenDraggableNode-root { + &:hover { + >div { + box-shadow: var(--oxygen-shadows-6); + cursor: move; + } + } +} diff --git a/packages/react/src/components/dnd/droppable-container.scss b/packages/react/src/components/dnd/droppable-container.scss new file mode 100644 index 00000000..75ce376d --- /dev/null +++ b/packages/react/src/components/dnd/droppable-container.scss @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.OxygenDroppableContainer-root { + display: flex; + flex-direction: column; + gap: 8px; + + .OxygenDraggableItem-root { + cursor: grab; + transition: transform 0.2s, background 0.2s; + + &:active { + background: #f9f9f9; + cursor: grabbing; + transform: scale(1.03); + } + } +} diff --git a/packages/react/src/components/dnd/index.ts b/packages/react/src/components/dnd/index.ts new file mode 100644 index 00000000..5eb995e4 --- /dev/null +++ b/packages/react/src/components/dnd/index.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export {default as DnDContext} from './DnDContext'; +export * from './DnDContext'; + +export {default as DnDProvider} from './DnDProvider'; +export * from './DnDProvider'; + +export {default as DroppableContainer} from './DroppableContainer'; +export * from './DroppableContainer'; + +export {default as useDnD} from './useDnD'; +export * from './useDnD'; diff --git a/packages/react/src/components/dnd/useDnD.ts b/packages/react/src/components/dnd/useDnD.ts new file mode 100644 index 00000000..7ced76ec --- /dev/null +++ b/packages/react/src/components/dnd/useDnD.ts @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import {useContext} from 'react'; +import DnDContext, {DnDContextProps} from './DnDContext'; + +/** + * Props interface of {@link useDnD} + */ +export type UseDnDProps = DnDContextProps; + +/** + * Hook that provides Drag & Drop context. + * + * This hook allows elements to access drag-and-drop related data and functions + * provided by the Drag & Drop context. It returns an object containing + * the context values defined in the Drag & Drop context. + * + * @returns An object containing the context values of the Drag & Drop context. + * + * @throws Will throw an error if the hook is used outside of a Drag & Drop provider. + * + * @example + * ```tsx + * const { node, setNode } = useDnD(); + * + * useEffect(() => { + * // Perform drag-and-drop related operations + * }, []); + * ``` + */ +const useDnD = (): UseDnDProps => { + const context: DnDContextProps = useContext(DnDContext); + + if (context === undefined) { + throw new Error('useDnD must be used within a DnDProvider'); + } + + return context; +}; + +export default useDnD; diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index ef397e47..f88b2cb4 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -127,6 +127,8 @@ export * from './DialogContentText'; export {default as DialogTitle} from './DialogTitle'; export * from './DialogTitle'; +export * from './dnd'; + export {default as Divider} from './Divider'; export * from './Divider';