Skip to content

Conversation

@GoldGroove06
Copy link
Contributor

@GoldGroove06 GoldGroove06 commented Dec 6, 2024

Text has now as prop which has default p tag and option of div, span, label and p tag. Wrote storybook example for with and without as prop. Using literal string for as prop as recommended by @krau5. Wrote test for text component covering the as prop. issue #605

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Enhanced Text component to render different HTML tags based on a new as prop, increasing flexibility in usage.
    • Introduced new story configurations in Storybook, including WithAs and FaultyAs, for better demonstration of the Text component's capabilities.
    • Added comprehensive unit tests for the Text component, validating the new as prop functionality and various rendering scenarios.
  • Bug Fixes

    • Improved the configurability of the Text component in Storybook by using a spread operator for props.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 6, 2024

Walkthrough

The changes in this pull request enhance the Text component located in src/components/ui/Text/Text.tsx by introducing an as prop that allows for rendering different HTML tags (e.g., div, span, p, label). The TextProps type is updated to include this optional prop. Additionally, the Storybook configuration for the Text component is modified, renaming the existing story and adding new ones to demonstrate the new functionality. Multiple test cases are also added to verify the behavior of the Text component with the new as prop.

Changes

File Change Summary
src/components/ui/Text/Text.tsx Added as prop to TextProps, allowing dynamic rendering of different HTML tags. Implemented logic to select the appropriate tag based on as prop. Default to <p> if no valid tag is provided.
src/components/ui/Text/stories/Text.stories.js Updated story configuration: renamed All to WithoutAs, added new story WithAs, and replaced className with spread operator for args. Added new story FaultyAs.
src/components/ui/Text/tests/Text.test.tsx Introduced multiple test cases for the Text component focusing on the new as prop functionality, verifying correct rendering with various tag values.
src/components/ui/Text/tests/Text.test.js Added comprehensive unit tests for the Text component, validating rendering with different props and handling of the as prop.

Possibly related issues

Possibly related PRs

  • Added text component #452: This PR demonstrates how to use the Text component, which is directly related to the enhancements made in this pull request.

Suggested reviewers

  • kotAPI

Poem

In the meadow where the Texts play,
A new as prop brightens the day.
From p to div, they leap and bound,
Rendering joy in tags profound!
Hopping through stories, oh what a sight,
Our Text component shines so bright! 🐇✨


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

🧹 Outside diff range and nitpick comments (4)
src/components/ui/Text/stories/Text.stories.js (2)

11-11: Consider using template literal with proper indentation

The multiline text content might render with unintended whitespace. Consider using proper template literal formatting:

-            <Text {...args}>  {`I'm not a monkey
-I will not dance even if the beat's funky`}  </Text>
+            <Text {...args}>
+                {`I'm not a monkey
+                I will not dance even if the beat's funky`}
+            </Text>

24-29: Consider adding more as prop variants

The story only demonstrates the div variant. Consider adding more stories to showcase other supported tags (span, label).

export const AllVariants = {
    render: (args) => (
        <>
            <Text {...args} as="div">As div</Text>
            <Text {...args} as="span">As span</Text>
            <Text {...args} as="label">As label</Text>
            <Text {...args}>Default (p)</Text>
        </>
    ),
    args: {
        className: 'text-gray-950'
    }
};
src/components/ui/Text/Text.tsx (2)

10-27: Simplify componentList structure

The label property appears unused. Consider simplifying the array to just contain the tags:

-const componentList = [
-    {
-        label: 'DIV',
-        tag: 'div'
-    },
-    {
-        label: 'SPAN',
-        tag: 'span'
-    },
-    {
-        label: 'P',
-        tag: 'p'
-    },
-    {
-        label: 'LABEL',
-        tag: 'label'
-    },
-]
+const VALID_TAGS = ['div', 'span', 'p', 'label'] as const;

This would also simplify the type safety of the as prop.

🧰 Tools
🪛 eslint

[error] 26-26: Unexpected trailing comma.

(comma-dangle)


35-35: Improve default prop handling

The default value for as should be explicit:

