UNPKG

@atlaskit/pragmatic-drag-and-drop-react-drop-indicator

Version:

An optional Pragmatic drag and drop package containing react components that provide a visual indication about what the user will achieve when the drop (eg lines)

378 lines (276 loc) 12.9 kB
--- order: 0 title: React drop indicator description: An optional package containing react components that provide a visual indication about what the user will achieve when the drop (eg lines) --- import SectionMessage from '@atlaskit/section-message'; import TreeItemExample from '../../examples/constellation/tree-item'; import { TypeExample } from '../../examples/constellation/box/box-type'; import { AppearanceExample } from '../../examples/constellation/box/box-appearance'; import { EdgeExample } from '../../examples/constellation/box/box-edge'; import { GapExample } from '../../examples/constellation/box/box-gap'; import { OverlapExample } from '../../examples/constellation/box/box-overlap'; import { IndentExample } from '../../examples/constellation/box/box-indent'; import { ListItemVerticalExample } from '../../examples/constellation/list-item/list-item-vertical'; import { ListItemHorizontalExample } from '../../examples/constellation/list-item/list-item-horizontal'; import { BorderShowcaseExample } from '../../examples/constellation/border/border-showcase'; import { GroupExample } from '../../examples/constellation/group/group-list'; This package contains `react` components to display hitbox information, which is usually generated by our optional [hitbox package](/components/pragmatic-drag-and-drop/optional-packages/hitbox). You are also able to create your own drop indicators to render hitbox information. These packages embody our [design guidelines](/components/pragmatic-drag-and-drop/design-guidelines). <SectionMessage> You are welcome to reimplement this small optional package using your own tech stack. This package depends on: - [the core package](/components/pragmatic-drag-and-drop/core-package) - [the hitbox package](/components/pragmatic-drag-and-drop/optional-packages/hitbox) - [`react`](https://react.dev/) - [`@atlaskit/tokens`](/components/tokens) for colors - [`compiled`](https://compiledcssinjs.com/) for styling [Compiled](https://compiledcssinjs.com/) is a styling solution with a tiny runtime (`~260B`) that you can use along side your existing styling solution(s). As long as your bundler supports CSS imports (eg `import './styles.css'`) then you shouldn't need to do anything! [More information for if you run into any difficulties](https://community.developer.atlassian.com/t/rfc-73-migrating-our-components-to-compiled-css-in-js/85953). </SectionMessage> <SectionMessage title="For Atlassians" appearance='information'> Please avoid creating your own drop indicators and use our provided ones. This helps us improve user experience consistency and reduce bundle sizes. </SectionMessage> <SectionMessage title="React 19" appearance='information'> We expect this package to work with React 19 and have included it in the supported peer dependency range. We do not currently test against React 19 directly, so there is a small risk of issues. If you have any problems, please raise an issue on [Github](https://github.com/atlassian/pragmatic-drag-and-drop) and we can look into it. </SectionMessage> ## `position: relative` Generally speaking, all drop indicators leverage `position:absolute` and will be positioned relative to the closest parent with `position:relative` (or any other [non-static position](https://developer.mozilla.org/en-US/docs/Web/CSS/position)). The exception to this is the [group drop indicator](#group). ```tsx const itemStyles = css({ position: 'relative', padding: token('space.100'), backgroundColor: token('elevation.surface'), }); export function Item({ content, instruction, }: { content: ReactNode; instruction: Instruction | null; }) { return ( <div css={itemStyles}> <span>{content}</span> {instruction && <DropIndicator edge={instruction} />} </div> ); } ``` ## List item <SectionMessage> [`position: relative` needed on parent](#position-relative) </SectionMessage> A drop indicator to draw a drop indicator for list item operations (`"reorder-before"`, `"combine"` and `"reorder-after"`). This output is commonly used in to visualize the [list item hitbox](/components/pragmatic-drag-and-drop/optional-packages/hitbox) results. ```ts import { DropIndicator } from '@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/list-item'; ``` This drop indicator will internally render either a [box drop indicator](#box) for `"reorder-before"` and `"reorder-after"` operations, and a [border drop indicator](#border) for `"combine"`. #### Props - `instruction`: the `Instruction` to be rendered. [See hitbox for details on instructions](/components/pragmatic-drag-and-drop/optional-packages/hitbox#list-item) - `lineGap`: the gap between list items ([see box > gap](#gap)) - `lineType`: the variant of lines to be used ([see box > type](#type)) #### Vertical list <Example Component={ListItemVerticalExample} appearance="showcase-only" /> #### Horizontal list <Example Component={ListItemHorizontalExample} appearance="showcase-only" /> ## Box <SectionMessage> [`position: relative` needed on parent](#position-relative) </SectionMessage> A drop indicator to draw a line on an `Edge` (`top`, `right`, `bottom`, `left`). This output is commonly used in to visualize the [box hitbox](/components/pragmatic-drag-and-drop/optional-packages/hitbox) results. ```ts import { DropIndicator } from '@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box'; ``` If the element you want to position against has a `border`, you will need to wrap the element in another element with `position:relative` for `gap` to work correctly. [Information about `gap`](#gap). ```tsx // position:relative on the parent of the element with a border const relativeStyles = css({ position: 'relative', }); const itemStyles = css({ padding: token('space.100'), backgroundColor: token('elevation.surface'), // Our item has a border borderWidth: token('border.width'), borderStyle: 'solid', borderColor: token('color.border'), }); export function Item({ content, closestEdge }: { content: ReactNode; closestEdge: Edge | null }) { return ( <div css={relativeStyles}> <div css={itemStyles}> <span>{content}</span> {closestEdge && <DropIndicator edge={closestEdge} />} </div> </div> ); } ``` ### Edge Drop indicators are rendered on for a provided `Edge` (`top`, `right`, `bottom`, `left`) <Example Component={EdgeExample} appearance="showcase-only" /> In order to help people be successful with performance, we have made `edge` a _required_ prop. This will force you to only render the indicator when it is relevant (minimizing `react` and DOM nodes) ```tsx export function Item() { const ref = useRef<HTMLDivElement | null>(null); const [closestEdge, setClosestEdge] = useState<Edge | null>(null); useEffect(() => { const el = ref.current; invariant(el); return combine( draggable({ element: el, }), dropTargetForElements({ element: el, // just being simple and always using the 'bottom' edge // ideally this is set using our `@atlaskit/pragmatic-drag-and-drop-hitbox/box` package onDragStart: () => setClosestEdge('bottom'), onDragEnter: () => setClosestEdge('bottom'), onDragLeave: () => setClosestEdge(null), onDrop: () => setClosestEdge(null), }), ); }, []); return ( <div css={relativeStyles} ref={ref}> <div css={itemStyles}>item A</div> {/* DropIndicator is being conditionally rendered */} {closestEdge && <DropIndicator edge={closestEdge} />} </div> ); } ``` ### Type ##### `"terminal"` - display a terminal (circle with a whole in it) at the start of the line - half the size of the terminal will "bleed out" of the containing element - **first** preference for Atlassian experiences ##### `"terminal-no-bleed"` - display a terminal (circle with a whole in it) at the start of the line - the terminal will _not_ "bleed out" of the containing element - this is useful in situations where the terminal cannot bleed out (such as when inside scroll containers with no padding) - **second** preference for Atlassian experiences ##### `"no-terminal"` - display a full width line with no terminal - **third** preference for Atlassian experiences <Example Component={TypeExample} appearance="showcase-only" /> ### Appearance The drop indicator can be a `"standard"` (blue) line or a `"warning"` (yellow) line. <Example Component={AppearanceExample} appearance="showcase-only" /> ### Gap Often you have drop targets that are separated by a gap (eg in a list). You can provide this `gap` to the drop indicator and the drop indicator will be rendered in the middle of the gap. <SectionMessage> If you want to continue to show a drop indicator when _between_ drop targets, it is also important that you enable [stickiness for your drop targets](https://atlassian.design/components/pragmatic-drag-and-drop/core-package/drop-targets#getissticky). </SectionMessage> <Example Component={GapExample} appearance="showcase-only" /> `gap` is helpful to ensure that the drop indicator stays in the same visual position when moving from one `Edge` of a drop target to another `Edge` of another drop target when there is space between the drop targets. <Example Component={OverlapExample} appearance="showcase-only" /> ### Indent For some experiences, you may need to indent the drop indicator. An example of this is in a tree interface where the drop indicator can shift based on which level the item belongs to. <Example Component={IndentExample} appearance="showcase-only" /> ## Border <SectionMessage> [`position: relative` needed on parent](#position-relative) </SectionMessage> A drop indicator which draws a border around an element. Right now, this component uses `position:absolute` rather than being a true border in order to support `tree-item` indenting. So as with the other drop indicators, this component will be rendered relative to the closest `position:relative` parent. #### Props - `appearance`: `'default' | 'warning'`: used to control the color of the border - `indent`: can be used to indent the border (needed for `tree-item`s). <Example Component={BorderShowcaseExample} appearance="showcase-only" /> ## Group Our group drop indicator is helpful to indicate that you are over a group of items. It is currently limited in the amount of control it provides. Unlike all other drop indicators: - The `GroupDropIndicator` does not need a `position:relative` parent (it wraps DOM elements) - It is always rendered, and it's visibility is toggled with the `isActive` prop. - We've found it's often helpful to make a `GroupDropIndicator` a drop target - `GroupDropIndicator` exposes a `ref` so you can wire it up to be a drop target. <Example Component={GroupExample} appearance="showcase-only" /> ```tsx import { GroupDropIndicator } from '@atlassian/pragmatic-drag-and-drop-react-drop-indicator/group'; function isInnerMostGroup({ location, self }: ElementDropTargetEventBasePayload): boolean { const [innerMost] = location.current.dropTargets.filter( (dropTarget) => dropTarget.data.type === 'menu-item-group', ); return innerMost?.element === self.element; } export function MyMenuItemGroup() { const [isInnerMostOver, setIsInnerMostOver] = useState<boolean>(false); const ref = useRef<HTMLDivElement | null>(null); useEffect(() => { const element = ref.current; invariant(element); return dropTargetForElements({ element, canDrop: ({ source }) => source.data.type === 'menu-item', getData: () => ({ type: 'menu-item-group' }), onDragStart: (args) => setIsInnerMostOver(isInnerMostGroup(args)), onDropTargetChange: (args) => setIsInnerMostOver(isInnerMostGroup(args)), onDrop: () => setIsInnerMostOver(false), }); }, []); return ( <GroupDropIndicator isActive={isInnerMostOver} ref={ref}> <OurLinkMenuItem /> <OurButtonItem /> <OurFlyoutItem /> <OurExpandableItem /> </GroupDropIndicator> ); } ``` ## Tree item <SectionMessage appearance="warning"> The usage of tree item is no longer encouraged. Please now use our more flexible [list item](#list-item) which can also be used to power trees. </SectionMessage> <SectionMessage> [`position: relative` needed on parent](#position-relative) </SectionMessage> A drop indicator used to visualize a [tree item hitbox `Instruction`](/components/pragmatic-drag-and-drop/optional-packages/hitbox). - A line is used to indicate reordering - A border is used to indicate that an item will be made a child (`"make-child"`) - Blocked instructions are drawn in a warning color. It was an intentional choice to provide users with a visual indication that an `instruction` is blocked during a drag. ```ts import { DropIndicator } from '@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/tree-item'; ``` <Example Component={TreeItemExample} appearance="showcase-only" />