Skip to content
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

[WEB-2799] chore: global component and code refactor #6131

Merged
merged 5 commits into from
Dec 2, 2024

Conversation

anmolsinghbhatia
Copy link
Collaborator

@anmolsinghbhatia anmolsinghbhatia commented Dec 2, 2024

Changes:

This PR includes the following updates:

  • Added the Global Tabs component to the package.
  • Updated the Collapsible Button and Linear Progress Indicator components.
  • Added a Local Storage helper and Filled Icon to the package.

Reference:

[WEB-2799]

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a Tabs component for a tabbed interface with local storage support.
    • Added new icons: CommentFillIcon, EpicIcon, and InfoFillIcon.
    • Enhanced LinearProgressIndicator with new size option and styling props.
    • New useLocalStorage hook for managing local storage.
  • Improvements

    • CollapsibleButton now supports additional styling through className and titleClassName.
  • Documentation

    • Updated exports to include new components and hooks for easier access.

Copy link
Contributor

coderabbitai bot commented Dec 2, 2024

Walkthrough

The pull request introduces several enhancements across multiple components in the codebase. Key changes include the addition of new utility functions and a custom hook for managing local storage, new icon components, and a tabbed interface component. Additionally, existing components have been updated to support new properties for improved styling flexibility. The changes involve both the introduction of new files and modifications to existing exports, enhancing the overall functionality and usability of the UI package.

Changes

File Path Change Summary
packages/helpers/hooks/index.ts Added export for use-local-storage.
packages/helpers/hooks/use-local-storage.tsx Introduced getValueFromLocalStorage, setValueIntoLocalStorage, and useLocalStorage hook.
packages/ui/src/collapsible/collapsible-button.tsx Added optional className and titleClassName props to CollapsibleButton.
packages/ui/src/icons/comment-fill-icon.tsx Added CommentFillIcon component.
packages/ui/src/icons/epic-icon.tsx Added EpicIcon component.
packages/ui/src/icons/index.ts Added exports for comment-fill-icon, epic-icon, and info-icon.
packages/ui/src/icons/info-fill-icon.tsx Added InfoFillIcon component.
packages/ui/src/index.ts Added export for tabs module.
packages/ui/src/progress/linear-progress-indicator.tsx Expanded size prop and added className and barClassName props to LinearProgressIndicator.
packages/ui/src/tabs/index.ts Added export for tabs module.
packages/ui/src/tabs/tabs.tsx Introduced Tabs component with support for local storage and customizable styling.

Possibly related PRs

Suggested labels

🌟enhancement

Suggested reviewers

  • sriramveeraghanta
  • SatishGandham
  • rahulramesha

Poem

🐇 In the land of code where rabbits play,
New hooks and icons brighten the day.
Tabs now dance with a stylish flair,
Local storage keeps secrets to share.
With each little change, we hop with glee,
Enhancements abound, oh what joy to see! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai 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

Documentation and Community

  • 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.

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: 7

🧹 Outside diff range and nitpick comments (6)
packages/ui/src/icons/comment-fill-icon.tsx (1)

5-14: Add accessibility attributes to SVG icon