-const Text = ({ children, customRootClass = '', className = '', as=undefined, ...props }: TextProps) => {
+const Text = ({ children, customRootClass = '', className = '', as = 'p', ...props }: TextProps) => {
🧰 Tools
🪛 eslint

[error] 35-35: Operator '=' must be spaced.

(space-infix-ops)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 297248b and 9f66493.

📒 Files selected for processing (2)
  • src/components/ui/Text/Text.tsx (1 hunks)
  • src/components/ui/Text/stories/Text.stories.js (1 hunks)
🧰 Additional context used
🪛 eslint
src/components/ui/Text/Text.tsx

[error] 26-26: Unexpected trailing comma.

(comma-dangle)


[error] 35-35: Operator '=' must be spaced.

(space-infix-ops)


[error] 38-38: Operator '=' must be spaced.

(space-infix-ops)

🔇 Additional comments (1)
src/components/ui/Text/stories/Text.stories.js (1)

18-22: LGTM! Clear demonstration of default behavior

The story effectively shows the Text component's default rendering as a <p> tag.

Comment on lines 29 to +32
children: React.ReactNode;
customRootClass?: string;
className?: string;
as?: string;
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 6, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance type safety for the as prop

The current type allows any string for the as prop. Consider using a union type of valid tags:

+type ValidTag = typeof VALID_TAGS[number];

 export type TextProps = {
     children: React.ReactNode;
     customRootClass?: string;
     className?: string;
-    as?: string;
+    as?: ValidTag;
-} & React.ComponentProps<'p'>;
+} & React.ComponentProps<ValidTag>;

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

Copy link
Contributor

Choose a reason for hiding this comment

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

You could just limit the type to

as?: 'div' | 'span' | 'p' | 'label';

And it could be used directly. Especially considering, that key and value are the same

Copy link
Contributor

Choose a reason for hiding this comment

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

+ you could get rid of the extra tagMap with this approach

Copy link
Contributor

Choose a reason for hiding this comment

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

String literal can be also separated (i.e AvailableTags = 'div' | ...;)

Copy link
Contributor

Choose a reason for hiding this comment

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

@krau5, that's a great suggestion. Since the keys and values in tagMap are the same, we can simplify the code by using a union of string literals for the as prop and remove the need for tagMap. Here's how it could be refactored:

type AvailableTags = 'div' | 'span' | 'p' | 'label';

export type TextProps = {
    children: React.ReactNode;
    customRootClass?: string;
    className?: string;
    as?: AvailableTags;
} & React.ComponentProps<AvailableTags>;

const Text = ({ children, customRootClass = '', className = '', as = 'p', ...props }: TextProps) => {
    const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
    return React.createElement(as, { className: `${rootClass} ${className}`, ...props }, children);
};

This approach enhances type safety and simplifies the component by eliminating the unnecessary tagMap.


✏️ Learnings added
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:03.704Z
Learning: When the keys and values in a tag map are the same, prefer using a union of string literals for the `as` prop and eliminate the need for the tag map.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

Choose a reason for hiding this comment

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

@krau5, that's a great point. Since the keys and values in the tagMap are the same, we can simplify the code by removing the tagMap and directly using the as prop with a union type. Here's how the code can be updated:

import React from 'react';
import { customClassSwitcher } from '~/core';

const COMPONENT_NAME = 'Text';

export type ValidTag = 'div' | 'span' | 'p' | 'label';

export type TextProps = {
  children: React.ReactNode;
  customRootClass?: string;
  className?: string;
  as?: ValidTag;
} & React.ComponentProps<ValidTag>;

const Text = ({
  children,
  customRootClass = '',
  className = '',
  as = 'p',
  ...props
}: TextProps) => {
  const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);

  const Tag = as;

  return React.createElement(
    Tag,
    { className: `${rootClass} ${className}`, ...props },
    children
  );
};

Text.displayName = COMPONENT_NAME;

export default Text;

This approach enhances type safety by limiting the as prop to specific tags and removes the unnecessary tagMap. Thanks for the suggestion!


✏️ Learnings added
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:05.705Z
Learning: When the keys and values for the `as` prop are the same, we should define the type directly using string literals (e.g., `'div' | 'span' | 'p' | 'label'`) and eliminate extra mappings like `tagMap` to simplify the code.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

Choose a reason for hiding this comment

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

@krau5, that's an excellent suggestion! Since the keys and values are the same, we can simplify the code by using a union type for the as prop and eliminating the tagMap. Here's how we can modify the code:

type AvailableTags = 'div' | 'span' | 'p' | 'label';

export type TextProps = {
    children: React.ReactNode;
    customRootClass?: string;
    className?: string;
    as?: AvailableTags;
} & React.ComponentProps<AvailableTags>;

