From 4a15fc017d85b8f7418f09824487a5ed797542de Mon Sep 17 00:00:00 2001 From: Yossi Saadi Date: Wed, 15 May 2024 22:25:28 +0300 Subject: [PATCH 1/5] feat(withLiveEdit): LiveEditorAction component for editor bottom actions --- .../LiveEditorAction.module.scss | 20 +++++++++++++++++++ .../LiveEditorAction/LiveEditorAction.tsx | 13 ++++++++++++ .../LiveEditorAction.types.ts | 5 +++++ 3 files changed, 38 insertions(+) create mode 100644 packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.module.scss create mode 100644 packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.tsx create mode 100644 packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.types.ts diff --git a/packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.module.scss b/packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.module.scss new file mode 100644 index 0000000000..66e417d954 --- /dev/null +++ b/packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.module.scss @@ -0,0 +1,20 @@ +.action { + opacity: 0.6; + border: 0; + padding: 4px 8px; + cursor: pointer; + font-size: 9px; + font-weight: bold; + background-color: #fff; +} + +.action:hover:not([disabled]) { + opacity: 0.8; + transition: opacity 0.1s ease; +} + +.action[disabled] { + cursor: not-allowed; + color: #000; + opacity: 0.3; +} diff --git a/packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.tsx b/packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.tsx new file mode 100644 index 0000000000..ae3011aecf --- /dev/null +++ b/packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import styles from "./LiveEditorAction.module.scss"; +import { LiveEditorActionProps } from "./LiveEditorAction.types"; + +const LiveEditorAction = ({ onClick, disabled, children }: LiveEditorActionProps) => { + return ( + + ); +}; + +export default LiveEditorAction; diff --git a/packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.types.ts b/packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.types.ts new file mode 100644 index 0000000000..8ab99b0f54 --- /dev/null +++ b/packages/core/src/storybook/decorators/withLiveEdit/components/LiveEditorAction/LiveEditorAction.types.ts @@ -0,0 +1,5 @@ +export interface LiveEditorActionProps { + onClick?: () => void; + disabled?: boolean; + children: string; +} From c3af55dab538dd55ba87f158c305afde18e2b8f4 Mon Sep 17 00:00:00 2001 From: Yossi Saadi Date: Wed, 15 May 2024 22:26:47 +0300 Subject: [PATCH 2/5] feat(withLiveEdit): add actions for copy, format, and reset at the bottom of the live editor --- .../hooks/useLiveEditorActions.ts | 52 +++++++++++++ .../withLiveEdit/withLiveEdit.module.scss | 9 +++ .../decorators/withLiveEdit/withLiveEdit.tsx | 77 ++++++++----------- 3 files changed, 95 insertions(+), 43 deletions(-) create mode 100644 packages/core/src/storybook/decorators/withLiveEdit/hooks/useLiveEditorActions.ts create mode 100644 packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.module.scss diff --git a/packages/core/src/storybook/decorators/withLiveEdit/hooks/useLiveEditorActions.ts b/packages/core/src/storybook/decorators/withLiveEdit/hooks/useLiveEditorActions.ts new file mode 100644 index 0000000000..d1170cb87b --- /dev/null +++ b/packages/core/src/storybook/decorators/withLiveEdit/hooks/useLiveEditorActions.ts @@ -0,0 +1,52 @@ +import { useCallback, useRef, useState } from "react"; +import { formatCode } from "../utils/prettier-utils"; +import { extractRenderAttributeFromCsf } from "../utils/parse-csf-utils"; + +function format(source: string): string { + try { + return formatCode(source); + } catch (e) { + return source; + } +} + +const useLiveEditorActions = (originalSource: string) => { + const originalRenderFromCsf = useRef( + originalSource ? extractRenderAttributeFromCsf(`(${originalSource})`) : "" + ); + const [code, setCode] = useState(format(originalRenderFromCsf.current)); + const [isDirty, setDirty] = useState(false); + const [isCopied, setCopied] = useState(false); + + const onChange = useCallback((newVal: string) => { + setCode(newVal); + setDirty(true); + }, []); + + const onCopyClick = useCallback(() => { + navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }, [code]); + + const onFormatClick = useCallback(() => { + setCode(format(code)); + }, [code]); + + const onResetClick = useCallback(() => { + setCode(format(originalRenderFromCsf.current)); + setDirty(false); + }, []); + + return { + code, + isDirty, + onChange, + onCopyClick, + isCopied, + onFormatClick, + onResetClick + }; +}; + +export default useLiveEditorActions; diff --git a/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.module.scss b/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.module.scss new file mode 100644 index 0000000000..62c33dab2a --- /dev/null +++ b/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.module.scss @@ -0,0 +1,9 @@ +.actions { + display: flex; + gap: 1px; + position: absolute; + bottom: 0; + right: 0; + border-top-left-radius: 4px; + overflow: hidden; +} diff --git a/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.tsx b/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.tsx index 6b3e7d2481..2ca8c9fa7c 100644 --- a/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.tsx +++ b/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.tsx @@ -1,41 +1,23 @@ import { Decorator, StoryContext } from "@storybook/react"; -import { useMemo, useRef, useState } from "react"; +import { useMemo } from "react"; import { vscodeDark } from "@uiw/codemirror-theme-vscode"; import { langs } from "@uiw/codemirror-extensions-langs"; import { createPortal } from "react-dom"; import LiveEditor from "../../components/live-editor/LiveEditor"; -import { formatCode } from "./utils/prettier-utils"; -import LiveContent from "./LiveContent/LiveContent"; -import { extractRenderAttributeFromCsf } from "./utils/parse-csf-utils"; - -function getInitialCodeValue(source: string, shouldPrintError: boolean): string { - try { - // need to wrap with parentheses to avoid syntax errors - const code = extractRenderAttributeFromCsf(`(${source})`); - return formatCode(code); - } catch (e) { - if (shouldPrintError) { - console.error(e); - } - return source; - } -} +import LiveContent from "./components/LiveContent/LiveContent"; +import LiveEditorAction from "./components/LiveEditorAction/LiveEditorAction"; +import styles from "./withLiveEdit.module.scss"; +import useLiveEditorActions from "./hooks/useLiveEditorActions"; const withLiveEdit: Decorator = (Story, context: StoryContext) => { const { id, parameters, viewMode, moduleExport } = context; + const { source: sourceParams, liveEdit: liveEditParams } = parameters.docs || {}; const canvasEditorContainer = useMemo(() => document.getElementById(id), [id]); - const shouldAllowLiveEdit = viewMode === "docs" && parameters.docs?.liveEdit?.isEnabled && !!canvasEditorContainer; + const shouldAllowLiveEdit = viewMode === "docs" && liveEditParams?.isEnabled && !!canvasEditorContainer; - const originalCode = useRef( - getInitialCodeValue(parameters.docs.source?.originalSource || "", shouldAllowLiveEdit) + const { code, isDirty, onChange, onCopyClick, isCopied, onFormatClick, onResetClick } = useLiveEditorActions( + sourceParams?.originalSource ); - const [code, setCode] = useState(originalCode.current); - const [dirty, setDirty] = useState(false); - - const handleChange = (newVal: string) => { - setCode(newVal); - setDirty(true); - }; if (!shouldAllowLiveEdit) { return ; @@ -43,10 +25,10 @@ const withLiveEdit: Decorator = (Story, context: StoryContext) => { return ( <> - {dirty ? ( + {isDirty ? ( @@ -54,20 +36,29 @@ const withLiveEdit: Decorator = (Story, context: StoryContext) => { )} {createPortal( - , + <> + +
+ + {isCopied ? "Copied" : "Copy"} + + Format + Reset +
+ , canvasEditorContainer )} From 4a01881310ef9323ee3d2335ec3aedd7b2634b0d Mon Sep 17 00:00:00 2001 From: Yossi Saadi Date: Wed, 15 May 2024 22:27:31 +0300 Subject: [PATCH 3/5] refactor(withLiveEdit): change hierarchy, use hex --- .../canvas-wrapper/CanvasWrapper.module.scss | 2 +- .../LiveContent/LiveContent.module.scss | 0 .../{ => components}/LiveContent/LiveContent.tsx | 10 +++++----- .../{ => components}/LiveContent/LiveContent.types.ts | 0 4 files changed, 6 insertions(+), 6 deletions(-) rename packages/core/src/storybook/decorators/withLiveEdit/{ => components}/LiveContent/LiveContent.module.scss (100%) rename packages/core/src/storybook/decorators/withLiveEdit/{ => components}/LiveContent/LiveContent.tsx (73%) rename packages/core/src/storybook/decorators/withLiveEdit/{ => components}/LiveContent/LiveContent.types.ts (100%) diff --git a/packages/core/src/storybook/components/canvas-wrapper/CanvasWrapper.module.scss b/packages/core/src/storybook/components/canvas-wrapper/CanvasWrapper.module.scss index 2f70716782..6a83c54483 100644 --- a/packages/core/src/storybook/components/canvas-wrapper/CanvasWrapper.module.scss +++ b/packages/core/src/storybook/components/canvas-wrapper/CanvasWrapper.module.scss @@ -9,7 +9,7 @@ overflow: auto; border-radius: 0 0 4px 4px; box-shadow: rgba(0, 0, 0, 0.1) 0 1px 3px 0; - border: 1px solid hsla(203, 50%, 30%, 0.15); + border: 1px solid #dde3e7; border-top: none; &[data-editor-open="true"] { diff --git a/packages/core/src/storybook/decorators/withLiveEdit/LiveContent/LiveContent.module.scss b/packages/core/src/storybook/decorators/withLiveEdit/components/LiveContent/LiveContent.module.scss similarity index 100% rename from packages/core/src/storybook/decorators/withLiveEdit/LiveContent/LiveContent.module.scss rename to packages/core/src/storybook/decorators/withLiveEdit/components/LiveContent/LiveContent.module.scss diff --git a/packages/core/src/storybook/decorators/withLiveEdit/LiveContent/LiveContent.tsx b/packages/core/src/storybook/decorators/withLiveEdit/components/LiveContent/LiveContent.tsx similarity index 73% rename from packages/core/src/storybook/decorators/withLiveEdit/LiveContent/LiveContent.tsx rename to packages/core/src/storybook/decorators/withLiveEdit/components/LiveContent/LiveContent.tsx index 0476a1c660..e54eae06f6 100644 --- a/packages/core/src/storybook/decorators/withLiveEdit/LiveContent/LiveContent.tsx +++ b/packages/core/src/storybook/decorators/withLiveEdit/components/LiveContent/LiveContent.tsx @@ -1,12 +1,12 @@ import React from "react"; import { LiveProvider } from "react-live"; -import LivePreview from "../../../components/live-preview/LivePreview"; -import useApplyDecorators from "../hooks/useApplyDecorators"; +import LivePreview from "../../../../components/live-preview/LivePreview"; +import useApplyDecorators from "../../hooks/useApplyDecorators"; import { LiveContentProps } from "./LiveContent.types"; import styles from "./LiveContent.module.scss"; -import * as VibeComponents from "../../../../components"; -import * as VibeIcons from "../../../../components/Icon/Icons"; -import * as VibeComponentsNext from "../../../../next"; +import * as VibeComponents from "../../../../../components"; +import * as VibeIcons from "../../../../../components/Icon/Icons"; +import * as VibeComponentsNext from "../../../../../next"; const vibeScope = { ...VibeComponents, VibeIcons, VibeNext: VibeComponentsNext }; const reactCommonHooksScope = { diff --git a/packages/core/src/storybook/decorators/withLiveEdit/LiveContent/LiveContent.types.ts b/packages/core/src/storybook/decorators/withLiveEdit/components/LiveContent/LiveContent.types.ts similarity index 100% rename from packages/core/src/storybook/decorators/withLiveEdit/LiveContent/LiveContent.types.ts rename to packages/core/src/storybook/decorators/withLiveEdit/components/LiveContent/LiveContent.types.ts From 68477e3dfdd0598d1a213e6d41ac5cee05cd410a Mon Sep 17 00:00:00 2001 From: Yossi Saadi Date: Thu, 16 May 2024 08:58:13 +0300 Subject: [PATCH 4/5] feat(withLiveEdit): change action button name --- .../src/storybook/components/canvas-wrapper/CanvasWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/storybook/components/canvas-wrapper/CanvasWrapper.tsx b/packages/core/src/storybook/components/canvas-wrapper/CanvasWrapper.tsx index 3463c4d8c5..9de26ffd43 100644 --- a/packages/core/src/storybook/components/canvas-wrapper/CanvasWrapper.tsx +++ b/packages/core/src/storybook/components/canvas-wrapper/CanvasWrapper.tsx @@ -12,7 +12,7 @@ const CanvasWrapper: FC = ({ of }) => { const toggleCodeAction = useMemo( () => ({ - title: "Toggle code editor", + title: "Story Editor", onClick: () => setOpen(prev => !prev) }), [] From 0fa301434b0683a8eedac5e8c7fff1f1ab0f7560 Mon Sep 17 00:00:00 2001 From: Yossi Saadi Date: Thu, 16 May 2024 09:57:51 +0300 Subject: [PATCH 5/5] feat(withLiveEdit): align theme - have the same theme as playground --- packages/core/package.json | 2 +- .../decorators/withLiveEdit/withLiveEdit.tsx | 4 +- yarn.lock | 49 ++++++++++++++----- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 14be64e85d..13f7af36c8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -187,7 +187,7 @@ "@typescript-eslint/eslint-plugin": "^5.36.1", "@typescript-eslint/parser": "^5.36.1", "@uiw/codemirror-extensions-langs": "^4.21.25", - "@uiw/codemirror-theme-vscode": "^4.21.25", + "@uiw/codemirror-theme-github": "^4.21.25", "@uiw/react-codemirror": "^4.21.25", "acorn": "^8.7.1", "autoprefixer": "^10.4.0", diff --git a/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.tsx b/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.tsx index 2ca8c9fa7c..639c36513e 100644 --- a/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.tsx +++ b/packages/core/src/storybook/decorators/withLiveEdit/withLiveEdit.tsx @@ -1,6 +1,6 @@ import { Decorator, StoryContext } from "@storybook/react"; import { useMemo } from "react"; -import { vscodeDark } from "@uiw/codemirror-theme-vscode"; +import { githubDark } from "@uiw/codemirror-theme-github"; import { langs } from "@uiw/codemirror-extensions-langs"; import { createPortal } from "react-dom"; import LiveEditor from "../../components/live-editor/LiveEditor"; @@ -40,7 +40,7 @@ const withLiveEdit: Decorator = (Story, context: StoryContext) => {