Skip to content

Commit 3792aa9

Browse files
authored
feat: support overriding TopBar (#112)
1 parent 828bfac commit 3792aa9

File tree

13 files changed

+215
-32
lines changed

13 files changed

+215
-32
lines changed

docs/tutorialkit.dev/astro.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ export default defineConfig({
5050
label: 'User Interface Reference',
5151
link: '/guides/ui/',
5252
},
53+
{
54+
label: 'Overriding Components',
55+
link: '/guides/overriding-components/',
56+
},
5357
],
5458
},
5559
{
Loading
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
title: 'Overriding Components'
3+
description: "Override TutorialKit's default components to fit your needs."
4+
---
5+
import { Image } from 'astro:assets';
6+
import uiTopBar from './images/ui-top-bar.png';
7+
8+
TutorialKit's default components are customizable with [theming](/reference/theming/) options.
9+
In cases where theming is not enough you can override some built-in components with your own ones.
10+
11+
## Configuration
12+
13+
Define `components` option in your `astro.config.ts`:
14+
15+
```ts
16+
import tutorialkit from '@tutorialkit/astro';
17+
import { defineConfig } from 'astro/config';
18+
19+
export default defineConfig({
20+
integrations: [
21+
tutorialkit({
22+
components: {
23+
// Component overrides go here
24+
},
25+
}),
26+
],
27+
});
28+
```
29+
30+
For example to override `TopBar`, provide a path to the new Astro component:
31+
32+
```ts
33+
tutorialkit({
34+
components: {
35+
TopBar: './src/components/CustomTopBar.astro',
36+
},
37+
}),
38+
```
39+
40+
### Top Bar
41+
42+
<Image src={uiTopBar} alt="TutorialKit's Top bar" />
43+
44+
When overriding `TopBar` you can place TutorialKit's default components using following [Astro slots](https://docs.astro.build/en/basics/astro-components/#named-slots):
45+
46+
- `logo`: Logo of the application
47+
- `theme-switch`: Switch for changing the theme
48+
- `login-button`: For StackBlitz Enterprise user, the login button
49+
50+
```jsx title="src/components/CustomTopBar.astro"
51+
<nav>
52+
<slot name="logo" />
53+
54+
<a href="https://tutorialkit.dev/">
55+
Home page
56+
</a>
57+
58+
<slot name="theme-switch" />
59+
60+
<LanguageSelect />
61+
62+
<slot name="login-button" />
63+
</nav>
64+
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"demo": "pnpm run --filter=demo.tutorialkit.dev dev",
1414
"demo:build": "pnpm run --filter=demo.tutorialkit.dev build",
1515
"lint": "eslint \"{packages,docs}/**/*\"",
16-
"test": "pnpm run --filter=@tutorialkit/* --filter=tutorialkit test"
16+
"test": "pnpm run --stream --filter=@tutorialkit/* --filter=tutorialkit test --run"
1717
},
1818
"license": "MIT",
1919
"packageManager": "[email protected]",

packages/astro/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
".": "./dist/index.js",
1818
"./types": "./types.d.ts",
1919
"./default/pages/index.astro": "./dist/default/pages/index.astro",
20-
"./default/pages/[...slug].astro": "./dist/default/pages/[...slug].astro"
20+
"./default/pages/[...slug].astro": "./dist/default/pages/[...slug].astro",
21+
"./default/components/TopBar.astro": "./dist/default/components/TopBar.astro"
2122
},
2223
"files": [
2324
"dist"

packages/astro/src/default/components/Header.astro

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<nav
2+
class="bg-tk-elements-topBar-backgroundColor border-b border-tk-elements-app-borderColor flex max-w-full items-center p-3 px-4 min-h-[56px]"
3+
>
4+
<div class="flex flex-1">
5+
<slot name="logo" />
6+
</div>
7+
<div>
8+
<slot name="theme-switch" />
9+
</div>
10+
<div>
11+
<slot name="login-button" />
12+
</div>
13+
</nav>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
import { TopBar } from 'tutorialkit:override-components';
3+
import { ThemeSwitch } from './ThemeSwitch';
4+
import { LoginButton } from './LoginButton';
5+
import Logo from './Logo.astro';
6+
import { useAuth } from './setup';
7+
8+
interface Props {
9+
logoLink: string;
10+
}
11+
12+
const { logoLink } = Astro.props;
13+
---
14+
15+
<TopBar>
16+
<Logo slot="logo" logoLink={logoLink ?? '/'} />
17+
18+
<ThemeSwitch client:load transition:persist slot="theme-switch" />
19+
20+
{useAuth && <LoginButton client:load transition:persist slot="login-button" />}
21+
</TopBar>

packages/astro/src/default/env-default.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,11 @@ interface WebContainerConfig {
77
scope: string;
88
}
99

10+
declare module 'tutorialkit:override-components' {
11+
const topBar: typeof import('./src/default/components/TopBar.astro').default;
12+
13+
export { topBar as TopBar };
14+
}
15+
1016
declare const __ENTERPRISE__: boolean;
1117
declare const __WC_CONFIG__: WebContainerConfig | undefined;

packages/astro/src/default/pages/[...slug].astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
import type { InferGetStaticPropsType } from 'astro';
3-
import Header from '../components/Header.astro';
3+
import TopBarWrapper from '../components/TopBarWrapper.astro';
44
import MainContainer from '../components/MainContainer.astro';
55
import Layout from '../layouts/Layout.astro';
66
import '../styles/base.css';
@@ -19,7 +19,7 @@ const { lesson, logoLink, navList, title } = Astro.props as Props;
1919
<Layout title={title}>
2020
<div id="previews-container"></div>
2121
<main class="max-w-full flex flex-col h-screen overflow-hidden" data-swap-root>
22-
<Header logoLink={logoLink ?? '/'} />
22+
<TopBarWrapper logoLink={logoLink ?? '/'} />
2323
<MainContainer lesson={lesson} navList={navList} />
2424
</main>
2525
</Layout>

packages/astro/src/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { updateMarkdownConfig } from './remark/index.js';
66
import { tutorialkitCore } from './vite-plugins/core.js';
77
import { userlandCSS, watchUserlandCSS } from './vite-plugins/css.js';
88
import { tutorialkitStore } from './vite-plugins/store.js';
9+
import { overrideComponents, type OverrideComponentsOptions } from './vite-plugins/override-components.js';
910
import { WebContainerFiles } from './webcontainer-files/index.js';
1011

1112
export const unoCSSConfig = {
@@ -27,6 +28,11 @@ export interface Options {
2728
*/
2829
defaultRoutes?: boolean | 'tutorial-only';
2930

31+
/**
32+
* Override components of TutorialKit.
33+
*/
34+
components?: OverrideComponentsOptions;
35+
3036
/**
3137
* The value of the Cross-Origin-Embedder-Policy header for the dev server.
3238
* This is required for webcontainer to works.
@@ -62,7 +68,12 @@ export interface Options {
6268
};
6369
}
6470

65-
export default function createPlugin({ defaultRoutes = true, isolation, enterprise }: Options = {}): AstroIntegration {
71+
export default function createPlugin({
72+
defaultRoutes = true,
73+
components,
74+
isolation,
75+
enterprise,
76+
}: Options = {}): AstroIntegration {
6677
const webcontainerFiles = new WebContainerFiles();
6778

6879
let _config: AstroConfig;
@@ -96,6 +107,7 @@ export default function createPlugin({ defaultRoutes = true, isolation, enterpri
96107
userlandCSS,
97108
tutorialkitStore,
98109
tutorialkitCore,
110+
overrideComponents({ components, defaultRoutes: !!defaultRoutes }),
99111
process.env.TUTORIALKIT_DEV ? (await import('vite-plugin-inspect')).default() : null,
100112
],
101113
},
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* A plugin that lets users override TutorialKit's components.
3+
*
4+
* The virtual module can be imported as:
5+
*
6+
* ```ts
7+
* import { TopBar } from 'tutorialkit:override-components';
8+
*
9+
* <TopBar />
10+
* ```
11+
*
12+
* User can override the components in `astro.config.ts`:
13+
*
14+
* ```ts
15+
* export default defineConfig({
16+
* integrations: [
17+
* tutorialkit({
18+
* components: {
19+
* TopBar: './CustomTopBar.astro',
20+
* },
21+
* }),
22+
* ],
23+
* });
24+
* ```
25+
*/
26+
import type { VitePlugin } from '../types.js';
27+
28+
export interface OverrideComponentsOptions {
29+
/**
30+
* Component for overriding the top bar.
31+
*
32+
* This component has 3 slots that are used to pass TutorialKit's default components:
33+
* - `logo`: Logo of the application
34+
* - `theme-switch`: Switch for changing the theme
35+
* - `login-button`: For StackBlitz Enterprise user, the login button
36+
*
37+
* Usage:
38+
*
39+
* ```jsx
40+
* <slot name="logo" />
41+
* <slot name="theme-switch" />
42+
* <slot name="login-button" />
43+
* ```
44+
*/
45+
TopBar?: string;
46+
}
47+
48+
interface Options {
49+
components?: OverrideComponentsOptions;
50+
defaultRoutes: boolean;
51+
}
52+
53+
const virtualModuleId = 'tutorialkit:override-components';
54+
const resolvedId = `\0${virtualModuleId}`;
55+
56+
export function overrideComponents({ components, defaultRoutes }: Options): VitePlugin {
57+
return {
58+
name: 'tutorialkit-override-components-plugin',
59+
resolveId(id) {
60+
if (id === virtualModuleId) {
61+
return resolvedId;
62+
}
63+
64+
return undefined;
65+
},
66+
async load(id) {
67+
if (id === resolvedId) {
68+
const topBar = components?.TopBar || resolveDefaultTopBar(defaultRoutes);
69+
70+
return `
71+
export { default as TopBar } from '${topBar}';
72+
`;
73+
}
74+
75+
return undefined;
76+
},
77+
};
78+
}
79+
80+
function resolveDefaultTopBar(defaultRoutes: boolean) {
81+
if (defaultRoutes) {
82+
return '@tutorialkit/astro/default/components/TopBar.astro';
83+
}
84+
85+
// default `TopBar` is used from local file when `defaultRoutes` is disabled
86+
return './src/components/TopBar.astro';
87+
}

packages/cli/tests/__snapshots__/create-tutorial.test.ts.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ exports[`create and eject a project 1`] = `
147147
"public/logo.svg",
148148
"src",
149149
"src/components",
150-
"src/components/Header.astro",
151150
"src/components/LoginButton.tsx",
152151
"src/components/Logo.astro",
153152
"src/components/MainContainer.astro",
@@ -156,6 +155,8 @@ exports[`create and eject a project 1`] = `
156155
"src/components/NavWrapper.tsx",
157156
"src/components/ResizablePanel.astro",
158157
"src/components/ThemeSwitch.tsx",
158+
"src/components/TopBar.astro",
159+
"src/components/TopBarWrapper.astro",
159160
"src/components/TutorialContent.astro",
160161
"src/components/WorkspacePanelWrapper.tsx",
161162
"src/components/setup.ts",

0 commit comments

Comments
 (0)