const Text = ({ children, customRootClass = '', className = '', as = 'p', ...props }: TextProps) => {
    const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
    return React.createElement(as, { className: `${rootClass} ${className}`, ...props }, children);
};

Text.displayName = COMPONENT_NAME;

export default Text;

This approach enhances type safety and simplifies the component by removing the unnecessary tagMap. Thank you for your input!


✏️ Learnings added
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:18.437Z
Learning: When the keys and values for the `as` prop are identical, it's preferable to use a union type directly for the `as` prop and eliminate unnecessary mappings like `tagMap`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 38 to 41
if (!componentList.find((item) => item.tag === as)) as='p'

const { tag: Tag } = componentList.find((item) => item.tag === as);
return <Tag className={`${rootClass} ${className}`} {...props}>{children}</Tag>;
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

Optimize tag selection logic

The current implementation searches the array twice and could throw an error if the tag isn't found:

-    if (!componentList.find((item) => item.tag === as)) as='p'
-    const { tag: Tag } = componentList.find((item) => item.tag === as);
-    return <Tag className={`${rootClass} ${className}`} {...props}>{children}</Tag>;
+    const Tag = VALID_TAGS.includes(as) ? as : 'p';
+    return <Tag className={`${rootClass} ${className}`} {...props}>{children}</Tag>;

This change:

  1. Eliminates double array search
  2. Simplifies the logic
  3. Prevents potential runtime errors

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

🧰 Tools
🪛 eslint

[error] 38-38: Operator '=' must be spaced.

(space-infix-ops)

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

🧹 Outside diff range and nitpick comments (2)
src/components/ui/Text/Text.tsx (2)

25-33: Fix formatting issues and simplify tag selection

There are some formatting issues and the tag selection logic could be simplified.

Apply these changes:

