-
Notifications
You must be signed in to change notification settings - Fork 71
Create TextNode component #3172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
--- | ||
'@leafygreen-ui/typography': minor | ||
--- | ||
|
||
Adds `TextNode` component. | ||
|
||
Wraps a string in the provided `as` component, | ||
or renders the provided `ReactNode`. | ||
|
||
Useful when rendering `children` props that can be any react node | ||
|
||
```tsx | ||
<TextNode as={h1}>Hello!</TextNode> | ||
// Renders: <h1>Hello!</h1> | ||
``` | ||
|
||
```tsx | ||
<TextNode> | ||
<h2>Hello!</h2> | ||
</TextNode> | ||
// Renders: <h2>Hello!</h2> | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import React, { PropsWithChildren } from 'react'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The filename 'TestNode.spec.tsx' contains a typo. It should be 'TextNode.spec.tsx' to match the component name. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
import { render, screen } from '@testing-library/react'; | ||
import { axe } from 'jest-axe'; | ||
|
||
import { TextNode } from './TextNode'; | ||
|
||
describe('packages/typography/TextNode', () => { | ||
describe('when children is a string', () => { | ||
test('renders string children wrapped in Polymorph component', () => { | ||
render(<TextNode>Test string content</TextNode>); | ||
expect(screen.getByText('Test string content')).toBeInTheDocument(); | ||
}); | ||
|
||
test('renders as a div by default', () => { | ||
render(<TextNode>Test string content</TextNode>); | ||
expect( | ||
screen.getByText('Test string content').tagName.toLowerCase(), | ||
).toEqual('div'); | ||
}); | ||
|
||
test('renders with HTML element', () => { | ||
const { container } = render( | ||
<TextNode as="p">Test paragraph content</TextNode>, | ||
); | ||
const paragraph = container.querySelector('p'); | ||
expect(paragraph).toBeInTheDocument(); | ||
expect(paragraph).toHaveTextContent('Test paragraph content'); | ||
}); | ||
|
||
test('renders as React component', () => { | ||
const Wrapper = ({ children }: PropsWithChildren<{}>) => ( | ||
<div data-testid="wrapper">{children}</div> | ||
); | ||
const { container } = render( | ||
<TextNode as={Wrapper}>Test paragraph content</TextNode>, | ||
); | ||
const wrapperEl = screen.getByTestId('wrapper'); | ||
expect(wrapperEl).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
describe('when children is a React node', () => { | ||
test('renders React node children directly without wrapping', () => { | ||
const testContent = ( | ||
<div data-testid="test-div"> | ||
<span>Nested content</span> | ||
</div> | ||
); | ||
|
||
render(<TextNode>{testContent}</TextNode>); | ||
|
||
expect(screen.getByTestId('test-div')).toBeInTheDocument(); | ||
expect(screen.getByText('Nested content')).toBeInTheDocument(); | ||
}); | ||
|
||
test('renders multiple React node children', () => { | ||
render( | ||
<TextNode> | ||
<span data-testid="first-span">First</span> | ||
<span data-testid="second-span">Second</span> | ||
</TextNode>, | ||
); | ||
|
||
expect(screen.getByTestId('first-span')).toBeInTheDocument(); | ||
expect(screen.getByTestId('second-span')).toBeInTheDocument(); | ||
}); | ||
|
||
test('ignores as prop when children is not a string', () => { | ||
const { container } = render( | ||
<TextNode as="p"> | ||
<div data-testid="test-div">React node content</div> | ||
</TextNode>, | ||
); | ||
|
||
// Should not create a paragraph wrapper | ||
expect(container.querySelector('p')).not.toBeInTheDocument(); | ||
// Should render the div directly | ||
expect(screen.getByTestId('test-div')).toBeInTheDocument(); | ||
}); | ||
|
||
test('renders complex nested React components', () => { | ||
const ComplexComponent = () => ( | ||
<div> | ||
<h2>Complex Title</h2> | ||
<p>Complex paragraph</p> | ||
</div> | ||
); | ||
|
||
render( | ||
<TextNode> | ||
<ComplexComponent /> | ||
</TextNode>, | ||
); | ||
|
||
expect(screen.getByText('Complex Title')).toBeInTheDocument(); | ||
expect(screen.getByText('Complex paragraph')).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
describe('edge cases', () => { | ||
test('handles empty children', () => { | ||
const { container } = render(<TextNode></TextNode>); | ||
expect(container.firstChild).toBeNull(); | ||
}); | ||
|
||
test('handles null children', () => { | ||
const { container } = render(<TextNode>{null}</TextNode>); | ||
expect(container.firstChild).toBeNull(); | ||
}); | ||
|
||
test('handles undefined children', () => { | ||
const { container } = render(<TextNode>{undefined}</TextNode>); | ||
expect(container.firstChild).toBeNull(); | ||
}); | ||
|
||
test('handles number children as string', () => { | ||
render(<TextNode as={'span'}>{42}</TextNode>); | ||
expect(screen.getByText('42')).toBeInTheDocument(); | ||
expect(screen.getByText('42').tagName.toLowerCase()).toEqual('span'); | ||
}); | ||
|
||
test('handles boolean children', () => { | ||
const { container } = render(<TextNode>{true}</TextNode>); | ||
// React doesn't render boolean values | ||
expect(container.firstChild).toBeNull(); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,29 @@ | ||||||||||||||||||
import React, { PropsWithChildren } from 'react'; | ||||||||||||||||||
import { Polymorph, type PolymorphicAs } from '@leafygreen-ui/polymorphic'; | ||||||||||||||||||
|
||||||||||||||||||
/** | ||||||||||||||||||
* Wraps a string in the provided `as` component, | ||||||||||||||||||
* or renders the provided `ReactNode`. | ||||||||||||||||||
* | ||||||||||||||||||
* Useful when rendering `children` props that can be any react node | ||||||||||||||||||
* | ||||||||||||||||||
* @example | ||||||||||||||||||
* ``` | ||||||||||||||||||
* <TextNode as={h1}>Hello!</TextNode> // <h1>Hello!</h1> | ||||||||||||||||||
* ``` | ||||||||||||||||||
* | ||||||||||||||||||
* @example | ||||||||||||||||||
* ``` | ||||||||||||||||||
* <TextNode><h2>Hello!</h2></TextNode> // <h2>Hello!</h2> | ||||||||||||||||||
* ``` | ||||||||||||||||||
*/ | ||||||||||||||||||
export const TextNode = ({ | ||||||||||||||||||
children, | ||||||||||||||||||
as, | ||||||||||||||||||
}: PropsWithChildren<{ as?: PolymorphicAs }>) => { | ||||||||||||||||||
return typeof children === 'string' || typeof children === 'number' ? ( | ||||||||||||||||||
<Polymorph as={as}>{children}</Polymorph> | ||||||||||||||||||
) : ( | ||||||||||||||||||
children | ||||||||||||||||||
); | ||||||||||||||||||
}; | ||||||||||||||||||
Comment on lines
+20
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In what cases will this be used? Is it intended to be a wrapper that replaces logic elsewhere like in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A use case for this could be leafygreen-ui/packages/drawer/src/Drawer/Drawer.tsx Lines 263 to 270 in bda06c5
but that could be replaced with const Wrapper = ({ children }: PropsWithChildren<{}>) => (
<Body
as={'h2'}
baseFontSize={BaseFontSize.Body2}
id={titleId}
className={titleStyles}
>
{title}
</Body>
);
<TextNode as={Wrapper}>{title}</TextNode> However, in this case, the consumer will lose the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good example! Yes, it seems like being able to pass attributes/props to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The component reference 'h1' should be a string 'h1' for HTML elements. This will cause a runtime error as h1 is not defined.
Copilot uses AI. Check for mistakes.