@sewadah/selaras-docs 1.2.2 → 1.2.4
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.
- package/CHANGELOG.md +34 -0
- package/README.md +59 -0
- package/app/app.config.ts +1 -1
- package/app/app.vue +3 -1
- package/app/components/DocsErrorBoundary.vue +27 -0
- package/app/components/DocsErrorIllustration.vue +20 -0
- package/app/components/DocsErrorPage.vue +81 -0
- package/app/components/DocsErrorState.vue +161 -0
- package/app/components/content/DocsExample.vue +3 -1
- package/app/error.vue +11 -0
- package/app/pages/[...slug].vue +21 -4
- package/package.json +2 -2
- package/types.d.ts +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.2.4
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/sewadahapp/selaras/compare/selaras-docs-v1.2.3...selaras-docs-v1.2.4)
|
|
6
|
+
|
|
7
|
+
### 🚀 Enhancements
|
|
8
|
+
|
|
9
|
+
- **feat(docs):** add interactive application blocks ([102ee68](https://github.com/sewadahapp/selaras/commit/102ee68bef82d12b07dc4788dc8ea2378435e0ea))
|
|
10
|
+
- **feat(docs):** add error pages and local recovery boundaries ([371ed7a](https://github.com/sewadahapp/selaras/commit/371ed7a4fd873fa7e4647ab0b89b85dc0bb7a4b7))
|
|
11
|
+
- **feat(docs):** add SEO metadata and static sharing images ([f6ec141](https://github.com/sewadahapp/selaras/commit/f6ec14152def06f91a62292b53ae2df50813b1d5))
|
|
12
|
+
- **feat(docs):** add Seirama branding and design kit ([9fd9f19](https://github.com/sewadahapp/selaras/commit/9fd9f1948feaa19897ae24e982d33099d50a732c))
|
|
13
|
+
- **feat(forms):** add leading and trailing content slots ([ebd0948](https://github.com/sewadahapp/selaras/commit/ebd094813a274d444319cb0354387c770ab8a6ef))
|
|
14
|
+
|
|
15
|
+
### 🩹 Fixes
|
|
16
|
+
|
|
17
|
+
- **fix(docs):** configure custom domain deployment ([b65908a](https://github.com/sewadahapp/selaras/commit/b65908a7aa4ad0d0b67029b41c338633e9faa50d))
|
|
18
|
+
|
|
19
|
+
Published against `@sewadah/selaras` ^0.9.0.
|
|
20
|
+
|
|
21
|
+
## v1.2.3
|
|
22
|
+
|
|
23
|
+
[compare changes](https://github.com/sewadahapp/selaras/compare/selaras-docs-v1.2.2...selaras-docs-v1.2.3)
|
|
24
|
+
|
|
25
|
+
### 🚀 Enhancements
|
|
26
|
+
|
|
27
|
+
- **feat(forms):** add listbox and transfer list ([e4ca547](https://github.com/sewadahapp/selaras/commit/e4ca54737d3ba492c20a61fb9e712afeaff99674))
|
|
28
|
+
|
|
29
|
+
### 🩹 Fixes
|
|
30
|
+
|
|
31
|
+
- **fix(select):** align selected option styles with listbox ([342eaf3](https://github.com/sewadahapp/selaras/commit/342eaf3c47851b3720fb686cac4adf50fc224c8a))
|
|
32
|
+
- **fix(avatar-group):** flatten dynamic slot children ([9c68a66](https://github.com/sewadahapp/selaras/commit/9c68a66cccc4e88f0ae2a83e2fcc5a59cfa3c3a2))
|
|
33
|
+
- **fix(color-mode):** keep controls stable during hydration ([4ef0d71](https://github.com/sewadahapp/selaras/commit/4ef0d7105308f5a1fe4fdf95f213933e3e2f4a34))
|
|
34
|
+
|
|
35
|
+
Published against `@sewadah/selaras` ^0.8.0.
|
|
36
|
+
|
|
3
37
|
## v1.2.2
|
|
4
38
|
|
|
5
39
|
[compare changes](https://github.com/sewadahapp/selaras/compare/selaras-docs-v1.2.1...selaras-docs-v1.2.2)
|
package/README.md
CHANGED
|
@@ -119,6 +119,65 @@ pieces can be replaced on their own:
|
|
|
119
119
|
For example, a `components/DocsFooterBrand.vue` containing `<DocsLogo />` adds
|
|
120
120
|
the logo to the footer while leaving the header as configured.
|
|
121
121
|
|
|
122
|
+
## Error recovery
|
|
123
|
+
|
|
124
|
+
The layer provides a default `error.vue` for missing pages and fatal errors.
|
|
125
|
+
It preserves the HTTP status code, keeps error pages out of search indexes and
|
|
126
|
+
offers a home link or reload action. The standalone screen avoids loading
|
|
127
|
+
navigation and Content data so it can render when those services fail.
|
|
128
|
+
|
|
129
|
+
Client-side page failures show a local recovery state inside the layout, keeping
|
|
130
|
+
the header and sidebar available. `DocsExample` isolates each live example;
|
|
131
|
+
retrying remounts its content. Navigating to another route clears the page
|
|
132
|
+
boundary. Server rendering errors still use the standalone error screen.
|
|
133
|
+
|
|
134
|
+
Content query failures show a retry state with HTTP 500 during server rendering.
|
|
135
|
+
Only a successful query with no matching document is treated as a 404.
|
|
136
|
+
If a request cannot recover in place, retry reloads the current page to start a
|
|
137
|
+
fresh session. Reloads happen only after the user chooses to retry.
|
|
138
|
+
|
|
139
|
+
Customize the messages and home destination in `app.config.ts`:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
export default defineAppConfig({
|
|
143
|
+
selarasDocs: {
|
|
144
|
+
errors: {
|
|
145
|
+
homeTo: '/guide/introduction',
|
|
146
|
+
homeLabel: 'Back to the guide',
|
|
147
|
+
retryLabel: 'Try again',
|
|
148
|
+
reloadLabel: 'Reload page',
|
|
149
|
+
retryingLabel: 'Trying again…',
|
|
150
|
+
notFound: { title: 'Page not found', description: 'Start again from the guide.' },
|
|
151
|
+
fatal: { title: 'Something went wrong' },
|
|
152
|
+
page: { title: 'This page couldn’t load' },
|
|
153
|
+
example: { title: 'This example couldn’t load' },
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Home destinations are internal routes resolved under the app's `baseURL`.
|
|
160
|
+
The default destination is `/`. Provide an existing page if your site has no
|
|
161
|
+
homepage. Public recovery copy never includes raw exceptions or stack traces.
|
|
162
|
+
|
|
163
|
+
Override `components/DocsErrorIllustration.vue` to replace the default geometric
|
|
164
|
+
document illustration. The optional `missing` boolean distinguishes a 404 from
|
|
165
|
+
other errors. Use inherited theme tokens in inline SVGs for light/dark support.
|
|
166
|
+
`DocsErrorState` also exposes an `illustration` slot. Override `DocsErrorPage`
|
|
167
|
+
for the standalone screen's composition, or supply your own `error.vue` for
|
|
168
|
+
application-specific providers. Applications with their own `app.vue` should
|
|
169
|
+
retain the page boundary inside their layout:
|
|
170
|
+
|
|
171
|
+
```vue
|
|
172
|
+
<template>
|
|
173
|
+
<NuxtLayout>
|
|
174
|
+
<DocsErrorBoundary>
|
|
175
|
+
<NuxtPage />
|
|
176
|
+
</DocsErrorBoundary>
|
|
177
|
+
</NuxtLayout>
|
|
178
|
+
</template>
|
|
179
|
+
```
|
|
180
|
+
|
|
122
181
|
For a host that owns Tailwind compilation or uses a class prefix, set
|
|
123
182
|
`selarasDocs: { css: false }` in `nuxt.config.ts` and import Selaras and the
|
|
124
183
|
docs stylesheet from the host CSS entry.
|
package/app/app.config.ts
CHANGED
package/app/app.vue
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SelarasDocsErrorKind } from '../../types'
|
|
3
|
+
import { useTemplateRef, watch } from 'vue'
|
|
4
|
+
import { useRouter } from '#app'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<{
|
|
7
|
+
kind?: Extract<SelarasDocsErrorKind, 'page' | 'example'>
|
|
8
|
+
resetKey?: string
|
|
9
|
+
}>(), { kind: 'page' })
|
|
10
|
+
|
|
11
|
+
const boundary = useTemplateRef('boundary')
|
|
12
|
+
const router = useRouter()
|
|
13
|
+
watch([() => router.currentRoute.value.fullPath, () => props.resetKey], () => boundary.value?.clearError())
|
|
14
|
+
|
|
15
|
+
function isMissing(error: Error) {
|
|
16
|
+
return 'statusCode' in error && error.statusCode === 404
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<NuxtErrorBoundary ref="boundary">
|
|
22
|
+
<slot />
|
|
23
|
+
<template #error="{ error, clearError }">
|
|
24
|
+
<DocsErrorState :kind="kind === 'page' && isMissing(error) ? 'notFound' : kind" @retry="clearError" />
|
|
25
|
+
</template>
|
|
26
|
+
</NuxtErrorBoundary>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{ missing?: boolean }>()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<svg viewBox="0 0 400 240" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
7
|
+
<path d="M40 187H360" stroke="var(--selaras-resolved-border-default)" stroke-width="2" stroke-linecap="round" />
|
|
8
|
+
<rect x="113" y="41" width="148" height="159" rx="12" fill="var(--selaras-resolved-surface-elevated)" stroke="var(--selaras-resolved-border-default)" stroke-width="2" />
|
|
9
|
+
<path d="M144 24H231L277 70V170C277 177 272 182 265 182H144C137 182 132 177 132 170V36C132 29 137 24 144 24Z" fill="var(--selaras-resolved-surface-default)" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="2.5" stroke-linejoin="round" />
|
|
10
|
+
<path d="M231 25V58C231 65 236 70 243 70H276" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="2.5" stroke-linejoin="round" />
|
|
11
|
+
<path d="M154 61H193M154 84H213" stroke="var(--selaras-resolved-border-default)" stroke-width="5" stroke-linecap="round" />
|
|
12
|
+
<rect x="154" y="108" width="57" height="48" rx="6" fill="var(--selaras-resolved-color-primary-subtle)" />
|
|
13
|
+
<path v-if="missing" d="M225 113H254V154H225V113Z" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="2" stroke-dasharray="4 5" stroke-linejoin="round" />
|
|
14
|
+
<path v-else d="M233 116V135M233 146V147" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="4" stroke-linecap="round" />
|
|
15
|
+
<path d="M56 153C56 122 84 119 105 132M290 109C318 103 342 122 333 146" stroke="var(--selaras-resolved-border-default)" stroke-width="2" stroke-dasharray="4 7" stroke-linecap="round" />
|
|
16
|
+
<circle cx="55" cy="159" r="6" fill="var(--selaras-resolved-color-primary-fill)" />
|
|
17
|
+
<rect x="311" y="144" width="44" height="37" rx="6" fill="var(--selaras-resolved-color-primary-subtle)" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="2" />
|
|
18
|
+
<path d="M323 163H343M333 153V173" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="2" stroke-linecap="round" />
|
|
19
|
+
</svg>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { NuxtError } from '#app'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ error: NuxtError }>()
|
|
5
|
+
const config = useRuntimeConfig()
|
|
6
|
+
const appConfig = useAppConfig()
|
|
7
|
+
const siteName = computed(() => appConfig.selarasDocs?.site?.name ?? 'Documentation')
|
|
8
|
+
const missing = computed(() => props.error.statusCode === 404)
|
|
9
|
+
const pending = ref(false)
|
|
10
|
+
|
|
11
|
+
useSeoMeta({
|
|
12
|
+
title: () => `${missing.value ? 'Page not found' : 'Something went wrong'} — ${siteName.value}`,
|
|
13
|
+
robots: 'noindex',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
function reload() {
|
|
17
|
+
pending.value = true
|
|
18
|
+
window.location.reload()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function goHome(event: MouseEvent) {
|
|
22
|
+
event.preventDefault()
|
|
23
|
+
const path = appConfig.selarasDocs?.errors?.homeTo ?? '/'
|
|
24
|
+
try {
|
|
25
|
+
await clearError({ redirect: path })
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
window.location.assign(`${config.app.baseURL.replace(/\/$/, '')}/${path.replace(/^\//, '')}`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div class="selaras-docs-root selaras-docs-error-page">
|
|
35
|
+
<header class="selaras-docs-error-header">
|
|
36
|
+
<a :href="config.app.baseURL" class="selaras-docs-error-brand" :aria-label="`${siteName} home`">
|
|
37
|
+
<DocsLogo />
|
|
38
|
+
<span>{{ siteName }}</span>
|
|
39
|
+
</a>
|
|
40
|
+
</header>
|
|
41
|
+
<main class="selaras-docs-error-main">
|
|
42
|
+
<DocsErrorState :kind="missing ? 'notFound' : 'fatal'" :pending="pending" @retry="reload" @home="goHome" />
|
|
43
|
+
</main>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<style scoped>
|
|
48
|
+
.selaras-docs-error-page {
|
|
49
|
+
min-height: 100svh;
|
|
50
|
+
color: var(--selaras-resolved-text-default);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.selaras-docs-error-header {
|
|
54
|
+
width: min(100%, 90rem);
|
|
55
|
+
margin: 0 auto;
|
|
56
|
+
padding: 1.5rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.selaras-docs-error-brand {
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 0.75rem;
|
|
63
|
+
color: inherit;
|
|
64
|
+
font-size: 1.125rem;
|
|
65
|
+
font-weight: 650;
|
|
66
|
+
text-decoration: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.selaras-docs-error-brand:focus-visible {
|
|
70
|
+
border-radius: var(--selaras-resolved-radius-sm);
|
|
71
|
+
outline: 2px solid var(--selaras-resolved-color-primary-focus);
|
|
72
|
+
outline-offset: 4px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.selaras-docs-error-main {
|
|
76
|
+
display: grid;
|
|
77
|
+
align-items: center;
|
|
78
|
+
min-height: calc(100svh - 6rem);
|
|
79
|
+
padding: 1.5rem 1.5rem 5rem;
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SelarasDocsErrorKind } from '../../types'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<{
|
|
5
|
+
kind?: SelarasDocsErrorKind
|
|
6
|
+
pending?: boolean
|
|
7
|
+
showHome?: boolean
|
|
8
|
+
}>(), { kind: 'page', pending: false, showHome: undefined })
|
|
9
|
+
|
|
10
|
+
const emit = defineEmits<{ retry: [], home: [event: MouseEvent] }>()
|
|
11
|
+
const appConfig = useAppConfig()
|
|
12
|
+
const config = useRuntimeConfig()
|
|
13
|
+
const homeHref = computed(() => {
|
|
14
|
+
const path = appConfig.selarasDocs?.errors?.homeTo ?? '/'
|
|
15
|
+
const base = config.app.baseURL.replace(/\/$/, '')
|
|
16
|
+
return `${base}/${path.replace(/^\//, '')}`
|
|
17
|
+
})
|
|
18
|
+
const headingId = useId()
|
|
19
|
+
const heading = useTemplateRef<HTMLElement>('heading')
|
|
20
|
+
onMounted(() => {
|
|
21
|
+
if (props.kind !== 'example')
|
|
22
|
+
heading.value?.focus({ preventScroll: true })
|
|
23
|
+
})
|
|
24
|
+
const copy = computed(() => {
|
|
25
|
+
const defaults = {
|
|
26
|
+
notFound: { title: 'Page not found', description: 'The link may have changed, or this page may no longer exist. You can start again from the homepage.' },
|
|
27
|
+
fatal: { title: 'Something went wrong', description: 'We couldn’t load this page. Reload it to try again, or return to the homepage.' },
|
|
28
|
+
page: { title: 'This page couldn’t load', description: 'Please try again. You can still use the navigation to explore other pages.' },
|
|
29
|
+
example: { title: 'This example couldn’t load', description: 'Try loading the example again. The rest of the documentation is still available.' },
|
|
30
|
+
}
|
|
31
|
+
return { ...defaults[props.kind], ...appConfig.selarasDocs?.errors?.[props.kind] }
|
|
32
|
+
})
|
|
33
|
+
const labels = computed(() => appConfig.selarasDocs?.errors ?? {})
|
|
34
|
+
const showHome = computed(() => props.showHome ?? props.kind !== 'example')
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<section
|
|
39
|
+
class="selaras-docs-error-state not-prose"
|
|
40
|
+
:data-error-kind="kind"
|
|
41
|
+
:aria-labelledby="headingId"
|
|
42
|
+
:role="kind === 'example' ? 'alert' : undefined"
|
|
43
|
+
>
|
|
44
|
+
<div class="selaras-docs-error-artwork">
|
|
45
|
+
<slot name="illustration">
|
|
46
|
+
<DocsErrorIllustration :missing="kind === 'notFound'" />
|
|
47
|
+
</slot>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="selaras-docs-error-copy">
|
|
50
|
+
<component :is="kind === 'example' ? 'h3' : 'h1'" :id="headingId" ref="heading" class="selaras-docs-error-title" :tabindex="kind === 'example' ? undefined : -1">
|
|
51
|
+
{{ copy.title }}
|
|
52
|
+
</component>
|
|
53
|
+
<p class="selaras-docs-error-description">
|
|
54
|
+
{{ copy.description }}
|
|
55
|
+
</p>
|
|
56
|
+
<div class="selaras-docs-error-actions">
|
|
57
|
+
<SButton v-if="kind !== 'notFound'" :loading="pending" :disabled="pending" @click="emit('retry')">
|
|
58
|
+
{{ pending ? labels.retryingLabel ?? 'Trying again…' : kind === 'fatal' ? labels.reloadLabel ?? 'Reload page' : labels.retryLabel ?? 'Try again' }}
|
|
59
|
+
</SButton>
|
|
60
|
+
<SButton v-if="showHome" as="a" :href="homeHref" :variant="kind === 'notFound' ? 'solid' : 'outline'" :color="kind === 'notFound' ? 'primary' : 'neutral'" @click="emit('home', $event)">
|
|
61
|
+
{{ labels.homeLabel ?? 'Go to homepage' }}
|
|
62
|
+
</SButton>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</section>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<style scoped>
|
|
69
|
+
.selaras-docs-error-state {
|
|
70
|
+
width: min(100%, 42rem);
|
|
71
|
+
margin: 3rem auto;
|
|
72
|
+
color: var(--selaras-resolved-text-default);
|
|
73
|
+
text-align: center;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.selaras-docs-error-artwork {
|
|
77
|
+
width: min(100%, 22rem);
|
|
78
|
+
margin: 0 auto 1.5rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.selaras-docs-error-artwork :deep(svg) {
|
|
82
|
+
display: block;
|
|
83
|
+
width: 100%;
|
|
84
|
+
height: auto;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.selaras-docs-error-title {
|
|
88
|
+
margin: 0;
|
|
89
|
+
font-size: clamp(1.75rem, 1.4rem + 1vw, 2.5rem);
|
|
90
|
+
font-weight: 650;
|
|
91
|
+
line-height: 1.15;
|
|
92
|
+
letter-spacing: -0.025em;
|
|
93
|
+
text-wrap: balance;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.selaras-docs-error-title:focus {
|
|
97
|
+
outline: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.selaras-docs-error-description {
|
|
101
|
+
max-width: 44ch;
|
|
102
|
+
margin: 1rem auto 0;
|
|
103
|
+
color: var(--selaras-resolved-text-muted);
|
|
104
|
+
font-size: 1rem;
|
|
105
|
+
line-height: 1.7;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.selaras-docs-error-actions {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-wrap: wrap;
|
|
111
|
+
justify-content: center;
|
|
112
|
+
gap: 0.75rem;
|
|
113
|
+
margin-top: 1.75rem;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.selaras-docs-error-state[data-error-kind="example"] {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 1.25rem;
|
|
120
|
+
width: 100%;
|
|
121
|
+
margin: 0;
|
|
122
|
+
padding: 1.5rem;
|
|
123
|
+
border: 1px solid var(--selaras-resolved-border-default);
|
|
124
|
+
border-radius: var(--selaras-resolved-radius-lg);
|
|
125
|
+
background: var(--selaras-resolved-surface-elevated);
|
|
126
|
+
text-align: start;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
[data-error-kind="example"] .selaras-docs-error-artwork {
|
|
130
|
+
flex: none;
|
|
131
|
+
width: 6rem;
|
|
132
|
+
margin: 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
[data-error-kind="example"] .selaras-docs-error-title {
|
|
136
|
+
font-size: 1rem;
|
|
137
|
+
line-height: 1.4;
|
|
138
|
+
letter-spacing: normal;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
[data-error-kind="example"] .selaras-docs-error-description {
|
|
142
|
+
margin: 0.375rem 0 0;
|
|
143
|
+
font-size: 0.875rem;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
[data-error-kind="example"] .selaras-docs-error-actions {
|
|
147
|
+
justify-content: flex-start;
|
|
148
|
+
margin-top: 1rem;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@media (max-width: 30rem) {
|
|
152
|
+
.selaras-docs-error-state[data-error-kind="example"] {
|
|
153
|
+
align-items: flex-start;
|
|
154
|
+
flex-direction: column;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
[data-error-kind="example"] .selaras-docs-error-artwork {
|
|
158
|
+
display: none;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
</style>
|
|
@@ -18,7 +18,9 @@ const resolvedComponent = computed(() => {
|
|
|
18
18
|
|
|
19
19
|
<template>
|
|
20
20
|
<section v-if="resolvedComponent" :data-example="name">
|
|
21
|
-
<
|
|
21
|
+
<DocsErrorBoundary kind="example" :reset-key="name">
|
|
22
|
+
<component :is="resolvedComponent" />
|
|
23
|
+
</DocsErrorBoundary>
|
|
22
24
|
</section>
|
|
23
25
|
<p v-else role="alert">
|
|
24
26
|
Example "{{ name }}" not found
|
package/app/error.vue
ADDED
package/app/pages/[...slug].vue
CHANGED
|
@@ -10,14 +10,30 @@ import ProsePre from '@sewadah/selaras/components/ProsePre.vue'
|
|
|
10
10
|
|
|
11
11
|
const route = useRoute()
|
|
12
12
|
const appConfig = useAppConfig()
|
|
13
|
-
const { data: page } = await useAsyncData(`selaras-docs:${route.path}`, () => queryCollection('docs').path(route.path).first())
|
|
13
|
+
const { data: page, error, status, refresh } = await useAsyncData(`selaras-docs:${route.path}`, () => queryCollection('docs').path(route.path).first())
|
|
14
14
|
|
|
15
|
-
if (!page.value) {
|
|
15
|
+
if (!page.value && !error.value) {
|
|
16
16
|
throw createError({ statusCode: 404, statusMessage: 'Documentation page not found', fatal: true })
|
|
17
17
|
}
|
|
18
|
+
if (import.meta.server && error.value)
|
|
19
|
+
setResponseStatus(500)
|
|
20
|
+
|
|
21
|
+
async function retryPage() {
|
|
22
|
+
await refresh()
|
|
23
|
+
if (import.meta.client && error.value) {
|
|
24
|
+
window.location.reload()
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
if (!page.value && !error.value)
|
|
28
|
+
showError({ statusCode: 404, statusMessage: 'Documentation page not found' })
|
|
29
|
+
}
|
|
18
30
|
|
|
19
31
|
definePageMeta({ layout: 'selaras-docs' })
|
|
20
|
-
useSeoMeta({
|
|
32
|
+
useSeoMeta({
|
|
33
|
+
title: () => page.value?.title ?? appConfig.selarasDocs?.errors?.page?.title ?? 'Documentation unavailable',
|
|
34
|
+
description: () => page.value?.description ?? appConfig.selarasDocs?.site?.description,
|
|
35
|
+
robots: () => error.value ? 'noindex' : undefined,
|
|
36
|
+
})
|
|
21
37
|
useHead(() => ({
|
|
22
38
|
...(appConfig.selarasDocs?.site?.titleTemplate && { titleTemplate: appConfig.selarasDocs.site.titleTemplate }),
|
|
23
39
|
...(appConfig.selarasDocs?.site?.url && {
|
|
@@ -40,7 +56,8 @@ const surround = useDocsSurround()
|
|
|
40
56
|
</script>
|
|
41
57
|
|
|
42
58
|
<template>
|
|
43
|
-
<
|
|
59
|
+
<DocsErrorState v-if="error || !page" kind="page" :pending="status === 'pending'" @retry="retryPage" />
|
|
60
|
+
<SContainer v-else size="full" class="selaras-docs-page" :class="{ 'selaras-docs-page--fluid': appConfig.selarasDocs?.main?.fluid }">
|
|
44
61
|
<div class="selaras-docs-page-grid">
|
|
45
62
|
<article class="selaras-docs-article">
|
|
46
63
|
<p v-if="page!.description" class="selaras-docs-description">
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sewadah/selaras-docs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.4",
|
|
5
5
|
"description": "Nuxt Content documentation layer for Selaras",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@nuxt/content": "^3.16.0",
|
|
35
35
|
"@nuxt/kit": "^4.5.2",
|
|
36
|
-
"@sewadah/selaras": "^0.
|
|
36
|
+
"@sewadah/selaras": "^0.9.0",
|
|
37
37
|
"tailwindcss": "^4.3.0"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/types.d.ts
CHANGED
|
@@ -37,7 +37,23 @@ export interface SelarasDocsLogo {
|
|
|
37
37
|
tint?: boolean
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export type SelarasDocsErrorKind = 'notFound' | 'fatal' | 'page' | 'example'
|
|
41
|
+
|
|
42
|
+
export interface SelarasDocsErrorCopy {
|
|
43
|
+
title?: string
|
|
44
|
+
description?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
export interface SelarasDocsAppConfig {
|
|
48
|
+
/** Recovery messages and the destination of error-screen home links. */
|
|
49
|
+
errors?: Partial<Record<SelarasDocsErrorKind, SelarasDocsErrorCopy>> & {
|
|
50
|
+
/** An internal route, resolved under the application's base URL. @default '/' */
|
|
51
|
+
homeTo?: string
|
|
52
|
+
homeLabel?: string
|
|
53
|
+
retryLabel?: string
|
|
54
|
+
reloadLabel?: string
|
|
55
|
+
retryingLabel?: string
|
|
56
|
+
}
|
|
41
57
|
site?: {
|
|
42
58
|
name?: string
|
|
43
59
|
description?: string
|