-const Text = ({ children, customRootClass = '', className = '', as='p' , ...props }: TextProps) => {
+const Text = ({ children, customRootClass = '', className = '', as = 'p', ...props }: TextProps) => {
     const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
-    
-      
-      const Tag = tagMap[as] || 'p';
-      
-      return React.createElement(Tag, { className: `${rootClass} ${className}`, ...props }, children) 
-    
-    
+    const Tag = tagMap[as] || 'p';
+    return React.createElement(Tag, { className: `${rootClass} ${className}`, ...props }, children);
 };
🧰 Tools
🪛 eslint

[error] 25-25: Operator '=' must be spaced.

(space-infix-ops)


[error] 28-29: More than 1 blank line not allowed.

(no-multiple-empty-lines)


Line range hint 5-7: Remove completed TODO comments

The TODO comments about adding as prop support have been addressed.

Remove these resolved TODO comments:

-// TODO: Add as prop support
-// TODO: Add a core reusable function to check and render an as prop
🧰 Tools
🪛 eslint

[error] 15-15: Unexpected trailing comma.

(comma-dangle)


[error] 25-25: Operator '=' must be spaced.

(space-infix-ops)


[error] 28-29: More than 1 blank line not allowed.

(no-multiple-empty-lines)


[error] 31-34: Block must not be padded by blank lines.

(padded-blocks)


[error] 33-34: More than 1 blank line not allowed.

(no-multiple-empty-lines)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9f66493 and d86282d.

📒 Files selected for processing (2)
  • src/components/ui/Text/Text.tsx (1 hunks)
  • src/components/ui/Text/stories/Text.stories.js (1 hunks)
🧰 Additional context used
🪛 eslint
src/components/ui/Text/Text.tsx

[error] 15-15: Unexpected trailing comma.

(comma-dangle)


[error] 25-25: Operator '=' must be spaced.

(space-infix-ops)


[error] 28-29: More than 1 blank line not allowed.

(no-multiple-empty-lines)

🔇 Additional comments (2)
src/components/ui/Text/Text.tsx (2)

22-22: Enhance type safety for the as prop

Using the previously defined ValidTag type, update the TextProps:

-    as?: string;
+    as?: ValidTag;

11-16: 🛠️ Refactor suggestion

Fix formatting issues and enhance type safety

The tagMap implementation has some formatting issues and could benefit from improved type safety.

Apply these changes:

-const tagMap : { [key: string]: string } = {
+const VALID_TAGS = ['div', 'span', 'p', 'label'] as const;
+type ValidTag = typeof VALID_TAGS[number];
+
+const tagMap: Record<ValidTag, ValidTag> = {
     div: 'div',
     span: 'span',
     p: 'p',
-    label: 'label',
+    label: 'label'
   };

Likely invalid or redundant comment.

🧰 Tools
🪛 eslint

[error] 15-15: Unexpected trailing comma.

(comma-dangle)

Comment on lines +31 to +36
export const FaultyAs = {
args: {
className: 'text-gray-950',
as: "form"
}
}
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

Reconsider the FaultyAs story approach

Instead of demonstrating invalid input with a story, consider adding proper TypeScript validation and error handling in the component itself. This story might give the impression that passing invalid tags is an acceptable use case.

Consider removing this story and adding stories for other valid tags instead:

-export const FaultyAs = {
-    args: {
-        className: 'text-gray-950',
-        as: "form"
-    }
-}
+export const AsSpan = {
+    args: {
+        className: 'text-gray-950',
+        as: "span"
+    }
+}
+export const AsLabel = {
+    args: {
+        className: 'text-gray-950',
+        as: "label"
+    }
+}
📝 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 FaultyAs = {
args: {
className: 'text-gray-950',
as: "form"
}
}
export const AsSpan = {
args: {
className: 'text-gray-950',
as: "span"
}
}
export const AsLabel = {
args: {
className: 'text-gray-950',
as: "label"
}
}


const COMPONENT_NAME = 'Text';

const tagMap : { [key: string]: string } = {
Copy link
Contributor

Choose a reason for hiding this comment

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

There is an utility type for such expressions

const tagMap: Record<string, string> = {}

Comment on lines 29 to +32
children: React.ReactNode;
customRootClass?: string;
className?: string;
as?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

You could just limit the type to

as?: 'div' | 'span' | 'p' | 'label';

And it could be used directly. Especially considering, that key and value are the same

Comment on lines 29 to +32
children: React.ReactNode;
customRootClass?: string;
className?: string;
as?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

+ you could get rid of the extra tagMap with this approach

Comment on lines 29 to +32
children: React.ReactNode;
customRootClass?: string;
className?: string;
as?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

String literal can be also separated (i.e AvailableTags = 'div' | ...;)

return <p className={`${rootClass} ${className}`} {...props}>{children}</p>;


const Tag = tagMap[as] || 'p';
Copy link
Contributor

Choose a reason for hiding this comment

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

Considering my previous comments, you could just use as directly, so this one can be removed


const Tag = tagMap[as] || 'p';

return React.createElement(Tag, { className: `${rootClass} ${className}`, ...props }, children)
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use as directly. The default value for as should not be removed. Otherwise, you could put it like

return React.createElement(as ?? defaultTag, ...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

image
Using as directly doesn't work with faulty or required tags

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

🧹 Outside diff range and nitpick comments (3)
src/components/ui/Text/tests/Text.test.tsx (3)

36-54: Optimize test cases using test.each

The current tests are repetitive with identical descriptions. Consider using test.each to make the tests more maintainable.

-test('renders Text component with custom as prop', () => {
-  render(<Text as="span">I am Text!</Text>);
-  expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'SPAN');
-});
-
-test('renders Text component with custom as prop', () => {
-  render(<Text as="div">I am Text!</Text>);
-  expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'DIV');
-});
-// ... more similar tests
+test.each([
+  ['span', 'SPAN'],
+  ['div', 'DIV'],
+  ['label', 'LABEL'],
+  ['p', 'P'],
+])('renders Text component as %s tag', (as, expectedTag) => {
+  render(<Text as={as}>I am Text!</Text>);
+  expect(screen.getByText('I am Text!')).toHaveProperty('tagName', expectedTag);
+});

65-65: Add missing semicolon

Add a semicolon at the end of the expect statement for consistency.

-expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'DIV')
+expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'DIV');

74-74: Fix spacing around operators and braces

Fix the inconsistent spacing in the test case.

-render(<Text as= {undefined} >I am Text!</Text>);
+render(<Text as={undefined}>I am Text!</Text>);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d86282d and 2f26180.

📒 Files selected for processing (2)
  • src/components/ui/Text/Text.tsx (1 hunks)
  • src/components/ui/Text/tests/Text.test.tsx (1 hunks)
