-
Notifications
You must be signed in to change notification settings - Fork 0
feat(avatar): Introduce Avatar component with image and fallback support #62
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
base: main
Are you sure you want to change the base?
Conversation
This commit introduces a new atomic `Avatar` component, designed to display user avatars. Key features include: - **Image display:** Renders an `<img>` element when a `src` prop is provided. - **Fallback mechanism:** Displays initials or a single character as a fallback when `src` is not available or fails to load. - **Multiple sizes:** Supports predefined sizes (`xs`, `sm`, `md`, `lg`, `xl`) for consistent scaling. - **Accessibility:** Includes `alt` text for images and `aria-label` for fallback elements. - **Storybook integration:** Provides comprehensive stories demonstrating various use cases, including default, image, broken image, and size variations. This component enhances the UI library by providing a flexible and robust solution for displaying user profiles.
📝 WalkthroughWalkthroughAdds a new Avatar component with its props/type definitions and Storybook stories. Renders an image when available, falls back to initials (or "?") on missing/broken images, manages load errors, and supports five sizes (xs–xl). Changes
Sequence Diagram(s)(omitted) Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✏️ Tip: You can disable this entire section by setting Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/atoms/Avatar/Avatar.tsx`:
- Around line 49-63: The img rendering branch in the Avatar component doesn't
handle load failures; add a local state (e.g., hasImageError via useState) in
the Avatar component, attach an onError handler to the <img> element (and
optionally onLoad to reset the flag) to set hasImageError = true when loading
fails, and change the render logic to show the existing fallback UI whenever src
is falsy or hasImageError is true (this will make the BrokenImage story actually
display the fallback).
🧹 Nitpick comments (2)
src/components/atoms/Avatar/Avatar.tsx (2)
57-60: Prefer TailwindCSS utility classes over inline styles.The coding guidelines state: "Use TailwindCSS v4 utility classes exclusively for styling" and "Use DaisyUI component classes." The current implementation uses inline
styleobjects forborderRadius,objectFit, flexbox layout, colors, and typography.Consider refactoring to use Tailwind classes and building dynamic class names using arrays.
♻️ Suggested refactor using TailwindCSS
// IMAGE RENDER if (src && !imgError) { + const imgClasses = [ + "rounded-full", + "object-cover", + className, + ].filter(Boolean).join(" "); + return ( <img src={src} alt={alt} width={pixelSize} height={pixelSize} - className={className} + className={imgClasses} onError={() => setImgError(true)} - style={{ - borderRadius: "50%", - objectFit: "cover", - }} /> ); } // FALLBACK RENDER +const fallbackClasses = [ + "rounded-full", + "flex", + "items-center", + "justify-content", + "bg-gray-200", + "text-gray-700", + "font-semibold", + "select-none", + className, +].filter(Boolean).join(" "); + return ( <div aria-label={alt} - className={className} + className={fallbackClasses} style={{ width: pixelSize, height: pixelSize, - borderRadius: "50%", - display: "flex", - alignItems: "center", - justifyContent: "center", - backgroundColor: "#e5e7eb", - color: "#374151", - fontWeight: 600, fontSize: Math.floor(pixelSize / 2.5), - userSelect: "none", }} > {fallback?.slice(0, 2).toUpperCase() ?? "?"} </div> );Note: Dynamic
fontSizebased onpixelSizemay still need an inline style or a size-specific class map.Also applies to: 70-82
66-86: Consider addingrole="img"to the fallback div.The fallback
<div>hasaria-labelbut lacksrole="img". Without the role, screen readers may not announce thearia-labelcorrectly since<div>elements don't have an implicit role that usesaria-label.♻️ Suggested accessibility improvement
return ( <div + role="img" aria-label={alt} className={className} style={{
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/atoms/Avatar/Avatar.stories.tsxsrc/components/atoms/Avatar/Avatar.tsxsrc/components/atoms/Avatar/AvatarProps.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use TypeScript for all files
Prefertypefor unions and primitives,interfacefor object shapes
Use?.optional chaining and??nullish coalescing
Files:
src/components/atoms/Avatar/AvatarProps.tssrc/components/atoms/Avatar/Avatar.stories.tsxsrc/components/atoms/Avatar/Avatar.tsx
src/components/**/*Props.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Props files: PascalCase with
Props.tssuffix (e.g.,ButtonProps.ts)
Files:
src/components/atoms/Avatar/AvatarProps.ts
src/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Other files: camelCase (e.g.,
setupTests.ts)
Files:
src/components/atoms/Avatar/AvatarProps.ts
src/components/**/[!.]*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Define interfaces for all component props in separate
[ComponentName]Props.tsfiles
Files:
src/components/atoms/Avatar/Avatar.stories.tsxsrc/components/atoms/Avatar/Avatar.tsx
src/components/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/components/**/*.tsx: Use function components (not arrow functions for exported components). Export named components:export function ComponentName() {}
Use destructuring for props, but keep the full props object for clarity in React components
Use React 19 features when appropriate
Use JSDoc comments for component documentation with@exampletags
Use TailwindCSS v4 utility classes exclusively for styling
Use DaisyUI component classes (e.g.,btn,card,input) and variants (e.g.,btn-primary,btn-secondary,btn-sm,btn-md,btn-lg)
Build dynamic class names using arrays and.join(' ')
Component files: PascalCase (e.g.,Button.tsx,UserProfile.tsx)
Follow established component directory structure:/src/components/[category]/[ComponentName]/with ComponentName.tsx, ComponentNameProps.ts, ComponentName.test.tsx, ComponentName.stories.tsx files
Files:
src/components/atoms/Avatar/Avatar.stories.tsxsrc/components/atoms/Avatar/Avatar.tsx
src/**/*.stories.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Story files: PascalCase with
.stories.tsxsuffix
Files:
src/components/atoms/Avatar/Avatar.stories.tsx
🧠 Learnings (6)
📚 Learning: 2025-12-23T08:13:56.218Z
Learnt from: CR
Repo: dbc2201/class-notes PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T08:13:56.218Z
Learning: Applies to src/components/**/[!.]*.tsx : Define interfaces for all component props in separate `[ComponentName]Props.ts` files
Applied to files:
src/components/atoms/Avatar/AvatarProps.ts
📚 Learning: 2025-12-23T08:13:56.218Z
Learnt from: CR
Repo: dbc2201/class-notes PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T08:13:56.218Z
Learning: Applies to src/components/**/*Props.ts : Props files: PascalCase with `Props.ts` suffix (e.g., `ButtonProps.ts`)
Applied to files:
src/components/atoms/Avatar/AvatarProps.ts
📚 Learning: 2025-12-23T08:13:56.218Z
Learnt from: CR
Repo: dbc2201/class-notes PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T08:13:56.218Z
Learning: Applies to src/components/**/*.tsx : Component files: PascalCase (e.g., `Button.tsx`, `UserProfile.tsx`)
Applied to files:
src/components/atoms/Avatar/AvatarProps.tssrc/components/atoms/Avatar/Avatar.tsx
📚 Learning: 2025-12-23T08:13:56.218Z
Learnt from: CR
Repo: dbc2201/class-notes PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T08:13:56.218Z
Learning: Applies to **/*.{ts,tsx} : Prefer `type` for unions and primitives, `interface` for object shapes
Applied to files:
src/components/atoms/Avatar/AvatarProps.ts
📚 Learning: 2025-12-23T08:13:56.218Z
Learnt from: CR
Repo: dbc2201/class-notes PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T08:13:56.218Z
Learning: Applies to src/**/*.stories.tsx : Story files: PascalCase with `.stories.tsx` suffix
Applied to files:
src/components/atoms/Avatar/Avatar.stories.tsx
📚 Learning: 2025-12-23T08:13:56.218Z
Learnt from: CR
Repo: dbc2201/class-notes PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T08:13:56.218Z
Learning: Applies to src/components/**/*.tsx : Use function components (not arrow functions for exported components). Export named components: `export function ComponentName() {}`
Applied to files:
src/components/atoms/Avatar/Avatar.tsx
🧬 Code graph analysis (2)
src/components/atoms/Avatar/Avatar.stories.tsx (1)
src/components/atoms/Avatar/Avatar.tsx (1)
Avatar(37-87)
src/components/atoms/Avatar/Avatar.tsx (1)
src/components/atoms/Avatar/AvatarProps.ts (2)
AvatarSize(1-1)AvatarProps(3-9)
🔇 Additional comments (7)
src/components/atoms/Avatar/AvatarProps.ts (1)
1-9: LGTM!The type definitions follow the coding guidelines correctly:
typefor the union (AvatarSize)interfacefor the object shape (AvatarProps)Props are appropriately optional with clear documentation for the
fallbackfield.src/components/atoms/Avatar/Avatar.tsx (3)
1-9: LGTM!Clean type-only import and well-structured size mapping.
11-35: LGTM!Excellent JSDoc documentation with multiple
@exampletags covering the main use cases. This follows the coding guidelines well.
37-44: LGTM!Correct use of function component (not arrow function), named export,
Readonly<>wrapper for props, and destructuring with sensible defaults.src/components/atoms/Avatar/Avatar.stories.tsx (3)
1-11: LGTM!Clean meta setup with
satisfies Meta<typeof Avatar>pattern and proper type alias for stories.
13-46: Good story coverage with JSDoc descriptions.The stories cover key scenarios: default with fallback, image rendering, and broken image handling. Note that the
BrokenImagestory will only display the fallback correctly once theonErrorhandler is added to theAvatarcomponent.
48-61: LGTM!The
Sizesstory effectively demonstrates all size variants in a single view. Therenderoverride is appropriate here for displaying multiple component instances.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
## Description Updated the `Avatar` component to gracefully handle image loading failures. Previously, a broken URL would result in a broken image icon. The component now tracks the image loading state and automatically renders the fallback content if the image fails to load. Additionally, removed verbose JSDoc comments to simplify the file. ## Type of Change - [x] New feature - [ ] Bug fix - [x] Refactoring - [ ] Documentation update - [ ] Test addition/update ## Changes Made - Imported `useState` to manage image error state. - Added `onError` event handler to the `<img>` element to detect loading failures. - Updated rendering logic to display the image only if `src` is present and no error has occurred. - Removed the JSDoc block for the `Avatar` function. - Removed inline comment regarding background color. ## Testing Done - [x] All tests pass - [ ] Added new tests - [x] Manual testing completed ## Screenshots (if applicable) N/A ## Related Issues N/A ## Checklist - [x] Code follows project style guidelines - [x] Self-review completed - [x] No console.log or debugging code in production logic - [x] All tests passing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/atoms/Avatar/Avatar.tsx`:
- Around line 21-25: The component's stale error state causes the fallback to
remain after src changes; in the Avatar component reset hasError when src
changes by adding an effect that calls setHasError(false) whenever the src prop
updates so showImage (src && !hasError) can become true for the new URL; update
the Avatar component to watch src and clear the error state (referencing
hasError, setHasError, src, showImage).
🧹 Nitpick comments (3)
src/components/atoms/Avatar/Avatar.tsx (3)
12-19: Add JSDoc documentation with@exampletag.Per coding guidelines, components should include JSDoc comments with
@exampletags for documentation.📝 Suggested JSDoc
+/** + * Avatar component displays a user profile image or styled fallback initials. + * + * `@example` + * // With image + * <Avatar src="/user.jpg" alt="Jane Doe" size="md" /> + * + * `@example` + * // With fallback initials + * <Avatar fallback="JD" size="lg" /> + */ export function Avatar(props: Readonly<AvatarProps>) {
37-40: Prefer TailwindCSS classes over inline styles.Per coding guidelines, TailwindCSS v4 utility classes should be used exclusively for styling. The inline styles here can be replaced with Tailwind classes.
♻️ Suggested refactor for image styles
<img src={src} alt={alt} width={pixelSize} height={pixelSize} - className={className} + className={[ + "rounded-full object-cover", + className, + ].filter(Boolean).join(" ")} onError={() => setHasError(true)} - style={{ - borderRadius: "50%", - objectFit: "cover", - }} />
46-65: Refactor fallback to use TailwindCSS and improve accessibility.Two suggestions:
TailwindCSS: Replace inline styles with Tailwind classes per coding guidelines. Consider a
sizeClassesmap for width/height.Accessibility: Add
role="img"to pair witharia-labelfor proper screen reader semantics.♻️ Suggested refactor
+const sizeClasses: Record<AvatarSize, string> = { + xs: "w-6 h-6 text-xs", + sm: "w-8 h-8 text-sm", + md: "w-10 h-10 text-base", + lg: "w-14 h-14 text-lg", + xl: "w-[72px] h-[72px] text-xl", +}; // FALLBACK RENDER return ( <div + role="img" aria-label={alt} - className={className} - style={{ - width: pixelSize, - height: pixelSize, - borderRadius: "50%", - display: "flex", - alignItems: "center", - justifyContent: "center", - backgroundColor: "#e5e7eb", - color: "#374151", - fontWeight: 600, - fontSize: Math.floor(pixelSize / 2.5), - userSelect: "none", - }} + className={[ + "rounded-full flex items-center justify-center", + "bg-gray-200 text-gray-700 font-semibold select-none", + sizeClasses[size], + className, + ].filter(Boolean).join(" ")} > {fallback?.slice(0, 2).toUpperCase() ?? "?"} </div> );
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/components/atoms/Avatar/Avatar.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use TypeScript for all files
Prefertypefor unions and primitives,interfacefor object shapes
Use?.optional chaining and??nullish coalescing
Files:
src/components/atoms/Avatar/Avatar.tsx
src/components/**/[!.]*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Define interfaces for all component props in separate
[ComponentName]Props.tsfiles
Files:
src/components/atoms/Avatar/Avatar.tsx
src/components/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/components/**/*.tsx: Use function components (not arrow functions for exported components). Export named components:export function ComponentName() {}
Use destructuring for props, but keep the full props object for clarity in React components
Use React 19 features when appropriate
Use JSDoc comments for component documentation with@exampletags
Use TailwindCSS v4 utility classes exclusively for styling
Use DaisyUI component classes (e.g.,btn,card,input) and variants (e.g.,btn-primary,btn-secondary,btn-sm,btn-md,btn-lg)
Build dynamic class names using arrays and.join(' ')
Component files: PascalCase (e.g.,Button.tsx,UserProfile.tsx)
Follow established component directory structure:/src/components/[category]/[ComponentName]/with ComponentName.tsx, ComponentNameProps.ts, ComponentName.test.tsx, ComponentName.stories.tsx files
Files:
src/components/atoms/Avatar/Avatar.tsx
🧠 Learnings (3)
📚 Learning: 2025-12-23T08:13:56.218Z
Learnt from: CR
Repo: dbc2201/class-notes PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T08:13:56.218Z
Learning: Applies to src/components/**/*.tsx : Component files: PascalCase (e.g., `Button.tsx`, `UserProfile.tsx`)
Applied to files:
src/components/atoms/Avatar/Avatar.tsx
📚 Learning: 2025-12-23T08:13:56.218Z
Learnt from: CR
Repo: dbc2201/class-notes PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T08:13:56.218Z
Learning: Applies to src/components/**/*.tsx : Use function components (not arrow functions for exported components). Export named components: `export function ComponentName() {}`
Applied to files:
src/components/atoms/Avatar/Avatar.tsx
📚 Learning: 2025-11-14T06:41:40.575Z
Learnt from: Arnab-Mandal1
Repo: dbc2201/class-notes PR: 39
File: src/components/ui/Input/Input.stories.tsx:25-79
Timestamp: 2025-11-14T06:41:40.575Z
Learning: For the Input component in src/components/ui/Input/, do not flag type issues in the Input.stories.tsx file related to the value prop being set to `{}` or other non-string/number values, as there is runtime protection in the component itself.
Applied to files:
src/components/atoms/Avatar/Avatar.tsx
🧬 Code graph analysis (1)
src/components/atoms/Avatar/Avatar.tsx (1)
src/components/atoms/Avatar/AvatarProps.ts (2)
AvatarSize(1-1)AvatarProps(3-9)
🔇 Additional comments (1)
src/components/atoms/Avatar/Avatar.tsx (1)
1-2: LGTM!Imports are appropriate —
useStatefor error state management and type-only import for props.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
add js doc
## Description Updated the `Avatar` component to correctly reset its error state when the `src` prop changes, ensuring that new images are attempted even after a previous failure. Enhanced the `onError` handler to prevent infinite loops and refined the JSDoc documentation for better readability. ## Type of Change - [ ] New feature - [x] Bug fix - [x] Refactoring - [x] Documentation update - [ ] Test addition/update ## Changes Made - Added `useEffect` to reset `hasError` to `false` when `src` changes - Updated `onError` to set `e.currentTarget.onerror = null` to prevent infinite loops - Changed `src` check to `Boolean(src)` for explicit type coercion - Simplified and clarified JSDoc comments and examples ## Testing Done - [x] All tests pass - [ ] Added new tests - [x] Manual testing completed
Description
Introduced a reusable
Avatarcomponent to display user profile images or fallback initials. This component is designed to be an atom within the design system, supporting multiple predefined sizes and custom styling options. It handles cases where an image source is missing by rendering a styled fallback with initials.Type of Change
Changes Made
AvatarProps.tsto defineAvatarSizetypes and component interface.Avatar.tsxwith conditional rendering for images and text fallbacks.sizeMapto handle standard sizes (xs,sm,md,lg,xl).Testing Done
Screenshots (if applicable)
N/A
Related Issues
N/A
Checklist
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.