Skip to content

Commit 50c7562

Browse files
committed
feat: inline actions & layout slots
1 parent 8da9792 commit 50c7562

File tree

13 files changed

+299
-7
lines changed

13 files changed

+299
-7
lines changed

packages/nimiq-icons/src/flags/icons.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1268,7 +1268,7 @@
12681268
"body": "<mask id=\"nimiq-flags-zw-hexagon-pbk987\" width=\"32\" height=\"28\" x=\"0\" y=\"2\" maskUnits=\"userSpaceOnUse\" style=\"mask-type:alpha\"><path fill=\"#fff\" d=\"M31.15 14.71 24.707 3.54a2.58 2.58 0 00-2.234-1.29H9.582c-.92 0-1.77.49-2.23 1.29L.907 14.71c-.46.8-.46 1.78 0 2.58l6.445 11.17c.46.8 1.31 1.29 2.23 1.29h12.89c.92 0 1.77-.49 2.23-1.29l6.445-11.17c.464-.8.464-1.78.004-2.58\"/></mask><g fill=\"none\"><g mask=\"url(#nimiq-flags-zw-hexagon-pbk987)\"><path fill=\"#6DA544\" d=\"M1.962 0H32v32H1.962z\"/><path fill=\"#FFDA44\" d=\"M3.613 4.581H32v4.582l-4.069 6.875 4.07 6.875v4.58H3.612z\"/><path fill=\"#D80027\" d=\"M8.25 9.162H32v4.582l-1.687 2.25L32 18.325v4.581H8.25z\"/><path fill=\"#EEE\" d=\"M0 0v32l17.488-16z\"/><path fill=\"#D80027\" d=\"m6.437 11.825 1.032 3.188h3.35l-2.713 1.975 1.038 3.187-2.713-1.969-2.712 1.969 1.037-3.187-2.712-1.976h3.35z\"/><path fill=\"#FFDA44\" d=\"m9.281 16.263-2.7-.957-.212-1.937a1.044 1.044 0 10-2.032.475l-.75.756h1.344c0 1.4-1.044 1.4-1.044 2.794l.575 1.387h3.482l.58-1.387q.086-.198.107-.413c.5-.2.65-.719.65-.719\"/><path fill=\"#333\" d=\"m1.963 0 13.75 13.75H32v4.575H15.638L1.962 32H0l16-16L0 0z\"/><path fill=\"url(#nimiq-flags-zw-hexagon-pbk987)\" d=\"M31.15 14.71 24.707 3.54a2.58 2.58 0 00-2.234-1.29H9.582c-.92 0-1.77.49-2.23 1.29L.907 14.71c-.46.8-.46 1.78 0 2.58l6.445 11.17c.46.8 1.31 1.29 2.23 1.29h12.89c.92 0 1.77-.49 2.23-1.29l6.445-11.17c.464-.8.464-1.78.004-2.58\"/></g><defs><radialGradient id=\"nimiq-flags-zw-hexagon-pbk987\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(30.943 0 0 30.9452 23.829 29.395)\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#1D1D1B\" stop-opacity=\".3\"/><stop offset=\"1\" stop-color=\"#E9B213\" stop-opacity=\"0\"/></radialGradient></defs></g>"
12691269
}
12701270
},
1271-
"lastModified": 1759234944,
1271+
"lastModified": 1759237102,
12721272
"width": 32,
12731273
"height": 32
12741274
}

packages/nimiq-icons/src/flags/info.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"prefix": "nimiq-flags",
33
"name": "nimiq-flags",
44
"total": 422,
5-
"version": "1.0.0-beta.107",
5+
"version": "1.0.0-beta.108",
66
"author": {
77
"name": "onmax",
88
"url": "https://github.com/onmax"

packages/nimiq-icons/src/flags/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "nimiq-flags",
33
"description": "The Nimiq Flags as an iconify icon set.",
44
"version": "1.0.0-beta.108",
5-
"iconSetVersion": "1.0.0-beta.107",
5+
"iconSetVersion": "1.0.0-beta.108",
66
"main": "index.js",
77
"module": "index.mjs",
88
"types": "index.d.ts",

packages/nimiq-icons/src/icons/icons.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1586,7 +1586,7 @@
15861586
"hidden": true
15871587
}
15881588
},
1589-
"lastModified": 1759234944,
1589+
"lastModified": 1759237102,
15901590
"width": 12,
15911591
"height": 12
15921592
}

