Skip to content

feat: ChoiceGroup#73

Merged
phonzammi merged 40 commits intomainfrom
phonzammi/feat/code-tabs
Jan 17, 2026
Merged

feat: ChoiceGroup#73
phonzammi merged 40 commits intomainfrom
phonzammi/feat/code-tabs

Conversation

@phonzammi
Copy link
Collaborator

@phonzammi phonzammi commented Oct 28, 2025

closes #52,
closes #53

This pull request introduces:

  • A new component : <ChoiceGroup>
  • Two new remark plugins:
    • remarkChoiceGroup which collects and groups code blocks by choice and wraps them with the ChoiceGroup component.
    • remarkPkgManager which converts npm/npx commands to their pnpm, yarn, and bun equivalents, then wraps them with the ChoiceGroup component.

@phonzammi phonzammi force-pushed the phonzammi/feat/code-tabs branch from 9ae228e to 6ee35c1 Compare October 28, 2025 16:09
@phonzammi
Copy link
Collaborator Author

phonzammi commented Oct 29, 2025

@brillout

I'm happy with this PR! Before marking it as Ready for Review, I just want to mention something.

I plan to add two new remark plugins that will also use the CodeTabs and CodeTabPanel components, similar to how remarkJsxVueTabs is used in this PR:

  • remarkPkgManager: Detects all sh/shell code blocks containing npm or npx, then converts those commands into equivalents for other package managers (pnpm, yarn, bun). The commands will be combined into tabs.

  • remarkCodeTabs: A generic plugin that detects tab directives (e.g. ::: tabs) and processes their contents, combining them into tabs .

Example usage for remarkCodeTabs:

:::tabs items=["hono", "express.js", "fastify"] defaultValue=hono persistId=selected_server

```ts tab=hono // We could skip adding `tab=tabname` if the content always follows the order of the items above
// hono code
```

```ts tab=express.js
// express.js code
```

```ts tab=fastify
// fastify code
```
:::

Questions:

  • Do you prefer having both remarkJsxVueTabs and remarkCodeTabs, or just a single generic remarkCodeTabs?

Please let me know what you think. If you’d prefer we handle this in separate PRs, I’ll go ahead and mark this one as Ready for Review.

@brillout
Copy link
Owner

I just played with the demo, very nice!

Detects all sh/shell code blocks containing npm or npx, then converts those commands into equivalents for other package managers (pnpm, yarn, bun)

Love it 💯

A generic plugin that detects tab directives

That sounds awesome.

or just a single generic

Yes, in general, let's avoid duplication of code and logic. Instead, let's try to cover all requirements with a one or two features, and with one cohesive piece of code. Actually, I wonder whether/how we can consolidate some of our previous work about the JS toggle with this PR. But, before that, let's first focus on the overall design.

