feat: integrate MarkdownEditor component and update ProfileGeneralTab for enhanced description input

This commit is contained in:
Sam Chau
2025-01-18 13:59:56 +10:30
parent 3aae9addcd
commit 025e12976d
3 changed files with 179 additions and 25 deletions

View File

@@ -1,6 +1,6 @@
import React, {useState} from 'react';
import PropTypes from 'prop-types';
import Textarea from '../ui/TextArea';
import MarkdownEditor from '@ui/MarkdownEditor';
const ProfileGeneralTab = ({
name,
@@ -39,7 +39,7 @@ const ProfileGeneralTab = ({
</p>
</div>
)}
<div className='space-y-6'>
<div className='space-y-8'>
<div className='space-y-2'>
<div className='flex items-center justify-between'>
<div className='space-y-1'>
@@ -94,20 +94,14 @@ const ProfileGeneralTab = ({
</label>
<p className='text-xs text-gray-500 dark:text-gray-400'>
Add any notes or details about this profile's
purpose and configuration
purpose and configuration. Use markdown to format
your description.
</p>
</div>
<Textarea
<MarkdownEditor
value={description}
onChange={e => onDescriptionChange(e.target.value)}
placeholder='Enter a description for this profile'
rows={4}
className='w-full rounded-md border border-gray-300 dark:border-gray-600
bg-white dark:bg-gray-700
text-gray-900 dark:text-gray-100
placeholder-gray-500 dark:placeholder-gray-400
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
transition-colors duration-200'
/>
</div>
@@ -142,7 +136,7 @@ const ProfileGeneralTab = ({
</button>
</div>
{tags.length > 0 ? (
<div className='flex flex-wrap gap-2 rounded-md '>
<div className='flex flex-wrap gap-2 rounded-md'>
{tags.map(tag => (
<span
key={tag}

View File

@@ -0,0 +1,157 @@
import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {
Bold,
Italic,
List,
ListOrdered,
Code,
Link,
Quote,
Eye,
Edit2
} from 'lucide-react';
import Textarea from '../ui/TextArea';
import ReactMarkdown from 'react-markdown';
const MarkdownEditor = ({value, onChange, placeholder}) => {
const [isPreview, setIsPreview] = useState(false);
const insertMarkdown = (prefix, suffix = '') => {
const textarea = document.querySelector('#markdown-textarea');
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = value.substring(start, end);
const beforeText = value.substring(0, start);
const afterText = value.substring(end);
const newText = selectedText
? `${beforeText}${prefix}${selectedText}${suffix}${afterText}`
: `${beforeText}${prefix}placeholder${suffix}${afterText}`;
onChange({target: {value: newText}});
setTimeout(() => {
textarea.focus();
const newPosition = selectedText
? start + prefix.length + selectedText.length + suffix.length
: start + prefix.length + 'placeholder'.length;
textarea.setSelectionRange(newPosition, newPosition);
}, 0);
};
const controls = [
{
icon: Bold,
label: 'Bold',
action: () => insertMarkdown('**', '**')
},
{
icon: Italic,
label: 'Italic',
action: () => insertMarkdown('*', '*')
},
{
icon: Code,
label: 'Code',
action: () => insertMarkdown('`', '`')
},
{
icon: Link,
label: 'Link',
action: () => insertMarkdown('[', '](url)')
},
{
icon: ListOrdered,
label: 'Numbered List',
action: () => insertMarkdown('\n1. ')
},
{
icon: List,
label: 'Bullet List',
action: () => insertMarkdown('\n- ')
},
{
icon: Quote,
label: 'Quote',
action: () => insertMarkdown('\n> ')
}
];
return (
<div className='rounded-md border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 overflow-hidden'>
{/* Markdown Controls */}
<div className='flex items-center gap-1 p-2 border-b border-gray-300 dark:border-gray-600'>
{isPreview ? (
<div className='flex items-center gap-2 text-gray-700 dark:text-gray-300 ml-2'>
<Eye className='w-4 h-4' />
<span className='text-sm font-medium'>
Preview Mode
</span>
</div>
) : (
controls.map(control => (
<button
key={control.label}
onClick={control.action}
className='p-1.5 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 transition-colors'
title={control.label}>
<control.icon className='w-4 h-4' />
</button>
))
)}
{/* Preview Toggle */}
<div className='ml-auto'>
<button
onClick={() => setIsPreview(!isPreview)}
className='flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 rounded transition-colors'>
{isPreview ? (
<>
<Edit2 className='w-4 h-4' />
Edit
</>
) : (
<>
<Eye className='w-4 h-4' />
Preview
</>
)}
</button>
</div>
</div>
{/* Editor/Preview Area */}
<div>
{isPreview ? (
<div className='px-4 py-3 min-h-[300px] h-full prose prose-sm dark:prose-invert max-w-none bg-white dark:bg-gray-700'>
{value ? (
<ReactMarkdown>{value}</ReactMarkdown>
) : (
<p className='text-gray-500 dark:text-gray-400 italic'>
Nothing to preview
</p>
)}
</div>
) : (
<Textarea
id='markdown-textarea'
value={value}
onChange={onChange}
placeholder={placeholder}
rows={12}
/>
)}
</div>
</div>
);
};
MarkdownEditor.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
placeholder: PropTypes.string
};
export default MarkdownEditor;

View File

@@ -1,15 +1,18 @@
import React from "react";
const Textarea = React.forwardRef(({ className, ...props }, ref) => {
return (
<textarea
className={`flex min-h-[80px] w-full rounded-md border border-dark-border bg-dark-card px-3 py-2 text-sm text-dark-text placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent ${className}`}
ref={ref}
{...props}
/>
);
import React from 'react';
const Textarea = React.forwardRef(({className, ...props}, ref) => {
return (
<textarea
className={`flex min-h-[300px] w-full border-none bg-white
dark:bg-gray-700 px-4 py-3 text-sm text-gray-900 dark:text-gray-100
placeholder-gray-500 dark:placeholder-gray-400
focus:outline-none focus:ring-2 focus:ring-blue-500
focus:border-transparent disabled:cursor-not-allowed
disabled:opacity-50 rounded-none ${className}`}
ref={ref}
{...props}
/>
);
});
Textarea.displayName = 'Textarea';
Textarea.displayName = "Textarea";
export default Textarea;
export default Textarea;