From 8d294b0bb193a788cad5ce8b585b17ce7ed6ad29 Mon Sep 17 00:00:00 2001 From: "leonardo.santos" Date: Fri, 31 Jul 2026 09:34:07 -0300 Subject: [PATCH] feat(markdown,ui): scrollbar fino global + botao de inserir imagem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scrollbar padrao do navegador (grosso, cinza, quadrado) trocado por um fino e discreto em toda a aplicacao (Firefox + Chrome/Edge/Safari), sem precisar caçar cada modal/textarea com scroll individualmente - editor de markdown ganha botao visivel "Inserir imagem" (input de arquivo) alem do paste — mais descobrivel que so colar via Ctrl+V Nota: o PR anterior que adicionava o paste de imagem (#56) foi mesclado mas a Vercel nunca disparou o deploy de producao pra esse commit (confirmado via GitHub Deployments API — nao existe deployment registrado). Nao e' bug de codigo, e' falha do lado da integracao Vercel/GitHub. Esse commit deve reacionar o webhook. Co-Authored-By: Claude Sonnet 5 --- src/app/globals.css | 26 ++++++++++++ src/components/markdown-editor.tsx | 66 +++++++++++++++++++++++------- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index b506a97..82abc13 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -177,3 +177,29 @@ margin: 0.5rem 0; display: block; } + +/* Scrollbar fino e discreto em toda a aplicação — o scrollbar padrão do + navegador (grosso, cinza puro, quadrado) destoa do resto do design. + Firefox usa scrollbar-width/scrollbar-color; Chrome/Edge/Safari usam os + pseudo-elementos ::-webkit-scrollbar-*. Aplicado globalmente pra não + precisar caçar cada overflow-y-auto individualmente. */ +* { + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; +} +*::-webkit-scrollbar { + width: 8px; + height: 8px; +} +*::-webkit-scrollbar-track { + background: transparent; +} +*::-webkit-scrollbar-thumb { + background-color: var(--border); + border-radius: 999px; + border: 2px solid transparent; + background-clip: padding-box; +} +*::-webkit-scrollbar-thumb:hover { + background-color: var(--muted-foreground); +} diff --git a/src/components/markdown-editor.tsx b/src/components/markdown-editor.tsx index 91ecb15..fb59fb7 100644 --- a/src/components/markdown-editor.tsx +++ b/src/components/markdown-editor.tsx @@ -3,6 +3,7 @@ import { useRef, useState } from "react"; import type { ReactNode } from "react"; import ReactMarkdown from "react-markdown"; +import { ImagePlus } from "lucide-react"; import { cn } from "@/lib/utils"; // Imagem vira base64 embutido no próprio markdown (data URL) — sem upload, @@ -59,6 +60,7 @@ export function MarkdownEditor({ const [preview, setPreview] = useState(false); const [pasteError, setPasteError] = useState(""); const textareaRef = useRef(null); + const fileInputRef = useRef(null); const insertAtCursor = (text: string) => { const el = textareaRef.current; @@ -77,32 +79,44 @@ export function MarkdownEditor({ }); }; - const handlePaste = async (e: React.ClipboardEvent) => { - const imageItem = Array.from(e.clipboardData.items).find((item) => - item.type.startsWith("image/") - ); - if (!imageItem) return; - - e.preventDefault(); + const insertImageFile = async (file: File) => { setPasteError(""); - const file = imageItem.getAsFile(); - if (!file) return; - + if (!file.type.startsWith("image/")) { + setPasteError("Só é possível inserir arquivos de imagem."); + return; + } if (file.size > MAX_PASTED_IMAGE_BYTES) { setPasteError( - "Imagem muito grande (máx. 2 MB). Reduza o tamanho e tente colar de novo." + "Imagem muito grande (máx. 2 MB). Reduza o tamanho e tente de novo." ); return; } - try { const dataUrl = await fileToDataUrl(file); insertAtCursor(`\n![imagem](${dataUrl})\n`); } catch { - setPasteError("Não foi possível colar a imagem. Tente novamente."); + setPasteError("Não foi possível inserir a imagem. Tente novamente."); } }; + const handlePaste = (e: React.ClipboardEvent) => { + const imageItem = Array.from(e.clipboardData.items).find((item) => + item.type.startsWith("image/") + ); + if (!imageItem) return; + + e.preventDefault(); + const file = imageItem.getAsFile(); + if (!file) return; + void insertImageFile(file); + }; + + const handleFilePick = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + e.target.value = ""; + if (file) void insertImageFile(file); + }; + const toggle = (
- {toggle} +
+ {!preview && ( + <> + + + + )} + {toggle} +
{preview ? ( @@ -199,7 +234,8 @@ export function MarkdownEditor({ {!preview && (

- Dica: cole (Ctrl+V) uma imagem copiada pra inserir direto no texto. + Cole (Ctrl+V) uma imagem copiada ou use "Inserir imagem" + acima.

)}