packages/nimiq-icons/src/icons/info.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"prefix": "nimiq",
33
"name": "nimiq-icons",
44
"total": 84,
5-
"version": "1.0.0-beta.107",
5+
"version": "1.0.0-beta.108",
66
"author": {
77
"name": "onmax",
88
"url": "https://github.com/onmax"

packages/nimiq-icons/src/icons/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "nimiq-icons",
33
"description": "The Nimiq Icons as an iconify icon set.",
44
"version": "1.0.0-beta.108",
5-
"iconSetVersion": "1.0.0-beta.107",
5+
"iconSetVersion": "1.0.0-beta.108",
66
"main": "index.js",
77
"module": "index.mjs",
88
"types": "index.d.ts",

packages/nimiq-vitepress-theme/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,47 @@ interface NimiqVitepressThemeConfig {
8585
}
8686
```
8787

88+
## Layout Slots
89+
90+
The theme provides layout slots for extending functionality:
91+
92+
### `layout-bottom`
93+
94+
A slot rendered at the bottom of the layout, useful for mounting global components like modals or notification systems.
95+
96+
**Example usage:**
97+
98+
```ts
99+
import type { Theme } from 'vitepress'
100+
// .vitepress/theme/index.ts
101+
import { defineNimiqThemeConfig } from 'nimiq-vitepress-theme/theme'
102+
import { h } from 'vue'
103+
import MyGlobalModal from './components/MyGlobalModal.vue'
104+
105+
const baseTheme = defineNimiqThemeConfig({
106+
enhanceApp({ app }) {
107+
// your app enhancements
108+
},
109+
})
110+
111+
export default {
112+
extends: baseTheme,
113+
Layout: () => {
114+
return h(baseTheme.Layout!, null, {
115+
'layout-bottom': () => h(MyGlobalModal),
116+
})
117+
},
118+
} satisfies Theme
119+
```
120+
121+
This slot is ideal for:
122+
123+
- Global modals and dialogs
124+
- Feedback widgets
125+
- Cookie consent banners
126+
- Analytics components
127+
- Any component that needs to be available across all pages
128+
88129
## Testing the 404 Page
89130

90131
To test the custom 404 error page:
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<script setup lang="ts">
2+
import type { OutlineAction } from '../types'
3+
import { Separator } from 'reka-ui'
4+
import { useData } from 'vitepress'
5+
import { computed, ref } from 'vue'
6+
import { useSourceCode } from '../composables/useSourceCode'
7+
8+
const { theme } = useData()
9+
const {
10+
copyMarkdownContent,
11+
copyMarkdownLink,
12+
chatGPTUrl,
13+
claudeUrl,
14+
viewAsMarkdown,
15+
copyOptionsConfig,
16+
showCopyMarkdown,
17+
} = useSourceCode()
18+
19+
const isExpanded = ref(false)
20+
21+
const customActions = computed<OutlineAction[]>(() => {
22+
return (theme.value.outlineActions as OutlineAction[] | undefined) || []
23+
})
24+
25+
const allActions = computed(() => {
26+
const actions: OutlineAction[] = []
27+
28+
if (showCopyMarkdown.value) {
29+
actions.push({
30+
icon: 'i-tabler:copy',
31+
label: 'Copy page',
32+
onClick: copyMarkdownContent,
33+
})
34+
}
35+
36+
return [...actions, ...customActions.value]
37+
})
38+
39+
const nativeOptions = computed(() => {
40+
const options: OutlineAction[] = []
41+
42+
if (copyOptionsConfig.value.markdownLink) {
43+
options.push({
44+
icon: 'i-tabler:link',
45+
label: 'Copy markdown link',
46+
onClick: copyMarkdownLink,
47+
})
48+
}
49+
50+
if (copyOptionsConfig.value.viewMarkdown) {
51+
options.push({
52+
icon: 'i-tabler:eye',
53+
label: 'View as markdown',
54+
onClick: viewAsMarkdown,
55+
})
56+
}
57+
58+
return options
59+
})
60+
61+
const externalOptions = computed(() => {
62+
const options: OutlineAction[] = []
63+
64+
if (copyOptionsConfig.value.chatgpt) {
65+
options.push({
66+
icon: 'i-local:openai',
67+
label: 'Open in ChatGPT',
68+
onClick: () => {
69+
if (typeof window !== 'undefined') {
70+
window.open(chatGPTUrl.value, '_blank', 'noopener,noreferrer')
71+
}
72+
},
73+
})
74+
}
75+
76+
if (copyOptionsConfig.value.claude) {
77+
options.push({
78+
icon: 'i-local:claude',
79+
label: 'Open in Claude',
80+
onClick: () => {
81+
if (typeof window !== 'undefined') {
82+
window.open(claudeUrl.value, '_blank', 'noopener,noreferrer')
83+
}
84+
},
85+
})
86+
}
87+
88+
return options
89+
})
90+
91+
const hasDropdown = computed(() => nativeOptions.value.length > 0 || externalOptions.value.length > 0)
92+
93+
function toggleExpanded() {
94+
isExpanded.value = !isExpanded.value
95+
}
96+
</script>
97+
98+
<template>
99+
<div v-if="allActions.length > 0" f-my-lg>
100+
<div flex="~ gap-8 items-center wrap">
101+
<div v-for="(action, index) in allActions" :key="index" flex="~ items-center" relative>
102+
<button
103+
type="button"
104+
flex="~ items-center gap-8"
105+
p="x-12 y-8"
106+
cursor-pointer
107+
hover:bg-neutral-100
108+
rounded-6
109+
transition-colors
110+
f-text-xs
111+
text-neutral-800
112+
border="1 neutral-300"
113+
@click="() => action.onClick()"
114+
>
115+
<div :class="action.icon" />
116+
<span>{{ action.label }}</span>
117+
</button>
118+
119+
<div v-if="index === 0 && hasDropdown" relative ml-8>
120+
<button
121+
type="button"
122+
p="x-8 y-8"
123+
hover:bg-neutral-100
124+
rounded-6
125+
transition-colors
126+
border="1 neutral-300"
127+
@click.stop="toggleExpanded"
128+
>
129+
<div i-tabler:dots />
130+
</button>
131+
132+
<Transition
133+
enter-active-class="transition-all duration-200"
134+
enter-from-class="opacity-0 scale-95"
135+
enter-to-class="opacity-100 scale-100"
136+
leave-active-class="transition-all duration-150"
137+
leave-from-class="opacity-100 scale-100"
138+
leave-to-class="opacity-0 scale-95"
139+
>
140+
<div v-if="isExpanded" absolute left-0 top="[calc(100%+4px)]" min-w-200 bg-white rounded-8 shadow-lg border="1 neutral-200" z-50 py-6>
141+
<div v-for="(option, idx) in nativeOptions" :key="`native-${idx}`" flex="~ items-center gap-8" px-10 py-6 cursor-pointer hover:bg-neutral-100 transition-colors f-text-xs text-neutral-800 @click="() => { option.onClick(); isExpanded = false }">
142+
<div :class="option.icon" text-14 />
143+
<span>{{ option.label }}</span>
144+
</div>
145+
146+
<Separator v-if="nativeOptions.length > 0 && externalOptions.length > 0" my-4 h-1 bg-neutral-200 />
147+
148+
<div v-for="(option, idx) in externalOptions" :key="`external-${idx}`" flex="~ items-center gap-8" px-10 py-6 cursor-pointer hover:bg-neutral-100 transition-colors f-text-xs text-neutral-800 @click="() => { option.onClick(); isExpanded = false }">
149+
<div :class="option.icon" text-14 />
150+
<span>{{ option.label }}</span>
151+
</div>
152+
</div>
153+
</Transition>
154+
</div>
155+
</div>
156+
</div>
157+
</div>
158+
</template>

packages/nimiq-vitepress-theme/src/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { NimiqVitepressThemeConfig } from 'nimiq-vitepress-theme/types.js'
22
import type { UserConfig } from 'vitepress'
33
import { defineConfig } from 'vitepress'
4+
import inlineActionsPlugin from './vite/inline-actions-plugin'
45

56
export function defineNimiqVitepressConfig<T = NimiqVitepressThemeConfig>(config: UserConfig<T>): UserConfig<T> {
67
const defaultConfig: UserConfig<T> = {
@@ -11,11 +12,30 @@ export function defineNimiqVitepressConfig<T = NimiqVitepressThemeConfig>(config
1112
light: 'vitesse-light',
1213
dark: 'vitesse-dark',
1314
},
15+
preConfig(md) {
16+
md.use(inlineActionsPlugin)
17+
},
1418
},
1519
}
1620

21+
// Merge user's preConfig with default if provided
22+
const userMarkdown = config.markdown || {}
23+
const userPreConfig = userMarkdown.preConfig
24+
1725
return defineConfig<T>({
1826
extends: defaultConfig,
1927
...config,
28+
markdown: {
29+
...defaultConfig.markdown,
30+
...userMarkdown,
31+
preConfig(md) {
32+
// Apply default plugin
33+
md.use(inlineActionsPlugin)
34+
// Then apply user's preConfig if exists
35+
if (userPreConfig) {
36+
userPreConfig(md)
37+
}
38+
},
39+
},
2040
})
2141
}

packages/nimiq-vitepress-theme/src/layout/Layout.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ const isMobileOrTablet = breakpoints.smaller('lg')
9393
<div id="toaster-container">
9494
<Toaster position="bottom-right" :duration="3000" class="nq-raw" style="padding: 0" />
9595
</div>
96+
97+
<!-- Custom slot for global components like modals -->
98+
<slot name="layout-bottom" />
9699
</template>
97100

98101
<style>

0 commit comments

Comments
 (0)