@sit-onyx/nuxt-docs 0.6.0 → 0.7.0-dev-20260513141728

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,73 @@
1
+ <script lang="ts" setup>
2
+ import type { TocLink } from "@nuxtjs/mdc";
3
+
4
+ const props = defineProps<{
5
+ toc: TocLink[];
6
+ /**
7
+ * Whether the TOC should be hidden completely.
8
+ * By default, the content space is still limited even when the TOC is empty
9
+ * so the layout width is the same when switching between pages with and without a TOC.
10
+ *
11
+ * When set to `true`, the content will be full width.
12
+ */
13
+ hidden?: boolean;
14
+ }>();
15
+
16
+ defineSlots<{
17
+ /**
18
+ * Page content.
19
+ */
20
+ default(): unknown;
21
+ }>();
22
+ </script>
23
+
24
+ <template>
25
+ <div v-if="props.hidden" class="layout__content">
26
+ <slot></slot>
27
+ </div>
28
+
29
+ <div v-else class="layout">
30
+ <div class="layout__content">
31
+ <slot></slot>
32
+ </div>
33
+
34
+ <TableOfContents v-if="props.toc.length" class="layout__toc" :links="props.toc" />
35
+ </div>
36
+ </template>
37
+
38
+ <style lang="scss" scoped>
39
+ @use "sit-onyx/breakpoints.scss";
40
+
41
+ .layout {
42
+ /** Gap between page content and TOC. Equivalent to one grid column + 2 * grid gutter/gap */
43
+ --onyx-content-toc-gap: calc(2 * var(--onyx-grid-gutter) + (100 / var(--onyx-grid-columns)) * 1%);
44
+ display: grid;
45
+ gap: var(--onyx-content-toc-gap);
46
+
47
+ // see: https://storybook.onyx.schwarz/?path=/docs/navigation-tableofcontents--docs
48
+ grid-template-columns: 1fr minmax(8rem, 15rem);
49
+
50
+ &__toc {
51
+ position: sticky;
52
+ top: var(--onyx-grid-margin-vertical);
53
+ height: calc(100vh - var(--onyx-nav-bar-height) - 2 * var(--onyx-grid-margin-vertical));
54
+ }
55
+
56
+ // hide TOC on smaller screens
57
+ @include breakpoints.container(max, md) {
58
+ display: flex;
59
+ flex-direction: column;
60
+
61
+ .layout__toc {
62
+ display: none;
63
+ }
64
+ }
65
+
66
+ &__content {
67
+ // remove the top margin of the first child since its redundant to the page padding
68
+ :deep(> div > :first-child) {
69
+ margin-top: 0;
70
+ }
71
+ }
72
+ }
73
+ </style>
@@ -1,24 +1,75 @@
1
1
  import type { Collections } from "@nuxt/content";
2
2
 
3
+ export type UseCollectionOptions<TCollection extends keyof Collections = keyof Collections> = {
4
+ /**
5
+ * Collection name to query.
6
+ */
7
+ collection: MaybeRef<TCollection>;
8
+ /**
9
+ * Path to query the collection item for.
10
+ *
11
+ * @default Current route.
12
+ */
13
+ path?: MaybeRef<string>;
14
+ };
15
+
3
16
  /**
4
- * Composable for loading the collection content for the current route and locale.
17
+ * Composable for loading the collection data for the current route and locale.
18
+ * Will throw an 404 error if the collection item could not be found.
19
+ * Sets SEO data with `useSeoMeta()` automatically.
5
20
  */
6
- export const useCollection = () => {
7
- const route = useRoute();
21
+ export const useCollection = async <TCollection extends keyof Collections = keyof Collections>(
22
+ options: UseCollectionOptions<TCollection>,
23
+ ) => {
24
+ const nuxtApp = useNuxtApp();
8
25
  const { locale } = useI18n();
26
+ const route = useRoute();
9
27
 
10
- const slug = computed(() => {
11
- const path = Array.isArray(route.params.slug)
12
- ? route.params.slug.join("/")
13
- : (route.params.slug ?? "");
14
- return path.startsWith("/") ? path : `/${path}`;
28
+ const collection = computed(() => toValue(options.collection));
29
+ const path = computed(() => {
30
+ let _path = toValue(options.path);
31
+ if (_path) return _path;
32
+
33
+ // get base path of current route (remove a potential locale prefix)
34
+ _path = route.path;
35
+ const localePrefix = `/${locale.value}`;
36
+
37
+ if (_path.startsWith(localePrefix)) return _path.slice(localePrefix.length);
38
+ return _path;
15
39
  });
16
40
 
17
- return useAsyncData(
18
- () => `page-${slug.value}-${locale.value}`,
19
- () => {
20
- const collection = `content_${locale.value}` as keyof Collections;
21
- return queryCollection(collection).path(slug.value).first();
41
+ const { data: collectionData } = await useAsyncData(
42
+ () => `collection-${collection.value}-${path.value}`,
43
+ () => queryCollection(collection.value).path(path.value).first(),
44
+ );
45
+
46
+ const data = computed<Collections[TCollection] | undefined>(() => {
47
+ return collectionData.value as Collections[TCollection];
48
+ });
49
+
50
+ watch(
51
+ data,
52
+ async (newValue) => {
53
+ // if data is "null", the page content was not found. "undefined" means it is not loaded yet
54
+ if (newValue !== null) return;
55
+ await nuxtApp.runWithContext(() =>
56
+ showError({
57
+ message: "Page not found",
58
+ statusCode: 404,
59
+ fatal: true,
60
+ }),
61
+ );
22
62
  },
63
+ { immediate: true },
23
64
  );
65
+
66
+ // runWithContext is needed due to async usage above
67
+ await nuxtApp.runWithContext(() => {
68
+ useSeoMeta({
69
+ title: () => data.value?.seo.title,
70
+ description: () => data.value?.seo.description,
71
+ });
72
+ });
73
+
74
+ return { data };
24
75
  };
@@ -34,20 +34,25 @@ export type SidebarNavigationOptions = {
34
34
  collapsed?: boolean;
35
35
  };
36
36
 
37
- export const useSidebarNavigation = async () => {
37
+ export type UseSidebarNavigationOptions = {
38
+ /**
39
+ * Collection name to use for querying the navigation.
40
+ */
41
+ collection: Ref<keyof Collections>;
42
+ };
43
+
44
+ /**
45
+ * Composable for querying and mapping the navigation items for a given collection.
46
+ */
47
+ export const useSidebarNavigation = async (options: UseSidebarNavigationOptions) => {
38
48
  const { locale } = useI18n();
39
49
  const localePath = useLocalePath();
40
50
  const route = useRoute();
41
51
 
42
52
  const { data } = await useAsyncData(
43
- () => `navigation-${locale.value}`,
44
- () => {
45
- const collection = `content_${locale.value}` as const;
46
- return queryCollectionNavigation(collection as keyof Collections);
47
- },
48
- {
49
- default: () => [],
50
- },
53
+ () => `navigation-${options.collection.value}-${locale.value}`,
54
+ () => queryCollectionNavigation(options.collection.value),
55
+ { default: () => [] },
51
56
  );
52
57
 
53
58
  /**
@@ -1,6 +1,8 @@
1
1
  <script lang="ts" setup>
2
+ import type { Collections } from "@nuxt/content";
2
3
  import { iconArrowSmallLeft } from "@sit-onyx/icons";
3
4
  import type { OnyxPageLayoutProps, OnyxSidebarProps } from "sit-onyx";
5
+ import TableOfContentsLayout from "../components/TableOfContentsLayout.vue";
4
6
  import type { SidebarNavigationItem } from "../composables/useSidebarNavigation.js";
5
7
 
6
8
  const props = defineProps<
@@ -41,10 +43,13 @@ const slots = defineSlots<{
41
43
  sidebarFooter?(): unknown;
42
44
  }>();
43
45
 
44
- const { navigation, previousRootItem } = await useSidebarNavigation();
46
+ const { locale } = useI18n();
47
+ const collectionName = computed(() => `content_${locale.value}` as keyof Collections);
45
48
 
46
- const collection = await useCollection();
47
- const toc = computed(() => collection.data.value?.body.toc?.links ?? []);
49
+ const { navigation, previousRootItem } = await useSidebarNavigation({ collection: collectionName });
50
+
51
+ const { data: collection } = await useCollection({ collection: collectionName });
52
+ const toc = computed(() => collection.value?.body.toc?.links ?? []);
48
53
  </script>
49
54
 
50
55
  <template>
@@ -85,13 +90,9 @@ const toc = computed(() => collection.data.value?.body.toc?.links ?? []);
85
90
 
86
91
  <slot name="hero"></slot>
87
92
 
88
- <div class="main onyx-grid-layout">
89
- <div class="content">
90
- <slot></slot>
91
- </div>
92
-
93
- <TableOfContents v-if="toc.length" class="main__toc" :links="toc" />
94
- </div>
93
+ <TableOfContentsLayout class="onyx-grid-layout" :toc>
94
+ <slot></slot>
95
+ </TableOfContentsLayout>
95
96
 
96
97
  <template v-if="!!slots.footer" #footer>
97
98
  <slot name="footer"></slot>
@@ -104,8 +105,6 @@ const toc = computed(() => collection.data.value?.body.toc?.links ?? []);
104
105
  </template>
105
106
 
106
107
  <style lang="scss" scoped>
107
- @use "sit-onyx/breakpoints.scss";
108
-
109
108
  .sidebar {
110
109
  &__empty {
111
110
  max-width: 100%;
@@ -116,36 +115,4 @@ const toc = computed(() => collection.data.value?.body.toc?.links ?? []);
116
115
  justify-content: flex-start;
117
116
  }
118
117
  }
119
-
120
- .main {
121
- /** Gap between page content and TOC. Equivalent to one grid column + 2 * grid gutter/gap */
122
- --onyx-content-toc-gap: calc(2 * var(--onyx-grid-gutter) + (100 / var(--onyx-grid-columns)) * 1%);
123
- display: grid;
124
- gap: var(--onyx-content-toc-gap);
125
-
126
- // see: https://storybook.onyx.schwarz/?path=/docs/navigation-tableofcontents--docs
127
- grid-template-columns: 1fr minmax(8rem, 15rem);
128
-
129
- &__toc {
130
- position: sticky;
131
- top: var(--onyx-grid-margin-vertical);
132
- height: calc(100vh - var(--onyx-nav-bar-height) - 2 * var(--onyx-grid-margin-vertical));
133
- }
134
-
135
- // hide TOC on smaller screens
136
- @include breakpoints.container(max, md) {
137
- grid-template-columns: 1fr;
138
-
139
- .main__toc {
140
- display: none;
141
- }
142
- }
143
- }
144
-
145
- .content {
146
- // remove the top margin of the first child since its redundant to the page padding
147
- :deep(> div > :first-child) {
148
- margin-top: 0;
149
- }
150
- }
151
118
  </style>
@@ -1,30 +1,16 @@
1
1
  <script setup lang="ts">
2
- definePageMeta({ layout: "sidebar" });
3
-
4
- const collection = await useCollection();
2
+ import type { Collections } from "@nuxt/content";
5
3
 
6
- watch(
7
- () => collection.data.value,
8
- (data) => {
9
- // if data is "null", the page content was not found. "undefined" means it is not loaded yet
10
- if (data !== null) return;
11
- throw showError({
12
- message: "Page not found",
13
- statusCode: 404,
14
- });
15
- },
16
- { immediate: true },
17
- );
4
+ definePageMeta({ layout: "sidebar" });
18
5
 
19
- const data = computed(() => collection.data.value);
6
+ const { locale } = useI18n();
20
7
 
21
- useSeoMeta({
22
- title: computed(() => collection.data.value?.seo.title),
23
- description: computed(() => collection.data.value?.seo.description),
8
+ const { data } = await useCollection({
9
+ collection: computed(() => `content_${locale.value}` as keyof Collections),
24
10
  });
25
11
  </script>
26
12
 
27
- <!-- eslint-disable-next-line vue/no-root-v-if -- the v-if here is theoretically not needed because we throw above it ifs undefined so the user will be redirected to the error page. However, there is still a console warning so we include the v-if here to prevent it. -->
13
+ <!-- eslint-disable-next-line vue/no-root-v-if -- The "useCollection" will already redirect to the error page when the data is undefined but the data might still be undefined while e.g. switching to another page -->
28
14
  <template>
29
15
  <ContentRenderer v-if="data" :value="data" />
30
16
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sit-onyx/nuxt-docs",
3
- "version": "0.6.0",
3
+ "version": "0.7.0-dev-20260513141728",
4
4
  "description": "Nuxt layer/template for creating documentations with the onyx design system",
5
5
  "type": "module",
6
6
  "author": "Schwarz IT KG",
@@ -33,9 +33,9 @@
33
33
  "@nuxtjs/i18n": ">= 10",
34
34
  "@nuxtjs/mdc": ">= 0.20.2",
35
35
  "sass-embedded": ">= 1",
36
- "@sit-onyx/icons": "^1.9.0",
37
- "@sit-onyx/nuxt": "^1.0.1",
38
- "sit-onyx": "^1.13.0"
36
+ "@sit-onyx/icons": "^1.9.1",
37
+ "sit-onyx": "^1.14.0-dev-20260513141728",
38
+ "@sit-onyx/nuxt": "^1.0.1"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@fontsource-variable/source-code-pro": "^5.2.7",
@@ -49,11 +49,11 @@
49
49
  "nuxt": "4.4.4",
50
50
  "sass-embedded": "1.99.0",
51
51
  "typescript": "5.9.3",
52
- "vue": "3.5.33",
53
- "@sit-onyx/icons": "^1.9.0",
52
+ "vue": "3.5.34",
54
53
  "@sit-onyx/nuxt": "^1.0.1",
55
- "@sit-onyx/shared": "^0.1.0",
56
- "sit-onyx": "^1.13.0"
54
+ "@sit-onyx/icons": "^1.9.1",
55
+ "sit-onyx": "^1.14.0-dev-20260513141728",
56
+ "@sit-onyx/shared": "^0.1.0"
57
57
  },
58
58
  "scripts": {
59
59
  "dev": "pnpm dev:prepare && nuxi dev playground",