I wonder if tabs are the best solution here. One neat thing about tabs is that the user sees all options at a glance. One issue with tabs is that we cannot have two sets of tabs. I remember we had some discussions about this (I believe it was on Discord) — let me re-read the discussion again and circle back on the overall design (IIRC we came to the conclusion that there aren't any use cases for two sets of tabs). In the meantime, feel free to bounce back ideas.

@brillout
Copy link
Owner

Tabs for the "main choice" (Express.js, Hono, ...)
And dropdown for the "side choices" (JSX/Vue, JS/TS)

So, yea, that was the conclusion of our discussion.

@brillout
Copy link
Owner

image

Would this use case be supported? Note how there are 6 * 3 = 18 code blocks for the package installation shell. Actually, thanks to the automatic detection plugin this would be reduced to 6 code blocks 👌

:::tabs items=["hono", "express.js", "fastify"] defaultValue=hono persistId=selected_server

I think we can skip this line and only have tab=hono instead? When tab doesn't have a value then its value is deduced. For example this:

```vue tab

Is equivalent to:

```vue tab=vue

@brillout
Copy link
Owner

I'm thinking maybe we can replace tabs with following idea.

How about, when localStorage is pristine, we show a (clean) dropdown of all choices? With a tooltip choose your stack => that is permanently shown until the user makes his choice. The dropdown is also permanently expanded until the user makes his choice.

With "clean dropdown" I mean something visually appealing, e.g. the same as the JS toggle but vertical instead of horizontal.

I think we can then have a single feature that covers all use cases.

WDYT?

@phonzammi
Copy link
Collaborator Author

Would this use case be supported?

I think so. We can do something similar to https://github.com/mrazauskas/docusaurus-remark-plugin-tab-blocks?tab=readme-ov-file#span

@phonzammi
Copy link
Collaborator Author

I'm thinking maybe we can replace tabs with following idea.

How about, when localStorage is pristine, we show a (clean) dropdown of all choices? With a tooltip choose your stack => that is permanently shown until the user makes his choice. The dropdown is also permanently expanded until the user makes his choice.

With "clean dropdown" I mean something visually appealing, e.g. the same as the JS toggle but vertical instead of horizontal.

I think we can then have a single feature that covers all use cases.

WDYT?

I was thinking more about how the usage would look in practice. For the simple case — one code block per choice — it seems straightforward. For example, with tabs we can do something like:

```tsx tab=jsx
// content
```
```vue tab=vue
// content
```

But how the usage would be if we have more than one child (e.g. multiple code blocks or other elements) for a single choice? e.g.
Screenshot 2025-11-02 at 20 47 12

@brillout
Copy link
Owner

brillout commented Nov 2, 2025

How about this?

```bash choice=hono,npm

@phonzammi
Copy link
Collaborator Author

Since we're going to create the remarkPkgManager plugin, which:

  • remarkPkgManager: Detects all sh/shell code blocks containing npm or npx, then converts those commands into equivalents for other package managers (pnpm, yarn, bun). The commands will be combined into tabs.

So, what about something like the following:

The remarkCodeTabs plugin runs before the remarkPkgManager plugin.
First, remarkCodeTabs combines the npm command block and the next code block (based on the tab-children count if any) into individual tab panels.

Later, remarkPkgManager take over and do what it’s supposed to do (convert npm commands, etc.).

```shell tab=hono tab-children=2
npm i hono @photonjs/hono
```

```ts
// server/index.ts

import { Hono } from 'hono'
import { apply, serve } from 'vike-photon/hono'

function startServer() {
  const app = new Hono()
  apply(app)
  return serve(app)
}

export default startServer()
```

```shell tab=express tab-children=2
npm i express @photonjs/express
```

```ts
// server/index.ts

import express from 'express'
import { apply, serve } from 'vike-photon/express'

function startServer() {
  const app = express()
  apply(app)
  return serve(app)
}

export default startServer()
```

@brillout
Copy link
Owner

brillout commented Nov 2, 2025

We can also have choice= together with the shell auto-detect feature.

So far, I still prefer choice= because I think it's simpler and more flexible.

```shell choice=hono
npm i hono @photonjs/hono
```

```shell choice=express
npm i express @photonjs/express
```

```ts choice=hono
// server/index.ts

import { Hono } from 'hono'
import { apply, serve } from 'vike-photon/hono'

function startServer() {
  const app = new Hono()
  apply(app)
  return serve(app)
}

export default startServer()
```

```ts choice=express
// server/index.ts

import express from 'express'
import { apply, serve } from 'vike-photon/express'

function startServer() {
  const app = express()
  apply(app)
  return serve(app)
}

export default startServer()
```

Note how the shell commands are co-located. (Another advantage is that we can have multiple choices if we need to in the future.)

I don't see advantages of tab-children over choice, but I'm happy to be shown otherwise. (In general, I feel like it's more brittle but that it's just an unformalized feeling so it doesn't count in this discussion.)

Also, if we go with this design then choices= conceptually makes more sense than thinking in terms of "tabs".

@phonzammi phonzammi changed the title feat: combine adjacent TSX and Vue code blocks into tabs feat: CodeGroup Jan 4, 2026
@phonzammi
Copy link
Collaborator Author

  • We now support standalone :::Choice, but why only one or all? Why not partial? I feel like the implementation is maybe brittle and/or could be simplified? I understand we look for the <code> block and wrap it — we could do that only if we find one and AFAICT that's the only difference, so we don't need to treat standalone/partial/entire differently (unless I'm missing something).

What do you think of these ?

@brillout
Copy link
Owner

👍 I think I like it. Little nitpick: I think we can remove some of the code introduced by a58173a.

@phonzammi
Copy link
Collaborator Author

phonzammi commented Jan 16, 2026

👍 I think I like it. Little nitpick: I think we can remove some of the code introduced by a58173a.

Like this : keep using <select> when single choice

I also make sure to hide the select dropdown for single choice, do you think that's okay ?

@brillout
Copy link
Owner

Yes, that's actually what I was thinking. (Maybe there is more potential to remove code, but I didn't dig much into it.)

@brillout
Copy link
Owner

I also make sure to hide the select dropdown for single choice, do you think that's okay ?

👍 Sounds good.

@phonzammi
Copy link
Collaborator Author

  • In principle, the only difference between the JS toggle and this new feature is how they are rendered?
  • Can we remove <CodeSnippets> in favor of re-using <CodeGroup>?
  • Can we remove <TypescriptOnly> in favor of :::Choice{#typescript}?
  • I like remarkPkgManager() — it's a simple implementation that simply re-uses <CodeGroup>. Can we do the same for the JS toggle? We can remove the beautiful JS<=>TS toggle for now. (I've ideas about improving all dropdowns/toggles, but let's keep this PR only about the overall structure. Let's polish the dropdowns in a subsequent and separate PR.)

I like your ideas and think they’re doable, but are we sure we want to include them in this PR? I’m worried it might get confusing.

@brillout
Copy link
Owner

👍 You're right, let's do it in a separate PR.

@brillout
Copy link
Owner

This PR doesn't change vike.dev at all, correct? (Since vike.dev doesn't use :::Choices nor choice=.)

So we can merge this PR, release a new version, then vike.dev wouldn't change at all?

@phonzammi
Copy link
Collaborator Author

👍 You're right, let's do it in a separate PR.

Great 👍.

This PR doesn't change vike.dev at all, correct? (Since vike.dev doesn't use :::Choices nor choice=.)

So we can merge this PR, release a new version, then vike.dev wouldn't change at all?

Correct, I’m sure it won’t.

I think the last thing to do in this PR is rename_full CodeGroup ChoiceGroup.
Also do you think we should revert example of partial choices ?

@brillout
Copy link
Owner

Also do you think we should revert example of partial choices ?

I think we can keep it. Actually, let's add some tests before merging. The tests will be extremely helpful for our subsequent refactor PR.

I think the last thing to do in this PR is rename_full CodeGroup ChoiceGroup.

If you want, I can do it (as you wish) after we have some tests.

@brillout
Copy link
Owner

This PR doesn't change vike.dev at all, correct? (Since vike.dev doesn't use :::Choices nor choice=.)
So we can merge this PR, release a new version, then vike.dev wouldn't change at all?

Correct, I’m sure it won’t.

Ah, actually, how about remarkPkgManager? I think that's a breaking change?

@phonzammi
Copy link
Collaborator Author

Ah, actually, how about remarkPkgManager? I think that's a breaking change?

Ah, yes — you’re right. My bad, I forgot about that.

@phonzammi
Copy link
Collaborator Author

I think the last thing to do in this PR is rename_full CodeGroup ChoiceGroup.

Done: rename_full CodeGroup ChoiceGroup

@brillout
Copy link
Owner

Ah, actually, how about remarkPkgManager? I think that's a breaking change?

Ah, yes — you’re right. My bad, I forgot about that.

It isn't that bad though, right? We just need to update the few places where it would break vike.dev?

After we have tests, feel free to go ahead and merge, release, and update vike.dev & telefunc.com.

@phonzammi
Copy link
Collaborator Author

After we have tests, feel free to go ahead and merge, release, and update vike.dev & telefunc.com.

Okay 👍

@phonzammi
Copy link
Collaborator Author

What do you think of the tests (add tests). Are they sufficient, or is it too much?

@brillout
Copy link
Owner

Nice. I definitely wouldn't say too much. If anything I'd recommend to make them cover more aspects of the implementation, so that you can refactor everything without testing manually (or just occasionally to double check). But as you wish.

Little nitpick: the last part of the test is difficult to understand (e.g not sure what the for-loop does, and not sure why you discriminate dev/prod). (But tbh I frequently write unclear test myself. Tests can be dirty, that's okay — bad tests don't jeopardize the overall structure of the program.)

@phonzammi phonzammi changed the title feat: CodeGroup feat: ChoiceGroup Jan 17, 2026
@phonzammi
Copy link
Collaborator Author

Thanks! Ready to squash and merge now.

@brillout
Copy link
Owner

💯 Feel free to self squash, merge, and release.

@phonzammi phonzammi merged commit 645248f into main Jan 17, 2026
2 checks passed
@phonzammi phonzammi deleted the phonzammi/feat/code-tabs branch January 17, 2026 12:33
@phonzammi
Copy link
Collaborator Author

Released as v0.16.8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow user to choose between JSX and Vue Allow user to select package manager

2 participants