Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[15] Adds line numbers to preview & full code view #20

Merged
Merged
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
6 changes: 5 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@monaco-editor/react": "^4.6.0",
"date-fns": "^4.1.0",
"framer-motion": "^11.11.9",
"lucide-react": "^0.452.0",
"marked": "^14.1.3",
"monaco-editor": "^0.52.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.6.1"
"react-syntax-highlighter": "^15.6.1",
"vite-plugin-monaco-editor": "^1.1.0"
},
"devDependencies": {
"@types/node": "^20.11.28",
Expand Down
69 changes: 47 additions & 22 deletions client/src/components/common/BaseDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@ const BaseDropdown: React.FC<BaseDropdownProps> = ({
}) => {
const [isOpen, setIsOpen] = useState(false);
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
const [internalValue, setInternalValue] = useState(value);
const dropdownRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const listRef = useRef<HTMLUListElement>(null);
const lastInteractionWasMouse = useRef(false);

useEffect(() => {
setInternalValue(value);
}, [value]);

const getAllItems = (sections: Section[]): string[] => {
return sections.reduce((acc: string[], section) => [...acc, ...section.items], []);
Expand All @@ -46,34 +52,42 @@ const BaseDropdown: React.FC<BaseDropdownProps> = ({
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
setHighlightedIndex(-1);
setInternalValue(value);
}
};

document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

useEffect(() => {
setHighlightedIndex(-1);
}, [isOpen, value]);
}, [value]);

useEffect(() => {
if (isOpen && highlightedIndex >= 0 && listRef.current) {
const highlightedElement = listRef.current.querySelector(`[data-index="${highlightedIndex}"]`);
if (highlightedElement) {
highlightedElement.scrollIntoView({ block: 'nearest' });
if (!lastInteractionWasMouse.current) {
highlightedElement.scrollIntoView({ block: 'nearest' });
}
}
}
}, [highlightedIndex, isOpen]);
}, [highlightedIndex]);

const handleMouseEnter = (index: number) => {
lastInteractionWasMouse.current = true;
setHighlightedIndex(index);
};

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = maxLength ? e.target.value.slice(0, maxLength) : e.target.value;
setInternalValue(newValue);
onChange(newValue);
setIsOpen(true);
setHighlightedIndex(-1);
};