🧰 Additional context used
📓 Learnings (2)
src/components/ui/Text/tests/Text.test.tsx (2)
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:05.834Z
Learning: When the keys and values for the `as` prop are the same, we should define the type directly using string literals (e.g., `'div' | 'span' | 'p' | 'label'`) and eliminate extra mappings like `tagMap` to simplify the code.
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:18.599Z
Learning: When the keys and values for the `as` prop are identical, it's preferable to use a union type directly for the `as` prop and eliminate unnecessary mappings like `tagMap`.
src/components/ui/Text/Text.tsx (3)
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:05.834Z
Learning: When the keys and values for the `as` prop are the same, we should define the type directly using string literals (e.g., `'div' | 'span' | 'p' | 'label'`) and eliminate extra mappings like `tagMap` to simplify the code.
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:18.599Z
Learning: When the keys and values for the `as` prop are identical, it's preferable to use a union type directly for the `as` prop and eliminate unnecessary mappings like `tagMap`.
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:03.895Z
Learning: When the keys and values in a tag map are the same, prefer using a union of string literals for the `as` prop and eliminate the need for the tag map.
🪛 Biome (1.9.4)
src/components/ui/Text/Text.tsx

[error] 23-23: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🪛 eslint
src/components/ui/Text/Text.tsx

[error] 20-20: Operator '=' must be spaced.

(space-infix-ops)


[error] 23-23: Unexpected whitespace before semicolon.

(semi-spacing)

🔇 Additional comments (2)
src/components/ui/Text/Text.tsx (2)

11-11: 🛠️ Refactor suggestion

Remove unnecessary tagMap array

Based on previous discussions, since we're using string literals directly in the type definition, this array is redundant and can be removed.

If you need to validate the tag, you can directly check against the union type:

type ValidTag = 'div' | 'span' | 'p' | 'label';
const isValidTag = (tag: string): tag is ValidTag => 
  ['div', 'span', 'p', 'label'].includes(tag as ValidTag);

17-18: 🛠️ Refactor suggestion

Update ComponentProps to use dynamic tag type

The component props should be based on the actual tag being used, not hardcoded to 'p'.

-} & React.ComponentProps<'p'>;
+} & React.ComponentProps<TextProps['as']>;
⛔ Skipped due to learnings
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:05.834Z
Learning: When the keys and values for the `as` prop are the same, we should define the type directly using string literals (e.g., `'div' | 'span' | 'p' | 'label'`) and eliminate extra mappings like `tagMap` to simplify the code.

Comment on lines 20 to 25
const Text = ({ children, customRootClass = '', className = '', as='p' , ...props }: TextProps) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);

return <p className={`${rootClass} ${className}`} {...props}>{children}</p>;
let Tag = 'p'
tagMap.map((tag) => tag === as && (Tag = as)) ;

return React.createElement(Tag, { className: `${rootClass} ${className}`, ...props }, children)
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

Simplify tag validation and fix formatting issues

The current implementation has several issues:

  1. Using map for validation is incorrect and inefficient
  2. Assignment within map expression is an anti-pattern
  3. Inconsistent spacing around operators
-const Text = ({ children, customRootClass = '', className = '', as='p' , ...props }: TextProps) => {
+const Text = ({ children, customRootClass = '', className = '', as = 'p', ...props }: TextProps) => {
   const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
-  let Tag = 'p'
-  tagMap.map((tag) => tag === as && (Tag = as)) ;
-  
-  return React.createElement(Tag, { className: `${rootClass} ${className}`, ...props }, children) 
+  return React.createElement(
+    as,
+    { className: `${rootClass} ${className}`, ...props },
+    children
+  );
};
📝 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
const Text = ({ children, customRootClass = '', className = '', as='p' , ...props }: TextProps) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
return <p className={`${rootClass} ${className}`} {...props}>{children}</p>;
let Tag = 'p'
tagMap.map((tag) => tag === as && (Tag = as)) ;
return React.createElement(Tag, { className: `${rootClass} ${className}`, ...props }, children)
const Text = ({ children, customRootClass = '', className = '', as = 'p', ...props }: TextProps) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
return React.createElement(
as,
{ className: `${rootClass} ${className}`, ...props },
children
);
🧰 Tools
🪛 Biome (1.9.4)

[error] 23-23: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🪛 eslint

[error] 20-20: Operator '=' must be spaced.

(space-infix-ops)


[error] 23-23: Unexpected whitespace before semicolon.

(semi-spacing)


return <p className={`${rootClass} ${className}`} {...props}>{children}</p>;
let Tag = 'p'
tagMap.map((tag) => tag === as && (Tag = as)) ;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This isn't being used anywhere

expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'DIV')
});

