A modern, extensible rich text editor built with Lexical and React, featuring a **colocation architecture** for maximum maintainability and developer experience.
npm install @seolhun/firstage-editorA modern, extensible rich text editor built with Lexical and React, featuring a colocation architecture for maximum maintainability and developer experience.
This editor supports two distinct modes optimized for different use cases:
- Purpose: Quick social media posts, comments, simple messages
- Features: Mentions (@user), Emojis (π), Images, Hashtags (#tag), Auto-links
- UI: Clean, minimal interface focused on speed
- Nodes: Limited to essential formatting only
- Purpose: Blog posts, articles, documentation, long-form content
- Features: All PlainText features plus tables, code blocks, headings, lists, embeds
- UI: Full toolbar with advanced formatting options
- Nodes: Complete feature set for professional content creation
The editor automatically loads appropriate nodes and plugins based on the isRichText setting:
``tsx
// PlainText mode - minimal features
// RichText mode - full features
`
This editor follows a feature-first colocation approach where all related files for a feature are grouped together in a single directory. This improves code organization, reduces cognitive load, and makes features more maintainable.
`sh`
packages/editor/src/features/emoji/
βββ components/ # Components used by Node/Renderer/Plugin
βββ emoji.node.ts # Lexical node definition
βββ emoji.plugin.tsx # Main plugin with FloatingPortal
βββ emoji.typeahead.tsx # Typeahead functionality
βββ emoji.renderer.tsx # EmojiPicker renderer (@emoji-mart/react)
βββ emoji.store.ts # Zustand state management
βββ emoji.types.ts # TypeScript type definitions
βββ emoji.utils.ts # Search and filtering utilities
βββ emoji.item.tsx # Individual emoji item component
βββ emoji.{feature-name}.tsx # Additional feature-specific files
βββ index.ts # Exports (export * from pattern)
Pattern: {feature}.{type}.{ext}
- Node: {feature}.node.ts (e.g., emoji.node.ts){feature}.plugin.tsx
- Plugin: (e.g., emoji.plugin.tsx){feature}.typeahead.tsx
- Typeahead: (e.g., emoji.typeahead.tsx){feature}.renderer.tsx
- Renderer: (e.g., emoji.renderer.tsx){feature}.store.ts
- Store: (e.g., emoji.store.ts){feature}.types.ts
- Types: (e.g., emoji.types.ts){feature}.utils.ts
- Utils: (e.g., emoji.utils.ts){feature}.{component}.tsx
- Components: (e.g., emoji.item.tsx)
`sh`
packages/editor/src/
βββ features/ # Feature-first colocation
β βββ emoji/ # β
Perfect colocation example
β β βββ emoji.node.ts # EmojiNode definition
β β βββ emoji.plugin.tsx # Main plugin
β β βββ emoji.typeahead.tsx # Typeahead functionality
β β βββ emoji.renderer.tsx # EmojiPicker renderer
β β βββ emoji.store.ts # State management
β β βββ emoji.types.ts # Type definitions
β β βββ emoji.utils.ts # Utilities
β β βββ emoji.item.tsx # Item component
β β βββ emoji.{feature-name}.ts # What you need for the feature
β β βββ index.ts # Exports
β βββ image/ # π Improved colocation
β β βββ image.node.tsx # ImageNode definition
β β βββ image.plugin.tsx # Main plugin
β β βββ image.component.tsx # Image component
β β βββ image.types.ts # Type definitions
β β βββ image.{feature-name}.ts # What you need for the feature
β β βββ index.ts # Exports
β β βββ [legacy folders] # Backward compatibility
β βββ mention/ # π New colocation structure
β β βββ mention.node.ts # MentionNode definition
β β βββ mention.plugin.tsx # Main plugin
β β βββ mention.item.tsx # Mention item component
β β βββ mention.types.ts # Type definitions
β β βββ mention.{feature-name}.ts # What you need for the feature
β β βββ index.ts # Exports
β βββ .../ # π New colocation structure
β β βββ ...node.ts # ...Node definition
β β βββ ...plugin.tsx # Main plugin
β β βββ ...types.ts # Type definitions
β β βββ ....{feature-name}.ts # What you need for the feature
β β βββ index.ts # Exports
β βββ editors/ # Core editor components
βββ plugins/ # Legacy plugins (migrating to features/)
βββ nodes/ # Legacy nodes (migrating to features/)
βββ ui/ # Shared UI components
βββ context/ # React contexts
βββ hooks/ # Custom hooks
βββ utils/ # Utility functions
`tsx
import { EmojiPlugin, EmojiTypeaheadPlugin } from '@/features/emoji';
function MyEditor() {
return (
);
}
`
`tsx
import { ImagePlugin, ImageNode, INSERT_IMAGE_COMMAND } from '@/features/image';
function MyEditor() {
const [editor] = useLexicalComposerContext();
const insertImage = (payload: ImagePayload) => {
editor.dispatchCommand(INSERT_IMAGE_COMMAND, payload);
};
return (
);
}
`
`tsx
import { MentionPlugin, MentionNode } from '@/features/mention';
function MyEditor() {
const fetchMentionOptions = async (query: string) => {
// Fetch users, teams, channels, etc.
return await api.searchMentions(query);
};
return (
fetchMentionOptions={fetchMentionOptions}
minLength={1}
maxResults={10}
/>
);
}
`
Each feature includes its own SCSS file with scoped styles:
`scss
// features/emoji/emoji.scss
.__RootEditor__ {
.emoji-container {
// Emoji-specific styles
}
}
// features/image/image.scss
.__RootEditor__ {
.image-container {
// Image-specific styles
}
}
// features/mention/mention.scss
.__RootEditor__ {
.mention {
// Mention-specific styles
}
}
`
1. Create feature directory: features/{feature-name}/{feature}.{type}.{ext}
2. Follow naming convention: {feature}.node.ts
3. Include all related files:
- Node definition (){feature}.plugin.tsx
- Plugin (){feature}.types.ts
- Types (){feature}.scss
- Styles ()index.ts
- Components as needed
- Barrel export ()
`sh`
features/table/
βββ table.node.ts # TableNode, TableRowNode, TableCellNode
βββ table.plugin.tsx # TablePlugin with commands
βββ table.toolbar.tsx # Table toolbar component
βββ table.types.ts # Table-related types
βββ table.utils.ts # Table utilities
βββ table.scss # Table styling
βββ index.ts # Exports
Each feature should include its own test files:
`sh`
features/emoji/
βββ emoji.node.test.ts
βββ emoji.plugin.test.tsx
βββ emoji.utils.test.ts
1. High Cohesion: Related functionality is grouped together
2. Low Coupling: Features are independent and reusable
3. Easy Maintenance: Changes are localized to one directory
4. Clear Responsibility: File names indicate exact purpose
5. Better DX: Developers can focus on one feature at a time
6. Scalability: Easy to add, remove, or modify features
- β
Paragraph: Custom paragraph node with text alignment and indentation support
- β
Heading: H1-H6 headings with markdown syntax (## heading) and styling
- β
Quote: Blockquotes, pullquotes, and citations with author/source metadata
- β
Emoji: Complete colocation implementation
- β
Image: Improved with colocation (legacy support maintained)
- β
Mention: Migrated from plugins/ to features/
- β
Hashtag: Auto-detection and styling with colocation pattern
- β
Link: Auto-link detection with URL validation and colocation pattern
- π Tables: Migrate to features/ with colocation
- π Code blocks: Migrate to features/ with colocation
- π Lists: Migrate to features/ with colocation
- π Legacy plugins: Complete migration to features/
When contributing to this editor:
1. Follow the colocation pattern for new features
2. Use the established naming conventions
3. Include comprehensive TypeScript types
4. Add feature-specific styling in SCSS files
5. Create exports using export * from pattern in index.ts`
6. Maintain backward compatibility when refactoring
---
This architecture ensures our editor remains maintainable, scalable, and developer-friendly as it grows in complexity and features.