Skip to content

Commit 83c89c2

Browse files
committed
feat: allow pinning of sidebar sections
Add button for pinning sidebar sections. Saves to localStorage for persistence. Expand collapsible sections to all sidebar sections. Ensure we have order-[0-15] available since they're dynamically calculated.
1 parent d9172a9 commit 83c89c2

11 files changed

Lines changed: 323 additions & 276 deletions

app/components/CollapsibleSection.vue

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<script setup lang="ts">
2-
import { shallowRef, computed } from 'vue'
2+
import { computed, shallowRef } from 'vue'
33
44
interface Props {
55
title: string
66
isLoading?: boolean
77
headingLevel?: `h${number}`
88
id: string
9+
order?: number
910
}
1011
1112
const props = withDefaults(defineProps<Props>(), {
@@ -20,6 +21,7 @@ const contentId = `${props.id}-collapsible-content`
2021
const headingId = `${props.id}-heading`
2122
2223
const isOpen = shallowRef(true)
24+
const isPinned = shallowRef(false)
2325
2426
onPrehydrate(() => {
2527
const settings = JSON.parse(localStorage.getItem('npmx-settings') || '{}')
@@ -38,13 +40,14 @@ onPrehydrate(() => {
3840
onMounted(() => {
3941
if (document?.documentElement) {
4042
isOpen.value = !(document.documentElement.dataset.collapsed?.includes(props.id) ?? false)
43+
isPinned.value = document.documentElement.dataset.pinned?.includes(props.id) ?? false
4144
}
4245
})
4346
4447
function toggle() {
4548
isOpen.value = !isOpen.value
4649
47-
const removed = appSettings.settings.value.sidebar.collapsed.filter(c => c !== props.id)
50+
const removed = appSettings.settings.value.sidebar.collapsed?.filter(c => c !== props.id) ?? []
4851
4952
if (isOpen.value) {
5053
appSettings.settings.value.sidebar.collapsed = removed
@@ -57,6 +60,21 @@ function toggle() {
5760
appSettings.settings.value.sidebar.collapsed.join(' ')
5861
}
5962
63+
function togglePin() {
64+
isPinned.value = !isPinned.value
65+
66+
const removed = appSettings.settings.value.sidebar.pinned?.filter(c => c !== props.id) ?? []
67+
68+
if (isPinned.value) {
69+
removed.push(props.id)
70+
appSettings.settings.value.sidebar.pinned = removed
71+
} else {
72+
appSettings.settings.value.sidebar.pinned = removed
73+
}
74+
75+
document.documentElement.dataset.pinned = appSettings.settings.value.sidebar.pinned.join(' ')
76+
}
77+
6078
const ariaLabel = computed(() => {
6179
const action = isOpen.value ? 'Collapse' : 'Expand'
6280
return props.title ? `${action} ${props.title}` : action
@@ -74,7 +92,11 @@ useHead({
7492
</script>
7593

7694
<template>
77-
<section class="scroll-mt-20" :data-anchor-id="id">
95+
<section
96+
class="scroll-mt-20"
97+
:class="order !== undefined ? `order-${order}` : ''"
98+
:data-anchor-id="id"
99+
>
78100
<div class="flex items-center justify-between mb-3">
79101
<component
80102
:is="headingLevel"
@@ -117,6 +139,15 @@ useHead({
117139

118140
<!-- Actions slot for buttons or other elements -->
119141
<slot name="actions" />
142+
<!-- pin button -->
143+
<button
144+
type="button"
145+
class="w-4 h-4 flex items-center justify-center text-fg-subtle hover:text-fg-muted transition-colors duration-200 shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
146+
@click="togglePin"
147+
>
148+
<span v-if="isPinned" class="i-carbon:pin-filled w-3 h-3" aria-hidden="true" />
149+
<span v-else class="i-carbon:pin w-3 h-3" aria-hidden="true" />
150+
</button>
120151
</div>
121152

122153
<div

0 commit comments

Comments
 (0)