Skip to content

Commit 4c4e440

Browse files
committed
Updated Components and css
1 parent 881c37f commit 4c4e440

15 files changed

Lines changed: 937 additions & 659 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!-- .vitepress/theme/components/VButton.vue -->
2+
<script setup lang="ts">
3+
import { useData } from 'vitepress'
4+
import { computed } from 'vue'
5+
6+
const props = defineProps<{ href: string, }>()
7+
const { page } = useData()
8+
9+
// Check if this button's link matches the current page URL
10+
const isActive = computed(() => {
11+
const currentPath = page.value.relativePath.replace(/\.md$/, '').replace(/\/index$/, '')
12+
const targetPath = props.href.replace(/^\//, '').replace(/\/$/, '')
13+
return currentPath === targetPath || (currentPath === '' && targetPath === 'index')
14+
})
15+
</script>
16+
17+
<template>
18+
<a :href="href" :class="[
19+
'px-5 py-2.5 text-[10px] font-black uppercase tracking-widest transition-all border-r border-zinc-200 last:border-r-0 dark:border-zinc-800 shrink-0 no-underline!',
20+
isActive
21+
? 'bg-zinc-900 text-white! dark:bg-white dark:text-zinc-900!'
22+
: 'bg-white text-zinc-500! hover:bg-zinc-50 dark:bg-zinc-900/50 dark:hover:bg-zinc-800'
23+
]">
24+
<slot />
25+
</a>
26+
</template>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!-- .vitepress/theme/components/VButtons.vue -->
2+
<script setup lang="ts">
3+
interface Props {
4+
layout?: 'bar' | 'grid' | 'list'
5+
}
6+
withDefaults(defineProps<Props>(), { layout: 'bar' })
7+
8+
</script>
9+
10+
<template>
11+
<nav :class="[
12+
'v-buttons overflow-hidden mb-8',
13+
// BAR: Joined horizontal scrolling bar
14+
layout === 'bar' && 'inline-flex w-full overflow-x-auto flex-nowrap rounded-xl border border-zinc-200 dark:border-zinc-800 shadow-xs no-scrollbar',
15+
// GRID: 2 columns on mobile, 4 on desktop
16+
layout === 'grid' && 'grid grid-cols-2 sm:grid-cols-4 gap-2',
17+
// LIST: Stacked vertically
18+
layout === 'list' && 'flex flex-col gap-1'
19+
]">
20+
<slot />
21+
</nav>
22+
</template>
23+
24+
<style scoped>
25+
@reference "../css/style.css";
26+
27+
.v-buttons :deep(a),
28+
.v-buttons :deep(button) {
29+
@apply px-5 py-3 text-[10px] font-black uppercase tracking-widest transition-all shrink-0 no-underline!;
30+
@apply bg-white text-zinc-500! hover:bg-zinc-50 dark:bg-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800;
31+
}
32+
33+
/* BAR Layout Specifics: Add dividers */
34+
.v-buttons :deep(a:not(:last-child)),
35+
.v-buttons :deep(button:not(:last-child)) {
36+
@apply border-r border-zinc-200 dark:border-zinc-800;
37+
}
38+
39+
/* GRID/LIST Layout Specifics: Add rounded corners to individual items */
40+
.v-buttons:not(.inline-flex) :deep(a),
41+
.v-buttons:not(.inline-flex) :deep(button) {
42+
@apply rounded-lg border border-zinc-200 dark:border-zinc-800 border-r!;
43+
}
44+
45+
.no-scrollbar::-webkit-scrollbar {
46+
display: none;
47+
}
48+
49+
.no-scrollbar {
50+
-ms-overflow-style: none;
51+
scrollbar-width: none;
52+
}
53+
</style>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<!-- .vitepress/theme/components/DownloadLink.vue -->
2+
<script setup lang="ts">
3+
import { computed } from 'vue'
4+
import { withBase } from 'vitepress'
5+
6+
interface Props {
7+
href: string
8+
/** style variant: primary, outline, ghost, danger */
9+
type?: "primary" | "outline" | "ghost" | "danger"
10+
/** SEO: Provide a clean filename for the saved file */
11+
filename?: string
12+
/** SEO/UX: Manually specify file extension (e.g., PDF, ZIP) */
13+
ext?: string
14+
/** SEO/UX: Display file size (e.g., 1.2 MB) */
15+
size?: string
16+
}
17+
18+
const props = withDefaults(defineProps<Props>(), {
19+
type: "outline"
20+
})
21+
22+
// 1. VitePress Path Normalization
23+
const normalizedHref = computed(() => withBase(props.href))
24+
25+
// 2. SEO Meta: Extract extension for screen readers & search crawlers
26+
const fileExtension = computed(() => {
27+
if (props.ext) return props.ext.toUpperCase()
28+
const parts = props.href.split('.')
29+
return parts.length > 1 ? parts.pop()?.toUpperCase() : 'FILE'
30+
})
31+
</script>
32+
33+
<template>
34+
<a
35+
:href="normalizedHref"
36+
:download="filename || true"
37+
:class="`btn-${props.type}`"
38+
style="display: inline-flex; align-items: center; gap: 12px; transition: all 0.2s ease; text-decoration: none;"
39+
type="application/octet-stream"
40+
>
41+
<!-- Left Icon (Unicode 📥) -->
42+
<slot name="left">
43+
<span style="font-size: 20px; line-height: 1; filter: saturate(0.8);">
44+
&#x1F4E5;
45+
</span>
46+
</slot>
47+
48+
<!-- Content Container -->
49+
<div style="display: flex; flex-direction: column; align-items: flex-start; line-height: 1.2;">
50+
<span style="font-weight: 700; font-size: 15px;">
51+
<slot>Download Resource</slot>
52+
</span>
53+
54+
<!-- Metadata Row: Helpful for accessibility and crawlers -->
55+
<span style="font-size: 10px; opacity: 0.6; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; margin-top: 2px;">
56+
{{ fileExtension }} <span v-if="size" style="margin: 0 4px;">•</span> {{ size }}
57+
</span>
58+
</div>
59+
60+
<!-- Hidden text for Screen Readers (SEO/Accessibility) -->
61+
<span style="position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0;">
62+
(Download {{ fileExtension }} file, {{ size || 'various sizes' }})
63+
</span>
64+
65+
<slot name="right" />
66+
</a>
67+
</template>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!-- .vitepress/theme/components/FileTree.vue -->
2+
<script setup lang="ts">
3+
interface FileItem { name: string; type: 'file' | 'dir'; depth?: number }
4+
defineProps<{ items: FileItem[] }>()
5+
</script>
6+
7+
<template>
8+
<div style="
9+
font-family: monospace; background: #f8fafc;
10+
padding: 16px; border-radius: 8px; border: 1px solid #e2e8f0;
11+
">
12+
<div v-for="item in items" :key="item.name"
13+
:style="{ paddingLeft: `${(item.depth || 0) * 20}px`, display: 'flex', gap: '8px', marginBottom: '4px' }">
14+
<span>{{ item.type === 'dir' ? '📁' : '📄' }}</span>
15+
<span :style="{ fontWeight: item.type === 'dir' ? 'bold' : 'normal' }">{{ item.name }}</span>
16+
</div>
17+
</div>
18+
</template>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
import { withBase } from 'vitepress'
4+
5+
interface BreadcrumbItem { text: string; link?: string }
6+
defineProps<{ items: BreadcrumbItem[] }>()
7+
8+
const hoverIdx = ref<number | null>(null)
9+
10+
11+
</script>
12+
13+
<template>
14+
<nav aria-label="Breadcrumb" style="margin-bottom: 1.5rem;">
15+
<ol itemscope itemtype="https://schema.org"
16+
style="display: flex; flex-wrap: wrap; align-items: center; gap: 4px; list-style: none; padding: 0; font-size: 14px;">
17+
18+
<li v-for="(item, index) in items" :key="index" itemprop="itemListElement" itemscope itemtype="https://schema.org"
19+
style="display: flex; align-items: center; gap: 4px;">
20+
21+
<template v-if="item.link && index < items.length - 1">
22+
<a itemprop="item" :href="withBase(item.link)" @mouseenter="hoverIdx = index" @mouseleave="hoverIdx = null"
23+
class="btn-ghost">
24+
<span itemprop="name">{{ item.text }}</span>
25+
</a>
26+
<!-- Unicode Separator -->
27+
<span aria-hidden="true" style="opacity: 0.4; user-select: none;">›</span>
28+
</template>
29+
30+
<template v-else>
31+
<span style="font-weight: 600; padding: 4px 8px;" aria-current="page">
32+
<span itemprop="name">{{ item.text }}</span>
33+
</span>
34+
</template>
35+
36+
<meta itemprop="position" :content="(index + 1).toString()" />
37+
</li>
38+
</ol>
39+
</nav>
40+
</template>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!-- .vitepress/theme/components/PFigure.vue -->
2+
<script setup lang="ts">
3+
import { withBase } from 'vitepress'
4+
5+
interface Props {
6+
src: string
7+
alt: string
8+
caption?: string
9+
credit?: string
10+
}
11+
12+
defineProps<Props>()
13+
</script>
14+
15+
<template>
16+
<figure
17+
itemscope
18+
itemtype="https://schema.org"
19+
class="not-prose my-8 flex flex-col items-center"
20+
>
21+
<!-- Image with Schema.org Link -->
22+
<img
23+
itemprop="contentUrl"
24+
:src="withBase(src)"
25+
:alt="alt"
26+
loading="lazy"
27+
class="rounded-2xl shadow-lg border border-zinc-200 dark:border-zinc-800 max-w-full h-auto"
28+
/>
29+
30+
<!-- SEO Friendly Caption -->
31+
<figcaption
32+
v-if="caption || $slots.default"
33+
class="mt-3 text-center"
34+
>
35+
<span itemprop="caption" class="text-sm text-slate-600 dark:text-slate-400 italic">
36+
<slot>{{ caption }}</slot>
37+
</span>
38+
39+
<!-- Optional Credit Line -->
40+
<span v-if="credit" class="block text-[10px] uppercase tracking-widest opacity-50 mt-1">
41+
Photo by {{ credit }}
42+
</span>
43+
</figcaption>
44+
</figure>
45+
</template>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<!-- .vitepress/theme/components/PImg.vue -->
2+
<script setup lang="ts">
3+
import { ref, computed, useSlots } from 'vue'
4+
import { withBase } from 'vitepress'
5+
6+
interface Props {
7+
src: string
8+
alt?: string
9+
title?: string
10+
width?: string | number
11+
height?: string | number
12+
align?: 'left' | 'center' | 'right'
13+
inline?: boolean // New inline prop
14+
loading?: "lazy" | "eager"
15+
}
16+
17+
const props = withDefaults(defineProps<Props>(), {
18+
loading: "lazy",
19+
align: 'center',
20+
inline: false
21+
})
22+
23+
const slots = useSlots()
24+
const hasCaption = computed(() => !!slots.default)
25+
26+
// --- AUTOMATIC META ---
27+
const autoAlt = computed(() => {
28+
if (props.alt) return props.alt;
29+
const filename = props.src.split('/').pop()?.split('.')[0] || '';
30+
return filename ? `Image of ${filename.replace(/[-_]/g, ' ').trim()}` : 'Image description not available';
31+
});
32+
33+
34+
const autoTitle = computed(() => props.title || autoAlt.value)
35+
36+
// --- DIMENSION DETECTION ---
37+
const autoWidth = ref<number | string | undefined>(props.width)
38+
const autoHeight = ref<number | string | undefined>(props.height)
39+
40+
const handleLoad = (event: Event) => {
41+
const img = event.target as HTMLImageElement
42+
if (!props.width) autoWidth.value = img.naturalWidth
43+
if (!props.height) autoHeight.value = img.naturalHeight
44+
}
45+
46+
// --- INLINE STYLES ---
47+
const containerStyle = computed(() => ({
48+
display: props.inline ? 'inline-flex' : 'flex',
49+
flexDirection: 'column' as const,
50+
alignItems: props.align === 'left' ? 'flex-start' : props.align === 'right' ? 'flex-end' : 'center',
51+
width: props.inline ? 'auto' : '100%',
52+
margin: props.inline ? '0 0.5rem' : '1.5rem 0',
53+
verticalAlign: 'middle'
54+
}))
55+
56+
const imgStyle = computed(() => ({
57+
maxWidth: '100%',
58+
height: 'auto',
59+
borderRadius: '0.75rem',
60+
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1)',
61+
aspectRatio: autoWidth.value && autoHeight.value ? `${autoWidth.value}/${autoHeight.value}` : 'auto',
62+
display: 'block',
63+
objectFit: 'cover' as const,
64+
}))
65+
66+
const captionStyle = {
67+
marginTop: '0.5rem',
68+
fontSize: '0.85rem',
69+
color: '#666666',
70+
fontStyle: 'italic'
71+
}
72+
</script>
73+
74+
<template>
75+
<figure :style="containerStyle" itemscope itemtype="https://schema.org/ImageObject">
76+
<img :src="withBase(src)" :alt="autoAlt" :title="autoTitle" :width="autoWidth" :height="autoHeight"
77+
:loading="loading" @load="handleLoad" :style="imgStyle" itemprop="image" />
78+
79+
<figcaption v-if="hasCaption && !inline" :style="captionStyle" itemprop="caption">
80+
<slot />
81+
</figcaption>
82+
83+
<meta itemprop="contentUrl" :content="withBase(src)">
84+
<meta itemprop="name" content="Image Title" />
85+
<meta itemprop="description" content="A brief description of the image." />
86+
<meta itemprop="author" content="AvN Learn" />
87+
<meta itemprop="dateCreated" content="2026-03-14" />
88+
</figure>
89+
</template>

0 commit comments

Comments
 (0)