While the implementation is clean, consider adding accessibility attributes to improve screen reader support.

 export const CommentFillIcon: React.FC<ISvgIcons> = ({ className = "text-current", ...rest }) => (
-  <svg viewBox="0 0 24 24" className={`${className}`} xmlns="http://www.w3.org/2000/svg" {...rest}>
+  <svg
+    viewBox="0 0 24 24"
+    className={`${className}`}
+    xmlns="http://www.w3.org/2000/svg"
+    aria-hidden="true"
+    role="img"
+    {...rest}
+  >
packages/ui/src/icons/epic-icon.tsx (1)

19-26: Optimize SVG paths

The second path appears to be a duplicate of part of the first path. Consider combining them into a single path for better maintainability.

packages/ui/src/collapsible/collapsible-button.tsx (1)

11-12: Consider adding JSDoc comments for the new props.

The new className props are well-typed and follow React conventions. Consider adding JSDoc comments to document their purpose and expected values.

Add documentation like this:

type Props = {
+ /** Additional class names to apply to the container */
  className?: string;
+ /** Additional class names to apply to the title text */
  titleClassName?: string;
};
packages/ui/src/progress/linear-progress-indicator.tsx (1)

9-9: Consider using consistent size increments.

The size increments could be more consistent:

  • sm: 8px (h-2)
  • md: 12px (h-3)
  • lg: 14px (h-3.5)
  • xl: 14px (h-[14px])

Consider using standard Tailwind increments or following a consistent pattern.

-  "h-[14px]": size === "xl",
+  "h-4": size === "xl", // 16px following the increment pattern

Also applies to: 49-49

packages/ui/src/tabs/tabs.tsx (2)

8-26: Add documentation and improve type definitions

The type definitions would benefit from:

  1. JSDoc documentation
  2. More descriptive prop names
  3. Validation for required props

Consider these improvements:

+/**
+ * Represents a single tab item in the tabbed interface
+ */
 type TabItem = {
   key: string;
+  /** Optional icon component to display alongside the label */
   icon?: FC<LucideProps>;
+  /** Optional label content */
   label?: React.ReactNode;
+  /** Content to display when this tab is active */
   content: React.ReactNode;
+  /** Whether this tab is disabled */
   disabled?: boolean;
 };

+/**
+ * Props for the Tabs component
+ */
 type TTabsProps = {
+  /** Array of tab items to display */
   tabs: TabItem[];
+  /** Unique key for storing selected tab in localStorage */
   storageKey: string;
+  /** Optional actions to display next to the tabs */
   actions?: React.ReactNode;
+  /** Optional key of the default selected tab */
   defaultTab?: string;
   containerClassName?: string;
   tabListContainerClassName?: string;
   tabListClassName?: string;
   tabClassName?: string;
   tabPanelClassName?: string;
 };

45-94: Improve accessibility and performance

The render implementation needs:

  1. Better accessibility
  2. Performance optimization
  3. Simplified className handling

Consider these improvements:

 return (
+  <ErrorBoundary fallback={<div>Error loading tabs</div>}>
     <div className="flex flex-col w-full h-full">
-      <Tab.Group defaultIndex={currentTabIndex(storedValue ?? defaultTab)}>
+      <Tab.Group
+        defaultIndex={currentTabIndex(storedValue ?? effectiveDefaultTab)}
+        // Improve accessibility
+        as="div"
+        role="tablist"
+        aria-orientation="horizontal"
+      >
         <div className={cn("flex flex-col w-full h-full gap-2", containerClassName)}>
           <div className={cn("flex w-full items-center gap-4", tabListContainerClassName)}>
             <Tab.List
               as="div"
               className={cn(
                 "flex w-full min-w-fit items-center justify-between gap-1.5 rounded-md text-sm p-0.5 bg-custom-background-80/60",
                 tabListClassName
               )}
             >
-              {tabs.map((tab) => (
+              {tabs.map((tab) => {
+                const Icon = tab.icon;
+                return (
                 <Tab
                   className={({ selected }) =>
                     cn(
                       `flex items-center justify-center p-1 min-w-fit w-full font-medium text-custom-text-100 outline-none focus:outline-none cursor-pointer transition-all rounded`,
                       selected
                         ? "bg-custom-background-100 text-custom-text-100 shadow-sm"
                         : tab.disabled
                           ? "text-custom-text-400 cursor-not-allowed"
                           : "text-custom-text-400 hover:text-custom-text-300 hover:bg-custom-background-80/60",
                       tabClassName
                     )
                   }
                   key={tab.key}
                   onClick={() => {
                     if (!tab.disabled) setValue(tab.key);
                   }}
                   disabled={tab.disabled}
+                  aria-selected={selected}
+                  role="tab"
                 >
-                  {tab.icon && <tab.icon className="size-4" />}
+                  {Icon && <Icon className="size-4" aria-hidden="true" />}
                   {tab.label}
                 </Tab>
-              ))}
+              )})}
             </Tab.List>
             {actions && <div className="flex-grow">{actions}</div>}
           </div>
           <Tab.Panels as={Fragment}>
             {tabs.map((tab) => (
-              <Tab.Panel key={tab.key} as="div" className={cn("relative outline-none", tabPanelClassName)}>
+              <Tab.Panel
+                key={tab.key}
+                as="div"
+                className={cn("relative outline-none", tabPanelClassName)}
+                role="tabpanel"
+              >
                 {tab.content}
               </Tab.Panel>
             ))}
           </Tab.Panels>
         </div>
       </Tab.Group>
     </div>
+  </ErrorBoundary>
 );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 75ada1b and 6a1a32f.

📒 Files selected for processing (11)
  • packages/helpers/hooks/index.ts (1 hunks)
  • packages/helpers/hooks/use-local-storage.tsx (1 hunks)
  • packages/ui/src/collapsible/collapsible-button.tsx (2 hunks)
  • packages/ui/src/icons/comment-fill-icon.tsx (1 hunks)
  • packages/ui/src/icons/epic-icon.tsx (1 hunks)
  • packages/ui/src/icons/index.ts (1 hunks)
  • packages/ui/src/icons/info-fill-icon.tsx (1 hunks)
  • packages/ui/src/index.ts (1 hunks)
  • packages/ui/src/progress/linear-progress-indicator.tsx (3 hunks)
  • packages/ui/src/tabs/index.ts (1 hunks)
  • packages/ui/src/tabs/tabs.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • packages/helpers/hooks/index.ts
  • packages/ui/src/icons/index.ts
  • packages/ui/src/tabs/index.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/helpers/hooks/use-local-storage.tsx

[error] 4-4: Invalid typeof comparison value: this expression is not a string literal

not a string literal
Unsafe fix: Compare the result of typeof with a valid type name

(lint/suspicious/useValidTypeof)


[error] 16-16: Invalid typeof comparison value: this expression is not a string literal

not a string literal
Unsafe fix: Compare the result of typeof with a valid type name

(lint/suspicious/useValidTypeof)

🔇 Additional comments (4)
packages/ui/src/index.ts (1)

31-31: LGTM! Export addition follows package conventions.

The new tabs module export maintains consistency with existing export patterns and preserves alphabetical ordering.

packages/ui/src/collapsible/collapsible-button.tsx (1)

26-31: LGTM! Proper implementation of className customization.

The className composition is well-implemented:

  • Preserves base styles while allowing customization
  • Uses cn helper correctly for class merging
  • Maintains existing functionality

Also applies to: 41-41

packages/ui/src/progress/linear-progress-indicator.tsx (2)

52-54: LGTM! Clean implementation of container customization.

The container class composition is well-implemented:

  • Preserves base styles
  • Uses cn helper correctly
  • Maintains existing background and rounded corners

38-38: Verify the impact of new bar class names.

The addition of rounded corners to individual bars might affect existing implementations.

✅ Verification successful

Let me analyze the usage of LinearProgressIndicator to verify if the new bar class names would have any impact.

From the search results, I can see that LinearProgressIndicator is used in three locations:

  1. module-card-item.tsx
  2. cycles/active-cycle/progress.tsx
  3. The component definition itself

In both usage locations, they don't override the bar styling through any custom className props. Let me verify if there's a barClassName prop being passed anywhere.


Let me try a different approach to search for barClassName usage.


The new bar class names are safe to implement

The rounded corners addition through first:rounded-l-sm last:rounded-r-sm classes is safe as:

  • The barClassName prop is optional and defaults to an empty string
  • No existing implementations are passing custom barClassName props
  • The new classes only affect the corners and won't interfere with other styling properties
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for existing usage of LinearProgressIndicator to verify styling impact
rg -l "LinearProgressIndicator" --type tsx --type jsx | xargs rg -A 5 "LinearProgressIndicator"

Length of output: 2980


Script:

#!/bin/bash
# Search for any usage of barClassName prop
rg "barClassName" --type tsx --type jsx

Length of output: 67


Script:

#!/bin/bash
# Search for barClassName without file type restriction
rg "barClassName"

Length of output: 329

Comment on lines +5 to +14
export const InfoFillIcon: React.FC<ISvgIcons> = ({ className = "text-current", ...rest }) => (
<svg viewBox="0 0 24 24" className={`${className}`} xmlns="http://www.w3.org/2000/svg" {...rest}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.25 12C2.25 6.615 6.615 2.25 12 2.25C17.385 2.25 21.75 6.615 21.75 12C21.75 17.385 17.385 21.75 12 21.75C6.615 21.75 2.25 17.385 2.25 12ZM10.956 10.558C12.102 9.985 13.393 11.021 13.082 12.264L12.373 15.1L12.415 15.08C12.5912 15.0025 12.7905 14.9958 12.9715 15.0612C13.1526 15.1265 13.3016 15.259 13.3877 15.4312C13.4737 15.6033 13.4903 15.802 13.434 15.9861C13.3777 16.1702 13.2527 16.3255 13.085 16.42L13.045 16.442C11.898 17.015 10.607 15.979 10.918 14.736L11.628 11.9L11.586 11.92C11.4975 11.9692 11.4 11.9999 11.2994 12.0104C11.1987 12.0209 11.097 12.0109 11.0003 11.981C10.9036 11.9511 10.8139 11.902 10.7367 11.8366C10.6595 11.7711 10.5964 11.6907 10.551 11.6002C10.5057 11.5098 10.4792 11.411 10.4731 11.31C10.4669 11.209 10.4813 11.1078 10.5153 11.0124C10.5493 10.9171 10.6022 10.8297 10.6709 10.7553C10.7396 10.681 10.8226 10.6214 10.915 10.58L10.956 10.558ZM12 9C12.1989 9 12.3897 8.92098 12.5303 8.78033C12.671 8.63968 12.75 8.44891 12.75 8.25C12.75 8.05109 12.671 7.86032 12.5303 7.71967C12.3897 7.57902 12.1989 7.5 12 7.5C11.8011 7.5 11.6103 7.57902 11.4697 7.71967C11.329 7.86032 11.25 8.05109 11.25 8.25C11.25 8.44891 11.329 8.63968 11.4697 8.78033C11.6103 8.92098 11.8011 9 12 9Z"
fill="currentColor"
/>
</svg>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider icon component abstraction and add accessibility

  1. Add accessibility attributes as suggested for CommentFillIcon.
  2. Consider creating a base icon component to reduce duplication across icon components.
 export const InfoFillIcon: React.FC<ISvgIcons> = ({ className = "text-current", ...rest }) => (
-  <svg viewBox="0 0 24 24" className={`${className}`} xmlns="http://www.w3.org/2000/svg" {...rest}>
+  <svg
+    viewBox="0 0 24 24"
+    className={`${className}`}
+    xmlns="http://www.w3.org/2000/svg"
+    aria-hidden="true"
+    role="img"
+    {...rest}
+  >

Consider creating a base icon component:

type BaseIconProps = ISvgIcons & {
  children: React.ReactNode;
  viewBox?: string;
};

const BaseIcon: React.FC<BaseIconProps> = ({
  className = "text-current",
  viewBox = "0 0 24 24",
  children,
  ...rest
}) => (
  <svg
    viewBox={viewBox}
    className={className}
    xmlns="http://www.w3.org/2000/svg"
    aria-hidden="true"
    role="img"
    {...rest}
  >
    {children}
  </svg>
);

Comment on lines +3 to +8
export type Props = {
className?: string;
width?: string | number;
height?: string | number;
color?: string;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Standardize props interface with other icons

The component uses a custom Props type while other icons use ISvgIcons. Also, the color prop is defined but unused.

-export type Props = {
-  className?: string;
-  width?: string | number;
-  height?: string | number;
-  color?: string;
-};
+import { ISvgIcons } from "./type";

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +10 to +28
export const EpicIcon: React.FC<Props> = ({ width = "16", height = "16", className }) => (
<svg
width={width}
height={height}
className={className}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.900146 9.33203V12.0142C0.900146 12.3736 1.17392 12.6654 1.51126 12.6654H14.9557C15.1178 12.6654 15.2732 12.5968 15.3878 12.4746C15.5024 12.3525 15.5668 12.1869 15.5668 12.0142V10.3299L13.375 7.99523C13.1458 7.75134 12.8351 7.61436 12.5113 7.61436C12.1874 7.61436 11.8767 7.75134 11.6476 7.99523L10.1257 9.35919L10.2534 9.56204L11.7209 9.60056C11.7809 9.66017 11.8291 9.73206 11.8625 9.81194C11.8959 9.89181 11.9138 9.97804 11.9153 10.0655C11.9167 10.1529 11.9017 10.2397 11.8709 10.3208C11.8402 10.4019 11.7944 10.4756 11.7364 10.5374C11.6784 10.5992 11.6092 10.648 11.5332 10.6807C11.4571 10.7135 11.3756 10.7296 11.2935 10.728C11.2114 10.7265 11.1305 10.7073 11.0556 10.6717C10.9806 10.6362 10.9131 10.5848 10.8572 10.5209L10.2534 9.56204L6.60385 3.76614C6.37468 3.52226 6.11293 3.33203 5.78904 3.33203C5.46515 3.33203 5.20339 3.52226 4.97422 3.76614L0.900146 9.33203Z"
fill="currentColor"
/>
<path
d="M11.7209 9.60056L10.2534 9.56204L10.8572 10.5209C10.9131 10.5848 10.9806 10.6362 11.0556 10.6717C11.1305 10.7073 11.2114 10.7265 11.2935 10.728C11.3756 10.7296 11.4571 10.7135 11.5332 10.6807C11.6092 10.648 11.6784 10.5992 11.7364 10.5374C11.7944 10.4756 11.8402 10.4019 11.8709 10.3208C11.9017 10.2397 11.9167 10.1529 11.9153 10.0655C11.9138 9.97804 11.8959 9.89181 11.8625 9.81194C11.8291 9.73206 11.7809 9.66017 11.7209 9.60056Z"
fill="currentColor"
/>
</svg>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize SVG structure and add accessibility

  1. Use ISvgIcons interface for consistency
  2. Remove redundant fill="none" since paths use currentColor
  3. Add accessibility attributes
-export const EpicIcon: React.FC<Props> = ({ width = "16", height = "16", className }) => (
+export const EpicIcon: React.FC<ISvgIcons> = ({ className = "text-current", ...rest }) => (
   <svg
-    width={width}
-    height={height}
     className={className}
     viewBox="0 0 16 16"
-    fill="none"
     xmlns="http://www.w3.org/2000/svg"
+    aria-hidden="true"
+    role="img"
+    {...rest}
   >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const EpicIcon: React.FC<Props> = ({ width = "16", height = "16", className }) => (
<svg
width={width}
height={height}
className={className}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.900146 9.33203V12.0142C0.900146 12.3736 1.17392 12.6654 1.51126 12.6654H14.9557C15.1178 12.6654 15.2732 12.5968 15.3878 12.4746C15.5024 12.3525 15.5668 12.1869 15.5668 12.0142V10.3299L13.375 7.99523C13.1458 7.75134 12.8351 7.61436 12.5113 7.61436C12.1874 7.61436 11.8767 7.75134 11.6476 7.99523L10.1257 9.35919L10.2534 9.56204L11.7209 9.60056C11.7809 9.66017 11.8291 9.73206 11.8625 9.81194C11.8959 9.89181 11.9138 9.97804 11.9153 10.0655C11.9167 10.1529 11.9017 10.2397 11.8709 10.3208C11.8402 10.4019 11.7944 10.4756 11.7364 10.5374C11.6784 10.5992 11.6092 10.648 11.5332 10.6807C11.4571 10.7135 11.3756 10.7296 11.2935 10.728C11.2114 10.7265 11.1305 10.7073 11.0556 10.6717C10.9806 10.6362 10.9131 10.5848 10.8572 10.5209L10.2534 9.56204L6.60385 3.76614C6.37468 3.52226 6.11293 3.33203 5.78904 3.33203C5.46515 3.33203 5.20339 3.52226 4.97422 3.76614L0.900146 9.33203Z"
fill="currentColor"
/>
<path
d="M11.7209 9.60056L10.2534 9.56204L10.8572 10.5209C10.9131 10.5848 10.9806 10.6362 11.0556 10.6717C11.1305 10.7073 11.2114 10.7265 11.2935 10.728C11.3756 10.7296 11.4571 10.7135 11.5332 10.6807C11.6092 10.648 11.6784 10.5992 11.7364 10.5374C11.7944 10.4756 11.8402 10.4019 11.8709 10.3208C11.9017 10.2397 11.9167 10.1529 11.9153 10.0655C11.9138 9.97804 11.8959 9.89181 11.8625 9.81194C11.8291 9.73206 11.7809 9.66017 11.7209 9.60056Z"
fill="currentColor"
/>
</svg>
);
export const EpicIcon: React.FC<ISvgIcons> = ({ className = "text-current", ...rest }) => (
<svg
className={className}
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
{...rest}
>
<path
d="M0.900146 9.33203V12.0142C0.900146 12.3736 1.17392 12.6654 1.51126 12.6654H14.9557C15.1178 12.6654 15.2732 12.5968 15.3878 12.4746C15.5024 12.3525 15.5668 12.1869 15.5668 12.0142V10.3299L13.375 7.99523C13.1458 7.75134 12.8351 7.61436 12.5113 7.61436C12.1874 7.61436 11.8767 7.75134 11.6476 7.99523L10.1257 9.35919L10.2534 9.56204L11.7209 9.60056C11.7809 9.66017 11.8291 9.73206 11.8625 9.81194C11.8959 9.89181 11.9138 9.97804 11.9153 10.0655C11.9167 10.1529 11.9017 10.2397 11.8709 10.3208C11.8402 10.4019 11.7944 10.4756 11.7364 10.5374C11.6784 10.5992 11.6092 10.648 11.5332 10.6807C11.4571 10.7135 11.3756 10.7296 11.2935 10.728C11.2114 10.7265 11.1305 10.7073 11.0556 10.6717C10.9806 10.6362 10.9131 10.5848 10.8572 10.5209L10.2534 9.56204L6.60385 3.76614C6.37468 3.52226 6.11293 3.33203 5.78904 3.33203C5.46515 3.33203 5.20339 3.52226 4.97422 3.76614L0.900146 9.33203Z"
fill="currentColor"
/>
<path
d="M11.7209 9.60056L10.2534 9.56204L10.8572 10.5209C10.9131 10.5848 10.9806 10.6362 11.0556 10.6717C11.1305 10.7073 11.2114 10.7265 11.2935 10.728C11.3756 10.7296 11.4571 10.7135 11.5332 10.6807C11.6092 10.648 11.6784 10.5992 11.7364 10.5374C11.7944 10.4756 11.8402 10.4019 11.8709 10.3208C11.9017 10.2397 11.9167 10.1529 11.9153 10.0655C11.9138 9.97804 11.8959 9.89181 11.8625 9.81194C11.8291 9.73206 11.7809 9.66017 11.7209 9.60056Z"
fill="currentColor"
/>
</svg>
);

Comment on lines +3 to +13
export const getValueFromLocalStorage = (key: string, defaultValue: any) => {
if (typeof window === undefined || typeof window === "undefined")
return defaultValue;
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
window.localStorage.removeItem(key);
return defaultValue;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve type safety and error handling

Several improvements can be made to this function:

  1. The typeof check is incorrect
  2. JSON.parse result needs type safety
  3. Error handling could be more informative

Apply these changes:

-export const getValueFromLocalStorage = (key: string, defaultValue: any) => {
-  if (typeof window === undefined || typeof window === "undefined")
+export const getValueFromLocalStorage = <T>(key: string, defaultValue: T): T => {
+  if (typeof window === "undefined")
     return defaultValue;
   try {
     const item = window.localStorage.getItem(key);
-    return item ? JSON.parse(item) : defaultValue;
+    return item ? (JSON.parse(item) as T) : defaultValue;
   } catch (error) {
+    console.warn(`Error reading ${key} from localStorage:`, error);
     window.localStorage.removeItem(key);
     return defaultValue;
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getValueFromLocalStorage = (key: string, defaultValue: any) => {
if (typeof window === undefined || typeof window === "undefined")
return defaultValue;
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
window.localStorage.removeItem(key);
return defaultValue;
}
};
export const getValueFromLocalStorage = <T>(key: string, defaultValue: T): T => {
if (typeof window === "undefined")
return defaultValue;
try {
const item = window.localStorage.getItem(key);
return item ? (JSON.parse(item) as T) : defaultValue;
} catch (error) {
console.warn(`Error reading ${key} from localStorage:`, error);
window.localStorage.removeItem(key);
return defaultValue;
}
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 4-4: Invalid typeof comparison value: this expression is not a string literal

not a string literal
Unsafe fix: Compare the result of typeof with a valid type name

(lint/suspicious/useValidTypeof)

Comment on lines +15 to +24
export const setValueIntoLocalStorage = (key: string, value: any) => {
if (typeof window === undefined || typeof window === "undefined")
return false;
try {
window.localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
return false;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add type safety and improve error handling

Similar improvements needed as the get function:

  1. Fix typeof check
  2. Add type safety
  3. Improve error handling

Apply these changes:

-export const setValueIntoLocalStorage = (key: string, value: any) => {
-  if (typeof window === undefined || typeof window === "undefined")
+export const setValueIntoLocalStorage = <T>(key: string, value: T): boolean => {
+  if (typeof window === "undefined")
     return false;
   try {
     window.localStorage.setItem(key, JSON.stringify(value));
     return true;
   } catch (error) {
+    console.warn(`Error writing ${key} to localStorage:`, error);
     return false;
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const setValueIntoLocalStorage = (key: string, value: any) => {
if (typeof window === undefined || typeof window === "undefined")
return false;
try {
window.localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
return false;
}
};
export const setValueIntoLocalStorage = <T>(key: string, value: T): boolean => {
if (typeof window === "undefined")
return false;
try {
window.localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
console.warn(`Error writing ${key} to localStorage:`, error);
return false;
}
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 16-16: Invalid typeof comparison value: this expression is not a string literal

not a string literal
Unsafe fix: Compare the result of typeof with a valid type name

(lint/suspicious/useValidTypeof)

Comment on lines +26 to +59
export const useLocalStorage = <T,>(key: string, initialValue: T) => {
const [storedValue, setStoredValue] = useState<T | null>(() =>
getValueFromLocalStorage(key, initialValue)
);

const setValue = useCallback(
(value: T) => {
window.localStorage.setItem(key, JSON.stringify(value));
setStoredValue(value);
window.dispatchEvent(new Event(`local-storage:${key}`));
},
[key]
);

const clearValue = useCallback(() => {
window.localStorage.removeItem(key);
setStoredValue(null);
window.dispatchEvent(new Event(`local-storage:${key}`));
}, [key]);

const reHydrate = useCallback(() => {
const data = getValueFromLocalStorage(key, initialValue);
setStoredValue(data);
}, [key, initialValue]);

useEffect(() => {
window.addEventListener(`local-storage:${key}`, reHydrate);
return () => {
window.removeEventListener(`local-storage:${key}`, reHydrate);
};
}, [key, reHydrate]);

return { storedValue, setValue, clearValue } as const;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and type safety in the hook

Several improvements needed in the hook implementation:

  1. Add error handling for localStorage operations
  2. Add type safety for custom events
  3. Ensure proper cleanup of event listeners

Apply these changes:

 export const useLocalStorage = <T,>(key: string, initialValue: T) => {
   const [storedValue, setStoredValue] = useState<T | null>(() =>
     getValueFromLocalStorage(key, initialValue)
   );

   const setValue = useCallback(
     (value: T) => {
+      try {
         window.localStorage.setItem(key, JSON.stringify(value));
         setStoredValue(value);
         window.dispatchEvent(new Event(`local-storage:${key}`));
+      } catch (error) {
+        console.warn(`Error in setValue for ${key}:`, error);
+      }
     },
     [key]
   );

   const clearValue = useCallback(() => {
+    try {
       window.localStorage.removeItem(key);
       setStoredValue(null);
       window.dispatchEvent(new Event(`local-storage:${key}`));
+    } catch (error) {
+      console.warn(`Error in clearValue for ${key}:`, error);
+    }
   }, [key]);

   const reHydrate = useCallback(() => {
     const data = getValueFromLocalStorage(key, initialValue);
     setStoredValue(data);
   }, [key, initialValue]);

   useEffect(() => {
+    const eventName = `local-storage:${key}`;
+    const handler = () => reHydrate();
+
-    window.addEventListener(`local-storage:${key}`, reHydrate);
+    window.addEventListener(eventName, handler);
     return () => {
-      window.removeEventListener(`local-storage:${key}`, reHydrate);
+      window.removeEventListener(eventName, handler);
     };
   }, [key, reHydrate]);

   return { storedValue, setValue, clearValue } as const;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const useLocalStorage = <T,>(key: string, initialValue: T) => {
const [storedValue, setStoredValue] = useState<T | null>(() =>
getValueFromLocalStorage(key, initialValue)
);
const setValue = useCallback(
(value: T) => {
window.localStorage.setItem(key, JSON.stringify(value));
setStoredValue(value);
window.dispatchEvent(new Event(`local-storage:${key}`));
},
[key]
);
const clearValue = useCallback(() => {
window.localStorage.removeItem(key);
setStoredValue(null);
window.dispatchEvent(new Event(`local-storage:${key}`));
}, [key]);
const reHydrate = useCallback(() => {
const data = getValueFromLocalStorage(key, initialValue);
setStoredValue(data);
}, [key, initialValue]);
useEffect(() => {
window.addEventListener(`local-storage:${key}`, reHydrate);
return () => {
window.removeEventListener(`local-storage:${key}`, reHydrate);
};
}, [key, reHydrate]);
return { storedValue, setValue, clearValue } as const;
};
export const useLocalStorage = <T,>(key: string, initialValue: T) => {
const [storedValue, setStoredValue] = useState<T | null>(() =>
getValueFromLocalStorage(key, initialValue)
);
const setValue = useCallback(
(value: T) => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
setStoredValue(value);
window.dispatchEvent(new Event(`local-storage:${key}`));
} catch (error) {
console.warn(`Error in setValue for ${key}:`, error);
}
},
[key]
);
const clearValue = useCallback(() => {
try {
window.localStorage.removeItem(key);
setStoredValue(null);
window.dispatchEvent(new Event(`local-storage:${key}`));
} catch (error) {
console.warn(`Error in clearValue for ${key}:`, error);
}
}, [key]);
const reHydrate = useCallback(() => {
const data = getValueFromLocalStorage(key, initialValue);
setStoredValue(data);
}, [key, initialValue]);
useEffect(() => {
const eventName = `local-storage:${key}`;
const handler = () => reHydrate();
window.addEventListener(eventName, handler);
return () => {
window.removeEventListener(eventName, handler);
};
}, [key, reHydrate]);
return { storedValue, setValue, clearValue } as const;
};

Comment on lines +28 to +44
export const Tabs: FC<TTabsProps> = (props: TTabsProps) => {
const {
tabs,
storageKey,
actions,
defaultTab = tabs[0]?.key,
containerClassName = "",
tabListContainerClassName = "",
tabListClassName = "",
tabClassName = "",
tabPanelClassName = "",
} = props;
// local storage
const { storedValue, setValue } = useLocalStorage(`tab-${storageKey}`, defaultTab);

const currentTabIndex = (tabKey: string): number => tabs.findIndex((tab) => tab.key === tabKey);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling and validation

The component needs better error handling:

  1. Add error boundary
  2. Handle empty tabs array
  3. Validate defaultTab value

Consider these improvements:

+import { ErrorBoundary } from "react-error-boundary";

 export const Tabs: FC<TTabsProps> = (props: TTabsProps) => {
   const {
     tabs,
     storageKey,
     actions,
-    defaultTab = tabs[0]?.key,
+    defaultTab,
     containerClassName = "",
     tabListContainerClassName = "",
     tabListClassName = "",
     tabClassName = "",
     tabPanelClassName = "",
   } = props;

+  if (!tabs.length) {
+    return null;
+  }

+  const effectiveDefaultTab = defaultTab || tabs[0].key;
+
   // local storage
-  const { storedValue, setValue } = useLocalStorage(`tab-${storageKey}`, defaultTab);
+  const { storedValue, setValue } = useLocalStorage(`tab-${storageKey}`, effectiveDefaultTab);

   const currentTabIndex = (tabKey: string): number => tabs.findIndex((tab) => tab.key === tabKey);

Committable suggestion skipped: line range outside the PR's diff.

@pushya22 pushya22 merged commit 1b90339 into preview Dec 2, 2024
14 of 15 checks passed
@pushya22 pushya22 deleted the dev-global-component-and-refactor branch December 2, 2024 07:52
@sriramveeraghanta sriramveeraghanta modified the milestones: v0.24.0, v0.24.1 Dec 2, 2024
@coderabbitai coderabbitai bot mentioned this pull request Jan 22, 2025
6 tasks
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.

4 participants