Commit e8a4fb4d authored by tom's avatar tom

fix copy to clipboard component

parent 0a79c372
...@@ -72,6 +72,7 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>( ...@@ -72,6 +72,7 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
open={ open } open={ open }
onOpenChange={ isMobile ? undefined : handleOpenChange } onOpenChange={ isMobile ? undefined : handleOpenChange }
closeOnClick={ false } closeOnClick={ false }
closeOnPointerDown={ false }
variant={ variant } variant={ variant }
lazyMount={ lazyMount } lazyMount={ lazyMount }
unmountOnExit={ unmountOnExit } unmountOnExit={ unmountOnExit }
......
import { useCopyToClipboard } from '@uidotdev/usehooks'; import { useCopyToClipboard } from '@uidotdev/usehooks';
import React from 'react'; import React from 'react';
export default function useClipboard(text: string, timeout?: number) { import { SECOND } from 'lib/consts';
const timeoutRef = React.useRef<number | null>(null);
import { useDisclosure } from './useDisclosure';
// NOTE: If you don't need the disclosure and the timeout features, please use the useCopyToClipboard hook directly
export default function useClipboard(text: string, timeout = SECOND) {
const flagTimeoutRef = React.useRef<number | null>(null);
const disclosureTimeoutRef = React.useRef<number | null>(null);
const [ hasCopied, setHasCopied ] = React.useState(false); const [ hasCopied, setHasCopied ] = React.useState(false);
const [ , copyToClipboard ] = useCopyToClipboard(); const [ , copyToClipboard ] = useCopyToClipboard();
const { open, onOpenChange } = useDisclosure();
const copy = React.useCallback(() => { const copy = React.useCallback(() => {
copyToClipboard(text); copyToClipboard(text);
setHasCopied(true); setHasCopied(true);
timeoutRef.current = window.setTimeout(() => {
setHasCopied(false); disclosureTimeoutRef.current = window.setTimeout(() => {
onOpenChange({ open: false });
}, timeout); }, timeout);
}, [ text, copyToClipboard, timeout ]);
// We need to wait for the disclosure to close before setting the flag to false
flagTimeoutRef.current = window.setTimeout(() => {
setHasCopied(false);
}, timeout + 200);
}, [ text, copyToClipboard, timeout, onOpenChange ]);
React.useEffect(() => { React.useEffect(() => {
return () => { return () => {
if (timeoutRef.current) { if (disclosureTimeoutRef.current) {
window.clearTimeout(timeoutRef.current); window.clearTimeout(disclosureTimeoutRef.current);
}
if (flagTimeoutRef.current) {
window.clearTimeout(flagTimeoutRef.current);
} }
}; };
}, []); }, []);
...@@ -26,6 +43,10 @@ export default function useClipboard(text: string, timeout?: number) { ...@@ -26,6 +43,10 @@ export default function useClipboard(text: string, timeout?: number) {
return { return {
hasCopied, hasCopied,
copy, copy,
disclosure: {
open,
onOpenChange,
},
}; };
}, [ hasCopied, copy ]); }, [ hasCopied, copy, open, onOpenChange ]);
} }
...@@ -366,8 +366,11 @@ const semanticTokens: ThemingConfig['semanticTokens'] = { ...@@ -366,8 +366,11 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
error: { value: '{colors.red.500}' }, error: { value: '{colors.red.500}' },
}, },
icon: { icon: {
// TODO @tom2drum revise this colors
backTo: { value: '{colors.gray.400}' }, backTo: { value: '{colors.gray.400}' },
externalLink: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.400}' } }, externalLink: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.400}' } },
content: { value: { _light: '{colors.gray.500}', _dark: '{colors.gray.300}' } },
info: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.500}' } },
}, },
address: { address: {
highlighted: { highlighted: {
......
...@@ -31,6 +31,7 @@ const TriggerButton = ( ...@@ -31,6 +31,7 @@ const TriggerButton = (
const textColor = useColorModeValue('blackAlpha.800', 'whiteAlpha.800'); const textColor = useColorModeValue('blackAlpha.800', 'whiteAlpha.800');
const onFocusCapture = usePreventFocusAfterModalClosing(); const onFocusCapture = usePreventFocusAfterModalClosing();
// TODO @tom2drum remove all such occurrences of useDisclosure
// have to implement controlled tooltip on mobile because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107 // have to implement controlled tooltip on mobile because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107
const { isOpen, onToggle, onClose } = useDisclosure(); const { isOpen, onToggle, onClose } = useDisclosure();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
......
...@@ -23,6 +23,7 @@ import AlertShowcase from 'ui/showcases/Alert'; ...@@ -23,6 +23,7 @@ import AlertShowcase from 'ui/showcases/Alert';
import BadgeShowcase from 'ui/showcases/Badge'; import BadgeShowcase from 'ui/showcases/Badge';
import ButtonShowcase from 'ui/showcases/Button'; import ButtonShowcase from 'ui/showcases/Button';
import CheckboxShowcase from 'ui/showcases/Checkbox'; import CheckboxShowcase from 'ui/showcases/Checkbox';
import ClipboardShowcase from 'ui/showcases/Clipboard';
import DialogShowcase from 'ui/showcases/Dialog'; import DialogShowcase from 'ui/showcases/Dialog';
import InputShowcase from 'ui/showcases/Input'; import InputShowcase from 'ui/showcases/Input';
import LinkShowcase from 'ui/showcases/Link'; import LinkShowcase from 'ui/showcases/Link';
...@@ -54,6 +55,7 @@ const ChakraShowcases = () => { ...@@ -54,6 +55,7 @@ const ChakraShowcases = () => {
<TabsTrigger value="badge">Badge</TabsTrigger> <TabsTrigger value="badge">Badge</TabsTrigger>
<TabsTrigger value="button">Button</TabsTrigger> <TabsTrigger value="button">Button</TabsTrigger>
<TabsTrigger value="checkbox">Checkbox</TabsTrigger> <TabsTrigger value="checkbox">Checkbox</TabsTrigger>
<TabsTrigger value="clipboard">Clipboard</TabsTrigger>
<TabsTrigger value="dialog">Dialog</TabsTrigger> <TabsTrigger value="dialog">Dialog</TabsTrigger>
<TabsTrigger value="input">Input</TabsTrigger> <TabsTrigger value="input">Input</TabsTrigger>
<TabsTrigger value="link">Link</TabsTrigger> <TabsTrigger value="link">Link</TabsTrigger>
...@@ -71,6 +73,7 @@ const ChakraShowcases = () => { ...@@ -71,6 +73,7 @@ const ChakraShowcases = () => {
<BadgeShowcase/> <BadgeShowcase/>
<ButtonShowcase/> <ButtonShowcase/>
<CheckboxShowcase/> <CheckboxShowcase/>
<ClipboardShowcase/>
<DialogShowcase/> <DialogShowcase/>
<InputShowcase/> <InputShowcase/>
<LinkShowcase/> <LinkShowcase/>
......
import { chakra } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { IconButtonProps } from 'toolkit/chakra/icon-button';
import { IconButton } from 'toolkit/chakra/icon-button'; import { IconButton } from 'toolkit/chakra/icon-button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tooltip } from 'toolkit/chakra/tooltip'; import { Tooltip } from 'toolkit/chakra/tooltip';
import useClipboard from 'toolkit/hooks/useClipboard'; import useClipboard from 'toolkit/hooks/useClipboard';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
export interface Props { export interface Props extends Omit<IconButtonProps, 'type' | 'loading'> {
text: string; text: string;
className?: string; type?: 'link' | 'text' | 'share';
isLoading?: boolean; isLoading?: boolean;
onClick?: (event: React.MouseEvent) => void;
size?: number;
type?: 'link';
icon?: IconName;
// TODO @tom2drum check if we need this
visual?: string;
// TODO @tom2drum check if we need this
colorScheme?: string;
} }
const CopyToClipboard = ({ text, className, isLoading, onClick, size = 5, type, icon, colorScheme }: Props) => { const CopyToClipboard = (props: Props) => {
const { hasCopied, copy } = useClipboard(text, 1000); const { text, type = 'text', isLoading, onClick, boxSize = 5, ...rest } = props;
// const [ copiedText, copyToClipboard ] = useCopyToClipboard();
// const [ copied, setCopied ] = useState(false);
// TODO @tom2drum check if we need this
// have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107
const { open, onOpen, onClose } = useDisclosure();
const colorProps = colorScheme ? {} : { color: { _light: 'gray.400', _dark: 'gray.500' } };
const iconName = icon || (type === 'link' ? 'link' : 'copy');
// useEffect(() => { const { hasCopied, copy, disclosure } = useClipboard(text);
// if (hasCopied) {
// setCopied(true);
// } else {
// setCopied(false);
// }
// }, [ hasCopied ]);
const handleClick = React.useCallback((event: React.MouseEvent) => { const handleClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.stopPropagation(); event.stopPropagation();
copy(); copy();
onClick?.(event); onClick?.(event);
}, [ onClick, copy ]); }, [ onClick, copy ]);
if (isLoading) { const tooltipContent = (() => {
return <Skeleton boxSize={ size } className={ className } borderRadius="sm" flexShrink={ 0 } ml={ 2 } display="inline-block" loading/>; if (hasCopied) {
} return 'Copied';
}
if (type === 'link') {
return 'Copy link to clipboard';
}
return 'Copy to clipboard';
})();
const iconName = (() => {
switch (type) {
case 'link':
return 'link';
case 'share':
return 'share';
default:
return 'copy';
}
})();
return ( return (
<Tooltip <Tooltip
content={ hasCopied ? 'Copied' : `Copy${ type === 'link' ? ' link ' : ' ' }to clipboard` } content={ tooltipContent }
contentProps={{ contentProps={{ zIndex: 'tooltip2' }}
zIndex: 'tooltip2', open={ disclosure.open }
}} onOpenChange={ disclosure.onOpenChange }
// open={ hasCopied }
> >
<IconButton <IconButton
{ ...colorProps }
aria-label="copy" aria-label="copy"
boxSize={ size } boxSize={ boxSize }
colorScheme={ colorScheme }
onClick={ handleClick } onClick={ handleClick }
className={ className }
// onMouseEnter={ onOpen }
// onMouseLeave={ onClose }
ml={ 2 } ml={ 2 }
borderRadius="none" borderRadius="sm"
loading={ isLoading }
color="icon.info"
_hover={{ color: 'link.primary.hover' }}
{ ...rest }
> >
<IconSvg name={ iconName } boxSize={ size }/> <IconSvg name={ iconName } boxSize="full"/>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
); );
......
...@@ -8,6 +8,7 @@ import { BACKGROUND_DEFAULT } from 'ui/home/HeroBanner'; ...@@ -8,6 +8,7 @@ import { BACKGROUND_DEFAULT } from 'ui/home/HeroBanner';
import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHeader } from './parts'; import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHeader } from './parts';
// ?? buttons on marketplace card
const ButtonShowcase = () => { const ButtonShowcase = () => {
return ( return (
<Container value="button"> <Container value="button">
......
import React from 'react';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import { Section, Container, SectionHeader, SamplesStack, Sample } from './parts';
const ClipboardShowcase = () => {
return (
<Container value="clipboard">
<Section>
<SectionHeader>Type</SectionHeader>
<SamplesStack>
<Sample label="type: text">
<CopyToClipboard text="Hello, world!" type="text"/>
</Sample>
<Sample label="type: link">
<CopyToClipboard text="Hello, world!" type="link"/>
</Sample>
<Sample label="type: share">
<CopyToClipboard text="Hello, world!" type="share" boxSize={ 4 }/>
</Sample>
</SamplesStack>
</Section>
<Section>
<SectionHeader>Loading</SectionHeader>
<SamplesStack>
<Sample label="loading: true">
<CopyToClipboard text="Hello, world!" type="text" isLoading/>
</Sample>
</SamplesStack>
</Section>
</Container>
);
};
export default React.memo(ClipboardShowcase);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment