UNPKG

helios-editor

Version:

A comprehensive React rich text editor component with image upload, resize functionality, and standalone CSS. Ready for NPM publishing.

794 lines (695 loc) 15.3 kB
/* DZ Rich Text Editor - Core CSS */ /** * Base reset to ensure consistent styling in standalone implementations */ .dz-editor-reset * { box-sizing: border-box; margin: 0; padding: 0; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } /* Standalone container */ .dz-editor-container { width: 100%; max-width: 100%; margin: 0; padding: 0; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } /* Editor wrapper */ .editor-wrapper { border: 1px solid #d1d5db; border-radius: 0.375rem; background-color: white; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); font-size: 16px; margin: 0; padding: 0; max-width: 100%; } /* Toolbar styles */ .editor-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem; padding: 0.375rem; border-bottom: 1px solid #e5e7eb; background-color: #f9fafb; border-top-left-radius: 0.375rem; border-top-right-radius: 0.375rem; } /* Button groups for consistent spacing */ .button-group { display: inline-flex; margin-right: 0.5rem; } /* Add separator between button groups */ .toolbar-separator { width: 1px; height: 20px; background-color: #e5e7eb; margin: 0 0.5rem; display: inline-block; align-self: center; } /* Content area */ .editor-content { padding: 1rem; outline: none; overflow-y: auto; min-height: 100px; background-color: white; color: #1f2937; font-size: 1rem; line-height: 1.5; border-bottom-left-radius: 0.375rem; border-bottom-right-radius: 0.375rem; } /* Placeholder styles for contenteditable */ [contenteditable][data-placeholder]:empty:before { content: attr(data-placeholder); color: #9ca3af; pointer-events: none; position: absolute; } /* Selection styles */ ::selection { background-color: #dbeafe; color: #1e3a8a; } /* Button Styles */ .toolbar-btn, .alignment-btn { padding: 0.375rem 0.75rem; font-size: 0.875rem; border: 1px solid #d1d5db; border-radius: 0.25rem; background-color: white; color: #374151; transition: all 0.15s ease-in-out; height: 30px; min-width: 30px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; } /* Format buttons (B, I, U, S) */ .format-btn { width: 30px; padding: 0; } .format-btn.bold { font-weight: bold; } .format-btn.italic { font-style: italic; } .format-btn.underline { text-decoration: underline; } .format-btn.strikethrough { text-decoration: line-through; } /* Icon buttons */ .icon-btn { width: 30px; height: 30px; padding: 0; font-size: 16px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; } /* Special buttons */ .color-btn { position: relative; overflow: hidden; } .color-btn::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background-color: currentColor; } /* Toolbar button hover state */ .toolbar-btn:hover, .alignment-btn:hover { background-color: #f3f4f6; border-color: #9ca3af; } .toolbar-btn:focus, .alignment-btn:focus { outline: none; box-shadow: 0 0 0 2px #3b82f6; border-color: #3b82f6; } /* Button active state */ .toolbar-btn-active, .alignment-btn.active { background-color: #dbeafe; border-color: #60a5fa; color: #1d4ed8; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); } /* Alignment button group */ .alignment-group { display: inline-flex; align-items: center; margin-right: 4px; } .alignment-group .alignment-btn { border-radius: 0; border-right-width: 0; margin: 0; } .alignment-group .alignment-btn:first-child { border-top-left-radius: 0.25rem; border-bottom-left-radius: 0.25rem; } .alignment-group .alignment-btn:last-child { border-top-right-radius: 0.25rem; border-bottom-right-radius: 0.25rem; border-right-width: 1px; } /* Dropdown System */ .dropdown-container { position: relative; display: inline-block; } /* Wide dropdown for headings, font family, line spacing, and bullet list */ .wide-dropdown { min-width: 140px; margin-right: 4px; } .dropdown-trigger { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; padding: 0.375rem 0.75rem; background-color: #ffffff; border: 1px solid #d1d5db; border-radius: 0.25rem; font-size: 0.875rem; font-weight: 500; color: #374151; cursor: pointer; transition: all 0.15s ease-in-out; height: 30px; min-width: 100%; text-align: left; } .dropdown-trigger:hover { background-color: #f9fafb; border-color: #9ca3af; } .dropdown-trigger.active { background-color: #dbeafe; border-color: #60a5fa; color: #1d4ed8; } .dropdown-arrow { transition: transform 0.2s ease-in-out; font-size: 0.75rem; opacity: 0.7; } .dropdown-trigger.active .dropdown-arrow { transform: rotate(180deg); opacity: 1; } .dropdown-menu { position: absolute; top: calc(100% + 0.25rem); left: 0; right: 0; min-width: 12rem; max-width: 240px; width: 100%; background: #ffffff; border: 1px solid #d1d5db; border-radius: 0.25rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); z-index: 60; overflow: hidden; animation: dropdownSlideIn 0.2s ease-out; transform-origin: top; } /* Make dropdown menu at least as wide as its container for wide dropdowns */ .wide-dropdown .dropdown-menu { min-width: 100%; max-width: none; } @keyframes dropdownSlideIn { from { opacity: 0; transform: translateY(-8px) scale(0.95); filter: blur(4px); } to { opacity: 1; transform: translateY(0) scale(1); filter: blur(0px); } } .dropdown-item { display: block; width: 100%; padding: 0.5rem 0.75rem; font-size: 0.875rem; color: #374151; text-align: left; background: transparent; border: none; cursor: pointer; transition: background-color 0.15s ease-in-out; border-bottom: 1px solid #e5e7eb; position: relative; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .dropdown-item:last-child { border-bottom: none; } .dropdown-item:hover { background-color: #f3f4f6; } .dropdown-item.active { background-color: #dbeafe; color: #1d4ed8; } /* List style specific items */ .list-style-item { display: flex; align-items: center; gap: 0.5rem; } .list-style-disc::before { content: "•"; display: inline-block; width: 12px; font-weight: bold; } .list-style-circle::before { content: "○"; display: inline-block; width: 12px; font-weight: bold; } .list-style-square::before { content: "■"; display: inline-block; width: 12px; font-weight: bold; font-size: 0.7em; } /* Font family specific styling */ .dropdown-item.font-preview { font-family: var(--font-family); font-weight: 500; } /* Sticky toolbar styles */ .editor-toolbar-sticky { box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.1), 0 4px 16px -4px rgba(0, 0, 0, 0.05); background-color: rgba(249, 250, 251, 0.95); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-left: none; border-right: none; border-top: none; } /* Enhanced backdrop blur support for better browser compatibility */ @supports (backdrop-filter: blur(12px)) { .editor-toolbar-sticky { background-color: rgba(249, 250, 251, 0.9); } } /* Fallback for browsers without backdrop-filter support */ @supports not (backdrop-filter: blur(12px)) { .editor-toolbar-sticky { background-color: rgba(249, 250, 251, 0.98); } } /* Ensure toolbar buttons maintain proper spacing in sticky mode */ .editor-toolbar-sticky .dropdown-container { position: relative; } .editor-toolbar-sticky .dropdown-menu { position: absolute; z-index: 1001; } /* Modal backdrop */ .modal-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: radial-gradient(circle at center, rgba(99, 102, 241, 0.15), rgba(0, 0, 0, 0.7)); backdrop-filter: blur(12px) saturate(200%) brightness(110%); z-index: 50; display: flex; align-items: center; justify-content: center; padding: 1rem; animation: backdropFadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1); } @keyframes backdropFadeIn { from { opacity: 0; backdrop-filter: blur(0px) saturate(100%) brightness(100%); transform: scale(1.02); } to { opacity: 1; backdrop-filter: blur(12px) saturate(200%) brightness(110%); transform: scale(1); } } /* Compact Modal System */ .compact-modal { background: linear-gradient(145deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.95)); padding: 1.25rem; border-radius: 1rem; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.15), 0 10px 10px -5px rgba(0, 0, 0, 0.04), 0 0 0 1px rgba(255, 255, 255, 0.2), 0 4px 16px rgba(99, 102, 241, 0.08); border: 1px solid rgba(255, 255, 255, 0.3); backdrop-filter: blur(20px) saturate(180%); animation: compactModalSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1); position: relative; overflow: hidden; transform-origin: center; min-width: 280px; max-width: 90vw; } .compact-modal h3 { font-size: 1.125rem; font-weight: 700; color: #1f2937; margin-bottom: 1rem; text-align: center; letter-spacing: -0.01em; } /* Compact Form Styles */ .compact-form { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1rem; } .compact-input { width: 100%; padding: 0.625rem 0.875rem; border: 1.5px solid #e5e7eb; border-radius: 0.5rem; background: rgba(255, 255, 255, 0.9); font-size: 0.875rem; transition: all 0.15s ease-in-out; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.04); } /* Compact Actions */ .compact-actions { display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 1rem; } /* Compact Buttons */ .btn-compact-primary { padding: 0.5rem 1rem; background: linear-gradient(135deg, #3b82f6, #1d4ed8); color: white; border-radius: 0.5rem; border: none; font-weight: 600; font-size: 0.8rem; transition: all 0.15s ease-in-out; box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); cursor: pointer; } .btn-compact-secondary { padding: 0.5rem 1rem; color: #6b7280; border: 1.5px solid #e5e7eb; border-radius: 0.5rem; background: rgba(255, 255, 255, 0.8); font-weight: 500; font-size: 0.8rem; transition: all 0.15s ease-in-out; cursor: pointer; } /* Rich Text Editor content styles */ .prose { max-width: none; color: #111827; } .prose h1 { font-size: 1.5rem; font-weight: 700; margin-bottom: 1rem; margin-top: 1.5rem; color: #111827; } .prose h2 { font-size: 1.25rem; font-weight: 700; margin-bottom: 0.75rem; margin-top: 1.25rem; color: #111827; } .prose h3 { font-size: 1.125rem; font-weight: 700; margin-bottom: 0.5rem; margin-top: 1rem; color: #111827; } .prose h4 { font-size: 1rem; font-weight: 700; margin-bottom: 0.5rem; margin-top: 0.75rem; color: #111827; } .prose p { margin-bottom: 0.75rem; line-height: 1.625; color: #1f2937; } .prose ul { list-style-type: disc; padding-left: 1.5rem; margin-bottom: 0.75rem; } .prose ul > li { margin-bottom: 0.25rem; } .prose ol { list-style-type: decimal; padding-left: 1.5rem; margin-bottom: 0.75rem; } .prose ol > li { margin-bottom: 0.25rem; } .prose blockquote { border-left: 4px solid #d1d5db; padding-left: 1rem; font-style: italic; margin: 1rem 0; color: #4b5563; background-color: #f9fafb; padding-top: 0.5rem; padding-bottom: 0.5rem; } .prose pre { background-color: #111827; color: #f9fafb; padding: 1rem; border-radius: 0.5rem; font-family: monospace; font-size: 0.875rem; overflow-x: auto; margin: 0.75rem 0; } .prose code { background-color: #f3f4f6; color: #1f2937; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.875rem; font-family: monospace; } .prose a { color: #2563eb; text-decoration: underline; transition: color 0.15s ease-in-out; } .prose a:hover { color: #1e40af; } /* Print styles */ @media print { .editor-toolbar { display: none; } .prose div[style*="page-break-after"] { page-break-after: always; border: none; } } /* Responsive styles */ @media (max-width: 768px) { .editor-toolbar { padding: 0.25rem; gap: 0.125rem; } .toolbar-btn, .alignment-btn, .dropdown-trigger { padding: 0.25rem 0.5rem; font-size: 0.8125rem; } .wide-dropdown { min-width: 120px; } .button-group { margin-right: 0.25rem; } .toolbar-separator { margin: 0 0.25rem; } } @media (max-width: 480px) { .editor-toolbar { padding: 0.25rem 0.125rem; flex-wrap: wrap; justify-content: flex-start; gap: 0.125rem; } .toolbar-btn, .alignment-btn, .format-btn, .icon-btn { width: 28px; height: 28px; min-width: 28px; font-size: 0.75rem; padding: 0; } .dropdown-trigger { height: 28px; padding: 0.25rem 0.375rem; font-size: 0.75rem; } .wide-dropdown { min-width: 100px; } /* Stack buttons in two rows on very small screens */ .button-group.formatting { order: 1; } .wide-dropdown { order: 2; } .alignment-group { order: 3; } } /* Link Hover Tooltip Styles */ .link-tooltip { position: fixed; background: linear-gradient(145deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.95)); border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 8px; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(255, 255, 255, 0.2); backdrop-filter: blur(8px); z-index: 10000; animation: tooltipFadeIn 0.2s ease-out; max-width: 300px; min-width: 200px; } .link-tooltip-content { padding: 12px; } .link-url { font-size: 0.875rem; color: #1f2937; font-weight: 500; margin-bottom: 8px; word-break: break-all; line-height: 1.4; } .link-hint { font-size: 0.75rem; color: #6b7280; font-style: italic; margin-top: 8px; text-align: center; } @keyframes tooltipFadeIn { from { opacity: 0; transform: translateY(-4px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } /* Enhanced link styling in editor */ .editor-content a { color: #2563eb; text-decoration: underline; transition: all 0.15s ease-in-out; position: relative; cursor: pointer; } .editor-content a:hover { color: #1d4ed8; text-decoration: underline; background-color: rgba(59, 130, 246, 0.1); border-radius: 3px; padding: 1px 2px; margin: -1px -2px; } /* Add visual hint for Ctrl+Click */ .editor-content a::after { content: ''; position: absolute; bottom: -2px; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, transparent, #3b82f6, transparent); opacity: 0; transition: opacity 0.2s ease-in-out; } .editor-content a:hover::after { opacity: 0.6; } /* Link tooltip positioning adjustments */ .link-tooltip { transform-origin: top left; } /* Ensure tooltip doesn't go off-screen */ .link-tooltip[data-position="top"] { transform: translateY(-100%); margin-top: -8px; } .link-tooltip[data-position="bottom"] { transform: translateY(0); margin-top: 8px; }