test('renders Text component with no as prop', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Make the tests description more specific, they sound very generic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will change the description


const COMPONENT_NAME = 'Text';

const tagMap = ['div' , 'span' ,'p','label']
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not a map anymore, so we can probably just call it TAGS since they are sort of enums


return <p className={`${rootClass} ${className}`} {...props}>{children}</p>;
let Tag = 'p'
tagMap.map((tag) => tag === as && (Tag = as)) ;
Copy link
Collaborator

Choose a reason for hiding this comment

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

If as doesn't exist inside TAGS, reinitialize as to p

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

🧹 Outside diff range and nitpick comments (2)
src/components/ui/Text/Text.tsx (1)

20-29: Simplify component implementation

The current implementation can be improved for clarity and consistency:

  1. Avoid renaming the as prop to Component as it might be misleading
  2. Simplify the return statement
-const Text = ({ children, customRootClass = '', className = '', as: Component = 'p', ...props }: TextProps) => {
+const Text = ({ children, customRootClass = '', className = '', as = 'p', ...props }: TextProps) => {
     const rootClassName = customClassSwitcher(customRootClass, COMPONENT_NAME);
-    const component = TAGS.includes(Component) ? Component : 'p';
+    const tag = VALID_TAGS.includes(as) ? as : 'p';

-    return React.createElement(
-        component,
-        { className: `${rootClassName} ${className}`, ...props },
-        children
-    );
+    return React.createElement(tag, { className: `${rootClassName} ${className}`, ...props }, children);
 };
src/components/ui/Text/tests/Text.test.js (1)

68-101: Fix formatting and consider additional edge cases

The edge case tests are good, but there are some formatting issues and potential additional cases to consider.

  1. Fix formatting in the array test case:
-        render(<Text as = {["testing", "text" ]} >I am Text!</Text>);
+        render(<Text as={["testing", "text"]}>I am Text!</Text>);
  1. Consider adding these test cases:
  • Test with empty string as prop
  • Test with null as prop
  • Test with object as prop
🧰 Tools
🪛 eslint

[error] 89-89: There should be no space before ']'.

(array-bracket-spacing)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 2f26180 and cb6d8c5.

📒 Files selected for processing (2)
  • src/components/ui/Text/Text.tsx (1 hunks)
  • src/components/ui/Text/tests/Text.test.js (1 hunks)
🧰 Additional context used
📓 Learnings (1)
src/components/ui/Text/Text.tsx (3)
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:05.834Z
Learning: When the keys and values for the `as` prop are the same, we should define the type directly using string literals (e.g., `'div' | 'span' | 'p' | 'label'`) and eliminate extra mappings like `tagMap` to simplify the code.
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:18.599Z
Learning: When the keys and values for the `as` prop are identical, it's preferable to use a union type directly for the `as` prop and eliminate unnecessary mappings like `tagMap`.
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:03.895Z
Learning: When the keys and values in a tag map are the same, prefer using a union of string literals for the `as` prop and eliminate the need for the tag map.
🪛 eslint
src/components/ui/Text/tests/Text.test.js

[error] 89-89: There should be no space before ']'.

(array-bracket-spacing)


[error] 101-103: Block must not be padded by blank lines.

(padded-blocks)

🔇 Additional comments (4)
src/components/ui/Text/Text.tsx (2)

11-11: 🛠️ Refactor suggestion

Fix formatting and consider using string literals

The array declaration has inconsistent spacing. Additionally, based on previous feedback, consider using string literals directly in the type definition.

-const TAGS = ['div' , 'span' ,'p','label']
+type ValidTag = 'div' | 'span' | 'p' | 'label';

Likely invalid or redundant comment.


17-17: 🛠️ Refactor suggestion

Improve type safety for the as prop

The current type allows any string, which is too permissive. Constrain it to only valid tags.

-    as?: string;
+    as?: ValidTag;
} & React.ComponentProps<'p'>;
⛔ Skipped due to learnings
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:05.834Z
Learning: When the keys and values for the `as` prop are the same, we should define the type directly using string literals (e.g., `'div' | 'span' | 'p' | 'label'`) and eliminate extra mappings like `tagMap` to simplify the code.
src/components/ui/Text/tests/Text.test.js (2)

1-34: LGTM! Well-structured basic test cases

The basic test cases are comprehensive and well-organized, covering essential functionality like className, style, and custom attributes.


36-66: LGTM! Comprehensive testing of the as prop

The test cases thoroughly verify the rendering of different HTML tags and their combination with other props.

@GoldGroove06
Copy link
Contributor Author

Refactored the code for better readability. Tests migrated to js addressing issue #611 changed as type back to string to prevent breaking if used in ts and having a typo

const Text = ({ children, customRootClass = '', className = '', as: Component = 'p', ...props }: TextProps) => {
const rootClassName = customClassSwitcher(customRootClass, COMPONENT_NAME);

const component = TAGS.includes(Component) ? Component : 'p';
Copy link
Collaborator

@kotAPI kotAPI Dec 7, 2024

Choose a reason for hiding this comment

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

the variables here are getting confusing, we dont need Component, or component - both of these are just tags and not components

check if as is included in TAGS, else reassign default, simple.


return <p className={`${rootClass} ${className}`} {...props}>{children}</p>;
return React.createElement(
component,
Copy link
Collaborator

Choose a reason for hiding this comment

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

this should just be as

expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'P');
});

