Skip to content

Commit 88b34fa

Browse files
committed
style: enhance layout structure and improve component configurations
- Added 'vue/no-empty-component-block' rule to ESLint configuration for better code quality. - Updated TableOfContents component props to be readonly for improved type safety. - Refactored default and post layouts to utilize BaseGridLayout, simplifying the structure and enhancing responsiveness. - Adjusted post layout to handle TOC data directly within the layout, streamlining the component logic.
1 parent b1e4068 commit 88b34fa

10 files changed

Lines changed: 216 additions & 65 deletions

File tree

components/BaseGridLayout.vue

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<script setup lang="ts">
2+
interface GridVariant {
3+
name: 'default' | 'with-sidebars' | 'left-sidebar' | 'right-sidebar' | 'centered' | 'full-width'
4+
gridClass: string
5+
mainClass: string
6+
showLeftSidebar: boolean
7+
showRightSidebar: boolean
8+
}
9+
10+
const { variant = 'default' } = defineProps<{
11+
/** Layout variant determining grid structure and sidebar visibility */
12+
variant?: GridVariant['name']
13+
}>()
14+
15+
const { $slots } = getCurrentInstance()?.proxy || {}
16+
17+
const gridVariants: Record<GridVariant['name'], Omit<GridVariant, 'name'>> = {
18+
'centered': {
19+
gridClass: 'grid-rows-[auto_1fr_auto] grid-cols-1',
20+
mainClass: 'px-4 max-w-prose mx-auto w-full row-start-2',
21+
showLeftSidebar: false,
22+
showRightSidebar: false,
23+
},
24+
'default': {
25+
gridClass: 'grid-rows-[auto_1fr_1fr_1fr_auto] grid-cols-1 sm:grid-cols-5',
26+
mainClass: 'px-4 col-span-1 row-start-2 row-end-5 sm:px-0 sm:col-start-2 sm:col-end-5',
27+
showLeftSidebar: false,
28+
showRightSidebar: false,
29+
},
30+
'full-width': {
31+
gridClass: 'grid-rows-[auto_1fr_auto] grid-cols-1',
32+
mainClass: 'row-start-2 w-full',
33+
showLeftSidebar: false,
34+
showRightSidebar: false,
35+
},
36+
'left-sidebar': {
37+
gridClass: 'grid-rows-[auto_1fr_1fr_1fr_auto] grid-cols-1 sm:grid-cols-5',
38+
mainClass: 'px-4 col-span-1 row-start-2 row-end-5 sm:px-0 sm:col-start-2 sm:col-end-5',
39+
showLeftSidebar: true,
40+
showRightSidebar: false,
41+
},
42+
'right-sidebar': {
43+
gridClass: 'grid-rows-[auto_1fr_1fr_1fr_auto] grid-cols-1 sm:grid-cols-5',
44+
mainClass: 'px-4 col-span-1 row-start-2 row-end-5 sm:px-0 sm:col-start-2 sm:col-end-5',
45+
showLeftSidebar: false,
46+
showRightSidebar: true,
47+
},
48+
'with-sidebars': {
49+
gridClass: 'grid-rows-[auto_1fr_1fr_1fr_auto] grid-cols-1 sm:grid-cols-5',
50+
mainClass: 'px-4 col-span-1 row-start-2 row-end-5 sm:px-0 sm:col-start-2 sm:col-end-5',
51+
showLeftSidebar: true,
52+
showRightSidebar: true,
53+
},
54+
}
55+
56+
const currentVariant = computed(() => gridVariants[variant])
57+
58+
const hasLeftSidebar = computed(() =>
59+
currentVariant.value.showLeftSidebar && $slots?.['sidebar-left'],
60+
)
61+
62+
const hasRightSidebar = computed(() =>
63+
currentVariant.value.showRightSidebar && $slots?.['sidebar-right'],
64+
)
65+
</script>
66+
67+
<template>
68+
<div
69+
class="font-mono grid min-h-screen"
70+
:class="currentVariant.gridClass"
71+
>
72+
<!-- Header -->
73+
<slot
74+
name="header"
75+
:class="variant === 'full-width' ? 'col-span-1' : 'col-span-1 row-start-1 row-end-2 sm:col-start-2 sm:col-end-5'"
76+
>
77+
<TheHeader :class="variant === 'full-width' ? 'col-span-1' : 'col-span-1 row-start-1 row-end-2 sm:col-start-2 sm:col-end-5'" />
78+
</slot>
79+
80+
<!-- Left Sidebar -->
81+
<aside
82+
v-if="hasLeftSidebar"
83+
id="left-sidebar"
84+
class="hidden sm:col-start-1 sm:row-start-2 sm:col-end-2 sm:row-end-5 sm:block"
85+
aria-label="Left sidebar"
86+
>
87+
<div
88+
id="left-sidebar-content"
89+
class="px-4 top-4 sticky"
90+
>
91+
<slot name="sidebar-left" />
92+
</div>
93+
</aside>
94+
95+
<!-- Main Content -->
96+
<main
97+
id="main-content"
98+
:class="currentVariant.mainClass"
99+
>
100+
<slot />
101+
</main>
102+
103+
<!-- Right Sidebar -->
104+
<aside
105+
v-if="hasRightSidebar"
106+
id="right-sidebar"
107+
class="hidden sm:col-start-5 sm:row-start-2 sm:col-end-6 sm:row-end-5 sm:block"
108+
aria-label="Right sidebar"
109+
>
110+
<div
111+
id="right-sidebar-content"
112+
class="px-4 top-4 sticky"
113+
>
114+
<slot name="sidebar-right" />
115+
</div>
116+
</aside>
117+
118+
<!-- Footer -->
119+
<slot
120+
name="footer"
121+
:class="variant === 'full-width' || variant === 'centered' ? 'col-span-1' : 'col-span-1 row-start-5 row-end-6 sm:col-start-2 sm:col-end-5'"
122+
>
123+
<TheFooter :class="variant === 'full-width' || variant === 'centered' ? 'col-span-1' : 'col-span-1 row-start-5 row-end-6 sm:col-start-2 sm:col-end-5'" />
124+
</slot>
125+
</div>
126+
</template>

components/TableOfContents.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { tocKey } from './tocKey'
55
66
const { links = [] } = defineProps<{
77
/** Array of table of contents links with hierarchy */
8-
links?: TocLink[]
8+
links?: readonly TocLink[]
99
}>()
1010
1111
const { activeId, scrollToHeading } = useTableOfContents()

composables/useLayoutConfig.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export interface LayoutConfig {
2+
showLeftSidebar: boolean
3+
showRightSidebar: boolean
4+
leftSidebarContent?: any
5+
rightSidebarContent?: any
6+
variant?: 'default' | 'with-sidebars' | 'left-sidebar' | 'right-sidebar' | 'centered' | 'full-width'
7+
}
8+
9+
const layoutConfigKey: InjectionKey<LayoutConfig> = Symbol('layoutConfig')
10+
11+
export function useLayoutConfig() {
12+
const config = inject(layoutConfigKey, {
13+
showLeftSidebar: false,
14+
showRightSidebar: false,
15+
variant: 'default',
16+
})
17+
18+
const setLayoutConfig = (newConfig: Partial<LayoutConfig>) => {
19+
Object.assign(config, newConfig)
20+
}
21+
22+
return {
23+
config: readonly(config),
24+
setLayoutConfig,
25+
}
26+
}
27+
28+
export function provideLayoutConfig(config: LayoutConfig) {
29+
provide(layoutConfigKey, config)
30+
}

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export default antfu(
143143
'vue/new-line-between-multi-line-property': ['error', {
144144
minLineOfMultilineProperty: 2,
145145
}],
146+
'vue/no-empty-component-block': 'error',
146147
'vue/no-mutating-props': 'error',
147148
'vue/no-ref-object-reactivity-loss': 'error',
148149
'vue/no-required-prop-with-default': ['error', {

layouts/centered.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script setup lang="ts">
2+
// Centered layout for content that needs a narrow, focused column
3+
</script>
4+
5+
<template>
6+
<BaseGridLayout variant="centered">
7+
<slot />
8+
</BaseGridLayout>
9+
</template>

layouts/default.vue

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,5 @@
1-
<script setup lang="ts">
2-
// Default layout with 5x5 grid structure
3-
</script>
4-
51
<template>
6-
<div class="font-mono grid grid-rows-[auto_1fr_1fr_1fr_auto] grid-cols-1 min-h-screen sm:grid-cols-5">
7-
<!-- Header - spans full width on mobile, columns 2-4 on desktop -->
8-
<TheHeader class="col-span-1 row-start-1 row-end-2 sm:col-start-2 sm:col-end-5" />
9-
10-
<!-- Left Sidebar - hidden on mobile, spans row 2-4 in column 1 on desktop -->
11-
<aside
12-
id="left-sidebar"
13-
class="hidden sm:col-start-1 sm:row-start-2 sm:col-end-2 sm:row-end-5 sm:block"
14-
>
15-
<div
16-
id="left-sidebar-content"
17-
class="px-4 top-4 sticky"
18-
/>
19-
</aside>
20-
21-
<!-- Main Content - full width on mobile, spans columns 2-4 on desktop -->
22-
<main
23-
id="main-content"
24-
class="px-4 col-span-1 row-start-2 row-end-5 sm:px-0 sm:col-start-2 sm:col-end-5"
25-
>
26-
<slot />
27-
</main>
28-
29-
<!-- Right Sidebar - hidden on mobile, spans row 2-4 in column 5 on desktop -->
30-
<div
31-
id="right-sidebar"
32-
class="hidden sm:col-start-5 sm:row-start-2 sm:col-end-6 sm:row-end-5 sm:block"
33-
/>
34-
35-
<!-- Footer - full width on mobile, spans columns 2-4 on desktop -->
36-
<TheFooter class="col-span-1 row-start-5 row-end-6 sm:col-start-2 sm:col-end-5" />
37-
</div>
2+
<BaseGridLayout variant="default">
3+
<slot />
4+
</BaseGridLayout>
385
</template>

layouts/full-width.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template>
2+
<BaseGridLayout variant="full-width">
3+
<slot />
4+
</BaseGridLayout>
5+
</template>

layouts/post.vue

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const route = useRoute()
55
const { postSlug } = route.params
66
77
const { data: post } = await useAsyncData(
8-
`layout-post-${postSlug}`,
8+
`layout-post-toc-${postSlug}`,
99
async () => {
1010
const result = await queryCollection('posts').path(`/posts/${postSlug}`).first()
1111
return result
@@ -21,33 +21,14 @@ const hasTableOfContents = computed(() => tocLinks.value.length >= 1)
2121
</script>
2222

2323
<template>
24-
<div class="font-mono grid grid-rows-[auto_1fr_1fr_1fr_auto] grid-cols-1 min-h-screen sm:grid-cols-5">
25-
<TheHeader class="col-span-1 row-start-1 row-end-2 sm:col-start-2 sm:col-end-5" />
26-
27-
<aside
28-
id="left-sidebar"
29-
class="hidden sm:col-start-1 sm:row-start-2 sm:col-end-2 sm:row-end-5 sm:block"
30-
>
31-
<div
32-
v-if="hasTableOfContents"
33-
class="px-4 top-4 sticky"
34-
>
35-
<TableOfContents :links="tocLinks" />
36-
</div>
37-
</aside>
38-
39-
<main
40-
id="main-content"
41-
class="px-4 col-span-1 row-start-2 row-end-5 sm:px-0 sm:col-start-2 sm:col-end-5"
24+
<BaseGridLayout variant="left-sidebar">
25+
<template
26+
v-if="hasTableOfContents"
27+
#sidebar-left
4228
>
43-
<slot />
44-
</main>
45-
46-
<div
47-
id="right-sidebar"
48-
class="hidden sm:col-start-5 sm:row-start-2 sm:col-end-6 sm:row-end-5 sm:block"
49-
/>
29+
<TableOfContents :links="tocLinks" />
30+
</template>
5031

51-
<TheFooter class="col-span-1 row-start-5 row-end-6 sm:col-start-2 sm:col-end-5" />
52-
</div>
32+
<slot />
33+
</BaseGridLayout>
5334
</template>

layouts/sidebar.vue

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<script setup lang="ts">
2+
const { sidebarPosition = 'left' } = defineProps<{
3+
/** Position of the sidebar */
4+
sidebarPosition?: 'left' | 'right'
5+
}>()
6+
7+
const variant = computed(() =>
8+
(sidebarPosition === 'left' ? 'left-sidebar' : 'right-sidebar'),
9+
)
10+
</script>
11+
12+
<template>
13+
<BaseGridLayout :variant="variant">
14+
<template
15+
v-if="sidebarPosition === 'left'"
16+
#sidebar-left
17+
>
18+
<slot name="sidebar" />
19+
</template>
20+
21+
<template
22+
v-if="sidebarPosition === 'right'"
23+
#sidebar-right
24+
>
25+
<slot name="sidebar" />
26+
</template>
27+
28+
<slot />
29+
</BaseGridLayout>
30+
</template>

pages/posts/[postSlug].vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const readingTimeText = computed(() => {
2727
return minutes === 1 ? '1 min read' : `${minutes} min read`
2828
})
2929
30+
// TOC data is now handled by the layout itself
31+
3032
definePageMeta({
3133
layout: 'post',
3234
})

0 commit comments

Comments
 (0)