Skip to content

[WEB-4734] feat: replace emoji picker with frimousse#7639

Merged
pushya22 merged 32 commits intopreviewfrom
feat-replace-emoji-picker-with-frimousse
Sep 2, 2025
Merged

[WEB-4734] feat: replace emoji picker with frimousse#7639
pushya22 merged 32 commits intopreviewfrom
feat-replace-emoji-picker-with-frimousse

Conversation

@anmolsinghbhatia
Copy link
Collaborator

@anmolsinghbhatia anmolsinghbhatia commented Aug 25, 2025

Changes

  • Added lightweight frimousse emoji picker package to the Propel package.
  • Refactored the existing emoji picker component and utility functions to support frimousse.
  • Replaced legacy emoji picker instances with the new Propel emoji picker.
  • Updated logo_prop types and conversion methods for consistency.
  • Introduced positionerClassname prop in the Popover component.

Notes

  • This reduces bundle size and improves performance with a lighter package.
  • Verified compatibility of the new picker across supported flows.

Type of Change

  • Improvement
  • Code refactoring
  • Performance improvements

Summary by CodeRabbit

  • New Features

    • Unified Emoji/Icon picker with tabs, search, icon styles, color selection and a Storybook demo.
    • Emoji sizing now uses rem-based scaling for more consistent logo appearance.
  • Bug Fixes

    • Logos no longer render when unset, preventing stray visuals.
    • Improved emoji selection, rendering and sizing consistency across the UI.
  • Chores

    • Localized Logo component usage across the app.
    • Exposed emoji-icon-picker in the design system and updated runtime dependencies.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 25, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new emoji/icon picker UI and helpers under packages/propel, exports it, updates Popover to accept a positionerClassName, switches Logo to a local implementation that renders emojis via stringToEmoji/getEmojiSize, and integrates the new picker across web forms while simplifying emoji payloads.

Changes