test('renders Text component with span as prop and custom data attribute', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

we can remove this, its already covered

expect(screen.getByText('I am Text!')).toHaveAttribute('data-testid', 'text-data');
});

test('renders Text component with custom style and div as prop', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can remove

expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'DIV')
});

test('renders Text component with no as prop', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

"renders Text component with no props"

expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'P');
});

test('renders Text component with undefined as prop', () => {
Copy link
Collaborator

@kotAPI kotAPI Dec 7, 2024

Choose a reason for hiding this comment

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

renders Text component as a default <p> tag when "as" is undefined

expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'P');
});

test('renders Text component with random text as prop', () => {
Copy link
Collaborator

@kotAPI kotAPI Dec 7, 2024

Choose a reason for hiding this comment

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

renders Text component with default <p> tag when "as" prop is unsupported

expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'P');
});

test('renders Text component with number as prop', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

follow the same description as mentioned above for remaining cases below

@GoldGroove06
Copy link
Contributor Author

GoldGroove06 commented Dec 7, 2024

suggested changes have been made 🚀

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

🧹 Outside diff range and nitpick comments (4)
src/components/ui/Text/Text.tsx (1)

20-20: Fix operator spacing

Add spaces around the assignment operator.

-const Text = ({ children, customRootClass = '', className = '', as= 'p', ...props }: TextProps) => {
+const Text = ({ children, customRootClass = '', className = '', as = 'p', ...props }: TextProps) => {
🧰 Tools
🪛 eslint

[error] 20-20: Operator '=' must be spaced.

(space-infix-ops)

src/components/ui/Text/tests/Text.test.js (3)

11-19: Consolidate redundant test cases

The first two test cases are testing similar behavior. Consider combining them into a single, more comprehensive test.

-    test('renders Text component', () => {
-        render(<Text className='font-bold'>I am Text!</Text>);
-        expect(screen.getByText('I am Text!')).toBeInTheDocument();
-    });
-
-    test('renders Text component with className', () => {
-        render(<Text className='font-bold'>I am Text!</Text>);
-        expect(screen.getByText('I am Text!')).toHaveClass('font-bold');
-    });
+    test('renders Text component with className correctly', () => {
+        const text = 'I am Text!';
+        render(<Text className='font-bold'>{text}</Text>);
+        const element = screen.getByText(text);
+        expect(element).toBeInTheDocument();
+        expect(element).toHaveClass('font-bold');
+    });

56-57: Fix formatting inconsistencies

There are a few formatting issues that should be addressed:

  1. Remove extra blank line at line 56
  2. Fix array spacing at line 78
-        render(<Text as = {["testing", "text" ]} >I am Text!</Text>);
+        render(<Text as={["testing", "text"]}>I am Text!</Text>);

Also applies to: 78-78

🧰 Tools
🪛 eslint

[error] 56-57: More than 1 blank line not allowed.

(no-multiple-empty-lines)


62-90: Improve organization of edge case tests

Consider using a test.each pattern for the invalid as prop tests to reduce repetition and improve maintainability.

-    test('renders Text component as a default <p> tag when "as" is undefined', () => {
-        render(<Text as={undefined} >I am Text!</Text>);
-        expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'P');
-    });
-    // ... other invalid cases ...
+    test.each([
+        ['undefined', undefined],
+        ['random text', 'text'],
+        ['number', 0],
+        ['array', ['testing', 'text']],
+        ['false', false],
+        ['true', true]
+    ])('renders as default <p> tag when "as" is %s', (_, invalidValue) => {
+        render(<Text as={invalidValue}>I am Text!</Text>);
+        expect(screen.getByText('I am Text!')).toHaveProperty('tagName', 'P');
+    });
🧰 Tools
🪛 eslint

[error] 78-78: There should be no space before ']'.

(array-bracket-spacing)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between cb6d8c5 and 9863d79.

📒 Files selected for processing (2)
  • src/components/ui/Text/Text.tsx (1 hunks)
  • src/components/ui/Text/tests/Text.test.js (1 hunks)
🧰 Additional context used
📓 Learnings (1)
src/components/ui/Text/Text.tsx (3)
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:05.834Z
Learning: When the keys and values for the `as` prop are the same, we should define the type directly using string literals (e.g., `'div' | 'span' | 'p' | 'label'`) and eliminate extra mappings like `tagMap` to simplify the code.
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:18.599Z
Learning: When the keys and values for the `as` prop are identical, it's preferable to use a union type directly for the `as` prop and eliminate unnecessary mappings like `tagMap`.
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:03.895Z
Learning: When the keys and values in a tag map are the same, prefer using a union of string literals for the `as` prop and eliminate the need for the tag map.
🪛 eslint
src/components/ui/Text/Text.tsx

[error] 20-20: Operator '=' must be spaced.

(space-infix-ops)

src/components/ui/Text/tests/Text.test.js

[error] 56-57: More than 1 blank line not allowed.

(no-multiple-empty-lines)


[error] 78-78: There should be no space before ']'.

(array-bracket-spacing)


[error] 90-92: Block must not be padded by blank lines.

(padded-blocks)

🔇 Additional comments (1)
src/components/ui/Text/Text.tsx (1)

17-17: 🛠️ Refactor suggestion

Improve type safety of the as prop

Following the previous discussions, we should use a union type for better type safety.

-    as?: string;
+    as?: 'div' | 'span' | 'p' | 'label';
⛔ Skipped due to learnings
Learnt from: krau5
PR: rad-ui/ui#609
File: src/components/ui/Text/Text.tsx:19-22
Timestamp: 2024-12-06T14:30:05.834Z
Learning: When the keys and values for the `as` prop are the same, we should define the type directly using string literals (e.g., `'div' | 'span' | 'p' | 'label'`) and eliminate extra mappings like `tagMap` to simplify the code.


const COMPONENT_NAME = 'Text';

const TAGS = ['div' , 'span' ,'p','label']
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

Fix formatting and enhance type safety

The TAGS array has inconsistent spacing and could benefit from type safety improvements.

-const TAGS = ['div' , 'span' ,'p','label']
+const TAGS = ['div', 'span', 'p', 'label'] 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
const TAGS = ['div' , 'span' ,'p','label']
const TAGS = ['div', 'span', 'p', 'label'] as const;

Comment on lines +23 to +29
if (!TAGS.includes(as)) as = 'p'

return <p className={`${rootClass} ${className}`} {...props}>{children}</p>;
return React.createElement(
as,
{ className: `${rootClassName} ${className}`, ...props },
children
);
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 tag validation logic

Avoid mutating the parameter directly and make the code more functional.

-    if (!TAGS.includes(as)) as = 'p'
-
-    return React.createElement(
-        as,
-        { className: `${rootClassName} ${className}`, ...props },
-        children
-    );
+    const tag = TAGS.includes(as) ? as : 'p';
+
+    return React.createElement(
+        tag,
+        { className: `${rootClassName} ${className}`, ...props },
+        children
+    );
📝 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
if (!TAGS.includes(as)) as = 'p'
return <p className={`${rootClass} ${className}`} {...props}>{children}</p>;
return React.createElement(
as,
{ className: `${rootClassName} ${className}`, ...props },
children
);
const tag = TAGS.includes(as) ? as : 'p';
return React.createElement(
tag,
{ className: `${rootClassName} ${className}`, ...props },
children
);

const Text = ({ children, customRootClass = '', className = '', as= 'p', ...props }: TextProps) => {
const rootClassName = customClassSwitcher(customRootClass, COMPONENT_NAME);

if (!TAGS.includes(as)) as = 'p'
Copy link
Collaborator

Choose a reason for hiding this comment

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

as = TAGS.includes(as) ? as : 'p';

@kotAPI kotAPI merged commit db8664c into rad-ui:main Dec 8, 2024
3 checks passed
@GoldGroove06 GoldGroove06 deleted the text-as-prop branch December 12, 2024 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants