Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/[post]/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { fetchPost, listPosts } from '../../utils/posts.js'
import './HighlightCode.css'
import { Pill } from '../components/Pill.jsx'
import { ButtonRead } from '../components/ButtonRead.jsx'
import { PostActionsSheet } from '../components/PostActionsSheet.jsx'

export async function generateStaticParams() {
return listPosts()
Expand Down Expand Up @@ -45,6 +46,7 @@ export default async function Post(props) {
className='prose max-w-none pb-4 [&>hr]:hidden [&>h1]:text-3xl [&>h1]:font-bold [&>h1]:text-blue-900 [&>h1]:pb-8 [&>p]:pb-6 [&>p]:text-lg md:[&>p]:text-xl [&>p>strong]:bg-yellow-50 [&_a]:text-blue-700 [&_a:hover]:underline [&>ul>li]:list-disc [&>ul]:text-lg md:[&>ul]:text-xl [&>ul]:text-blue-900 dark:[&>ul]:text-blue-200 [&>ul]:pb-4 [&>ul>li]:ml-5 [&>ul]:space-y-3 [&>pre]:overflow-x-auto [&>pre]:rounded-xl [&>pre]:text-white [&>pre]:mb-8 [&>pre]:p-8 [&>pre]:bg-slate-800'
dangerouslySetInnerHTML={{ __html: content }}
/>
<PostActionsSheet postId={post} />
<footer className='py-12 clear-both text-center [&>a]:leading-snug [&>a]:hover:underline [&>a]:block [&>a]:my-2'>
{prev && (
<Link className='lg:float-left' href={`/${prev.id}/#content`}>
Expand Down
103 changes: 103 additions & 0 deletions app/components/PostActionsSheet.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'use client'

import { useState, useEffect, useCallback, useRef } from 'react'
import { createPortal } from 'react-dom'
import { Quiz } from './Quiz.jsx'

export function PostActionsSheet({ postId }) {
const [open, setOpen] = useState(null) // 'practicar' | 'evaluar' | null
const [mounted, setMounted] = useState(false)
const sheetRef = useRef(null)

useEffect(() => {
setMounted(true)
}, [])

const close = useCallback(() => setOpen(null), [])

useEffect(() => {
if (!open) return
const onKey = e => {
if (e.key === 'Escape') close()
}
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [open, close])

useEffect(() => {
if (!open) return
const handleClickOutside = e => {
if (sheetRef.current && !sheetRef.current.contains(e.target)) {
close()
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [open, close])

const openSheet = type => setOpen(type)

const baseBtn =
'border dark:border-white uppercase mix rounded-[4px] font-bold inline-block px-2 py-[3px] text-[10px] bg-white dark:bg-secondry hover:bg-blue-50 dark:hover:bg-blue-900 transition-colors'

const sheet = (
<div
aria-hidden={!open}
className={`fixed inset-0 z-40 ${open ? 'pointer-events-auto' : 'pointer-events-none'}`}
>
<div
className={`absolute inset-0 bg-black/30 backdrop-blur-sm transition-opacity ${open ? 'opacity-100' : 'opacity-0'}`}
onClick={close}
/>
<div
ref={sheetRef}
role='dialog'
aria-modal='true'
aria-label={open === 'practicar' ? 'Prácticar' : 'Evaluar'}
className={`absolute bottom-0 left-0 right-0 mx-auto max-w-3xl rounded-t-xl border border-blue-200 dark:border-blue-800 bg-white dark:bg-secondry shadow-xl p-6 transition-transform duration-300 ${open ? 'translate-y-0' : 'translate-y-full'}`}
>
<div className='flex items-start justify-between pb-4'>
<h2 className='text-base font-bold text-blue-900 dark:text-blue-100'>
{open === 'practicar' ? 'Prácticar' : 'Evaluar'}
</h2>
<button
onClick={close}
className='text-xs font-semibold px-2 py-1 rounded hover:bg-blue-100 dark:hover:bg-blue-900'
>
Cerrar
</button>
</div>
<div className='prose dark:prose-invert max-w-none text-sm md:text-base'>
{open === 'practicar' && (
<div className='space-y-4'>
<p>
Próximamente: área de práctica con mini retos o tarjetas
(flashcards).
</p>
<p className='text-xs opacity-60'>
Indica qué dinámica quieres y la añado.
</p>
</div>
)}
{open === 'evaluar' && (
<div className='mt-2'>
<Quiz slug={postId} />
</div>
)}
</div>
</div>
</div>
)

return (
<div className='pt-4 flex gap-2 justify-center'>
{/* <button onClick={() => openSheet('practicar')} className={baseBtn}>
prácticar
</button> */}
<button onClick={() => openSheet('evaluar')} className={baseBtn}>
evaluar
</button>
{mounted && createPortal(sheet, document.body)}
</div>
)
}
Loading