From 1f33495ea77daf49c43fdb006771f0183c16f3ca Mon Sep 17 00:00:00 2001 From: Rodrigo Pombo Date: Sun, 26 Dec 2021 17:28:55 +0100 Subject: [PATCH 1/3] Add slideshow component --- packages/mdx/src/client/slideshow.scss | 41 +++++ packages/mdx/src/client/slideshow.tsx | 119 +++++++++++++ packages/mdx/src/components.tsx | 2 + packages/mdx/src/index.scss | 1 + packages/mdx/src/plugin.ts | 2 + packages/mdx/src/plugin/slideshow.ts | 42 +++++ packages/playground/content/slideshow.mdx | 207 ++++++++++++++++++++++ 7 files changed, 414 insertions(+) create mode 100644 packages/mdx/src/client/slideshow.scss create mode 100644 packages/mdx/src/client/slideshow.tsx create mode 100644 packages/mdx/src/plugin/slideshow.ts create mode 100644 packages/playground/content/slideshow.mdx diff --git a/packages/mdx/src/client/slideshow.scss b/packages/mdx/src/client/slideshow.scss new file mode 100644 index 00000000..bd2fd35d --- /dev/null +++ b/packages/mdx/src/client/slideshow.scss @@ -0,0 +1,41 @@ +.ch-slideshow { + margin: 1rem 0; +} + +.ch-slideshow-slide { + display: flex; + flex-flow: row; + gap: 0.5rem; + align-items: stretch; + aspect-ratio: 16 / 9; +} + +.ch-slideshow-slide .ch-editor-frame, +.ch-slideshow-slide .ch-code { + flex: 2; +} + +.ch-slideshow-preview { + flex: 1; + height: auto; +} + +.ch-slideshow-range { + margin-top: 1rem; + display: flex; + flex-flow: row; + gap: 0.5rem; +} + +.ch-slideshow-range input { + flex: 1; +} + +.ch-slideshow-note { + height: 140px; + overflow: auto; + border-radius: 0.25rem; + margin-top: 1rem; + padding: 1rem; + border: 1px solid #e3e3e3; +} diff --git a/packages/mdx/src/client/slideshow.tsx b/packages/mdx/src/client/slideshow.tsx new file mode 100644 index 00000000..8935676f --- /dev/null +++ b/packages/mdx/src/client/slideshow.tsx @@ -0,0 +1,119 @@ +import React from "react" +import { + EditorProps, + EditorStep, +} from "@code-hike/mini-editor" +import { InnerCode, updateEditorStep } from "./code" +import { Preview, PresetConfig } from "./preview" + +export function Slideshow({ + children, + editorSteps, + codeConfig, + presetConfig, +}: { + children: React.ReactNode + editorSteps: EditorStep[] + codeConfig: EditorProps["codeConfig"] + presetConfig?: PresetConfig +}) { + const stepsChildren = React.Children.toArray(children) + + const hasNotes = stepsChildren.some( + (child: any) => child.props?.children + ) + + const [state, setState] = React.useState({ + stepIndex: 0, + step: editorSteps[0], + }) + const tab = state.step + + function onTabClick(filename: string) { + const newStep = updateEditorStep( + state.step, + filename, + null + ) + setState({ ...state, step: newStep }) + } + + return ( +
+
+ + {presetConfig && ( + + )} +
+ +
+
+ + + setState({ + stepIndex: +e.target.value, + step: editorSteps[+e.target.value], + }) + } + /> + +
+ + {hasNotes && ( +
+ {stepsChildren[state.stepIndex]} +
+ )} +
+
+ ) +} diff --git a/packages/mdx/src/components.tsx b/packages/mdx/src/components.tsx index 1d6228c9..50ca0846 100644 --- a/packages/mdx/src/components.tsx +++ b/packages/mdx/src/components.tsx @@ -6,6 +6,7 @@ import { import { Code } from "./client/code" import { Spotlight } from "./client/spotlight" import { Scrollycoding } from "./client/scrollycoding" +import { Slideshow } from "./client/slideshow" import { annotationsMap, Annotation, @@ -20,4 +21,5 @@ export const CH = { Scrollycoding, annotations: annotationsMap, Annotation, + Slideshow, } diff --git a/packages/mdx/src/index.scss b/packages/mdx/src/index.scss index 211eb133..c0c0e00b 100644 --- a/packages/mdx/src/index.scss +++ b/packages/mdx/src/index.scss @@ -2,6 +2,7 @@ @import "~@code-hike/mini-browser/dist/index.scss"; @import "./client/spotlight.scss"; @import "./client/scrollycoding.scss"; +@import "./client/slideshow.scss"; .ch-code { border-radius: 6px; diff --git a/packages/mdx/src/plugin.ts b/packages/mdx/src/plugin.ts index a402b21d..2c19acd9 100644 --- a/packages/mdx/src/plugin.ts +++ b/packages/mdx/src/plugin.ts @@ -5,6 +5,7 @@ import { transformSections } from "./plugin/section" import { transformSpotlights } from "./plugin/spotlight" import { transformScrollycodings } from "./plugin/scrollycoding" import visit from "unist-util-visit" +import { transformSlideshows } from "./plugin/slideshow" export function remarkCodeHike({ theme }: { theme: any }) { return async (tree: Node) => { @@ -27,6 +28,7 @@ export function remarkCodeHike({ theme }: { theme: any }) { try { await transformScrollycodings(tree, { theme }) await transformSpotlights(tree, { theme }) + await transformSlideshows(tree, { theme }) await transformSections(tree, { theme }) await transformEditorNodes(tree, { theme }) await transformCodeNodes(tree, { theme }) diff --git a/packages/mdx/src/plugin/slideshow.ts b/packages/mdx/src/plugin/slideshow.ts new file mode 100644 index 00000000..711f5bb2 --- /dev/null +++ b/packages/mdx/src/plugin/slideshow.ts @@ -0,0 +1,42 @@ +import { visitAsync, toJSX } from "./unist-utils" +import { Node, Parent } from "unist" +import { extractStepsInfo } from "./steps" +import { getPresetConfig } from "./preview" + +export async function transformSlideshows( + tree: Node, + config: { theme: any } +) { + await visitAsync( + tree, + "mdxJsxFlowElement", + async node => { + if (node.name === "CH.Slideshow") { + await transformSlideshow(node, config) + } + } + ) +} +async function transformSlideshow( + node: Node, + { theme }: { theme: any } +) { + const editorSteps = await extractStepsInfo( + node as Parent, + { theme }, + "merge step with previous" + ) + + const presetConfig = await getPresetConfig( + (node as any).attributes + ) + + toJSX(node, { + props: { + codeConfig: { theme }, + editorSteps: editorSteps, + presetConfig, + }, + appendProps: true, + }) +} diff --git a/packages/playground/content/slideshow.mdx b/packages/playground/content/slideshow.mdx new file mode 100644 index 00000000..e0a62e2b --- /dev/null +++ b/packages/playground/content/slideshow.mdx @@ -0,0 +1,207 @@ +# Slideshow with preview + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies. + + + +Lorem ipsum dolor sit amet, consectetur adipiscing something about points, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +```jsx src/App.js +import { motion } from "framer-motion" + +const transition = { duration: 1 } + +export default function App() { + const bg = "hsl(20, 100%, 50%)" + return ( +
+ +
+ ) +} +``` + +--- + +## Step 2 + +Velit euismod in pellentesque massa placerat. Mi bibendum neque egestas congue quisque egestas diam in arcu. Nisi lacus sed viverra tellus in. + +```jsx src/App.js focus=1,6,9:15 +import { motion } from "framer-motion" + +const transition = { duration: 1 } + +export default function App() { + const bg = "hsl(110, 100%, 50%)" + return ( +
+ +
+ ) +} +``` + +--- + +## Step 3 + +Id aliquet risus feugiat in ante metus dictum at tempor. Sed blandit libero volutpat sed cras. + +```jsx src/App.js focus=1,6,9:15 +import { motion } from "framer-motion" + +const transition = { duration: 1 } + +export default function App() { + const bg = "hsl(200, 100%, 50%)" + return ( +
+ +
+ ) +} +``` + +--- + +## Step 4 + +Velit euismod in pellentesque massa placerat. Mi bibendum neque egestas congue quisque egestas diam in arcu. + +```jsx src/App.js focus=1,6,9:15 +import { motion } from "framer-motion" + +const transition = { duration: 1 } + +export default function App() { + const bg = "hsl(290, 100%, 50%)" + return ( +
+ +
+ ) +} +``` + +
+ +--- + +Middle + +--- + + + +```jsx src/App.js +import { motion } from "framer-motion" + +const transition = { duration: 1 } + +export default function App() { + const bg = "hsl(20, 100%, 50%)" + return
+} +``` + +--- + +```jsx src/App.js focus=1,6,9:15 +import { motion } from "framer-motion" + +const transition = { duration: 1 } + +export default function App() { + const bg = "hsl(110, 100%, 50%)" + return ( +
+ +
+ ) +} +``` + +--- + +```jsx src/App.js focus=1,6,9:15 +import { motion } from "framer-motion" + +const transition = { duration: 1 } + +export default function App() { + const bg = "hsl(200, 100%, 50%)" + return ( +
+ +
+ ) +} +``` + +--- + +```jsx src/App.js focus=1,6,9:15 +import { motion } from "framer-motion" + +const transition = { duration: 1 } + +export default function App() { + const bg = "hsl(290, 100%, 50%)" + return ( +
+ +
+ ) +} +``` + +
+ +# Rest + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies. + +Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies. From dea83e90d74711b385faf7a8236089967a9a54cc Mon Sep 17 00:00:00 2001 From: Rodrigo Pombo Date: Mon, 27 Dec 2021 22:19:57 +0100 Subject: [PATCH 2/3] Fix zoom --- packages/mdx/src/client/slideshow.scss | 13 +- packages/mdx/src/client/slideshow.tsx | 7 +- .../playground/content/slideshow-demo.mdx | 529 ++++++++++++++++++ packages/smooth-code/src/smooth-container.tsx | 9 +- packages/smooth-code/src/use-dimensions.tsx | 2 +- packages/storybook/src/code-tween.story.js | 11 +- 6 files changed, 556 insertions(+), 15 deletions(-) create mode 100644 packages/playground/content/slideshow-demo.mdx diff --git a/packages/mdx/src/client/slideshow.scss b/packages/mdx/src/client/slideshow.scss index bd2fd35d..c01296da 100644 --- a/packages/mdx/src/client/slideshow.scss +++ b/packages/mdx/src/client/slideshow.scss @@ -18,10 +18,10 @@ .ch-slideshow-preview { flex: 1; height: auto; + min-width: 0; } .ch-slideshow-range { - margin-top: 1rem; display: flex; flex-flow: row; gap: 0.5rem; @@ -31,11 +31,16 @@ flex: 1; } -.ch-slideshow-note { - height: 140px; - overflow: auto; +.ch-slideshow-notes { border-radius: 0.25rem; margin-top: 1rem; padding: 1rem; border: 1px solid #e3e3e3; } + +.ch-slideshow-note { + min-height: 140px; + max-height: 140px; + padding: 0.05px; + overflow: auto; +} diff --git a/packages/mdx/src/client/slideshow.tsx b/packages/mdx/src/client/slideshow.tsx index 8935676f..75544ade 100644 --- a/packages/mdx/src/client/slideshow.tsx +++ b/packages/mdx/src/client/slideshow.tsx @@ -11,11 +11,13 @@ export function Slideshow({ editorSteps, codeConfig, presetConfig, + code, }: { children: React.ReactNode editorSteps: EditorStep[] codeConfig: EditorProps["codeConfig"] presetConfig?: PresetConfig + code?: EditorProps["codeConfig"] }) { const stepsChildren = React.Children.toArray(children) @@ -47,7 +49,10 @@ export function Slideshow({
{presetConfig && ( diff --git a/packages/playground/content/slideshow-demo.mdx b/packages/playground/content/slideshow-demo.mdx new file mode 100644 index 00000000..b0399cea --- /dev/null +++ b/packages/playground/content/slideshow-demo.mdx @@ -0,0 +1,529 @@ +# Slideshow with preview + +This is how to use the `` component. Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quia! Quidem, quisquam. + + + + + +```jsx src/index.js +import React from "react" +import ReactDOM from "react-dom" + +const app = React.createElement( + "h1", + { style: { color: "teal" } }, + "Hello React" +) + +ReactDOM.render(app, document.getElementById("root")) +``` + + + +React provides a createElement function to declare what we want to render to the DOM + +--- + + + +```jsx src/index.js focus=4 +import React from "react" +import ReactDOM from "react-dom" + +const app =

Hello React

+ +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +But instead of using createElement directly you can use JSX. + +--- + + + +```jsx src/index.js focus=4:10 +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent() { + return ( +
+ +
+ ) +} + +const app =

Hello React

+ +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +To create a component you only need to write a function with a name that starts with a capital letter. + +--- + + + +```jsx src/index.js focus=4[10:20],12:17 +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent() { + return ( +
+ +
+ ) +} + +const app = ( +
+ + +
+) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +Now you can use that function in JSX. + +--- + + + +```jsx src/index.js focus=14[18:29],15[18:31] +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent() { + return ( +
+ +
+ ) +} + +const app = ( +
+ + +
+) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +You can assign attributes + +--- + + + +```jsx src/index.js focus=4[22:29],14[18:29],15[18:31] +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent({ name }) { + return ( +
+ +
+ ) +} + +const app = ( +
+ + +
+) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +And React will pass them to the component as parameters + +--- + + + +```jsx src/index.js focus=4[22:29],7 +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent({ name }) { + return ( +
+ +
+ ) +} + +const app = ( +
+ + +
+) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +Inside JSX, you use curly braces to wrap dynamic data + +--- + + + +```jsx src/index.js focus=5,9 +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent({ name }) { + const goalCount = 2 + return ( +
+ + {"⚽".repeat(goalCount)} +
+ ) +} + +const app = ( +
+ + +
+) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +In fact you can wrap any javascript expression. + +--- + + + +```jsx src/index.js focus=7:9,13[15:35] +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent({ name }) { + const goalCount = 2 + + const handleClick = event => { + // do something + } + + return ( +
+ + {"⚽".repeat(goalCount)} +
+ ) +} + +const app = ( +
+ + +
+) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +To add event listeners you pass a function to the corresponding attribute + +--- + + + +```jsx src/index.js focus=5 +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent({ name }) { + const [goalCount, setCount] = React.useState(2) + + const handleClick = event => { + // do something + } + return ( +
+ + {"⚽".repeat(goalCount)} +
+ ) +} + +const app = ( +
+ + +
+) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +To add state to a component there's the useState function from React. + +--- + + + +```jsx src/index.js focus=5,7:9 +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent({ name }) { + const [goalCount, setCount] = React.useState(2) + + const handleClick = event => { + setCount(goalCount + 1) + } + + return ( +
+ + {"⚽".repeat(goalCount)} +
+ ) +} + +const app = ( +
+ + +
+) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +It gives you a function to update the state. + +--- + + + +```jsx src/index.js focus=5,7:9,13,14 + +``` + + + +When you call it, React will know it needs to re-render the component. + +--- + + + +```jsx src/index.js focus=19 +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent({ name }) { + const [goalCount, setCount] = React.useState(2) + + const handleClick = event => { + setCount(goalCount + 1) + } + + return ( +
+ + {"⚽".repeat(goalCount)} +
+ ) +} + +const players = ["Messi", "Ronaldo", "Laspada"] + +const app = ( +
+ + +
+) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +To render a list + +--- + + + +```jsx src/index.js focus=19,23[6:34],24,25[1:6] +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent({ name }) { + const [goalCount, setCount] = React.useState(2) + + const handleClick = event => { + setCount(goalCount + 1) + } + + return ( +
+ + {"⚽".repeat(goalCount)} +
+ ) +} + +const players = ["Messi", "Ronaldo", "Laspada"] + +const app = ( +
+ {players.map(playerName => ( + + ))} +
+) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +you can map each list item to an element using javascript. + +--- + + + +```jsx src/index.js focus=24[38:54] + +``` + + + +React only needs a unique key for each element, to find out when something changes. + +--- + + + +```jsx src/index.js focus=21:27,30,34 +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent({ name }) { + const [goalCount, setCount] = React.useState(2) + + const handleClick = event => { + setCount(goalCount + 1) + } + + return ( +
+ + {"⚽".repeat(goalCount)} +
+ ) +} + +const players = ["Messi", "Ronaldo", "Laspada"] + +function MyBox() { + return ( +
+ // TODO something +
+ ) +} + +const app = ( + + {players.map(playerName => ( + + ))} + +) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +If you want to compose components together + +--- + + + +```jsx src/index.js focus=21[16:27],24,30:34 +import React from "react" +import ReactDOM from "react-dom" + +function MyComponent({ name }) { + const [goalCount, setCount] = React.useState(2) + + const handleClick = event => { + setCount(goalCount + 1) + } + + return ( +
+ + {"⚽".repeat(goalCount)} +
+ ) +} + +const players = ["Messi", "Ronaldo", "Laspada"] + +function MyBox({ children }) { + return ( +
+ {children} +
+ ) +} + +const app = ( + + {players.map(playerName => ( + + ))} + +) + +ReactDOM.render(app, document.getElementById("root")) +``` + +
+ +React passes the nested elements inside a special property called children. + +
diff --git a/packages/smooth-code/src/smooth-container.tsx b/packages/smooth-code/src/smooth-container.tsx index 3aae15a9..93cc03a6 100644 --- a/packages/smooth-code/src/smooth-container.tsx +++ b/packages/smooth-code/src/smooth-container.tsx @@ -90,10 +90,7 @@ function Container({ width, height, position: "relative", - overflowX: "auto", - // padding: "0 16px", - // boxSizing: "border-box", - // overflow: "auto", + // overflowX: "auto", }} > {children} @@ -134,7 +131,7 @@ function Content({ top: 0, left: 0, transform: `translateX(${dx}px) translateY(${dy}px) scale(${scale})`, - transformOrigin: "top left", + transformOrigin: "16px top", width: "100%", }} > @@ -214,7 +211,7 @@ function getContentProps({ (extremes[1] - extremes[0] + 3) * lineHeight const zoom = Math.max( Math.min( - containerWidth / lineWidth, + (containerWidth - 16 * 2) / lineWidth, containerHeight / originalFocusHeight, maxZoom ), diff --git a/packages/smooth-code/src/use-dimensions.tsx b/packages/smooth-code/src/use-dimensions.tsx index 89bddae7..068a9be1 100644 --- a/packages/smooth-code/src/use-dimensions.tsx +++ b/packages/smooth-code/src/use-dimensions.tsx @@ -173,7 +173,7 @@ function getLongestLine( function getWidthWithoutPadding(element: HTMLElement) { const computedStyle = getComputedStyle(element) return ( - element.clientWidth - + parseFloat(computedStyle.width) - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight) ) diff --git a/packages/storybook/src/code-tween.story.js b/packages/storybook/src/code-tween.story.js index 6092a689..68d723e6 100644 --- a/packages/storybook/src/code-tween.story.js +++ b/packages/storybook/src/code-tween.story.js @@ -30,7 +30,7 @@ console.log(3) const nextCode = ` console.log(1) console.log(3) -const x = (y) => y++ +const x = (y) => y++ * foobarbaz `.trim() const prevAnnotations = [ @@ -58,7 +58,7 @@ const x = (y) => y++ }, next: { code: nextCode, - focus: "2", + focus: "2,3", annotations: nextAnnotations, }, }) @@ -78,7 +78,12 @@ const x = (y) => y++ progress={progress} tween={tween} style={{ height: "100%" }} - config={{ horizontalCenter: center, theme }} + config={{ + horizontalCenter: center, + theme, + minZoom: 0.5, + maxZoom: 2, + }} /> ) : ( "Loading..." From 4abcdea54694ae7f5a5a8a9368eefb56b2fb5100 Mon Sep 17 00:00:00 2001 From: Rodrigo Pombo Date: Mon, 27 Dec 2021 22:28:25 +0100 Subject: [PATCH 3/3] Better small mini-browser --- packages/mini-browser/src/index.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mini-browser/src/index.scss b/packages/mini-browser/src/index.scss index 9a6e28d1..ebaaadc6 100644 --- a/packages/mini-browser/src/index.scss +++ b/packages/mini-browser/src/index.scss @@ -21,6 +21,7 @@ flex: 1; padding: 0 10px; color: #544; + min-width: 5px; } .ch-browser-button {