Enhance/settings#34
Conversation
… and remove export as markdown
There was a problem hiding this comment.
Pull request overview
This PR comprehensively redesigns the Settings page with a modern tabbed interface while preserving all existing functionality. The changes introduce a persistent sidebar navigation pattern with three distinct sections (General, Storage, About), enhanced visual hierarchy with color-coded cards, and polished micro-interactions throughout.
Changes:
- Introduced tabbed sidebar navigation splitting Settings into General, Storage, and About sections with smooth fade-in animations
- Removed "Export as Markdown" functionality and cleaned up the JSZip import dependency
- Enhanced the Storage tab (formerly "Data") with visual stat cards, storage progress bar, and improved import/export UI patterns
- Added new About tab with branded app identity card, system information list, and external links section including GitHub, placeholder Pulm link, and Luxion Labs
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| </p> | ||
| <div className="mt-3 inline-flex items-center gap-2 rounded-full bg-stone-100 px-3 py-1"> | ||
| <span className="h-2 w-2 rounded-full bg-emerald-400 animate-pulse" /> | ||
| <span className="text-xs font-medium text-stone-600">v1.0.0</span> |
There was a problem hiding this comment.
Maintainability issue: The version number "v1.0.0" is hardcoded but doesn't match the version in package.json (0.0.1). Consider importing the version from package.json to keep it synchronized, or if this is intentional, add a comment explaining why the displayed version differs from the package version.
| <nav className="w-56 shrink-0 border-r border-stone-200/70 bg-[#f0efed] p-4 flex flex-col gap-1 overflow-y-auto"> | ||
| <p className="px-3 pt-1 pb-3 text-[10px] uppercase tracking-[0.25em] font-bold text-stone-400 select-none"> | ||
| Settings | ||
| </p> | ||
|
|
||
| {NAV_ITEMS.map((item) => { | ||
| const isActive = activeTab === item.key; | ||
| return ( | ||
| <button | ||
| key={item.key} | ||
| type="button" | ||
| onClick={handleClearLibrary} | ||
| className="text-sm text-red-600 underline-offset-4 hover:text-red-700" | ||
| onClick={() => { | ||
| setActiveTab(item.key); | ||
| contentRef.current?.scrollTo({ top: 0, behavior: 'smooth' }); | ||
| }} | ||
| className={` | ||
| group flex items-center gap-3 rounded-xl px-3 py-2.5 text-left transition-all duration-150 | ||
| ${isActive | ||
| ? 'bg-white shadow-sm shadow-stone-200/60 text-stone-900' | ||
| : 'text-stone-500 hover:bg-white/60 hover:text-stone-700'} | ||
| `} | ||
| > | ||
| Clear library | ||
| <span className={`transition-colors ${isActive ? 'text-stone-800' : 'text-stone-400 group-hover:text-stone-500'}`}> | ||
| {item.icon} | ||
| </span> | ||
| <div className="min-w-0"> | ||
| <p className={`text-sm leading-tight ${isActive ? 'font-semibold' : 'font-medium'}`}> | ||
| {item.label} | ||
| </p> | ||
| <p className="text-[11px] text-stone-400 leading-tight mt-0.5 truncate"> | ||
| {item.description} | ||
| </p> | ||
| </div> | ||
| </button> | ||
| </div> | ||
| </section> | ||
| ); | ||
| })} | ||
| </nav> |
There was a problem hiding this comment.
Accessibility issue: The navigation buttons lack proper ARIA attributes for screen readers. Consider adding aria-current="page" for the active tab and role="tablist" for the nav container to indicate this is a tab navigation pattern. Each button should also have role="tab" and aria-selected to properly communicate the tab interface to assistive technologies.
| <style>{` | ||
| @keyframes fadeIn { | ||
| from { opacity: 0; transform: translateY(6px); } | ||
| to { opacity: 1; transform: translateY(0); } | ||
| } | ||
| `}</style> | ||
|
|
There was a problem hiding this comment.
Maintainability issue: The fadeIn keyframe animation is already defined globally in editor/styles/_keyframe-animations.scss but redefined here inline. This creates duplication and potential inconsistency. Consider removing the inline style definition and relying on the global animation, or updating the global definition to include the transform if that's needed.
| <style>{` | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(6px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| `}</style> |
| <a | ||
| href="https://github.com/dev-Ninjaa/PulmNotes" | ||
| target="_blank" | ||
| rel="noreferrer" |
There was a problem hiding this comment.
Security issue: The external link uses rel="noreferrer" but should use rel="noopener noreferrer" to prevent tabnabbing attacks. The "noopener" directive prevents the new page from accessing the window.opener property.
| <a | ||
| href="https://luxionlabs.com" | ||
| target="_blank" | ||
| rel="noreferrer" |
There was a problem hiding this comment.
Security issue: The external link uses rel="noreferrer" but should use rel="noopener noreferrer" to prevent tabnabbing attacks. The "noopener" directive prevents the new page from accessing the window.opener property.
| <div ref={contentRef} className="flex-1 overflow-y-auto scrollbar-none"> | ||
| <div className="max-w-2xl mx-auto px-8 py-10"> | ||
| {/* Section header */} | ||
| <div className="mb-8"> | ||
| <h1 className="text-2xl font-bold text-stone-900 tracking-tight"> | ||
| {NAV_ITEMS.find((n) => n.key === activeTab)?.label} | ||
| </h1> | ||
| <p className="mt-1 text-sm text-stone-400"> | ||
| {NAV_ITEMS.find((n) => n.key === activeTab)?.description} | ||
| </p> | ||
| </div> | ||
| </section> | ||
|
|
||
| {/* Tab content */} | ||
| {tabContent[activeTab]()} | ||
| </div> |
There was a problem hiding this comment.
Accessibility issue: The tab content container should have role="tabpanel" and aria-labelledby pointing to the corresponding tab button to properly associate the content with its tab. This helps screen reader users understand the relationship between the navigation and content.
| type="file" | ||
| accept=".json" | ||
| onChange={handleBackupFileChange} | ||
| className="hidden" |
There was a problem hiding this comment.
Accessibility issue: The file input is visually hidden but the label doesn't have proper keyboard navigation. Users relying on keyboard navigation won't be able to activate this file picker. Consider adding onKeyDown handler to the label element to trigger the file input when Enter or Space is pressed, or ensure the hidden input remains keyboard-focusable.
| className="hidden" | |
| className="sr-only" |
| <a | ||
| href="#" | ||
| className="group flex items-center justify-between px-5 py-3.5 transition-colors hover:bg-stone-50" | ||
| > | ||
| <div className="flex items-center gap-3"> | ||
| <ExternalLink size={16} className="text-stone-400" /> | ||
| <span className="text-sm text-stone-600">Pulm</span> | ||
| </div> | ||
| <div className="flex items-center gap-1.5 text-stone-400 transition-colors group-hover:text-stone-600"> | ||
| <span className="text-xs">Coming soon</span> | ||
| </div> | ||
| </a> |
There was a problem hiding this comment.
Maintainability issue: The Pulm link uses href="#" which is a placeholder. Consider using href="#pulm" or a specific placeholder URL, or disabling the link element entirely (using a div or button styled as a link) to prevent accidental navigation. The current implementation will cause the page to scroll to the top when clicked.
| import { | ||
| Shield, | ||
| Database, | ||
| Info, | ||
| Download, | ||
| Upload, | ||
| Trash2, | ||
| HardDrive, | ||
| BookOpen, | ||
| Archive, | ||
| ExternalLink, | ||
| ChevronRight, | ||
| AlertTriangle, | ||
| FolderArchive, | ||
| Github, | ||
| Scale, | ||
| MonitorSmartphone, | ||
| MessageSquare, | ||
| } from 'lucide-react'; |
There was a problem hiding this comment.
Maintainability issue: The JSZip import has been removed from this file, but the jszip package remains in package.json as a dependency. If JSZip is no longer used anywhere in the codebase, consider removing it from package.json to reduce bundle size and maintain a cleaner dependency list.
dev-Ninjaa
left a comment
There was a problem hiding this comment.
Review of PR #34: Enhance/settings - SettingsView.tsx Refactor What Works Really Well:
Architecture & Structure
Beautiful tab-based navigation system (General, Storage, About) following macOS patterns
Smart SettingsTab type union for type safety
Modular render functions that are clean and maintainable
NAV_ITEMS configuration is elegant and extensible
UI/UX Design
Dark gradient hero banner with excellent privacy messaging
Four philosophy cards with color themes (emerald, sky, amber, violet) - really well thought out
Smooth fade-in animations on tab transitions
Modern card layouts with hover states that feel polished
Storage progress bar is intuitive and informative
Clear visual hierarchy throughout
Code Quality
Good use of React hooks (useState, useRef)
Proper TypeScript typing
Clean JSX structure - readable and maintainable
Smart platform detection and storage info display
Icon integration from lucide-react (lightweight alternative to JSZip)
Thoughtful "Danger zone" section with proper warnings
UX Details That Stand Out
Smooth scroll behavior when switching tabs via contentRef
Drag-and-drop style file input for backups
System info section with Platform, Storage engine, Data size
Links section with GitHub, Pulm placeholder, Labs, License
Animated pulse on version badge
About the DCO Check Failure:
The failing DCO (Developer Certificate of Origin) check isn't your fault. It appears to be a configuration issue with the repo's .dco.yml file. Your commits look good to me and follow guidelines perfectly. I will review and fix the DCO workflow configuration.
Description
Redesigned the Settings page UI with a modern tabbed layout and polished visual hierarchy, without disrupting any existing functionality.
Key changes:
JSZip/FileTextimports)fadeInanimations on tab switch, hover micro-interactions throughoutRelated Issue
Closes #
Type of Change
Platform
Testing
Test Environment
Test Steps
Checklist
Breaking Changes
Screenshots
Additional Notes
#href) to be updated later with the actual URLhttps://luxionlabs.com— URL can be updated as neededJSZipimport was removed since it was only used by the markdown export