diff --git a/src/components/SyntaxHighlighter/Snippet/Snippet.tsx b/src/components/SyntaxHighlighter/Snippet/Snippet.tsx index 71ad9c7..0649309 100644 --- a/src/components/SyntaxHighlighter/Snippet/Snippet.tsx +++ b/src/components/SyntaxHighlighter/Snippet/Snippet.tsx @@ -6,7 +6,7 @@ import { SyntaxHighlighter as StorybookSyntaxHighlighter } from "@storybook/comp import { ThemeProvider, ensure, themes } from "@storybook/theming"; interface Props { - contents: { content: string; toggle?: boolean }[]; + content: { code: string; toggle?: boolean }[]; active: boolean; open?: boolean; } @@ -23,7 +23,12 @@ const wrapperVariants = { }; export const Snippet = forwardRef( - ({ active, contents, open }, ref) => { + ({ active, content, open }, ref) => { + const customStyle = { + fontSize: "0.8125rem", + lineHeight: "1.1875rem", + }; + return ( ( variants={wrapperVariants} transition={{ ease: "easeInOut", duration: 0.6 }} > - {contents.map(({ toggle, content }, i) => ( + {content.map(({ toggle, code }, i) => ( {toggle === undefined && ( - {content} + {code} )} {toggle && !open && ( {` // ...`} @@ -62,10 +67,10 @@ export const Snippet = forwardRef( > - {content} + {code} )} diff --git a/src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx b/src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx index ea7bab4..d16e88f 100644 --- a/src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx +++ b/src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx @@ -1,7 +1,7 @@ import { Meta, StoryObj } from "@storybook/react"; import { SyntaxHighlighter } from "./SyntaxHighlighter"; import React from "react"; -import { fireEvent, userEvent, within } from "@storybook/testing-library"; +import { userEvent, within } from "@storybook/testing-library"; import { expect } from "@storybook/jest"; import { textContentMatcher } from "../../helpers/textContentMatcher"; @@ -16,22 +16,22 @@ export default meta; type Story = StoryObj; -const newData = [ +const data = [ [ { - content: `// Button.stories.tsx`, + code: `// Button.stories.tsx`, }, ], [ { - content: `import type { Meta, StoryObj } from '@storybook/react'; + code: `import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button';`, }, ], [ { - content: `const meta: Meta = { + code: `const meta: Meta = { title: 'Example/Button', component: Button, // ... @@ -41,20 +41,20 @@ export default meta;`, }, ], [ - { content: `export const Primary: Story = {` }, + { code: `export const Primary: Story = {` }, { - content: `args: { + code: `args: { primary: true, label: 'Click', background: 'red' }`, toggle: true, }, - { content: `};` }, + { code: `};` }, ], [ { - content: `// Copy the code below + code: `// Copy the code below export const Warning: Story = { args: { @@ -74,7 +74,11 @@ export const Default: Story = {
- @@ -82,9 +86,9 @@ export const Default: Story = { ); }, args: { - contents: newData, + data: data, activeStep: 1, - width: "50%", + width: 480, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -92,13 +96,13 @@ export const Default: Story = { const nextButton = canvas.getByText("Next"); const firstElement = await canvas.findByText( - textContentMatcher(newData[0][0].content) + textContentMatcher(data[0][0].code) ); const secondElement = await canvas.findByText( - textContentMatcher(newData[1][0].content) + textContentMatcher(data[1][0].code) ); const thirdElement = await canvas.findByText( - textContentMatcher(newData[2][0].content) + textContentMatcher(data[2][0].code) ); await expect( diff --git a/src/components/SyntaxHighlighter/SyntaxHighlighter.styled.tsx b/src/components/SyntaxHighlighter/SyntaxHighlighter.styled.tsx index 499dfac..df61247 100644 --- a/src/components/SyntaxHighlighter/SyntaxHighlighter.styled.tsx +++ b/src/components/SyntaxHighlighter/SyntaxHighlighter.styled.tsx @@ -6,11 +6,11 @@ export const Code = styled(motion.div)` z-index: 2; `; -export const Container = styled.div<{ width: string }>` +export const Container = styled.div<{ width: number }>` position: relative; box-sizing: border-box; background: #171c23; - width: ${({ width }) => width}; + width: ${({ width }) => width}px; height: 100%; overflow: hidden; padding-left: 15px; diff --git a/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx b/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx index a5fda91..bb04404 100644 --- a/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx +++ b/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx @@ -1,27 +1,38 @@ -import React, { useCallback, useEffect, useLayoutEffect, useMemo } from "react"; +import React, { + createRef, + useCallback, + useLayoutEffect, + useMemo, + useState, +} from "react"; import { Backdrop, Code, Container } from "./SyntaxHighlighter.styled"; import { Snippet } from "./Snippet/Snippet"; type SyntaxHighlighterProps = { - contents: { content: string; toggle?: boolean }[][]; + data: { code: string; toggle?: boolean }[][]; activeStep: number; - width: string; + width: number; +}; + +type StepsProps = { + yPos: number; + backdropHeight: number; + index: number; + open: boolean; }; const OFFSET = 49; export const SyntaxHighlighter = ({ activeStep, - contents, + data, width, }: SyntaxHighlighterProps) => { - const [steps, setSteps] = React.useState< - { yPos: number; height: number; index: number; open: boolean }[] - >([]); + const [steps, setSteps] = useState([]); const refs = useMemo( - () => contents.map(() => React.createRef()), - [contents] + () => data.map(() => createRef()), + [data] ); const getYPos = (idx: number) => { @@ -33,12 +44,12 @@ export const SyntaxHighlighter = ({ }; const setNewSteps = useCallback(() => { - const newSteps = contents.flatMap((content, i) => { - const height = refs[i].current!.getBoundingClientRect().height; + const newSteps = data.flatMap((content, i) => { + const backdropHeight = refs[i].current!.getBoundingClientRect().height; const finalSteps = [ { yPos: getYPos(i) + OFFSET - 7, - height, + backdropHeight, index: i, open: false, }, @@ -47,7 +58,7 @@ export const SyntaxHighlighter = ({ if (content.length > 1) { finalSteps.push({ yPos: getYPos(i) + OFFSET - 7, - height, + backdropHeight, index: i, open: true, }); @@ -57,9 +68,9 @@ export const SyntaxHighlighter = ({ }); setSteps(newSteps); - }, [contents]); + }, [data]); - useEffect(() => { + useLayoutEffect(() => { // Call setNewSteps every time height of the refs elements changes const resizeObserver = new ResizeObserver(() => { setNewSteps(); @@ -75,33 +86,31 @@ export const SyntaxHighlighter = ({ }, []); return ( - <> - - - {contents.map((content, idx: number) => ( - idx - ? true - : steps[activeStep]?.open ?? false - } - contents={content} - /> - ))} - - - - + + + {data.map((content, idx: number) => ( + idx + ? true + : steps[activeStep]?.open ?? false + } + content={content} + /> + ))} + + + ); }; diff --git a/src/features/WriteStoriesModal/WriteStoriesModal.styled.tsx b/src/features/WriteStoriesModal/WriteStoriesModal.styled.tsx index 4f1451b..55a5880 100644 --- a/src/features/WriteStoriesModal/WriteStoriesModal.styled.tsx +++ b/src/features/WriteStoriesModal/WriteStoriesModal.styled.tsx @@ -1,12 +1,14 @@ -import { styled } from "@storybook/theming"; +import { keyframes, styled } from "@storybook/theming"; export const ModalContent = styled.div` display: flex; flex-direction: row; + height: 100%; max-height: 85vh; `; export const Main = styled.div` + position: relative; flex: 1; display: flex; flex-direction: column; @@ -14,36 +16,37 @@ export const Main = styled.div` `; export const Header = styled.div` + box-sizing: border-box; display: flex; justify-content: space-between; align-items: center; - color: ${({ theme }) => theme.color.darkest}; - padding: 1em; + padding: 0 15px; border-bottom: 1px solid ${({ theme }) => theme.appBorderColor}; + height: 40px; +`; - h2 { - margin: 0; - padding: 0; - display: flex; - align-items: center; - font-size: 13px; - font-weight: bold; - } +export const ModalTitle = styled.div` + display: flex; + align-items: center; + gap: 5px; + font-size: 13px; + font-weight: bold; + color: ${({ theme }) => theme.color.darkest}; - svg { - margin-right: 0.5em; + span { + margin-top: 2px; } `; -export const Description = styled.div` +export const Content = styled.div` font-size: 13px; - padding: 1em; + padding: 15px; flex: 1; display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; - color: #454e54; + color: ${({ theme }) => theme.color.darker}; h3 { font-size: 13px; @@ -53,11 +56,6 @@ export const Description = styled.div` } `; -export const ContentWrapper = styled.div` - display: flex; - flex-direction: column; -`; - export const SpanHighlight = styled.span` color: ${({ theme }) => theme.color.secondary}; display: inline-block; @@ -72,3 +70,80 @@ export const Image = styled.img` max-width: 100%; margin-top: 1em; `; + +export const Background = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + overflow: hidden; +`; + +export const circle1Anim = keyframes` + 0% { transform: translate(0px, 0px) } + 50% { transform: translate(120px, 0px) } + 100% { transform: translate(0px, 0px) } +`; + +export const Circle1 = styled.div` + position: absolute; + width: 350px; + height: 350px; + left: -160px; + top: -260px; + background: radial-gradient( + circle at center, + rgba(255, 119, 119, 1) 0%, + rgba(255, 119, 119, 0) 70% + ); + animation: ${circle1Anim} 8s linear infinite; + animation-timing-function: ease-in-out; + z-index: 2; +`; + +export const circle2Anim = keyframes` + 0% { transform: translate(0px, 0px) } + 33% { transform: translate(-64px, 0px) } + 66% { transform: translate(120px, 0px) } + 100% { transform: translate(0px, 0px) } +`; + +export const Circle2 = styled.div` + position: absolute; + width: 350px; + height: 350px; + left: -54px; + top: -250px; + background: radial-gradient( + circle at center, + rgba(253, 255, 147, 1) 0%, + rgba(253, 255, 147, 0) 70% + ); + animation: ${circle2Anim} 12s linear infinite; + animation-timing-function: ease-in-out; + z-index: 3; +`; + +export const circle3Anim = keyframes` + 0% { transform: translate(0px, 0px) } + 50% { transform: translate(-120px, 0px) } + 100% { transform: translate(0px, 0px) } +`; + +export const Circle3 = styled.div` + position: absolute; + width: 350px; + height: 350px; + left: 150px; + top: -220px; + background: radial-gradient( + circle at center, + rgba(119, 255, 247, 0.8) 0%, + rgba(119, 255, 247, 0) 70% + ); + animation: ${circle3Anim} 4s linear infinite; + animation-timing-function: ease-in-out; + z-index: 4; +`; diff --git a/src/features/WriteStoriesModal/WriteStoriesModal.tsx b/src/features/WriteStoriesModal/WriteStoriesModal.tsx index b1d30e8..80849f6 100644 --- a/src/features/WriteStoriesModal/WriteStoriesModal.tsx +++ b/src/features/WriteStoriesModal/WriteStoriesModal.tsx @@ -1,15 +1,19 @@ -import React, { useRef } from "react"; +import React, { useState } from "react"; import { Button } from "../../components/Button/Button"; - import { Modal } from "../../components/Modal/Modal"; import { Icons } from "@storybook/components"; import useMeasure from "react-use-measure"; import { - Description, + Background, + Circle1, + Circle2, + Circle3, + Content, Header, Image, Main, ModalContent, + ModalTitle, SpanHighlight, } from "./WriteStoriesModal.styled"; import { SyntaxHighlighter } from "../../components/SyntaxHighlighter/SyntaxHighlighter"; @@ -21,13 +25,15 @@ import { useGetBackdropBoundary } from "./hooks/useGetBackdropBoundary"; import titleSidebarImg from "./assets/01-title-sidebar.png"; import storyNameSidebarImg from "./assets/02-story-name-sidebar.png"; import argsImg from "./assets/03-args.png"; - import dataJavascript from "./code/javascript"; import dataTypescript from "./code/typescript"; import dataTypescriptNextjs from "./code/nextjs-typescript"; import { useGetProject } from "./hooks/useGetFrameworkName"; import { API, AddonStore } from "@storybook/manager-api"; +// TODO: Add warning if backdropBoundary && !warningButtonStatus?.data is not true. +// backdropBoundary && !warningButtonStatus?.data + export function WriteStoriesModal({ onFinish, api, @@ -37,11 +43,19 @@ export function WriteStoriesModal({ api: API; addonsStore: AddonStore; }) { - const [step, setStep] = React.useState< + const [step, setStep] = useState< "imports" | "meta" | "story" | "args" | "customStory" >("imports"); - const [isWarningStoryCopied, setWarningStoryCopied] = React.useState(false); + const stepIndex = { + imports: 1, + meta: 2, + story: 3, + args: 4, + customStory: 5, + }; + + const [isWarningStoryCopied, setWarningStoryCopied] = useState(false); const [clipboardButtonRef, clipboardButtonBounds] = useMeasure(); @@ -66,66 +80,58 @@ export function WriteStoriesModal({ : dataTypescript; const copyWarningStory = () => { - const warningContent = data[4][0].content; + const warningContent = data[4][0].code; navigator.clipboard.writeText( warningContent.replace("// Copy the code below", "") ); setWarningStoryCopied(true); }; - const stepIndex = { - imports: 1, - meta: 2, - story: 3, - args: 4, - customStory: 5, - }; - return ( - - {({ Title, Description: DefaultDescription, Close }) => ( + + {({ Title, Description, Close }) => ( -
- {data ? ( - - ) : null} - {backdropBoundary && !warningButtonStatus?.data && ( - - )} -
+ {data ? ( + + ) : null} + {backdropBoundary && !warningButtonStatus?.data && ( + + )}
- - <Icons icon="bookmarkhollow" />{" "} - <span>How to write a story</span> + <Title asChild> + <ModalTitle> + <Icons icon="bookmarkhollow" width={13} /> + <span>How to write a story</span> + </ModalTitle> - +
- - + + {step === "imports" && ( <>
@@ -274,8 +280,13 @@ export function WriteStoriesModal({ ) : null} ) : null)} - - + + + + + + +
)} diff --git a/src/features/WriteStoriesModal/code/javascript.tsx b/src/features/WriteStoriesModal/code/javascript.tsx index 5168980..941a588 100644 --- a/src/features/WriteStoriesModal/code/javascript.tsx +++ b/src/features/WriteStoriesModal/code/javascript.tsx @@ -1,17 +1,17 @@ export default [ [ { - content: `// Button.stories.jsx`, + code: `// Button.stories.jsx`, }, ], [ { - content: `import { Button } from './Button';`, + code: `import { Button } from './Button';`, }, ], [ { - content: `const meta = { + code: `const meta = { title: 'Example/Button', component: Button, // ... @@ -21,20 +21,20 @@ export default [ }, ], [ - { content: `export const Primary = {` }, + { code: `export const Primary = {` }, { - content: `args: { + code: `args: { primary: true, label: 'Click', background: 'red' }`, toggle: true, }, - { content: `};` }, + { code: `};` }, ], [ { - content: `// Copy the code below + code: `// Copy the code below export const Warning = { args: { primary: true, diff --git a/src/features/WriteStoriesModal/code/nextjs-typescript.tsx b/src/features/WriteStoriesModal/code/nextjs-typescript.tsx index f177b7b..764260c 100644 --- a/src/features/WriteStoriesModal/code/nextjs-typescript.tsx +++ b/src/features/WriteStoriesModal/code/nextjs-typescript.tsx @@ -1,19 +1,19 @@ export default [ [ { - content: `// Button.stories.tsx`, + code: `// Button.stories.tsx`, }, ], [ { - content: `import type { Meta, StoryObj } from '@storybook/nextjs'; + code: `import type { Meta, StoryObj } from '@storybook/nextjs'; import { Button } from './Button';`, }, ], [ { - content: `const meta: Meta = { + code: `const meta: Meta = { title: 'Example/Button', component: Button, // ... @@ -23,20 +23,20 @@ export default [ }, ], [ - { content: `export const Primary: Story = {` }, + { code: `export const Primary: Story = {` }, { - content: `args: { + code: `args: { primary: true, label: 'Click', background: 'red' }`, toggle: true, }, - { content: `};` }, + { code: `};` }, ], [ { - content: `// Copy the code below + code: `// Copy the code below export const Warning: Story = { args: { primary: true, diff --git a/src/features/WriteStoriesModal/code/typescript.tsx b/src/features/WriteStoriesModal/code/typescript.tsx index c6f5a7b..de1d760 100644 --- a/src/features/WriteStoriesModal/code/typescript.tsx +++ b/src/features/WriteStoriesModal/code/typescript.tsx @@ -1,19 +1,19 @@ export default [ [ { - content: `// Button.stories.tsx`, + code: `// Button.stories.tsx`, }, ], [ { - content: `import type { Meta, StoryObj } from '@storybook/react'; + code: `import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button';`, }, ], [ { - content: `const meta: Meta = { + code: `const meta: Meta = { title: 'Example/Button', component: Button, // ... @@ -23,20 +23,20 @@ export default [ }, ], [ - { content: `export const Primary: Story = {` }, + { code: `export const Primary: Story = {` }, { - content: `args: { + code: `args: { primary: true, label: 'Click', background: 'red' }`, toggle: true, }, - { content: `};` }, + { code: `};` }, ], [ { - content: `// Copy the code below + code: `// Copy the code below export const Warning: Story = { args: { primary: true,