collapsables

This commit is contained in:
Michael Freno
2025-12-21 00:43:42 -05:00
parent 53e6d0aafe
commit 291971a1d7
4 changed files with 113 additions and 36 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -19,6 +19,9 @@
"@tiptap/core": "^3.14.0",
"@tiptap/extension-code-block-lowlight": "^3.14.0",
"@tiptap/extension-color": "^3.14.0",
"@tiptap/extension-details": "^3.14.0",
"@tiptap/extension-details-content": "^2.26.2",
"@tiptap/extension-details-summary": "^2.26.2",
"@tiptap/extension-image": "^3.14.0",
"@tiptap/extension-link": "^3.14.0",
"@tiptap/extension-list-item": "^3.14.0",

View File

@@ -810,7 +810,6 @@ a.hover-underline-animation:hover::after {
pointer-events: none;
}
/* Task list styles */
ul[data-type="taskList"] {
list-style: none;
padding: 0;
@@ -820,51 +819,80 @@ ul[data-type="taskList"] {
ul[data-type="taskList"] li {
display: flex;
align-items: center;
margin: 0.5rem 0;
gap: 0.5rem;
line-height: 0;
}
ul[data-type="taskList"] li > label {
flex: 0 0 auto;
user-select: none;
display: flex;
align-items: center;
margin: 0;
padding: 0;
line-height: 1;
/* Collapsible section (details/summary) styles */
details {
margin: 1.5rem 0;
padding: 1rem;
border: 1px solid var(--color-surface2);
border-radius: 0.5rem;
background-color: var(--color-surface0);
}
ul[data-type="taskList"] li > div {
flex: 1 1 auto;
}
ul[data-type="taskList"] input[type="checkbox"] {
summary {
cursor: pointer;
width: 1em;
height: 1em;
margin: 0;
padding: 0;
flex-shrink: 0;
accent-color: var(--color-blue);
transform: none;
vertical-align: middle;
-webkit-appearance: checkbox;
appearance: checkbox;
border: 2px solid var(--color-blue);
border-radius: 0.15em;
background-color: transparent;
font-weight: 600;
user-select: none;
padding: 0.5rem;
margin: -1rem -1rem 0 -1rem;
background-color: var(--color-surface1);
border-radius: 0.5rem 0.5rem 0 0;
transition: background-color 0.2s;
list-style: none;
}
ul[data-type="taskList"] input[type="checkbox"]:checked {
background-color: var(--color-blue);
summary::-webkit-details-marker {
display: none;
}
ul[data-type="taskList"] li[data-checked="true"] > div {
text-decoration: line-through;
opacity: 0.6;
summary::before {
content: "▶ ";
color: var(--color-blue);
display: inline-block;
transition: transform 0.2s;
}
/* Nested task lists */
ul[data-type="taskList"] ul[data-type="taskList"] {
margin: 0.5rem 0 0.5rem 1.5rem;
details[open] summary::before {
transform: rotate(90deg);
color: var(--color-green);
}
summary:hover {
background-color: var(--color-surface2);
}
details[open] summary {
margin-bottom: 1rem;
border-bottom: 1px solid var(--color-surface2);
border-radius: 0.5rem 0.5rem 0 0;
}
/* Content inside details */
details div[data-type="details-content"] {
padding: 0.5rem;
}
/* Nested details */
details details {
margin: 1rem 0;
border-color: var(--color-surface1);
}
/* Animation */
details[open] div[data-type="details-content"] {
animation: slideDown 0.2s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@@ -10,6 +10,9 @@ import { TableHeader } from "@tiptap/extension-table-header";
import { TableCell } from "@tiptap/extension-table-cell";
import TaskList from "@tiptap/extension-task-list";
import TaskItem from "@tiptap/extension-task-item";
import Details from "@tiptap/extension-details";
import DetailsSummary from "@tiptap/extension-details-summary";
import DetailsContent from "@tiptap/extension-details-content";
import { Node } from "@tiptap/core";
import { createLowlight, common } from "lowlight";
import css from "highlight.js/lib/languages/css";
@@ -225,6 +228,9 @@ export default function TextEditor(props: TextEditorProps) {
class: "task-item"
}
}),
Details,
DetailsSummary,
DetailsContent,
Table.configure({
resizable: true,
HTMLAttributes: {
@@ -336,6 +342,38 @@ export default function TextEditor(props: TextEditorProps) {
}
};
const insertCollapsibleSection = () => {
const instance = editor();
if (!instance) return;
const title = window.prompt("Section title:", "Click to expand");
if (title !== null) {
instance
.chain()
.focus()
.insertContent({
type: "details",
content: [
{
type: "detailsSummary",
content: [{ type: "text", text: title }]
},
{
type: "detailsContent",
content: [
{
type: "paragraph",
content: [{ type: "text", text: "Add your content here..." }]
}
]
}
]
})
.run();
}
};
const insertCodeBlock = (language: string | null) => {
const instance = editor();
if (!instance) return;
@@ -1013,6 +1051,14 @@ export default function TextEditor(props: TextEditorProps) {
>
" Quote
</button>
<button
type="button"
onClick={insertCollapsibleSection}
class="hover:bg-surface1 rounded px-2 py-1 text-xs"
title="Insert Collapsible Section"
>
▼ Details
</button>
<div class="border-surface2 mx-1 border-l"></div>
<button
type="button"