Cohort / File(s) Summary
Emoji/Icon picker core
packages/propel/src/emoji-icon-picker/index.ts, packages/propel/src/emoji-icon-picker/emoji-picker.tsx, packages/propel/src/emoji-icon-picker/helper.tsx
New EmojiPicker component, types (EmojiIconPickerTypes, TChangeHandlerProps, TCustomEmojiPicker), conversion/color helpers (emojiToString, stringToEmoji, getEmojiSize, adjustColorForContrast), and barrel export.
Emoji UI
packages/propel/src/emoji-icon-picker/emoji/emoji.tsx, packages/propel/src/emoji-icon-picker/emoji/index.ts
New EmojiRoot wrapping frimousse picker with search, skin-tone selector, and custom rendering; barrel export.
Icon UI & lists
packages/propel/src/emoji-icon-picker/icon/icon-root.tsx, packages/propel/src/emoji-icon-picker/icon/index.ts, packages/propel/src/emoji-icon-picker/icon/lucide-root.tsx, packages/propel/src/emoji-icon-picker/icon/material-root.tsx, packages/propel/src/emoji-icon-picker/lucide-icons.tsx, packages/propel/src/emoji-icon-picker/material-icons.tsx
New IconRoot (color picker, hex/swatches, search), Lucide/Material icon list components, and static icon registries.
Storybook
packages/propel/src/emoji-icon-picker/emoji-picker.stories.tsx
Adds controlled EmojiPicker story demonstrating usage and props.
Propel package config
packages/propel/package.json
Adds ./emoji-icon-picker export and runtime deps frimousse and use-font-face-observer.
Popover enhancement
packages/propel/src/popover/root.tsx
Adds optional positionerClassName?: string to PopoverContentProps and forwards it to PopoverPositioner.
Logo rendering
apps/web/core/components/common/logo.tsx, packages/ui/src/emoji/logo.tsx
Logo now renders emojis via stringToEmoji(...) and sizes using getEmojiSize(size) (rem-based); UI package logo file adds "use client" and an early guard for in_use.
Web integration: replace pickers & payload shape
apps/web/core/components/project/create/header.tsx, apps/web/core/components/project/form.tsx, apps/web/core/components/views/form.tsx
Replaced prior custom pickers with new EmojiPicker (iconType set), simplified emoji payload to { value: string }, removed decimal/url conversions.
Logo import path updates (web)
apps/web/.../breadcrumbs/project.tsx, apps/web/.../analytics/..., apps/web/.../home/widgets/recents/*.tsx, apps/web/.../common/switcher-label.tsx, apps/web/.../cycles/...
Switched Logo imports from @plane/ui to local @/components/common/logo; usages unchanged.
Workspace & client flags
pnpm-workspace.yaml, packages/ui/src/emoji/icons-list.tsx
Bumped @types/react to 18.3.11; added "use client" directive to icons-list.tsx.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Form as Parent Component
  participant Picker as EmojiPicker
  participant Pop as Popover
  participant Tabs as Tabs
  participant Emoji as EmojiRoot
  participant Icon as IconRoot
  participant AppLogo as Logo

  User->>Form: open logo control
  Form->>Picker: render(isOpen, handleToggle, defaultOpen)
  Picker->>Pop: open popover (positionerClassName forwarded)
  Pop->>Tabs: render Emoji / Icon tabs
  alt Emoji selected
    Tabs->>Emoji: show search/skin-tone
    User->>Emoji: select emoji
    Emoji-->>Picker: onChange({ type: EMOJI, value: "dash-separated-decimals" })
  else Icon selected
    Tabs->>Icon: show icons + color picker
    User->>Icon: select icon+color
    Icon-->>Picker: onChange({ type: ICON, value: { name, color } })
  end
  Picker-->>Form: onChange(payload)
  Form->>AppLogo: render updated logo_props
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

ready_to_merge

Suggested reviewers

  • prateekshourya29
  • sriramveeraghanta

Poem

A rabbit nibbles keys and hums a tune,
Pickers bloom like stars beneath the moon.
Emojis, icons, colors in a row—
Popovers open, choices softly glow.
Hooray, the logo shines — hop, hop, let's go! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-replace-emoji-picker-with-frimousse

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@makeplane
Copy link

makeplane bot commented Aug 25, 2025

Pull Request Linked with Plane Work Items

Comment Automatically Generated by Plane

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/web/core/components/project/create/header.tsx (1)

69-99: Fix icon rendering (missing lucide type) and hardening for undefined value

Logo defaults to "material". When users pick a lucide icon, rendering will fall back to Material Symbols text unless type="lucide" is provided. Also, value.in_use access can throw if value is undefined.

Apply this diff:

-              label={
+              label={
                 <span className="grid h-11 w-11 place-items-center rounded-md bg-custom-background-80">
-                  <Logo logo={value} size={20} />
+                  <Logo logo={value} size={20} type="lucide" />
                 </span>
               }
@@
-              defaultIconColor={value.in_use && value.in_use === "icon" ? value.icon?.color : undefined}
+              defaultIconColor={value?.in_use && value.in_use === "icon" ? value.icon?.color : undefined}
               defaultOpen={
-                value.in_use && value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON
+                value?.in_use && value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON
               }
apps/web/core/components/project/form.tsx (1)

206-234: Set Logo to lucide and type the picker change payload

  • Without type="lucide", icon logos render as Material text.
  • Prefer a discriminated union for the picker payload to avoid shape drift.

Apply this diff:

-                  label={<Logo logo={value} size={28} />}
+                  label={<Logo logo={value} size={28} type="lucide" />}
@@
-                  onChange={(val) => {
-                    let logoValue = {};
-
-                    if (val?.type === "emoji")
-                      logoValue = {
-                        value: val.value,
-                      };
-                    else if (val?.type === "icon") logoValue = val.value;
-
-                    onChange({
-                      in_use: val?.type,
-                      [val?.type]: logoValue,
-                    });
+                  onChange={(val: { type: "emoji" | "icon"; value: any }) => {
+                    const payload =
+                      val.type === "emoji"
+                        ? { in_use: "emoji", emoji: { value: val.value } }
+                        : { in_use: "icon", icon: val.value };
+                    onChange(payload as any);
                     setIsOpen(false);
                   }}
apps/web/core/components/common/logo.tsx (1)

40-51: Don’t gate emoji rendering on Material Symbols font readiness

The skeleton placeholder is shown until the Material font loads, even for emojis which don’t depend on that font. This can cause unnecessary flashing or delayed emoji display.

Apply this diff to gate only the Material icon branch:

-  if (!isMaterialSymbolsFontLoaded) {
-    return (
-      <span
-        style={{
-          height: size,
-          width: size,
-        }}
-        className="rounded animate-pulse bg-custom-background-80"
-      />
-    );
-  }
+  // Defer the Material Symbols loading check to the material icon branch,
+  // so emoji and lucide icons render immediately.
@@
   // emoji
   if (in_use === "emoji") {
     return (
       <span
         className="flex items-center justify-center"
         style={{
           fontSize: `${getEmojiSize(size)}rem`,
           lineHeight: `${getEmojiSize(size)}rem`,
           height: size,
           width: size,
         }}
       >
         {stringToEmoji(emoji?.value || "")}
       </span>
     );
   }
@@
   // icon
   if (in_use === "icon") {
     return (
       <>
-        {type === "lucide" ? (
+        {type === "lucide" ? (
           <>
             {lucideIcon && (
               <lucideIcon.element
                 style={{
                   color: color,
                   height: size,
                   width: size,
                 }}
               />
             )}
           </>
         ) : (
-          <span
-            className="material-symbols-rounded"
-            style={{
-              fontSize: size,
-              color: color,
-              scale: "115%",
-            }}
-          >
-            {value}
-          </span>
+          <>
+            {!isMaterialSymbolsFontLoaded ? (
+              <span
+                style={{ height: size, width: size }}
+                className="rounded animate-pulse bg-custom-background-80"
+              />
+            ) : (
+              <span
+                className="material-symbols-rounded"
+                style={{ fontSize: size, color: color, scale: "115%" }}
+              >
+                {value}
+              </span>
+            )}
+          </>
         )}
       </>
     );
   }

Also applies to: 54-66

🧹 Nitpick comments (17)
packages/ui/src/emoji/emoji-icon-helper.tsx (1)

140-153: Fix doc mismatch (comma vs hyphen) and harden stringToEmoji against empty/invalid input

Docs mention “comma-separated” while implementation uses hyphens. Also, stringToEmoji("") currently produces a NUL character. Guard and sanitize.

Apply:

- * This creates a comma-separated string of decimal values
+ * This creates a hyphen-separated string of decimal values
@@
-export function emojiToString(emoji: string): string {
+export function emojiToString(emoji: string): string {
   const decimals = emojiToDecimalEnhanced(emoji);
-  return decimals.join("-");
+  return decimals.join("-");
 }
@@
- * @param emojiString - Comma-separated string of decimal values
+ * @param emojiString - Hyphen-separated string of decimal values
  * @returns The reconstructed emoji string
  */
-export function stringToEmoji(emojiString: string): string {
-  const decimals = emojiString.split("-").map(Number);
-  return decimalToEmojiEnhanced(decimals);
-}
+export function stringToEmoji(emojiString: string): string {
+  if (!emojiString || !emojiString.trim()) return "";
+  const decimals = emojiString
+    .split("-")
+    .map((s) => Number.parseInt(s, 10))
+    .filter((n) => Number.isFinite(n) && n > 0);
+  return decimalToEmojiEnhanced(decimals);
+}
packages/editor/src/core/extensions/callout/logo-selector.tsx (1)

54-63: Clear stale data-emoji-url when switching to string-based emojis

You’re updating data-emoji-unicode but not clearing data-emoji-url. To avoid stale attributes lingering in the document, explicitly unset it during emoji updates (assuming updateAttributes removes undefined).

Apply:

