From 7c74d172eac0fedacdb8f8ebfb21372493aeb1ef Mon Sep 17 00:00:00 2001 From: Pranay Kothapalli Date: Sun, 17 Nov 2024 23:32:07 +0530 Subject: [PATCH 1/2] Add CopyPrimitive components for copying text functionality --- .../Copy/contexts/CopyPrimitiveContext.tsx | 5 ++++ .../Copy/fragments/CopyPrimitiveFeedback.tsx | 15 +++++++++++ .../Copy/fragments/CopyPrimitiveRoot.tsx | 21 +++++++++++++++ .../Copy/fragments/CopyPrimitiveTrigger.tsx | 26 +++++++++++++++++++ src/core/primitives/Copy/index.tsx | 11 ++++++++ .../Copy/stories/CopyPrimitive.stories.tsx | 24 +++++++++++++++++ 6 files changed, 102 insertions(+) create mode 100644 src/core/primitives/Copy/contexts/CopyPrimitiveContext.tsx create mode 100644 src/core/primitives/Copy/fragments/CopyPrimitiveFeedback.tsx create mode 100644 src/core/primitives/Copy/fragments/CopyPrimitiveRoot.tsx create mode 100644 src/core/primitives/Copy/fragments/CopyPrimitiveTrigger.tsx create mode 100644 src/core/primitives/Copy/index.tsx create mode 100644 src/core/primitives/Copy/stories/CopyPrimitive.stories.tsx 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..6c51b470 --- /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, isCopiedText = 'Copied!', ...props }: any) => { + const { isCopied } = useContext(CopyPrimitiveContext); + + if (!isCopied) { + return null; + } + + return {isCopiedText}; +}; + +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..7557233e --- /dev/null +++ b/src/core/primitives/Copy/fragments/CopyPrimitiveRoot.tsx @@ -0,0 +1,21 @@ +import React, { useState } from 'react'; +import Primitive from '~/core/primitives/Primitive'; + +import CopyPrimitiveContext from '../contexts/CopyPrimitiveContext'; + +const CopyPrimitiveRoot = ({ children, ...props }: any) => { + const [isCopied, setIsCopied] = useState(false); + + const values = { + isCopied, + setIsCopied + }; + + 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..df10d1ac --- /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, text = '', resetDelay = 2000, ...props }: any) => { + const { setIsCopied, isCopied } = useContext(CopyPrimitiveContext); + + const handleClick = () => { + if (text) { + setIsCopied(true); + navigator.clipboard.writeText(text); + setTimeout(() => { + setIsCopied(false); + }, resetDelay); + } + }; + + if (!text || 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..65afc535 --- /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' + } +}; From b192cc06d8e263490af1b22e53c8de895224f183 Mon Sep 17 00:00:00 2001 From: Pranay Kothapalli Date: Mon, 18 Nov 2024 00:07:01 +0530 Subject: [PATCH 2/2] Add Copy component with trigger, feedback, and styles --- src/components/ui/Avatar/Avatar.tsx | 3 +- src/components/ui/Copy/Copy.tsx | 20 ++++++++++ .../ui/Copy/stories/Copy.stories.tsx | 39 +++++++++++++++++++ .../Copy/fragments/CopyPrimitiveFeedback.tsx | 6 +-- .../Copy/fragments/CopyPrimitiveRoot.tsx | 10 +++-- .../Copy/fragments/CopyPrimitiveTrigger.tsx | 12 +++--- .../Copy/stories/CopyPrimitive.stories.tsx | 2 +- styles/themes/components/copy.scss | 23 +++++++++++ styles/themes/default.scss | 1 + 9 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 src/components/ui/Copy/Copy.tsx create mode 100644 src/components/ui/Copy/stories/Copy.stories.tsx create mode 100644 styles/themes/components/copy.scss 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/fragments/CopyPrimitiveFeedback.tsx b/src/core/primitives/Copy/fragments/CopyPrimitiveFeedback.tsx index 6c51b470..bab77ec0 100644 --- a/src/core/primitives/Copy/fragments/CopyPrimitiveFeedback.tsx +++ b/src/core/primitives/Copy/fragments/CopyPrimitiveFeedback.tsx @@ -2,14 +2,14 @@ import React, { useContext } from 'react'; import Primitive from '~/core/primitives/Primitive'; import CopyPrimitiveContext from '../contexts/CopyPrimitiveContext'; -const CopyPrimitiveFeedback = ({ children, isCopiedText = 'Copied!', ...props }: any) => { - const { isCopied } = useContext(CopyPrimitiveContext); +const CopyPrimitiveFeedback = ({ children, className = '', ...props }: any) => { + const { isCopied, rootClass } = useContext(CopyPrimitiveContext); if (!isCopied) { return null; } - return {isCopiedText}; + return {children}; }; export default CopyPrimitiveFeedback; diff --git a/src/core/primitives/Copy/fragments/CopyPrimitiveRoot.tsx b/src/core/primitives/Copy/fragments/CopyPrimitiveRoot.tsx index 7557233e..3c564bca 100644 --- a/src/core/primitives/Copy/fragments/CopyPrimitiveRoot.tsx +++ b/src/core/primitives/Copy/fragments/CopyPrimitiveRoot.tsx @@ -1,19 +1,23 @@ import React, { useState } from 'react'; import Primitive from '~/core/primitives/Primitive'; +import { customClassSwitcher } from '~/core/customClassSwitcher'; + import CopyPrimitiveContext from '../contexts/CopyPrimitiveContext'; -const CopyPrimitiveRoot = ({ children, ...props }: any) => { +const CopyPrimitiveRoot = ({ customRootClass = '', className = '', children, ...props }: any) => { const [isCopied, setIsCopied] = useState(false); + const rootClass = customClassSwitcher(customRootClass, 'Copy'); const values = { isCopied, - setIsCopied + setIsCopied, + rootClass }; return ( - {children} + {children} ); }; diff --git a/src/core/primitives/Copy/fragments/CopyPrimitiveTrigger.tsx b/src/core/primitives/Copy/fragments/CopyPrimitiveTrigger.tsx index df10d1ac..101b799f 100644 --- a/src/core/primitives/Copy/fragments/CopyPrimitiveTrigger.tsx +++ b/src/core/primitives/Copy/fragments/CopyPrimitiveTrigger.tsx @@ -3,24 +3,24 @@ 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, text = '', resetDelay = 2000, ...props }: any) => { - const { setIsCopied, isCopied } = useContext(CopyPrimitiveContext); +const CopyTrigger = ({ children, className = '', copyContent = '', resetDelay = 2000, ...props }: any) => { + const { setIsCopied, isCopied, rootClass } = useContext(CopyPrimitiveContext); const handleClick = () => { - if (text) { + if (copyContent) { setIsCopied(true); - navigator.clipboard.writeText(text); + navigator.clipboard.writeText(copyContent); setTimeout(() => { setIsCopied(false); }, resetDelay); } }; - if (!text || isCopied) { + if (!copyContent || isCopied) { return null; } - return {children}; + return {children}; }; export default CopyTrigger; diff --git a/src/core/primitives/Copy/stories/CopyPrimitive.stories.tsx b/src/core/primitives/Copy/stories/CopyPrimitive.stories.tsx index 65afc535..4a3521e0 100644 --- a/src/core/primitives/Copy/stories/CopyPrimitive.stories.tsx +++ b/src/core/primitives/Copy/stories/CopyPrimitive.stories.tsx @@ -9,7 +9,7 @@ export default { render: (args: any) =>
- Copy + Copy Copied!
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";