const handleOptionClick = (option: string) => {
onSelect(option);
const finalValue = option.startsWith('Add new:') ? option.slice(9).trim() : option;
setInternalValue(finalValue);
onSelect(finalValue);
setIsOpen(false);
setHighlightedIndex(-1);
};
Expand All @@ -84,7 +98,7 @@ const BaseDropdown: React.FC<BaseDropdownProps> = ({
if (e.defaultPrevented) return;
}

const sections = getSections(value);
const sections = getSections(internalValue);
const allItems = getAllItems(sections);
const totalItems = allItems.length;

Expand All @@ -109,29 +123,35 @@ const BaseDropdown: React.FC<BaseDropdownProps> = ({

case 'Enter':
e.preventDefault();
if (isOpen && highlightedIndex >= 0 && highlightedIndex < totalItems) {
handleOptionClick(allItems[highlightedIndex]);
} else if (sections.length > 0) {
const lastSection = sections[sections.length - 1];
if (lastSection.items.length > 0) {
handleOptionClick(lastSection.items[0]);
if (isOpen) {
if (highlightedIndex >= 0 && highlightedIndex < totalItems) {
handleOptionClick(allItems[highlightedIndex]);
} else if (sections.length > 0) {
const lastSection = sections[sections.length - 1];
if (lastSection.items.length > 0) {
handleOptionClick(lastSection.items[0]);
}
}
setIsOpen(false);
}
break;

case 'Escape':
e.preventDefault();
setIsOpen(false);
setHighlightedIndex(-1);
setInternalValue(value); // Reset to selected value
break;

case 'Tab':
setIsOpen(false);
setHighlightedIndex(-1);
setInternalValue(value); // Reset to selected value
break;
}
};

const sections = getSections(value);
const sections = getSections(internalValue);
let currentIndex = -1;

return (
Expand All @@ -141,31 +161,34 @@ const BaseDropdown: React.FC<BaseDropdownProps> = ({
ref={inputRef}
type="text"
className={`block w-full rounded-md bg-gray-700 text-white p-2 pr-8 text-sm ${className}`}
value={value}
value={internalValue}
onChange={handleInputChange}
onFocus={() => setIsOpen(true)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
disabled={disabled}
maxLength={maxLength}
aria-expanded={isOpen}
aria-haspopup="listbox"
role="combobox"
/>
{showChevron && (
<ChevronDown
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none"
className={`absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
size={16}
/>
)
}
)}
</div>
{isOpen && sections.length > 0 && (
<ul
ref={listRef}
className="absolute z-10 mt-1 w-full bg-gray-700 border border-gray-600 rounded-md shadow-lg max-h-60 overflow-auto"
role="listbox"
>
{sections.map((section, sectionIndex) => (
<React.Fragment key={section.title}>
{sectionIndex > 0 && (
<li className="border-t border-gray-600" />
<li className="border-t border-gray-600" role="separator" />
)}
<li className="px-3 py-1 text-xs font-medium text-gray-400 bg-gray-800">
{section.title}
Expand All @@ -180,8 +203,10 @@ const BaseDropdown: React.FC<BaseDropdownProps> = ({
}`}
onClick={() => handleOptionClick(item)}
onMouseDown={(e) => e.preventDefault()}
onMouseEnter={() => setHighlightedIndex(currentIndex)}
onMouseEnter={() => handleMouseEnter(currentIndex)}
data-index={currentIndex}
role="option"
aria-selected={highlightedIndex === currentIndex}
>
{item}
</li>
Expand Down
162 changes: 88 additions & 74 deletions client/src/components/settings/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, settings
const [includeCodeInSearch, setIncludeCodeInSearch] = useState(settings.includeCodeInSearch);
const [showCategories, setShowCategories] = useState(settings.showCategories);
const [expandCategories, setExpandCategories] = useState(settings.expandCategories);
const [showLineNumbers, setShowLineNumbers] = useState(settings.showLineNumbers);

const handleSave = () => {
onSettingsChange({
Expand All @@ -17,96 +18,109 @@ const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, settings
previewLines,
includeCodeInSearch,
showCategories,
expandCategories
expandCategories,
showLineNumbers
});
onClose();
};

return (
<Modal isOpen={isOpen} onClose={onClose}>
<h2 className="text-xl font-bold text-gray-100 mb-4">Settings</h2>
<div className="space-y-4">
<div className="flex items-center justify-between">
<label htmlFor="compactView" className="text-gray-300">Compact View</label>
<input
type="checkbox"
id="compactView"
checked={compactView}
onChange={(e) => setCompactView(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
</div>
<div className="flex items-center justify-between">
<label htmlFor="showCodePreview" className="text-gray-300">Show Code Preview</label>
<input
type="checkbox"
id="showCodePreview"
checked={showCodePreview}
onChange={(e) => setShowCodePreview(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
</div>
{showCodePreview && (
<div className="pb-4">
<h2 className="text-xl font-bold text-gray-100 mb-4">Settings</h2>
<div className="space-y-4">
<div className="flex items-center justify-between">
<label htmlFor="previewLines" className="text-gray-300">Preview Lines</label>
<label htmlFor="compactView" className="text-gray-300">Compact View</label>
<input
type="number"
id="previewLines"
value={previewLines}
onChange={(e) => setPreviewLines(Math.max(1, Math.min(20, parseInt(e.target.value) || 1)))}
min="1"
max="20"
className="form-input w-20 rounded-md bg-gray-700 border border-gray-600 text-white p-2 text-sm"
type="checkbox"
id="compactView"
checked={compactView}
onChange={(e) => setCompactView(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
</div>
)}
<div className="flex items-center justify-between">
<label htmlFor="includeCodeInSearch" className="text-gray-300">Include Code in Search Queries</label>
<input
type="checkbox"
id="includeCodeInSearch"
checked={includeCodeInSearch}
onChange={(e) => setIncludeCodeInSearch(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
</div>
<div className="flex items-center justify-between">
<label htmlFor="showCategories" className="text-gray-300">Show Categories</label>
<input
type="checkbox"
id="showCategories"
checked={showCategories}
onChange={(e) => setShowCategories(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
</div>

{showCategories && (
<div className="flex items-center justify-between">
<label htmlFor="expandCategories" className="text-gray-300">Expand Categories</label>
<label htmlFor="showCodePreview" className="text-gray-300">Show Code Preview</label>
<input
type="checkbox"
id="expandCategories"
checked={expandCategories}
onChange={(e) => setExpandCategories(e.target.checked)}
id="showCodePreview"
checked={showCodePreview}
onChange={(e) => setShowCodePreview(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
</div>
)}
</div>
<div className="mt-6 flex justify-end">
<button
onClick={onClose}
className="mr-2 px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 text-sm"
>
Cancel
</button>
<button
onClick={handleSave}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-sm"
>
Save
</button>
{showCodePreview && (
<div className="flex items-center justify-between">
<label htmlFor="previewLines" className="text-gray-300">Preview Lines</label>
<input
type="number"
id="previewLines"
value={previewLines}
onChange={(e) => setPreviewLines(Math.max(1, Math.min(20, parseInt(e.target.value) || 1)))}
min="1"
max="20"
className="form-input w-20 rounded-md bg-gray-700 border border-gray-600 text-white p-2 text-sm"
/>
</div>
)}
<div className="flex items-center justify-between">
<label htmlFor="includeCodeInSearch" className="text-gray-300">Include Code in Search Queries</label>
<input
type="checkbox"
id="includeCodeInSearch"
checked={includeCodeInSearch}
onChange={(e) => setIncludeCodeInSearch(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
</div>
<div className="flex items-center justify-between">
<label htmlFor="showCategories" className="text-gray-300">Show Categories</label>
<input
type="checkbox"
id="showCategories"
checked={showCategories}
onChange={(e) => setShowCategories(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
</div>

{showCategories && (
<div className="flex items-center justify-between">
<label htmlFor="expandCategories" className="text-gray-300">Expand Categories</label>
<input
type="checkbox"
id="expandCategories"
checked={expandCategories}
onChange={(e) => setExpandCategories(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
</div>
)}
<div className="flex items-center justify-between">
<label htmlFor="showLineNumbers" className="text-gray-300">Show Line Numbers</label>
<input
type="checkbox"
id="showLineNumbers"
checked={showLineNumbers}
onChange={(e) => setShowLineNumbers(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
/>
</div>
</div>
<div className="mt-6 flex justify-end">
<button
onClick={onClose}
className="mr-2 px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 text-sm"
>
Cancel
</button>
<button
onClick={handleSave}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-sm"
>
Save
</button>
</div>
</div>
</Modal>
);
Expand Down
Loading