Refonte design : 4 humeurs, onboarding, sections avec boite a outils
- Systeme de themes adaptatifs : Peps (light chaud), Zen (light calme), Chagrine (dark violet), Grave (dark ambre) avec CSS custom properties - Dashboard d'accueil orienté onboarding avec cartes-portes et teaser boite a outils - SectionLayout reutilisable : liste + sidebar toolbox + status pills cliquables (En prepa / En vote / En vigueur / Clos) - ToolboxVignette : vignettes Contexte / Tutos / Choisir / Demarrer - Seed : Acte engagement certification + forgeron, Runtime Upgrade (decision on-chain), 3 modalites de vote (majoritaire, quadratique, permanent) - Backend adapte SQLite (Uuid portable, 204 fix, pool conditionnel) - Correction noms composants (pathPrefix: false), pinia/nuxt ^0.11 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -105,7 +105,7 @@ async function archiveToSanctuary() {
|
||||
<UBadge variant="subtle" color="primary">
|
||||
{{ typeLabel(documents.current.doc_type) }}
|
||||
</UBadge>
|
||||
<CommonStatusBadge :status="documents.current.status" type="document" />
|
||||
<StatusBadge :status="documents.current.status" type="document" />
|
||||
<span class="text-sm text-gray-500 font-mono">
|
||||
v{{ documents.current.version }}
|
||||
</span>
|
||||
@@ -155,7 +155,7 @@ async function archiveToSanctuary() {
|
||||
<div>
|
||||
<p class="text-gray-500">Ancrage IPFS</p>
|
||||
<div class="mt-1">
|
||||
<SanctuaryIPFSLink :cid="documents.current.ipfs_cid" />
|
||||
<IPFSLink :cid="documents.current.ipfs_cid" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,7 +164,7 @@ async function archiveToSanctuary() {
|
||||
<div v-if="documents.current.chain_anchor" class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-800">
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="text-sm text-gray-500">Ancrage on-chain :</p>
|
||||
<SanctuaryChainAnchor :tx-hash="documents.current.chain_anchor" :block="null" />
|
||||
<ChainAnchor :tx-hash="documents.current.chain_anchor" :block="null" />
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
@@ -181,7 +181,7 @@ async function archiveToSanctuary() {
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-4">
|
||||
<DocumentsItemCard
|
||||
<ItemCard
|
||||
v-for="item in documents.items"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
|
||||
@@ -218,7 +218,7 @@ function formatDate(dateStr: string): string {
|
||||
<UIcon name="i-lucide-file-text" class="text-gray-400" />
|
||||
<h2 class="text-sm font-semibold text-gray-500 uppercase">Texte en vigueur</h2>
|
||||
</div>
|
||||
<CommonMarkdownRenderer :content="currentItem.current_text" />
|
||||
<MarkdownRenderer :content="currentItem.current_text" />
|
||||
<div class="flex items-center gap-4 pt-3 border-t border-gray-100 dark:border-gray-800 text-xs text-gray-400">
|
||||
<span>Cree le {{ formatDate(currentItem.created_at) }}</span>
|
||||
<span>Mis a jour le {{ formatDate(currentItem.updated_at) }}</span>
|
||||
@@ -259,7 +259,7 @@ function formatDate(dateStr: string): string {
|
||||
</template>
|
||||
|
||||
<div v-else class="space-y-4">
|
||||
<DocumentsItemVersionDiff
|
||||
<ItemVersionDiff
|
||||
v-for="version in documents.versions"
|
||||
:key="version.id"
|
||||
:version="version"
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Documents de reference — page index.
|
||||
*
|
||||
* Utilise SectionLayout avec status filters, recherche, tri,
|
||||
* et sidebar "Boite a outils" affichant les protocoles de vote.
|
||||
*/
|
||||
import type { DocumentCreate } from '~/stores/documents'
|
||||
|
||||
const documents = useDocumentsStore()
|
||||
const protocols = useProtocolsStore()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const filterType = ref<string | undefined>(undefined)
|
||||
const filterStatus = ref<string | undefined>(undefined)
|
||||
|
||||
const docTypeOptions = [
|
||||
{ label: 'Tous les types', value: undefined },
|
||||
{ label: 'Licence', value: 'licence' },
|
||||
{ label: 'Engagement', value: 'engagement' },
|
||||
{ label: 'Reglement', value: 'reglement' },
|
||||
{ label: 'Constitution', value: 'constitution' },
|
||||
]
|
||||
|
||||
const statusOptions = [
|
||||
{ label: 'Tous les statuts', value: undefined },
|
||||
{ label: 'Brouillon', value: 'draft' },
|
||||
{ label: 'Actif', value: 'active' },
|
||||
{ label: 'Archive', value: 'archived' },
|
||||
]
|
||||
const activeStatus = ref<string | null>(null)
|
||||
const searchQuery = ref('')
|
||||
const sortBy = ref<'date' | 'title' | 'status'>('date')
|
||||
|
||||
// New document modal state
|
||||
const showNewDocModal = ref(false)
|
||||
@@ -40,30 +33,78 @@ const newDocTypeOptions = [
|
||||
{ label: 'Constitution', value: 'constitution' },
|
||||
]
|
||||
|
||||
async function loadDocuments() {
|
||||
await documents.fetchAll({
|
||||
doc_type: filterType.value,
|
||||
status: filterStatus.value,
|
||||
})
|
||||
const sortOptions = [
|
||||
{ label: 'Date', value: 'date' },
|
||||
{ label: 'Titre', value: 'title' },
|
||||
{ label: 'Statut', value: 'status' },
|
||||
]
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
documents.fetchAll(),
|
||||
protocols.fetchProtocols(),
|
||||
])
|
||||
})
|
||||
|
||||
/** Status filter pills with counts. */
|
||||
const statuses = computed(() => [
|
||||
{ id: 'draft', label: 'En prepa', count: documents.list.filter(d => d.status === 'draft').length },
|
||||
{ id: 'voting', label: 'En vote', count: documents.list.filter(d => d.status === 'voting').length },
|
||||
{ id: 'active', label: 'En vigueur', count: documents.list.filter(d => d.status === 'active').length },
|
||||
{ id: 'archived', label: 'Clos', count: documents.list.filter(d => d.status === 'archived').length },
|
||||
])
|
||||
|
||||
/** Filtered and sorted documents. */
|
||||
const filteredDocuments = computed(() => {
|
||||
let list = [...documents.list]
|
||||
|
||||
// Filter by status
|
||||
if (activeStatus.value) {
|
||||
list = list.filter(d => d.status === activeStatus.value)
|
||||
}
|
||||
|
||||
// Filter by search query (client-side)
|
||||
if (searchQuery.value.trim()) {
|
||||
const q = searchQuery.value.toLowerCase()
|
||||
list = list.filter(d => d.title.toLowerCase().includes(q))
|
||||
}
|
||||
|
||||
// Sort
|
||||
switch (sortBy.value) {
|
||||
case 'title':
|
||||
list.sort((a, b) => a.title.localeCompare(b.title, 'fr'))
|
||||
break
|
||||
case 'status':
|
||||
list.sort((a, b) => a.status.localeCompare(b.status))
|
||||
break
|
||||
case 'date':
|
||||
default:
|
||||
list.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
|
||||
break
|
||||
}
|
||||
|
||||
return list
|
||||
})
|
||||
|
||||
/** Toolbox vignettes from protocols. */
|
||||
const toolboxTitle = 'Modalites de vote'
|
||||
|
||||
const typeLabel = (docType: string): string => {
|
||||
switch (docType) {
|
||||
case 'licence': return 'Licence'
|
||||
case 'engagement': return 'Engagement'
|
||||
case 'reglement': return 'Reglement'
|
||||
case 'constitution': return 'Constitution'
|
||||
default: return docType
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDocuments()
|
||||
})
|
||||
|
||||
watch([filterType, filterStatus], () => {
|
||||
loadDocuments()
|
||||
})
|
||||
|
||||
function openNewDocModal() {
|
||||
newDoc.value = {
|
||||
slug: '',
|
||||
title: '',
|
||||
doc_type: 'licence',
|
||||
description: null,
|
||||
version: '1.0.0',
|
||||
}
|
||||
showNewDocModal.value = true
|
||||
function formatDate(dateStr: string): string {
|
||||
return new Date(dateStr).toLocaleDateString('fr-FR', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
})
|
||||
}
|
||||
|
||||
function generateSlug(title: string): string {
|
||||
@@ -83,6 +124,17 @@ watch(() => newDoc.value.title, (title) => {
|
||||
}
|
||||
})
|
||||
|
||||
function openNewDocModal() {
|
||||
newDoc.value = {
|
||||
slug: '',
|
||||
title: '',
|
||||
doc_type: 'licence',
|
||||
description: null,
|
||||
version: '1.0.0',
|
||||
}
|
||||
showNewDocModal.value = true
|
||||
}
|
||||
|
||||
async function createDocument() {
|
||||
creating.value = true
|
||||
try {
|
||||
@@ -91,153 +143,301 @@ async function createDocument() {
|
||||
if (doc) {
|
||||
navigateTo(`/documents/${doc.slug}`)
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
catch {
|
||||
// Error handled in store
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
Documents de reference
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
Documents fondateurs de la communaute Duniter/G1 sous vote permanent
|
||||
<SectionLayout
|
||||
title="Documents de reference"
|
||||
subtitle="Textes fondateurs sous vote permanent de la communaute"
|
||||
:statuses="statuses"
|
||||
:active-status="activeStatus"
|
||||
@update:active-status="activeStatus = $event"
|
||||
>
|
||||
<!-- Search / sort bar -->
|
||||
<template #search>
|
||||
<UInput
|
||||
v-model="searchQuery"
|
||||
placeholder="Rechercher un document..."
|
||||
icon="i-lucide-search"
|
||||
size="sm"
|
||||
class="w-full sm:w-64"
|
||||
/>
|
||||
<USelect
|
||||
v-model="sortBy"
|
||||
:items="sortOptions"
|
||||
size="sm"
|
||||
class="w-36"
|
||||
/>
|
||||
<UButton
|
||||
v-if="auth.isAuthenticated"
|
||||
label="Nouveau"
|
||||
icon="i-lucide-plus"
|
||||
color="primary"
|
||||
size="sm"
|
||||
@click="openNewDocModal"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Main content: document list -->
|
||||
<template #default>
|
||||
<!-- Error state -->
|
||||
<div v-if="documents.error" class="flex items-center gap-3 p-4 rounded-lg" style="background: var(--mood-surface); border: 1px solid var(--mood-border);">
|
||||
<UIcon name="i-lucide-alert-circle" class="text-xl" style="color: var(--mood-error);" />
|
||||
<p style="color: var(--mood-text);">{{ documents.error }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
<div v-else-if="documents.loading" class="space-y-3">
|
||||
<LoadingSkeleton v-for="i in 4" :key="i" :lines="2" card />
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div
|
||||
v-else-if="filteredDocuments.length === 0"
|
||||
class="text-center py-12"
|
||||
style="color: var(--mood-text-muted);"
|
||||
>
|
||||
<UIcon name="i-lucide-book-open" class="text-4xl mb-3 block mx-auto" />
|
||||
<p>Aucun document trouve</p>
|
||||
<p v-if="searchQuery || activeStatus" class="text-sm mt-1">
|
||||
Essayez de modifier vos filtres
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- New document button for authenticated users -->
|
||||
<UButton
|
||||
v-if="auth.isAuthenticated"
|
||||
label="Nouveau document"
|
||||
icon="i-lucide-plus"
|
||||
color="primary"
|
||||
@click="openNewDocModal"
|
||||
/>
|
||||
</div>
|
||||
<!-- Document cards -->
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="doc in filteredDocuments"
|
||||
:key="doc.id"
|
||||
class="doc-card"
|
||||
@click="navigateTo(`/documents/${doc.slug}`)"
|
||||
>
|
||||
<div class="doc-card__header">
|
||||
<h3 class="doc-card__title">
|
||||
{{ doc.title }}
|
||||
</h3>
|
||||
<StatusBadge :status="doc.status" type="document" />
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<USelect
|
||||
v-model="filterType"
|
||||
:items="docTypeOptions"
|
||||
placeholder="Type de document"
|
||||
class="w-48"
|
||||
/>
|
||||
<USelect
|
||||
v-model="filterStatus"
|
||||
:items="statusOptions"
|
||||
placeholder="Statut"
|
||||
class="w-48"
|
||||
/>
|
||||
</div>
|
||||
<div class="doc-card__meta">
|
||||
<UBadge variant="subtle" color="primary" size="xs">
|
||||
{{ typeLabel(doc.doc_type) }}
|
||||
</UBadge>
|
||||
<span class="doc-card__version">v{{ doc.version }}</span>
|
||||
<span class="doc-card__items">
|
||||
<UIcon name="i-lucide-list" class="text-xs" />
|
||||
{{ doc.items_count }} item{{ doc.items_count !== 1 ? 's' : '' }}
|
||||
</span>
|
||||
<span class="doc-card__date">
|
||||
{{ formatDate(doc.updated_at) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
<template v-if="documents.error">
|
||||
<UCard>
|
||||
<div class="flex items-center gap-3 text-red-500">
|
||||
<UIcon name="i-lucide-alert-circle" class="text-xl" />
|
||||
<p>{{ documents.error }}</p>
|
||||
<p v-if="doc.description" class="doc-card__description">
|
||||
{{ doc.description }}
|
||||
</p>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Document list component -->
|
||||
<DocumentsDocumentList
|
||||
:documents="documents.list"
|
||||
:loading="documents.loading"
|
||||
/>
|
||||
<!-- Toolbox sidebar -->
|
||||
<template #toolbox>
|
||||
<div class="toolbox-section-title">
|
||||
{{ toolboxTitle }}
|
||||
</div>
|
||||
<template v-if="protocols.protocols.length > 0">
|
||||
<ToolboxVignette
|
||||
v-for="protocol in protocols.protocols"
|
||||
:key="protocol.id"
|
||||
:title="protocol.name"
|
||||
:description="protocol.description || undefined"
|
||||
context-label="Documents"
|
||||
:actions="[
|
||||
{ label: 'Voir', icon: 'i-lucide-eye', to: `/protocols/${protocol.id}` },
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<p v-else class="toolbox-empty-text">
|
||||
Aucun protocole configure
|
||||
</p>
|
||||
</template>
|
||||
</SectionLayout>
|
||||
|
||||
<!-- New document modal -->
|
||||
<UModal v-model:open="showNewDocModal">
|
||||
<template #content>
|
||||
<div class="p-6 space-y-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Nouveau document de reference
|
||||
</h3>
|
||||
<!-- New document modal -->
|
||||
<UModal v-model:open="showNewDocModal">
|
||||
<template #content>
|
||||
<div class="p-6 space-y-4">
|
||||
<h3 class="text-lg font-semibold" style="color: var(--mood-text);">
|
||||
Nouveau document de reference
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Titre
|
||||
</label>
|
||||
<UInput
|
||||
v-model="newDoc.title"
|
||||
placeholder="Ex: Licence G1"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Slug (identifiant URL)
|
||||
</label>
|
||||
<UInput
|
||||
v-model="newDoc.slug"
|
||||
placeholder="Ex: licence-g1"
|
||||
class="w-full font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Type de document
|
||||
</label>
|
||||
<USelect
|
||||
v-model="newDoc.doc_type"
|
||||
:items="newDocTypeOptions"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Version
|
||||
</label>
|
||||
<UInput
|
||||
v-model="newDoc.version"
|
||||
placeholder="1.0.0"
|
||||
class="w-full font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Description (optionnelle)
|
||||
</label>
|
||||
<UTextarea
|
||||
v-model="newDoc.description"
|
||||
:rows="3"
|
||||
placeholder="Decrivez brievement ce document..."
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium" style="color: var(--mood-text-muted);">
|
||||
Titre
|
||||
</label>
|
||||
<UInput
|
||||
v-model="newDoc.title"
|
||||
placeholder="Ex: Licence G1"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 pt-2">
|
||||
<UButton
|
||||
label="Annuler"
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
@click="showNewDocModal = false"
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium" style="color: var(--mood-text-muted);">
|
||||
Slug (identifiant URL)
|
||||
</label>
|
||||
<UInput
|
||||
v-model="newDoc.slug"
|
||||
placeholder="Ex: licence-g1"
|
||||
class="w-full font-mono text-sm"
|
||||
/>
|
||||
<UButton
|
||||
label="Creer le document"
|
||||
icon="i-lucide-plus"
|
||||
color="primary"
|
||||
:loading="creating"
|
||||
:disabled="!newDoc.title.trim() || !newDoc.slug.trim()"
|
||||
@click="createDocument"
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium" style="color: var(--mood-text-muted);">
|
||||
Type de document
|
||||
</label>
|
||||
<USelect
|
||||
v-model="newDoc.doc_type"
|
||||
:items="newDocTypeOptions"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium" style="color: var(--mood-text-muted);">
|
||||
Version
|
||||
</label>
|
||||
<UInput
|
||||
v-model="newDoc.version"
|
||||
placeholder="1.0.0"
|
||||
class="w-full font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium" style="color: var(--mood-text-muted);">
|
||||
Description (optionnelle)
|
||||
</label>
|
||||
<UTextarea
|
||||
v-model="newDoc.description"
|
||||
:rows="3"
|
||||
placeholder="Decrivez brievement ce document..."
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 pt-2">
|
||||
<UButton
|
||||
label="Annuler"
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
@click="showNewDocModal = false"
|
||||
/>
|
||||
<UButton
|
||||
label="Creer le document"
|
||||
icon="i-lucide-plus"
|
||||
color="primary"
|
||||
:loading="creating"
|
||||
:disabled="!newDoc.title.trim() || !newDoc.slug.trim()"
|
||||
@click="createDocument"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.doc-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.875rem 1rem;
|
||||
background: var(--mood-surface);
|
||||
border: 1px solid var(--mood-border);
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.doc-card:hover {
|
||||
border-color: var(--mood-accent);
|
||||
box-shadow: 0 2px 8px var(--mood-shadow);
|
||||
}
|
||||
|
||||
.doc-card__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.doc-card__title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--mood-text);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.doc-card__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.doc-card__version {
|
||||
font-size: 0.6875rem;
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
color: var(--mood-text-muted);
|
||||
}
|
||||
|
||||
.doc-card__items {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.6875rem;
|
||||
color: var(--mood-text-muted);
|
||||
}
|
||||
|
||||
.doc-card__date {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--mood-text-muted);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.doc-card__description {
|
||||
font-size: 0.75rem;
|
||||
color: var(--mood-text-muted);
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toolbox-section-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--mood-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.toolbox-empty-text {
|
||||
font-size: 0.75rem;
|
||||
color: var(--mood-text-muted);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user