From 4a4c23eeb3af90685813477317601e6ed75c4d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dtalo=20Andrade?= Date: Sun, 4 Apr 2021 17:38:37 +0200 Subject: [PATCH 1/3] poc: implement #189 --- packages/leva/src/components/Leva/Filter.tsx | 52 ++++++++++++++++- .../leva/src/components/Leva/LevaRoot.tsx | 57 +++++++++++-------- packages/leva/src/components/UI/Label.tsx | 4 +- packages/leva/src/types/public.ts | 2 + packages/leva/src/utils/input.ts | 4 +- .../leva/stories/input-options.stories.tsx | 38 +++++++++++-- 6 files changed, 122 insertions(+), 35 deletions(-) diff --git a/packages/leva/src/components/Leva/Filter.tsx b/packages/leva/src/components/Leva/Filter.tsx index 0df99dfb..511fd05b 100644 --- a/packages/leva/src/components/Leva/Filter.tsx +++ b/packages/leva/src/components/Leva/Filter.tsx @@ -1,9 +1,11 @@ import React, { useMemo, useState, useEffect, useRef } from 'react' import { useDrag } from 'react-use-gesture' -import { debounce } from '../../utils' +import { debounce, LevaErrors, warn } from '../../utils' import { FolderTitleProps } from '../Folder' import { Chevron } from '../UI' import { StyledFilterInput, StyledTitleWithFilter, TitleContainer, Icon, FilterWrapper } from './StyledFilter' +import { useStoreContext } from '../../context' +import { DataInput } from '../../types' type FilterProps = { setFilter: (value: string) => void } @@ -52,6 +54,8 @@ export type TitleWithFilterProps = FilterProps & title: React.ReactNode drag: boolean filterEnabled: boolean + hideCopyButton: boolean + copy?: (values: unknown) => string } export function TitleWithFilter({ @@ -62,9 +66,12 @@ export function TitleWithFilter({ title, drag, filterEnabled, + hideCopyButton, + copy, }: TitleWithFilterProps) { const [filterShown, setShowFilter] = useState(false) const inputRef = useRef(null) + const store = useStoreContext() useEffect(() => { if (filterShown) inputRef.current?.focus() @@ -83,13 +90,35 @@ export function TitleWithFilter({ return () => window.removeEventListener('keydown', handleShortcut) }, []) + const [copied, setCopied] = useState(false) + const handleCopyClick = async () => { + const data: any = store.getData() + try { + for (let key in data) { + if (data.hasOwnProperty(key)) { + const keyData = data[key] as DataInput + if (!keyData.disabled) { + data[key] = keyData.value + } else { + delete data[key] + } + } + } + await navigator.clipboard.writeText(copy ? copy(data) : JSON.stringify(data)) + setCopied(true) + } catch { + warn(LevaErrors.CLIPBOARD_ERROR, data) + } + } + return ( <> - + setCopied(false)}> toggle()}> + {!hideCopyButton &&
} {title === undefined && drag ? ( @@ -103,6 +132,25 @@ export function TitleWithFilter({ title )} + {!hideCopyButton && ( + + {!copied ? ( + + + + + ) : ( + + + + + )} + + )} {filterEnabled && ( setShowFilter((f) => !f)}> diff --git a/packages/leva/src/components/Leva/LevaRoot.tsx b/packages/leva/src/components/Leva/LevaRoot.tsx index 1c62296d..682bb6e9 100644 --- a/packages/leva/src/components/Leva/LevaRoot.tsx +++ b/packages/leva/src/components/Leva/LevaRoot.tsx @@ -61,6 +61,10 @@ export type LevaRootProps = { * If true, the copy button will be hidden */ hideCopyButton?: boolean + /** + * Change what will be copied when clicking the global copy button + */ + copy?: (values: unknown) => string } @@ -97,6 +101,7 @@ const LevaCore = React.memo( filter: true }, hideCopyButton = false, + copy, toggled, setToggle, }: LevaCoreProps) => { @@ -115,31 +120,33 @@ const LevaCore = React.memo( return ( - - {titleBar && ( - setToggle((t) => !t)} - toggled={toggled} - title={title} - drag={drag} - filterEnabled={filterEnabled} - /> - )} - {shouldShow && ( - - - - )} - + + + {titleBar && ( + setToggle((t) => !t)} + toggled={toggled} + title={title} + drag={drag} + filterEnabled={filterEnabled} + hideCopyButton={hideCopyButton} + copy={copy} + /> + )} + {shouldShow && ( + + )} + + ) } diff --git a/packages/leva/src/components/UI/Label.tsx b/packages/leva/src/components/UI/Label.tsx index db99c79f..b04e5df1 100644 --- a/packages/leva/src/components/UI/Label.tsx +++ b/packages/leva/src/components/UI/Label.tsx @@ -45,7 +45,7 @@ function RawLabel(props: LabelProps) { } export function Label({ align, ...props }: LabelProps) { - const { value, label, key } = useInputContext() + const { value, label, key, copy = (key, value) => JSON.stringify({ [key]: value ?? '' }) } = useInputContext() const { hideCopyButton } = usePanelSettingsContext() const copyEnabled = !hideCopyButton && key !== undefined @@ -54,7 +54,7 @@ export function Label({ align, ...props }: LabelProps) { const handleClick = async () => { try { - await navigator.clipboard.writeText(JSON.stringify({ [key]: value ?? '' })) + await navigator.clipboard.writeText(copy(key, value)) setCopied(true) } catch { warn(LevaErrors.CLIPBOARD_ERROR, { [key]: value }) diff --git a/packages/leva/src/types/public.ts b/packages/leva/src/types/public.ts index a5bcc6ee..555f5aa9 100644 --- a/packages/leva/src/types/public.ts +++ b/packages/leva/src/types/public.ts @@ -125,6 +125,7 @@ type GenericSchemaItemOptions = { render?: RenderFn label?: string | JSX.Element hint?: string + copy?: (key: string, value: unknown) => string } export type InputOptions = GenericSchemaItemOptions & { @@ -263,6 +264,7 @@ export type InputContextProps = { id: string label: string | JSX.Element hint?: string + copy: (key: string, value: unknown) => string path: string key: string optional: boolean diff --git a/packages/leva/src/utils/input.ts b/packages/leva/src/utils/input.ts index 071d4fd8..a646e4e6 100644 --- a/packages/leva/src/utils/input.ts +++ b/packages/leva/src/utils/input.ts @@ -38,8 +38,8 @@ export function parseOptions(_input: any, key: string, mergedOptions = {}, custo } // parse generic options from input object - const { render, label, optional, disabled, hint, onChange, ...inputWithType } = _input - const commonOptions = { render, key, label: label ?? key, hint, ...mergedOptions } + const { render, label, optional, disabled, hint, copy, onChange, ...inputWithType } = _input + const commonOptions = { render, key, label: label ?? key, hint, copy, ...mergedOptions } let { type, ...input } = inputWithType type = customType ?? type diff --git a/packages/leva/stories/input-options.stories.tsx b/packages/leva/stories/input-options.stories.tsx index b699c7a0..f5113992 100644 --- a/packages/leva/stories/input-options.stories.tsx +++ b/packages/leva/stories/input-options.stories.tsx @@ -3,7 +3,7 @@ import { Meta } from '@storybook/react' import Reset from './components/decorator-reset' import { Half2Icon, OpacityIcon, DimensionsIcon } from '@radix-ui/react-icons' -import { folder, useControls } from '../src' +import { folder, Leva, useControls } from '../src' export default { title: 'Misc/Input options', @@ -76,14 +76,14 @@ export const Optional = () => { function A() { const renderRef = React.useRef(0) - const divRef = React.useRef(null) + const divRef = React.useRef(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}` }, }, }) @@ -118,3 +118,33 @@ export const OnChange = () => { ) } + +export const CustomCopy = () => { + const { id, label } = useControls({ + id: { + value: 'button-id', + copy(key, value): string { + return `` + } + }, + label: { + value: 'Leva is awesome', + copy(key, value) { + return `` + } + }, + }) + + const handleLevaCopy = (values: any) => { + return `` + } + + return ( +
+ + +
+ ) +} \ No newline at end of file From c4f7fe121bd1ae1988d0ca2a0f4c693edb098ff7 Mon Sep 17 00:00:00 2001 From: David Bismut Date: Tue, 6 Apr 2021 14:44:24 +0200 Subject: [PATCH 2/3] fix: story copy function error --- packages/leva/stories/input-options.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/leva/stories/input-options.stories.tsx b/packages/leva/stories/input-options.stories.tsx index 79a61301..efe03ecb 100644 --- a/packages/leva/stories/input-options.stories.tsx +++ b/packages/leva/stories/input-options.stories.tsx @@ -137,8 +137,8 @@ export const CustomCopy = () => { const handleLevaCopy = (values: any) => { return `` + id="${id}" +>${label}` } return ( From 7a63d43ab991cdb7b991471ab8ecfd5c1baa4704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dtalo=20Andrade?= Date: Fri, 30 Apr 2021 19:07:56 +0200 Subject: [PATCH 3/3] fix: don't mutate the store directly and create a copy of it instead --- packages/leva/src/components/Leva/Filter.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/leva/src/components/Leva/Filter.tsx b/packages/leva/src/components/Leva/Filter.tsx index 620d09e1..e4b9c0aa 100644 --- a/packages/leva/src/components/Leva/Filter.tsx +++ b/packages/leva/src/components/Leva/Filter.tsx @@ -1,9 +1,9 @@ -import React, { useMemo, useState, useEffect, useRef } from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' import { useDrag } from 'react-use-gesture' import { debounce, LevaErrors, warn } from '../../utils' import { FolderTitleProps } from '../Folder' import { Chevron } from '../UI' -import { StyledFilterInput, StyledTitleWithFilter, TitleContainer, Icon, FilterWrapper } from './StyledFilter' +import { FilterWrapper, Icon, StyledFilterInput, StyledTitleWithFilter, TitleContainer } from './StyledFilter' import { useStoreContext } from '../../context' import { DataInput } from '../../types' @@ -93,7 +93,7 @@ export function TitleWithFilter({ const [copied, setCopied] = useState(false) const handleCopyClick = async () => { - const data: any = store.getData() + const data = { ...store.getData() } as any try { for (let key in data) { if (data.hasOwnProperty(key)) {