UI Components
Overview
The Notes App frontend uses a collection of reusable UI components that handle specific interface elements and user interactions. These components follow React best practices for modularity, reusability, and clear separation of concerns.
What are UI Components?
UI Components are self-contained pieces of the user interface that:
- Encapsulate specific functionality like displaying data or handling user input
- Accept props to customize their behavior and appearance
- Emit events through callback functions to communicate with parent components
- Maintain their own internal state when needed
- Provide consistent styling and behavior across the application
Card Components
NoteCard Component
File: frontend/notes-app/src/components/Cards/NoteCard.jsx
The NoteCard component displays individual notes in a card format with actions for editing, deleting, and pinning.
const NoteCard = ({ title, date, content, tags, isPinned, onEdit, onDelete, onPinNote }) => {
return (
<div className="border rounded p-4 bg-white hover:shadow:xl transition-all ease-in-out">
<div className="flex items-center justify-between">
<div>
<h6 className="text-sm font-medium">{title}</h6>
<span className="text-xs text-slate-500">{moment(date).format("Do MMM YYYY")}</span>
</div>
<MdOutlinePushPin className={`icon-btn ${isPinned ? 'text-primary' : 'text-slate-300'}`} onClick={onPinNote}/>
</div>
<p className="text-xs text-slate-600 mt-2">{content?.slice(0,60)}</p>
<div className="flex items-center justify-between mt-2">
<div className="text-xs text-slate-500">{tags.map((item) => `#${item} `)}</div>
<div className="flex items-center gap-2">
<MdCreate
className="icon-btn hover:text-green-600"
onClick={onEdit}
/>
<MdDelete
className="icon-btn hover:text-red-500"
onClick={onDelete}
/>
</div>
</div>
</div>
);
};
Props Interface
title
: The note's title textdate
: Creation or modification timestampcontent
: The note's main content texttags
: Array of tag stringsisPinned
: Boolean indicating if the note is pinnedonEdit
: Callback function triggered when edit button is clickedonDelete
: Callback function triggered when delete button is clickedonPinNote
: Callback function triggered when pin button is clicked
Key Features
Date Formatting: Uses Moment.js library to format dates
{moment(date).format("Do MMM YYYY")}
moment(date)
: Creates a Moment object from the date.format("Do MMM YYYY")
: Formats as "1st Jan 2024"
Content Truncation: Limits displayed content to 60 characters
{content?.slice(0,60)}
?.
: Optional chaining prevents errors if content is null/undefined.slice(0,60)
: Takes first 60 characters of the content
Conditional Styling: Pin button changes appearance based on state
className={`icon-btn ${isPinned ? 'text-primary' : 'text-slate-300'}`}
- Template literals: Uses backticks for string interpolation
- Conditional classes: Different colors for pinned vs unpinned states
Tag Display: Maps over tags array to display formatted tags
{tags.map((item) => `#${item} `)}
.map()
: Transforms each tag into a formatted string- Template literals: Adds # prefix to each tag
ProfileInfo Component
File: frontend/notes-app/src/components/Cards/ProfileInfo.jsx
Displays user profile information with avatar and logout functionality.
const ProfileInfo = ({userInfo, onLogout}) => {
return (
<div className="flex items-center gap-3">
<div className="w-12 h-12 flex items-center justify-center rounded-full text-slate-950 font-medium bg-slate-100">
{getInitials(userInfo?.fullName)}
</div>
<div>
<p className="text-sm font-medium">{userInfo?.fullName}</p>
<button className="" onClick={onLogout}>
Logout
</button>
</div>
</div>
)
}
Component Features
Avatar Generation: Creates a circular avatar with user initials
- Fixed dimensions:
w-12 h-12
creates a 48x48 pixel circle - Centered content: Flexbox centers the initials within the circle
- Helper function:
getInitials()
extracts initials from full name
User Information Display: Shows full name and logout option
- Optional chaining:
userInfo?.fullName
safely accesses nested properties - Callback integration:
onLogout
prop allows parent to handle logout logic
EmptyCard Component
File: frontend/notes-app/src/components/EmptyCard/EmptyCard.jsx
A reusable component for displaying empty states with images and messages.
const EmptyCard = ({imgSrc, message}) => {
return (
<div className="flex flex-col items-center justify-center mt-20">
<img src={imgSrc} alt="No notes" className="w-60" />
<p className="w-1/2 text-sm font-medium text-slate-700 text-center leading-7 mt-5">
{message}
</p>
</div>
)
}
Usage Scenarios
No Notes State: When user has no notes created No Search Results: When search query returns no matches Loading States: While data is being fetched
Design Features
- Centered layout: Flexbox centers content vertically and horizontally
- Visual hierarchy: Large image followed by descriptive text
- Responsive text:
w-1/2
makes text width responsive to container - Accessibility: Proper alt text for screen readers
Input Components
TagInput Component
File: frontend/notes-app/src/components/Input/TagInput.jsx
A specialized input component for managing tags with add and remove functionality.
const TagInput = ({ tags, setTags }) => {
const [inputValue, setInputValue] = useState("");
const handleInputChange = (e) => {
setInputValue(e.target.value);
};
const addNewTag = () => {
setTags([...tags, inputValue.trim()]);
setInputValue("");
};
const handleKeyDown = (e) => {
if (e.key === "Enter") {
addNewTag();
}
};
const handleRemoveTag = (tagToRemove) => {
setTags(tags.filter((tag) => tag !== tagToRemove))
};
State Management
Local State: inputValue
manages the current input field value
Parent State: tags
and setTags
are controlled by parent component
Tag Operations
Adding Tags:
const addNewTag = () => {
setTags([...tags, inputValue.trim()]);
setInputValue("");
};
- Spread operator:
...tags
creates a new array with existing tags .trim()
: Removes whitespace from input- State reset: Clears input field after adding
Removing Tags:
const handleRemoveTag = (tagToRemove) => {
setTags(tags.filter((tag) => tag !== tagToRemove))
};
.filter()
: Creates new array excluding the specified tag- Immutable updates: Doesn't modify original array
Keyboard Support:
const handleKeyDown = (e) => {
if (e.key === "Enter") {
addNewTag();
}
};
- Enter key: Allows adding tags without clicking button
- Event handling: Checks specific key press
User Interface
Tag Display:
{tags?.length > 0 && (
<div className="flex items-center gap-2 flex-wrap mt-2">
{tags.map((tag, index) => (
<span key={index} className="flex items-center gap-2 text-sm text-slate-900 bg-slate-100 px-3 py-1 rounded">
# {tag}
<button onClick={() => {
handleRemoveTag(tag);
}}>
<MdClose />
</button>
</span>
))}
</div>
)}
Features Explained:
- Conditional rendering: Only shows tags section if tags exist
- Flex wrap: Tags wrap to new lines when needed
- Individual removal: Each tag has its own remove button
- Visual styling: Tags appear as rounded chips with # prefix
Input Section:
<div className="flex items-center gap-4 mt-3">
<input
type="text"
value={inputValue}
className="text-sm bg-transparent border px-3 py-2 rounded outline-none"
placeholder="Add tags"
onChange={handleInputChange}
onKeyDown={handleKeyDown}
/>
<button className="w-8 h-8 flex items-center justify-center rounded border border-blue-700 hover:bg-blue-700"
onClick={() => {
addNewTag();
}}
>
<MdAdd className="text-2xl text-blue-700 hover:text-white" />
</button>
</div>
Input Features:
- Controlled input: Value managed by React state
- Event handlers: Responds to typing and key presses
- Add button: Visual button for adding tags
- Hover effects: Button changes appearance on hover
Component Design Patterns
Props Interface Design
Callback Props: Components receive functions to communicate with parents
// NoteCard receives multiple callback functions
const NoteCard = ({ title, date, content, tags, isPinned, onEdit, onDelete, onPinNote })
Data Props: Components receive data to display
// EmptyCard receives display data
const EmptyCard = ({imgSrc, message})
State Props: Components receive state and state setters
// TagInput receives both state and setter
const TagInput = ({ tags, setTags })
State Management Patterns
Local State: Components manage their own internal state when needed
const [inputValue, setInputValue] = useState("");
Lifted State: Important state is managed by parent components
// Tags state managed by parent, passed down as props
const [tags, setTags] = useState(noteData?.tags || []);
Event Handling Patterns
Event Delegation: Parent components handle events from children
// Parent defines the handler, child calls it
<NoteCard onEdit={() => handleEdit(note)} />
Event Prevention: Components handle their own simple events
// TagInput handles its own input changes
const handleInputChange = (e) => {
setInputValue(e.target.value);
};
Styling and Visual Design
CSS Framework
The application uses Tailwind CSS for styling:
- Utility classes:
flex
,items-center
,justify-between
- Responsive design:
w-1/2
,mt-20
- Color system:
text-slate-500
,bg-slate-100
- Interactive states:
hover:shadow-xl
,hover:text-green-600
Design Consistency
Spacing: Consistent use of margin and padding classes Colors: Standardized color palette using Tailwind's slate colors Typography: Consistent text sizes and weights Interactive elements: Hover effects on clickable items
Connection to Other Components
Parent-Child Communication
- Props down: Data flows from parent to child components
- Events up: User interactions bubble up through callback functions
- State lifting: Shared state managed at appropriate parent level
Integration Points
- Home component: Uses NoteCard to display notes
- Navbar: Uses ProfileInfo for user display
- AddEditNotes: Uses TagInput for tag management
- Dashboard: Uses EmptyCard for empty states
Reusability
- EmptyCard: Used for multiple empty states
- TagInput: Reusable across different forms
- ProfileInfo: Could be used in multiple locations
- NoteCard: Template for displaying any note data