Skip to content

Commit

Permalink
feat: add controlled position prop of the leva panel and add onDrag, …
Browse files Browse the repository at this point in the history
…onDragStart, onDragEnd event handlers (#362)

* feat: add controlled position prop of the leva panel and add onDrag, onDragStart, onDragEnd event handlers

* upd types

* ts: fix types in stories

* Create afraid-cycles-shave.md

Co-authored-by: David Bismut <[email protected]>
  • Loading branch information
sandiiarov and dbismut authored Aug 3, 2022
1 parent 81acf37 commit fd8b07f
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-cycles-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'leva': patch
---

feat: add `onDrag` / `onDragStart` / `onDragEnd` callbacks when dragging Leva panel.
26 changes: 24 additions & 2 deletions packages/leva/src/components/Leva/Filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,26 @@ const FilterInput = React.forwardRef<HTMLInputElement, FilterProps>(({ setFilter

export type TitleWithFilterProps = FilterProps &
FolderTitleProps & {
onDrag: (point: { x?: number | undefined; y?: number | undefined }) => void
onDrag: (point: { x?: number; y?: number }) => void
onDragStart: (point: { x?: number; y?: number }) => void
onDragEnd: (point: { x?: number; y?: number }) => void
title: React.ReactNode
drag: boolean
filterEnabled: boolean
from?: { x?: number; y?: number }
}

export function TitleWithFilter({
setFilter,
onDrag,
onDragStart,
onDragEnd,
toggle,
toggled,
title,
drag,
filterEnabled,
from,
}: TitleWithFilterProps) {
const [filterShown, setShowFilter] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
Expand All @@ -72,7 +78,23 @@ export function TitleWithFilter({
else inputRef.current?.blur()
}, [filterShown])

const bind = useDrag(({ offset: [x, y] }) => onDrag({ x, y }), { filterTaps: true })
const bind = useDrag(
({ offset: [x, y], first, last }) => {
onDrag({ x, y })

if (first) {
onDragStart({ x, y })
}

if (last) {
onDragEnd({ x, y })
}
},
{
filterTaps: true,
from: ({ offset: [x, y] }) => [from?.x || x, from?.y || y],
}
)

useEffect(() => {
const handleShortcut = (event: KeyboardEvent) => {
Expand Down
36 changes: 35 additions & 1 deletion packages/leva/src/components/Leva/LevaRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ export type LevaRootProps = {
* Toggle whether filtering should be enabled or disabled.
*/
filter?: boolean
/**
* The position(x and y coordinates) of the leva panel.
*/
position?: { x?: number; y?: number }
/**
* The callback is called when the leva panel is dragged.
*/
onDrag?: (position: { x?: number; y?: number }) => void
/**
* The callback is called when the leva panel starts to be dragged.
*/
onDragStart?: (position: { x?: number; y?: number }) => void
/**
* The callback is called when the leva panel stops being dragged.
*/
onDragEnd?: (position: { x?: number; y?: number }) => void
}
/**
* If true, the copy button will be hidden
Expand Down Expand Up @@ -128,6 +144,10 @@ const LevaCore = React.memo(
title: undefined,
drag: true,
filter: true,
position: undefined,
onDrag: undefined,
onDragStart: undefined,
onDragEnd: undefined,
},
hideCopyButton = false,
toggled,
Expand All @@ -145,6 +165,14 @@ const LevaCore = React.memo(
const title = typeof titleBar === 'object' ? titleBar.title || undefined : undefined
const drag = typeof titleBar === 'object' ? titleBar.drag ?? true : true
const filterEnabled = typeof titleBar === 'object' ? titleBar.filter ?? true : true
const position = typeof titleBar === 'object' ? titleBar.position || undefined : undefined
const onDrag = typeof titleBar === 'object' ? titleBar.onDrag || undefined : undefined
const onDragStart = typeof titleBar === 'object' ? titleBar.onDragStart || undefined : undefined
const onDragEnd = typeof titleBar === 'object' ? titleBar.onDragEnd || undefined : undefined

React.useEffect(() => {
set({ x: position?.x, y: position?.y })
}, [position, set])

globalStyles()

Expand All @@ -160,13 +188,19 @@ const LevaCore = React.memo(
style={{ display: shouldShow ? 'block' : 'none' }}>
{titleBar && (
<TitleWithFilter
onDrag={set}
onDrag={(point) => {
set(point)
onDrag?.(point)
}}
onDragStart={(point) => onDragStart?.(point)}
onDragEnd={(point) => onDragEnd?.(point)}
setFilter={setFilter}
toggle={(flag?: boolean) => setToggle((t) => flag ?? !t)}
toggled={toggled}
title={title}
drag={drag}
filterEnabled={filterEnabled}
from={position}
/>
)}
{shouldShow && (
Expand Down
4 changes: 2 additions & 2 deletions packages/leva/stories/controlled-inputs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ export const OnChangeAndSet: Story = () => {

useDrag(
({ first, last, offset: [x, y] }) => {
if (first) circleRef.current.style.cursor = 'grabbing'
if (last) circleRef.current.style.removeProperty('cursor')
if (first) circleRef.current!.style.cursor = 'grabbing'
if (last) circleRef.current!.style.removeProperty('cursor')
set({ position: { x, y } })
},
{ target: circleRef }
Expand Down
18 changes: 9 additions & 9 deletions packages/leva/stories/input-options.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ export const Optional = () => {

function A() {
const renderRef = React.useRef(0)
const divRef = React.useRef(null)
const divRef = React.useRef<HTMLDivElement>(null)
renderRef.current++
const data = useControls({
color: {
value: '#f00',
onChange: (v) => {
divRef.current.style.color = v
divRef.current.innerText = `Transient color is ${v}`
divRef.current!.style.color = v
divRef.current!.innerText = `Transient color is ${v}`
},
},
})
Expand Down Expand Up @@ -127,7 +127,7 @@ export const OnChangeWithRender = ({ transient }) => {
color: {
value: '#f00',
onChange: (value) => {
ref.current.innerHTML = value
ref.current!.innerHTML = value
},
transient,
},
Expand All @@ -149,16 +149,16 @@ OnChangeWithRender.args = {
OnChangeWithRender.storyName = 'onChange With Render'

export const OnChangeFromPanel = () => {
const ref = React.useRef<HTMLDivElement>()
const ref = React.useRef<HTMLDivElement>(null)
const [, set] = useControls(() => ({
value: {
value: 0.1,
optional: true,
onChange: (value, path, context) => {
const node = window.document.createElement('pre')
node.innerText = JSON.stringify({ value, path, context })
ref.current.appendChild(node)
ref.current.scrollTop = ref.current.scrollHeight
ref.current!.appendChild(node)
ref.current!.scrollTop = ref.current!.scrollHeight
},
},
}))
Expand Down Expand Up @@ -190,7 +190,7 @@ export const EnforceInputType = () => {

export const OnEditStartOnEditEnd = () => {
const [isEditing, setIsEditing] = React.useState(0)
const [editedInput, setEditedInput] = React.useState<{ value: any; path: string }>(null)
const [editedInput, setEditedInput] = React.useState<{ value: any; path: string } | null>(null)

const onEditStart = (value, path, context) => {
setIsEditing((i) => i + 1)
Expand Down Expand Up @@ -218,7 +218,7 @@ export const OnEditStartOnEditEnd = () => {
<pre>
{isEditing === 0
? 'Not Editing'
: `Editing ${editedInput.path} with initial value ${String(editedInput.value)}`}
: `Editing ${editedInput!.path} with initial value ${String(editedInput!.value)}`}
</pre>
</div>
)
Expand Down
9 changes: 8 additions & 1 deletion packages/leva/stories/panel-options.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ export const Filter: Story<any> = (args, context) => {
}
Filter.args = { filter: true }

export const PositionControlled: Story<any> = (args, context) => {
const [{ position }, set] = useControls(() => ({ position: { x: -50, y: 50 } }))

return Template({ titleBar: { drag: args.drag, position, onDrag: (point) => set({ position: point }) } }, context)
}
PositionControlled.args = { drag: true }

const Component = () => {
const values = useControls({ value: 3 })
return (
Expand All @@ -83,7 +90,7 @@ const Component = () => {
)
}

export const neverHide: Story<any> = () => {
export const NeverHide: Story<any> = () => {
const [shown, setShown] = React.useState(true)
return (
<div>
Expand Down

1 comment on commit fd8b07f

@vercel
Copy link

@vercel vercel bot commented on fd8b07f Aug 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

leva – ./

leva-git-main-pmndrs.vercel.app
leva-pmndrs.vercel.app
leva.pmnd.rs
leva.vercel.app

Please sign in to comment.