-          if (val.type === "emoji") {
-            newLogoValue = {
-              "data-emoji-unicode": val.value,
-            };
+          if (val.type === "emoji") {
+            newLogoValue = {
+              "data-emoji-unicode": val.value,
+              "data-emoji-url": undefined,
+            };
             newLogoValueToStoreInLocalStorage = {
               in_use: "emoji",
               emoji: {
                 value: val.value,
               },
             };
           }

If updateAttributes does not remove undefined keys, set it to null instead or extend the utility to delete keys.

packages/ui/src/emoji/logo.tsx (1)

61-73: Emoji render block: add aria-hidden for decorative usage

Since this is typically a decorative logo inside pickers/buttons, hiding it from screen readers avoids redundant announcements.

Apply:

-    return (
-      <span
+    return (
+      <span
+        aria-hidden="true"
         className="flex items-center justify-center"
         style={{
           fontSize: `${getEmojiSize(size)}rem`,
           lineHeight: `${getEmojiSize(size)}rem`,
           height: size,
           width: size,
         }}
       >
         {stringToEmoji(emoji?.value || "")}
       </span>
     );
apps/web/core/components/views/form.tsx (2)

177-191: Strongly type the onChange payload and avoid using an untyped intermediate object

Using an untyped {} for logoValue weakens type safety and can let invalid shapes slip through. Prefer a discriminated union and build the payload in one go.

Apply this diff:

-              onChange={(val) => {
-                let logoValue = {};
-
-                if (val?.type === "emoji")
-                  logoValue = {
-                    value: val.value,
-                  };
-                else if (val?.type === "icon") logoValue = val.value;
-
-                setValue("logo_props", {
-                  in_use: val?.type,
-                  [val?.type]: logoValue,
-                });
-                setIsOpen(false);
-              }}
+              onChange={(val: { type: "emoji" | "icon"; value: any }) => {
+                const payload =
+                  val.type === "emoji"
+                    ? { in_use: "emoji", emoji: { value: val.value } }
+                    : { in_use: "icon", icon: val.value };
+
+                setValue("logo_props", payload as any);
+                setIsOpen(false);
+              }}

161-166: Tailwind class typo: use flex-shrink-0

Minor, but this breaks the utility and may affect layout in narrow containers.

Apply this diff:

-              className="flex items-center justify-center flex-shrink0"
+              className="flex items-center justify-center flex-shrink-0"
apps/web/core/components/project/create/header.tsx (1)

79-93: Avoid any in onChange; use a discriminated union to preserve types

Keeps logo_props shape consistent and prevents accidental regressions.

Apply this diff:

-              onChange={(val: any) => {
-                let logoValue = {};
-
-                if (val?.type === "emoji")
-                  logoValue = {
-                    value: val.value,
-                  };
-                else if (val?.type === "icon") logoValue = val.value;
-
-                onChange({
-                  in_use: val?.type,
-                  [val?.type]: logoValue,
-                });
+              onChange={(val: { type: "emoji" | "icon"; value: any }) => {
+                const payload =
+                  val.type === "emoji"
+                    ? { in_use: "emoji", emoji: { value: val.value } }
+                    : { in_use: "icon", icon: val.value };
+                onChange(payload as any);
                 setIsOpen(false);
               }}
apps/web/core/components/pages/modals/page-form.tsx (1)

64-105: Harden the picker button styling and keep types explicit

  • Typo in Tailwind class (flex-shrink0).
  • Add a typed onChange payload as in other components for consistency.

Apply this diff:

-          <EmojiIconPicker
+          <EmojiIconPicker
             isOpen={isOpen}
             handleToggle={(val: boolean) => setIsOpen(val)}
-            className="flex items-center justify-center flex-shrink0"
+            className="flex items-center justify-center flex-shrink-0"
@@
-            onChange={(val) => {
+            onChange={(val: { type: "emoji" | "icon"; value: any }) => {
               let logoValue = {};
 
               if (val?.type === "emoji")
                 logoValue = {
                   value: val.value,
                 };
               else if (val?.type === "icon") logoValue = val.value;
 
               handleFormData("logo_props", {
                 in_use: val?.type,
                 [val?.type]: logoValue,
               });
               setIsOpen(false);
             }}
apps/web/core/components/project/form.tsx (1)

206-234: Optional: close-on-select behavior is contradictory

closeOnSelect={false} but you immediately setIsOpen(false) on change. Either remove the prop or set it to true for clarity.

Apply this diff:

-                  closeOnSelect={false}
+                  closeOnSelect
packages/ui/src/emoji/emoji-icon-picker.tsx (3)

77-78: Guard against invalid defaultIndex when tab key is not found

If defaultOpen is not present in TABS_LIST, findIndex returns -1. Headless UI expects a non-negative defaultIndex. Add a safe fallback.

Apply this diff:

-                defaultIndex={TABS_LIST.findIndex((tab) => tab.key === defaultOpen)}
+                defaultIndex={Math.max(0, TABS_LIST.findIndex((tab) => tab.key === defaultOpen))}

26-27: Remove or use the unused theme prop

theme is destructured but never used, which may trigger lint warnings and confuses readers.

Apply this diff to drop it from the destructuring:

-    searchPlaceholder = "Search",
-    theme,
+    searchPlaceholder = "Search",

Alternatively, if theming is intended, consider threading theme to EmojiPickerComponent (e.g., via data-attributes or a context).


94-94: Consider responsive height instead of fixed h-80

A hard height can clip the picker on smaller viewports. Prefer a max height with natural content height for better UX.

Example change:

-                <Tab.Panels as="div" className="h-80 w-full overflow-hidden overflow-y-auto">
+                <Tab.Panels as="div" className="max-h-[80vh] h-80 w-full overflow-hidden overflow-y-auto"

This keeps the existing look while preventing overflow on short screens.

packages/ui/src/emoji/emoji-icon-picker-new.tsx (4)

98-98: Remove stray console.log from the selection handler

Leftover logging in UI components adds noise and can leak PII in some contexts.

Apply this diff:

-                      onEmojiSelect={(val) => {
-                        console.log(val);
+                      onEmojiSelect={(val) => {

77-78: Guard against invalid defaultIndex when tab key is not found

Same concern as the other picker: ensure a non-negative index.

-                defaultIndex={TABS_LIST.findIndex((tab) => tab.key === defaultOpen)}
+                defaultIndex={Math.max(0, TABS_LIST.findIndex((tab) => tab.key === defaultOpen))}

26-27: Remove or use the unused theme prop

theme is destructured but unused.

-    searchPlaceholder = "Search",
-    theme,
+    searchPlaceholder = "Search",

107-115: Remove commented scaffolding

The inline comment {/* data-slot="emoji-picker-search-wrapper" */} appears to be a leftover from earlier scaffolding.

-                      {/* data-slot="emoji-picker-search-wrapper" */}
packages/ui/src/emoji/emoji-picker.tsx (2)

41-41: Verify non-standard Tailwind class outline-hidden

outline-hidden isn’t a standard Tailwind utility. If you don’t have a custom plugin for it, it won’t apply. Use outline-none instead.

-      className={cn("relative flex-1 outline-hidden", className)}
+      className={cn("relative flex-1 outline-none", className)}

If you do have a custom utility, feel free to ignore this.


67-99: Optional: stabilize the components object with useMemo to reduce re-renders

The inline components object and its lambdas are recreated on every render, which can trigger re-renders downstream. Memoizing improves stability.

Example:

const listComponents = React.useMemo(
  () => ({
    CategoryHeader: ({ category, className, ...rest }: any) => (
      <div {...rest} data-slot="emoji-picker-list-category-header" className={cn("bg-custom-background-100 text-custom-text-300 px-3 pb-1.5 text-xs font-medium", className)}>
        {category.label}
      </div>
    ),
    Row: ({ children, className, ...rest }: any) => (
      <div {...rest} data-slot="emoji-picker-list-row" className={cn("scroll-my-1.5 px-1.5", className)}>
        {children}
      </div>
    ),
    Emoji: ({ emoji, className, ...rest }: any) => (
      <button {...rest} type="button" data-slot="emoji-picker-list-emoji" className={cn("data-active:bg-accent flex size-8 items-center justify-center rounded-md text-lg", className)} aria-label={emoji.label} title={emoji.label}>
        {emoji.emoji}
      </button>
    ),
  }),
  []
);

// ...
<BaseEmojiPicker.List components={listComponents} ... />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bbc465a and 4289e75.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • apps/web/core/components/common/logo.tsx (2 hunks)
  • apps/web/core/components/pages/modals/page-form.tsx (2 hunks)
  • apps/web/core/components/project/create/header.tsx (2 hunks)
  • apps/web/core/components/project/form.tsx (2 hunks)
  • apps/web/core/components/views/form.tsx (2 hunks)
  • apps/web/core/store/pages/base-page.ts (1 hunks)
  • apps/web/package.json (0 hunks)
  • packages/editor/src/core/extensions/callout/logo-selector.tsx (2 hunks)
  • packages/ui/package.json (1 hunks)
  • packages/ui/src/emoji/emoji-icon-helper.tsx (3 hunks)
  • packages/ui/src/emoji/emoji-icon-picker-new.tsx (2 hunks)
  • packages/ui/src/emoji/emoji-icon-picker.tsx (2 hunks)
  • packages/ui/src/emoji/emoji-picker.tsx (1 hunks)
  • packages/ui/src/emoji/helpers.ts (0 hunks)
  • packages/ui/src/emoji/logo.tsx (2 hunks)
  • packages/utils/src/emoji.ts (0 hunks)
💤 Files with no reviewable changes (3)
  • apps/web/package.json
  • packages/utils/src/emoji.ts
  • packages/ui/src/emoji/helpers.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-14T13:16:23.323Z
Learnt from: vamsikrishnamathala
PR: makeplane/plane#7061
File: web/core/components/workspace-notifications/root.tsx:18-18
Timestamp: 2025-05-14T13:16:23.323Z
Learning: In the Plane project codebase, the path alias `@/plane-web` resolves to the `ce` directory, making imports like `@/plane-web/hooks/use-notification-preview` correctly resolve to files in `web/ce/hooks/`.

Applied to files:

  • apps/web/core/components/project/create/header.tsx
🧬 Code graph analysis (4)
packages/ui/src/emoji/logo.tsx (1)
packages/ui/src/emoji/emoji-icon-helper.tsx (2)
  • getEmojiSize (155-157)
  • stringToEmoji (150-153)
apps/web/core/components/common/logo.tsx (1)
packages/ui/src/emoji/emoji-icon-helper.tsx (2)
  • getEmojiSize (155-157)
  • stringToEmoji (150-153)
packages/ui/src/emoji/emoji-icon-picker.tsx (2)
packages/ui/src/emoji/emoji-picker.tsx (1)
  • EmojiPickerComponent (116-123)
packages/ui/src/emoji/emoji-icon-helper.tsx (1)
  • emojiToString (140-143)
packages/ui/src/emoji/emoji-icon-picker-new.tsx (2)
packages/ui/src/emoji/emoji-picker.tsx (1)
  • EmojiPickerComponent (116-123)
packages/ui/src/emoji/emoji-icon-helper.tsx (1)
  • emojiToString (140-143)
🪛 GitHub Check: Build and lint web apps
packages/ui/src/emoji/emoji-icon-helper.tsx

[failure] 155-155:
Unexpected block statement surrounding arrow body; move the returned value immediately after the =>

🪛 GitHub Actions: Build and lint web apps
packages/ui/src/emoji/emoji-icon-helper.tsx

[error] 155-155: ESLint: Unexpected block statement surrounding arrow body; move the returned value immediately after the =>. (arrow-body-style)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (16)
packages/ui/package.json (1)

44-44: Cleanup verification complete – no lingering emoji-picker-react usage

• No imports or requires of emoji-picker-react detected across the repo
• No references to removed helpers (emojiCodeToUnicode, convertHexEmojiToDecimal) remain
frimousse is correctly declared in packages/ui/package.json and imported in packages/ui/src/emoji/emoji-picker.tsx
• Lockfiles only reference frimousse; zero occurrences of emoji-picker-react found

packages/ui/src/emoji/emoji-icon-helper.tsx (4)

19-23: Event payload for emoji now a string — consistent with storage approach

The change to value: string for the emoji variant matches the new “hyphen-joined code points” storage format used elsewhere in the PR. Call sites appear to have been updated accordingly.


48-49: Theme type narrowed to literal union

Switching theme to "light" | "dark" is clearer and avoids importing theme types from external libs.


109-123: Emoji → decimal conversion is correct for ZWJ sequences and skin tones

Using Array.from ensures code point–aware iteration and preserves the full sequence (including variation selectors and ZWJs) when paired with fromCodePoint on the way back.


130-133: Decimal → emoji reconstruction is sound

String.fromCodePoint(...).join("") correctly reassembles complex emoji sequences.

apps/web/core/store/pages/base-page.ts (1)

443-447: ✅ Confirmed: Backend no longer expects logo_props.emoji.url

I’ve checked the API code and migration history and found:

  • No references to an emoji.url field in any serializer, view, or model in apps/api/plane – the model now has a simple emoji: CharField and an icon_prop: JSONField, and there’s no logo_props.emoji.url usage in the codebase.
  • The Django migration 0061_project_logo_props sets any legacy url values to an empty string, and subsequent serializers/tests only surface the emoji’s value (and unicode), not a URL.
  • The contract test in tests/contract/app/test_project_app.py asserts a response shape of
    "logo_props": {
      "in_use": "emoji",
      "emoji": { "value": "🚀", "unicode": "1f680" }
    }
    with no url property present.

All signs confirm the backend/API has moved to the new string-based emoji format and will not break when the front end drops the url field.

packages/ui/src/emoji/logo.tsx (1)

5-5: New helpers imported — matches the string-based emoji pipeline

Importing getEmojiSize and stringToEmoji keeps sizing consistent across components and centralizes the encoding/decoding logic.

apps/web/core/components/views/form.tsx (1)

22-22: Utils import cleanup looks good

Removing the emoji conversion util aligns with the new string-based emoji flow. No issues spotted.

apps/web/core/components/project/create/header.tsx (1)

10-14: Import changes align with the refactor

Switching to CustomEmojiIconPicker and local Logo import is consistent with the new emoji picker and rendering path.

apps/web/core/components/pages/modals/page-form.tsx (1)

10-10: Import cleanup is consistent with the migration

Only getTabIndex remains from utils—looks good.

apps/web/core/components/project/form.tsx (1)

21-21: Import changes OK

Removing the conversion util is aligned with the new emoji value model.

apps/web/core/components/common/logo.tsx (2)

10-10: Import updates align with string-based emoji rendering

Using stringToEmoji and getEmojiSize is consistent with the refactor. No issues with the import itself.


54-66: Emoji picker confirmed to emit decimal codepoints
I verified that the new picker’s onEmojiSelect handler uses emojiToString, which always converts the selected emoji into a hyphen-separated string of decimal Unicode codepoints. Consequently, stringToEmoji—which splits on “-” and casts via Number()—will always receive decimal strings and will not produce NaN. No changes required.

packages/ui/src/emoji/emoji-icon-picker.tsx (1)

96-119: Nice integration of the new EmojiPickerComponent and value conversion

The onEmojiSelect handler correctly converts the selection with emojiToString and respects closeOnSelect. The header composition (Search + SkinToneSelector) looks clean and extensible.

packages/ui/src/emoji/emoji-icon-picker-new.tsx (1)

123-135: Align panel height strategy with the first picker or make it responsive

This panel uses h-80 while the other file sets the second panel to h-full. The mismatch may cause subtle layout differences across screens.

If LucideIconsList virtualizes content assuming a fixed height, keep h-80; otherwise consider:

-                  <Tab.Panel className="h-80 w-full relative overflow-hidden overflow-y-auto">
+                  <Tab.Panel className="h-full w-full relative overflow-hidden overflow-y-auto">

Or switch both pickers to a consistent, responsive height (e.g., max-h-[80vh] h-80).

packages/ui/src/emoji/emoji-picker.tsx (1)

5-13: Clean, minimal wrapper for BaseEmojiPicker.Root

The compound-component approach and prop passthrough look solid, enabling a neat API for consumers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
packages/ui/src/emoji/emoji-icon-picker-new.tsx (5)

94-96: Avoid nested scroll containers; keep a single vertical scroller.

Tab.Panels sets both overflow-hidden overflow-y-auto, and the first panel’s internal list likely scrolls too. This can cause nested scrolling and wheel-event quirks. Prefer one scroller (either here or inside the panel).

Apply this diff to keep the outer container non-scrollable and let inner content handle scrolling:

-                <Tab.Panels as="div" className="h-80 w-full overflow-hidden overflow-y-auto">
+                <Tab.Panels as="div" className="h-80 w-full overflow-hidden">

96-105: Remove debug logging and guard against undefined selections.

console.log(val) will ship to users and pollute logs; also add a small null-guard in case the picker ever emits an incomplete value.

Apply this diff:

-                    <EmojiPickerComponent
-                      onEmojiSelect={(val) => {
-                        console.log(val);
-                        onChange({
-                          type: EmojiIconPickerTypes.EMOJI,
-                          value: emojiToString(val.emoji),
-                        });
-                        if (closeOnSelect) handleToggle(false);
-                      }}
-                      className="h-full w-full border-none p-2"
-                    >
+                    <EmojiPickerComponent
+                      onEmojiSelect={(val) => {
+                        const native = val?.emoji;
+                        if (!native) return;
+                        onChange({
+                          type: EmojiIconPickerTypes.EMOJI,
+                          value: emojiToString(native),
+                        });
+                        if (closeOnSelect) handleToggle(false);
+                      }}
+                      className="h-full w-full border-none p-2"
+                    >

107-115: Simplify the search wrapper styles; remove redundant attribute selectors.

You already pass className="flex-grow" to EmojiPickerComponent.Search. The attribute selectors for [data-slot='emoji-picker-search-wrapper'] are redundant and add specificity noise.

Apply this diff:

-                      <div className="flex items-center gap-2 justify-between [&>[data-slot='emoji-picker-search-wrapper']]:flex-grow [&>[data-slot='emoji-picker-search-wrapper']]:p-0 px-1.5 py-2 sticky top-0 z-10 bg-custom-background-100">
+                      <div className="flex items-center gap-2 justify-between px-1.5 py-2 sticky top-0 z-10 bg-custom-background-100">

73-79: Guard defaultIndex against -1 to prevent runtime issues.

If defaultOpen is ever out-of-sync with TABS_LIST, findIndex returns -1, which Headless UI Tabs does not accept.

Apply this diff:

-                defaultIndex={TABS_LIST.findIndex((tab) => tab.key === defaultOpen)}
+                defaultIndex={Math.max(0, TABS_LIST.findIndex((tab) => tab.key === defaultOpen))}

2-2: Close on Escape for better a11y and parity with expectations.

Since open/close is managed externally, Popover’s internal ESC handling won’t update isOpen. Add an ESC key handler so users can dismiss via keyboard.

Apply this diff:

-import React, { useRef, useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
@@
   // close dropdown on outside click
   useOutsideClickDetector(containerRef, () => handleToggle(false));
+  // close on Escape
+  useEffect(() => {
+    if (!isOpen) return;
+    const onKeyDown = (e: KeyboardEvent) => {
+      if (e.key === "Escape") handleToggle(false);
+    };
+    document.addEventListener("keydown", onKeyDown);
+    return () => document.removeEventListener("keydown", onKeyDown);
+  }, [isOpen, handleToggle]);

Also applies to: 45-47

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 13118b5 and b846e9a.

📒 Files selected for processing (4)
  • packages/ui/src/emoji/emoji-icon-picker-new.tsx (2 hunks)
  • packages/ui/src/emoji/emoji-icon-picker.tsx (2 hunks)
  • packages/ui/src/emoji/emoji-picker.tsx (1 hunks)
  • packages/utils/src/emoji.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/utils/src/emoji.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/emoji/emoji-picker.tsx
  • packages/ui/src/emoji/emoji-icon-picker.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/ui/src/emoji/emoji-icon-picker-new.tsx (2)
packages/ui/src/emoji/emoji-picker.tsx (1)
  • EmojiPickerComponent (116-123)
packages/ui/src/emoji/emoji-icon-helper.tsx (1)
  • emojiToString (140-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Build and lint web apps
🔇 Additional comments (2)
packages/ui/src/emoji/emoji-icon-picker-new.tsx (2)

96-121: Overall integration with the new EmojiPickerComponent looks clean.

Good use of composed subcomponents (Search, SkinToneSelector, Content) and a sane class structure. The selection flow with closeOnSelect reads well.


6-8: Verify emoji decimal-string contract

I audited how emojiToString (which outputs hyphen-joined decimal sequences) flows through pickers and handlers:

  • Both emoji-icon-picker.tsx and emoji-icon-picker-new.tsx emit value: emojiToString(val.emoji) to the onChange callback.
  • Downstream consumers—such as updatePageLogo in apps/web/core/store/pages/base-page.ts and handleFormData("logo_props") in page-form.tsx—accept TChangeHandlerProps.value: string and don’t appear to expect a native emoji character or image URL.
  • Rendering via the <Logo> component then uses stringToEmoji() to convert these decimal codes back into a displayable emoji.

No direct call sites were found that assume a raw emoji or URL instead of a decimal code. However, please double-check that:

  • Your API endpoints or contract tests (e.g. test_project_app.py) handle hyphenated decimal codes correctly when receiving or returning logo_props.emoji.value.
  • Any legacy integrations, plugins, or custom scripts that consume logo_props can tolerate this format.

Optional refactor: consider renaming emojiToStringemojiToDecimalString for greater clarity.

@anmolsinghbhatia anmolsinghbhatia marked this pull request as ready for review August 29, 2025 10:35
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/editor/src/core/extensions/callout/logo-selector.tsx (4)

22-32: Remove stale data-emoji-url and fix TLogoProps conformance

TLogoProps.emoji no longer carries url. Keeping it here will break type-checking and runtime expectations.

Apply:

-  emoji: {
-      value: blockAttributes["data-emoji-unicode"]?.toString(),
-      url: blockAttributes["data-emoji-url"],
-    },
+  emoji: {
+      value: blockAttributes["data-emoji-unicode"] ?? DEFAULT_CALLOUT_BLOCK_ATTRIBUTES["data-emoji-unicode"],
+    },

36-44: EmojiPicker has no className prop

EmojiPicker exposes buttonClassName and dropdownClassName, not className. This prop is ignored and fails TS if strict.

Apply:

-        className="flex-shrink-0 grid place-items-center"

55-66: Use enum constants instead of string literals for type checks

Prevents drift if the enum values change and improves TS narrowing.

Apply:

-          if (val.type === "emoji") {
+          if (val.type === EmojiIconPickerTypes.EMOJI) {
@@
-          } else if (val.type === "icon") {
+          } else if (val.type === EmojiIconPickerTypes.ICON) {

1-95: Remove all lingering data-emoji-url references
data-emoji-url is no longer used and should be removed across the repo:

  • In packages/editor/src/core/extensions/callout/logo-selector.tsx, drop the blockAttributes["data-emoji-url"] reference.
  • In apps/api/plane/seeds/data/issues.json, remove or update any data-emoji-url attributes.
🧹 Nitpick comments (3)
packages/editor/src/core/extensions/callout/logo-selector.tsx (1)

70-77: Optional: preserve last emoji when switching to icon (current behavior OK)

You’re keeping data-emoji-unicode as-is when picking an icon, which lets users switch back without losing the previous emoji. If you prefer strict cleanliness over convenience, clear it here as well.

Apply (only if you want symmetry):

             newLogoValue = {
               "data-icon-name": val.value.name,
               "data-icon-color": val.value.color,
+              "data-emoji-unicode": undefined,
             };
packages/editor/src/core/extensions/callout/utils.ts (2)

34-46: Harden parsing with a shape check before trusting parsedData

Parsing to TLogoProps assumes shape correctness. Add a narrow type guard to avoid malformed localStorage crashing downstream.

Example guard (outside selected lines for context):

const isLogoProps = (x: any): x is TLogoProps =>
  x && (x.in_use === "emoji" || x.in_use === "icon") &&
  (x.in_use === "emoji" ? typeof x.emoji?.value === "string" : typeof x.icon?.name === "string");

let parsedDataUnknown: unknown;
try {
  parsedDataUnknown = JSON.parse(storedData);
} catch { /* existing catch */ }

if (!isLogoProps(parsedDataUnknown)) return fallBackValues;
const parsedData = parsedDataUnknown;

61-64: Consider guarding setItem to avoid quota errors

LocalStorage can throw; a try/catch keeps the editor resilient.

Apply:

-export const updateStoredLogo = (value: TLogoProps): void => {
-  if (typeof window === "undefined") return;
-  localStorage.setItem("editor-calloutComponent-logo", JSON.stringify(value));
-};
+export const updateStoredLogo = (value: TLogoProps): void => {
+  if (typeof window === "undefined") return;
+  try {
+    localStorage.setItem("editor-calloutComponent-logo", JSON.stringify(value));
+  } catch (e) {
+    console.error("Failed to persist callout logo to localStorage", e);
+  }
+};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 360d633 and dd95d94.

📒 Files selected for processing (2)
  • packages/editor/src/core/extensions/callout/logo-selector.tsx (4 hunks)
  • packages/editor/src/core/extensions/callout/utils.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/editor/src/core/extensions/callout/logo-selector.tsx (2)
packages/propel/src/emoji-icon-picker/emoji-picker.tsx (1)
  • EmojiPicker (10-136)
packages/editor/src/core/extensions/callout/utils.ts (1)
  • DEFAULT_CALLOUT_BLOCK_ATTRIBUTES (12-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (5)
packages/editor/src/core/extensions/callout/logo-selector.tsx (3)

87-89: Defaults wiring looks correct

defaultIconColor and defaultOpen correctly derive from current attrs; no change needed.


46-86: Sanity: localStorage payload shape is consistent

updateStoredLogo receives the minimal TLogoProps payloads for both emoji and icon branches; good.


2-5: Imports align with the new picker and types

Switch to @plane/propel/emoji-icon-picker and TLogoProps is consistent across the PR.

packages/editor/src/core/extensions/callout/utils.ts (2)

2-2: Type source switch to @plane/types is correct

TLogoProps centralization is consistent with storage and UI usage.


12-19: Defaults look sane and match picker encoding

data-emoji-unicode: "128161" aligns with string-based codepoint storage.

@anmolsinghbhatia anmolsinghbhatia marked this pull request as draft September 1, 2025 10:17
@anmolsinghbhatia anmolsinghbhatia marked this pull request as ready for review September 1, 2025 15:31
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/propel/package.json (2)

41-41: Add frimousse dependency — consider pinning narrower for 0.x and validate SSR/tree-shaking.

^0.3.0 is fine but still pre-1.0; if you want stricter updates, pin to ~0.3.x.

-    "frimousse": "^0.3.0",
+    "frimousse": "~0.3.0",

Also verify: no window access at import-time, package has ESM exports and sideEffects:false (tree-shakable).


47-47: use-font-face-observer as runtime dep — ensure client-only usage.

Make sure calls live inside effects/dynamic client code to avoid SSR/hydration mismatches. If fonts gating is optional, lazy-load to keep initial bundle lean.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 31f0f50 and d2060f5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • packages/propel/package.json (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/propel/package.json (2)

46-46: tailwind-merge version entry — LGTM.


29-29: Confirm compiled artifacts for emoji-icon-picker export
No committed build output found under packages/propel; ensure the build/publish step emits and includes emoji-icon-picker/index.js and emoji-icon-picker/index.d.ts so Storybook and downstream consumers can resolve this subpath without path aliases.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
pnpm-workspace.yaml (2)

11-11: Keep @types/react-dom in sync with @types/react to avoid subtle type mismatches.

Consider bumping to 18.3.11 as well for parity with React 18.3.x types.

Apply:

-  "@types/react-dom": 18.3.1
+  "@types/react-dom": 18.3.11

7-14: Confirm catalog pinning strategy.

If your guideline prefers exact patch pins, this is fine; otherwise consider a patch range to reduce churn (e.g., 18.3.x). Align with repo standards.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d2060f5 and d581d6c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • pnpm-workspace.yaml (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Build and lint web apps
🔇 Additional comments (1)
pnpm-workspace.yaml (1)

10-10: Bump to @types/react 18.3.11: verify workspace-wide compatibility. The automated check didn’t list any versions nor run a full type-check (tsc not found). Manually confirm:

  • All workspaces resolve @types/react at 18.3.11 with no mixed versions of @types/react or @types/react-dom
  • A full workspace type-check (TS 5.8.3) passes without errors

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/propel/package.json (2)

1-33: Optional: declare sideEffects for CSS to improve tree-shaking without dropping fonts CSS.

Mark only the CSS export as side-effectful; everything else tree-shakeable.

Apply this diff:

 {
   "name": "@plane/propel",
   "version": "1.0.0",
   "private": true,
   "license": "AGPL-3.0",
+  "sideEffects": [
+    "./src/styles/fonts/index.css"
+  ],
   "scripts": {

48-48: Specify Tailwind CSS peer dependency or adjust tailwind-merge version
Propel currently uses tailwind-merge@^3.3.1 (which requires Tailwind CSS v4+). Since you don’t declare any tailwindcss peerDependency, consumers on v3 will silently break. Either:

  • Add a peerDependency "tailwindcss": ">=4.0.0" to package.json
  • Or if you must support Tailwind v3, pin tailwind-merge to ^2.x instead
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6b7d51a and b79b92b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • apps/web/core/components/project/form.tsx (3 hunks)
  • packages/propel/package.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/core/components/project/form.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
packages/propel/package.json (1)

43-43: Approve frimousse integration — Published as ESM with tree-shakeable exports, ships built-in TypeScript declarations, MIT-licensed, and is a client-only React component (add “use client” in Next.js).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (9)
packages/ui/src/emoji/icons-list.tsx (9)

35-42: Fix font-load detection: avoid mismatched stretch value.

Using stretch: "condensed" may never match the loaded face and keep placeholders forever. Prefer normal (or omit).

Apply:

   const isMaterialSymbolsFontLoaded = useFontFaceObserver([
     {
       family: `Material Symbols Rounded`,
       style: `normal`,
-      weight: `normal`,
-      stretch: `condensed`,
+      weight: `normal`,
+      stretch: `normal`,
     },
   ]);

19-19: Keep activeColor in sync with defaultColor and apply contrast adjustment.

Currently, activeColor won’t update if defaultColor prop changes, and initial render may skip contrast adjustment.

-  const [activeColor, setActiveColor] = useState(defaultColor);
+  const [activeColor, setActiveColor] = useState(() => adjustColorForContrast(defaultColor));
@@
-  useEffect(() => {
-    if (DEFAULT_COLORS.includes(defaultColor.toLowerCase())) setShowHexInput(false);
-    else {
-      setHexValue(defaultColor.slice(1, 7));
-      setShowHexInput(true);
-    }
-  }, [defaultColor]);
+  useEffect(() => {
+    const isPalette = DEFAULT_COLORS.includes(defaultColor.toLowerCase());
+    setShowHexInput(!isPalette);
+    const adjusted = adjustColorForContrast(defaultColor);
+    setActiveColor(adjusted);
+    setHexValue(adjusted.replace("#", "").slice(0, 6));
+  }, [defaultColor]);

Also applies to: 25-31


89-101: Also adjust palette selections for contrast.

Guarantees the same behavior as hex input.

-              <button
+              <button
                 key={curCol}
                 type="button"
                 className="grid place-items-center size-5"
                 onClick={() => {
-                  setActiveColor(curCol);
-                  setHexValue(curCol.slice(1, 7));
+                  const adjusted = adjustColorForContrast(curCol);
+                  setActiveColor(adjusted);
+                  setHexValue(adjusted.replace("#", "").slice(0, 6));
                 }}
               >

66-76: Preview dot should reflect the adjusted color, not raw hex.

Prevents mismatch between preview and the applied color after contrast fixes.

-              <span
-                className="h-4 w-4 flex-shrink-0 rounded-full mr-1"
-                style={{
-                  backgroundColor: `#${hexValue}`,
-                }}
-              />
+              <span
+                className="h-4 w-4 flex-shrink-0 rounded-full mr-1"
+                style={{ backgroundColor: activeColor }}
+              />

76-87: Constrain HEX input to valid 6-digit values.

Improves UX and prevents partial values.

               <Input
                 type="text"
                 value={hexValue}
                 onChange={(e) => {
                   const value = e.target.value;
                   setHexValue(value);
                   if (/^[0-9A-Fa-f]{6}$/.test(value)) setActiveColor(adjustColorForContrast(`#${value}`));
                 }}
-                className="flex-grow pl-0 text-xs text-custom-text-200"
+                className="flex-grow pl-0 text-xs text-custom-text-200"
+                maxLength={6}
+                pattern="[0-9A-Fa-f]{6}"
+                spellCheck={false}
+                autoCapitalize="off"
+                aria-label="Hex color (6 digits)"
                 mode="true-transparent"
                 autoFocus
               />

55-61: Improve search input accessibility.

Label the control and use the appropriate input type.

               <Input
-                placeholder="Search"
+                placeholder="Search"
+                aria-label="Search icons"
+                type="search"
                 value={query}
                 onChange={(e) => setQuery(e.target.value)}
                 className="text-[1rem] border-none p-0 h-full w-full "
               />

33-33: Memoize filtering to avoid recomputation on every keystroke.

Small win when the icon list grows.

-import React, { useEffect, useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";
@@
-  const filteredArray = MATERIAL_ICONS_LIST.filter((icon) => icon.name.toLowerCase().includes(query.toLowerCase()));
+  const filteredArray = useMemo(() => {
+    const q = query.toLowerCase().trim();
+    return q ? MATERIAL_ICONS_LIST.filter((icon) => icon.name.toLowerCase().includes(q)) : MATERIAL_ICONS_LIST;
+  }, [query]);

Also applies to: 4-4


127-137: Add accessible labels to icon buttons.

Helps screen readers; keeps UI unchanged.

-          <button
+          <button
             key={icon.name}
             type="button"
             className="h-9 w-9 select-none text-lg grid place-items-center rounded hover:bg-custom-background-80"
+            aria-label={`Choose icon ${icon.name}`}
+            title={icon.name}
             onClick={() => {
               onChange({
                 name: icon.name,
                 color: activeColor,
               });
             }}
           >

7-7: Import MATERIAL_ICONS_LIST directly from its defining module
Replace the barrel import to improve treeshaking and avoid any indirect circular reference:

packages/ui/src/emoji/icons-list.tsx:7

- import { MATERIAL_ICONS_LIST } from "..";
+ import { MATERIAL_ICONS_LIST } from "../constants/icons";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b79b92b and 5c8b021.

📒 Files selected for processing (3)
  • packages/propel/src/emoji-icon-picker/icon/material-root.tsx (1 hunks)
  • packages/ui/src/emoji/icons-list.tsx (1 hunks)
  • packages/ui/src/emoji/logo.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/propel/src/emoji-icon-picker/icon/material-root.tsx
  • packages/ui/src/emoji/logo.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T08:14:49.260Z
Learnt from: sriramveeraghanta
PR: makeplane/plane#7697
File: apps/web/app/(all)/[workspaceSlug]/(projects)/header.tsx:12-13
Timestamp: 2025-09-02T08:14:49.260Z
Learning: The star-us-link.tsx file in apps/web/app/(all)/[workspaceSlug]/(projects)/ already has "use client" directive at the top, making it a proper Client Component for hook usage.

Applied to files:

  • packages/ui/src/emoji/icons-list.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
packages/ui/src/emoji/icons-list.tsx (1)

1-2: "use client" is appropriate here.

Needed for hooks and font loading; no concerns.

@pushya22 pushya22 merged commit 652a6cc into preview Sep 2, 2025
6 of 7 checks passed
@pushya22 pushya22 deleted the feat-replace-emoji-picker-with-frimousse branch September 2, 2025 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants