diff --git a/src/components/ui/Avatar/Avatar.tsx b/src/components/ui/Avatar/Avatar.tsx index 3bdd781f..17d92c9d 100644 --- a/src/components/ui/Avatar/Avatar.tsx +++ b/src/components/ui/Avatar/Avatar.tsx @@ -16,12 +16,11 @@ export type AvatarProps = { const Avatar = ({ customRootClass = '', fallback, className, src, alt, ...props }: AvatarProps) => { return ( - + {fallback} diff --git a/src/components/ui/Copy/Copy.tsx b/src/components/ui/Copy/Copy.tsx new file mode 100644 index 00000000..f910f56d --- /dev/null +++ b/src/components/ui/Copy/Copy.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import CopyPrimitive from '~/core/primitives/Copy'; + +const COMPONENT_NAME = 'Copy'; + +const Copy = ({ customRootClass = '', children, isCopiedContent = 'Copied!', resetDelay = 2000, copyContent = '', ...props }: any) => { + return + {children} + + {isCopiedContent} + + ; +}; + +Copy.displayName = COMPONENT_NAME; +Copy.Root = CopyPrimitive.Root; +Copy.Trigger = CopyPrimitive.Trigger; +Copy.Feedback = CopyPrimitive.Feedback; + +export default Copy; diff --git a/src/components/ui/Copy/stories/Copy.stories.tsx b/src/components/ui/Copy/stories/Copy.stories.tsx new file mode 100644 index 00000000..c79369b6 --- /dev/null +++ b/src/components/ui/Copy/stories/Copy.stories.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import Copy from '../Copy'; +import SandboxEditor from '~/components/tools/SandboxEditor/SandboxEditor'; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export + +const CopyIcon = () => ; + +const TickIcon = () => ; + +export default { + title: 'Components/Copy', + component: Copy, + render: (args: any) => +
+ + +
+ Copy + +
+
+ +
+ Copied! + +
+
+
+
+
+}; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Button = { + args: { + children: 'Copy' + } +}; diff --git a/src/core/primitives/Copy/contexts/CopyPrimitiveContext.tsx b/src/core/primitives/Copy/contexts/CopyPrimitiveContext.tsx new file mode 100644 index 00000000..26ea58fb --- /dev/null +++ b/src/core/primitives/Copy/contexts/CopyPrimitiveContext.tsx @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +const CopyPrimitiveContext = createContext({}); + +export default CopyPrimitiveContext; diff --git a/src/core/primitives/Copy/fragments/CopyPrimitiveFeedback.tsx b/src/core/primitives/Copy/fragments/CopyPrimitiveFeedback.tsx new file mode 100644 index 00000000..bab77ec0 --- /dev/null +++ b/src/core/primitives/Copy/fragments/CopyPrimitiveFeedback.tsx @@ -0,0 +1,15 @@ +import React, { useContext } from 'react'; +import Primitive from '~/core/primitives/Primitive'; +import CopyPrimitiveContext from '../contexts/CopyPrimitiveContext'; + +const CopyPrimitiveFeedback = ({ children, className = '', ...props }: any) => { + const { isCopied, rootClass } = useContext(CopyPrimitiveContext); + + if (!isCopied) { + return null; + } + + return {children}; +}; + +export default CopyPrimitiveFeedback; diff --git a/src/core/primitives/Copy/fragments/CopyPrimitiveRoot.tsx b/src/core/primitives/Copy/fragments/CopyPrimitiveRoot.tsx new file mode 100644 index 00000000..3c564bca --- /dev/null +++ b/src/core/primitives/Copy/fragments/CopyPrimitiveRoot.tsx @@ -0,0 +1,25 @@ +import React, { useState } from 'react'; +import Primitive from '~/core/primitives/Primitive'; + +import { customClassSwitcher } from '~/core/customClassSwitcher'; + +import CopyPrimitiveContext from '../contexts/CopyPrimitiveContext'; + +const CopyPrimitiveRoot = ({ customRootClass = '', className = '', children, ...props }: any) => { + const [isCopied, setIsCopied] = useState(false); + const rootClass = customClassSwitcher(customRootClass, 'Copy'); + + const values = { + isCopied, + setIsCopied, + rootClass + }; + + return ( + + {children} + + ); +}; + +export default CopyPrimitiveRoot; diff --git a/src/core/primitives/Copy/fragments/CopyPrimitiveTrigger.tsx b/src/core/primitives/Copy/fragments/CopyPrimitiveTrigger.tsx new file mode 100644 index 00000000..101b799f --- /dev/null +++ b/src/core/primitives/Copy/fragments/CopyPrimitiveTrigger.tsx @@ -0,0 +1,26 @@ +import React, { useContext } from 'react'; +import Primitive from '~/core/primitives/Primitive'; +import CopyPrimitiveContext from '../contexts/CopyPrimitiveContext'; + +// The triggering action (button) is logically part of the copying mechanism +const CopyTrigger = ({ children, className = '', copyContent = '', resetDelay = 2000, ...props }: any) => { + const { setIsCopied, isCopied, rootClass } = useContext(CopyPrimitiveContext); + + const handleClick = () => { + if (copyContent) { + setIsCopied(true); + navigator.clipboard.writeText(copyContent); + setTimeout(() => { + setIsCopied(false); + }, resetDelay); + } + }; + + if (!copyContent || isCopied) { + return null; + } + + return {children}; +}; + +export default CopyTrigger; diff --git a/src/core/primitives/Copy/index.tsx b/src/core/primitives/Copy/index.tsx new file mode 100644 index 00000000..82d25646 --- /dev/null +++ b/src/core/primitives/Copy/index.tsx @@ -0,0 +1,11 @@ +import CopyPrimitiveRoot from './fragments/CopyPrimitiveRoot'; +import CopyTrigger from './fragments/CopyPrimitiveTrigger'; +import CopyFeedback from './fragments/CopyPrimitiveFeedback'; + +const CopyPrimitive = { + Root: CopyPrimitiveRoot, + Trigger: CopyTrigger, + Feedback: CopyFeedback +} as const; + +export default CopyPrimitive; diff --git a/src/core/primitives/Copy/stories/CopyPrimitive.stories.tsx b/src/core/primitives/Copy/stories/CopyPrimitive.stories.tsx new file mode 100644 index 00000000..4a3521e0 --- /dev/null +++ b/src/core/primitives/Copy/stories/CopyPrimitive.stories.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import CopyPrimitive from '../index'; +import SandboxEditor from '~/components/tools/SandboxEditor/SandboxEditor'; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +export default { + title: 'Primitives/CopyPrimitive', + component: CopyPrimitive, + render: (args: any) => +
+ + Copy + Copied! + +
+
+}; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Button = { + args: { + children: 'Copy' + } +}; diff --git a/styles/themes/components/copy.scss b/styles/themes/components/copy.scss new file mode 100644 index 00000000..bdad7882 --- /dev/null +++ b/styles/themes/components/copy.scss @@ -0,0 +1,23 @@ +.rad-ui-copy{ + border: 1px solid var(--rad-ui-color-gray-700); + display: inline-flex; + align-items: center; + gap: 8px; + background-color: var(--rad-ui-color-gray-100); + border-radius: 12px; + cursor: pointer; + + .rad-ui-copy-trigger{ + display: flex; + align-items: center; + gap: 8px; + padding: 12px; + } + + .rad-ui-copy-feedback{ + display: flex; + align-items: center; + gap: 8px; + padding: 12px; + } +} diff --git a/styles/themes/default.scss b/styles/themes/default.scss index 84640c0d..aa48dd70 100644 --- a/styles/themes/default.scss +++ b/styles/themes/default.scss @@ -7,6 +7,7 @@ @import "./components/button.scss"; @import "./components/callout.scss"; @import "./components/card.scss"; +@import "./components/copy.scss"; @import "./components/table.scss"; @import "./components/code.scss"; @import "./components/heading.scss";