code-hike / codehike

Build rich content websites with Markdown and React
https://codehike.org
MIT License
4.86k stars 152 forks source link

How to use other tag inside Scrollycoding #478

Open Windsooon opened 4 weeks ago

Windsooon commented 4 weeks ago

I'm trying to use Tag <CodeWithTooltips> inside <Scrollycoding> like this:

<Scrollycoding>

## !!steps A Game of Thrones

A Game of Thrones is the first book in the A Song of Ice and Fire series by George R.R. Martin. Set in a world where seasons last for years, it introduces a complex plot and a wide cast of characters, ranging from noble families vying for the Iron Throne to the supernatural threats in the North.

<CodeWithTooltips>
```js ! george.js
const houses = [
  "Stark",
  "Lannister",
  "Baratheon",
  "Targaryen",
]

const winner =
  houses[
    Math.floor(
      Math.random() * houses.length,
    )
  ]

console.log(`Iron Throne: ${winner}`)


However, it tells me 

Error: at

!!steps A Game of Thrones

Error for code: Required { "expected": "object", "received": "undefined" }


I guess because I use the example for CodeWithTooltips which return 

`return <Pre code={highlighted} handlers={[tooltip]} />`

It's not a code object so `<Scrollycoding>` can not parse it. So I tried another way to update the Code function for scrollycoding.tsx which would work

async function Code({ codeblock, tooltips }: { codeblock: RawCode; tooltips?: Block[] }) { console.log(tooltips); const highlighted = await highlight(codeblock, "github-from-css") highlighted.annotations = highlighted.annotations.map((a) => { const tooltip = tooltips.find((t) => t.title === a.query); if (!tooltip) return a; return { ...a, data: { ...a.data, children: tooltip.children },

}

}) return ( <Pre code={highlighted} handlers={[tooltip, tokenTransitions, wordWrap]} className="min-h-[40rem]" /> ) }



However, I also want to use other tags like `<CodeSwitcher>` inside  `<Scrollycoding>`, Are there any better way to implement is?
pomber commented 4 weeks ago

One way is to change the Scrollycoding component to accept a Block instead of a CodeBlock, then you can render anything inside the sticky part.

<Scrollycoding>

## !!steps A Game of Thrones

A Game of Thrones is the first book in the A Song of Ice and Fire series by George R.R. Martin. Set in a world where seasons last for years, it introduces a complex plot and a wide cast of characters, ranging from noble families vying for the Iron Throne to the supernatural threats in the North.

### !sticker

<CodeWithTooltips>
...
</CodeWithTooltips>

## !!steps ...

...
Windsooon commented 2 weeks ago

@pomber Thank you for your reply, I try several ways in the past few days but still couldn't figure it out:

First Way

I create a Schema like this:

const Schema = Block.extend({
  steps: z.array(
    Block.extend({
        sticker: Block.extend({
            code: CodeBlock,
            tooltips: z.array(Block).optional(),
    })
  })),
})

And the MDX looks like:

<Scrollycoding>

## !!steps A Game of Thrones

A Game of Thrones is the first book in the A Song of Ice and Fire series by George R.R. Martin. Set in a world where seasons last for years, it introduces a complex plot and a wide cast of characters, ranging from noble families vying for the Iron Throne to the supernatural threats in the North.

The Fellowship of the Ring

### !sticker

<CodeWithTooltips>

  ~~~js !code
  // !tooltip[/lorem/] description
  function lorem(ipsum, dolor = 1) {
    const sit = ipsum == null ? 0 : ipsum.sit
    dolor = sit - amet(dolor)
    // !tooltip[/consectetur/] inspect
    return sit ? consectetur(ipsum) : []
  }
  ~~~

## !!tooltips description

### Hello world

Lorem ipsum **dolor** sit amet `consectetur`.

</CodeWithTooltips>
</Scrollycoding>

I still got the error from const {steps} = parseProps(props, Schema)

Error: at 
## !!steps A Game of Thrones
### !sticker
Error for `code`: Required
{
  "expected": "object",
  "received": "undefined"
}

I don't know why the Schema didn't work.

Second Way

I created a Schema:

const Schema = Block.extend({
  steps: z.array(
    Block.extend({
        sticker: Block
  })),
})

const ToolSchema = Block.extend({
  code: CodeBlock,
  tooltips: z.array(Block).optional(),
})

And try to access code and tooltips using

export function Scrollycoding(props: unknown) {
  const { steps } = parseProps(props, Schema)
  return (
    <SelectionProvider className="flex gap-4">
      <div className="flex-1 mt-32 mb-[90vh] ml-2 prose min-w-60">
        {steps.map((step, i) => (
          <Selectable
            key={i}
            index={i}
            selectOn={["click", "scroll"]}
            className="border-l-4 data-[selected=true]:border-blue-400 px-5 py-2 mb-24 rounded bg-card"
          >
            <h2 className="mt-4 text-xl">{step.title}</h2>
            <div>{step.children}</div>
          </Selectable>
        ))}
      </div>
      <div className="w-1/2 bg-card">
        <div className="top-16 sticky overflow-auto">
          <Selection
            from={steps.map((step) => (
              <CodeWithTooltips props={step.sticker} />
            ))}
          />
        </div>
      </div>
    </SelectionProvider>
  )
}

async function CodeWithTooltips(props: unknown) {
  const { code, tooltips = [] } = parseProps(props, ToolSchema)
  const highlighted = await highlight(code, "github-from-css")

  highlighted.annotations = highlighted.annotations.map((a) => {
    const tooltip = tooltips.find((t) => t.title === a.query)
    if (!tooltip) return a
    return {
      ...a,
      data: { ...a.data, children: tooltip.children },

    }
  })
  return <Pre code={highlighted} handlers={[tooltip]} />
}

I still got

Error: at root
Error for `code`: Required
{
  "expected": "object",
  "received": "undefined"
}

I wonder which part I missed, Thank you.

pomber commented 2 weeks ago

In your first way, make the sticker a bare Block:

const Schema = Block.extend({
  steps: z.array(Block.extend({sticker: Block})),
})

and then you render the children:

<Selection
  from={steps.map((step) => (
    step.sticker.children
  ))}
/>
Windsooon commented 2 weeks ago

Thank you so much. It works well! I also found that token-transitions.tsx would add display: "inline-block"

export const tokenTransitions: AnnotationHandler = {
  name: "token-transitions",
  PreWithRef: SmoothPre,
  Token: (props) => (
    <InnerToken merge={props} style={{ display: "inline-block" }} />
  ),
}

so the tooltip classunderline decoration-dashed didn't work as expected. I solved it by adding an extra style

export const tooltip: AnnotationHandler = {
  name: "tooltip",
  Inline: ({ children, annotation }) => {
    const { query, data } = annotation
    return (
      <TooltipProvider>
        <Tooltip>
          <TooltipTrigger
            className="underline decoration-dashed"
            style={{ borderBottom: "1px dashed black" }} // Add border and padding
          >
            {children}
          </TooltipTrigger>
          <TooltipContent align="start">
            {data?.children || query}
          </TooltipContent>
        </Tooltip>
      </TooltipProvider>
    )
  },
}

@pomber Do you think we should add an demo for using nested tag? I can create a PR using my current example if needed.