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

[useRender] Add public hook #1418

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open

Conversation

mnajdova
Copy link
Member

@mnajdova mnajdova commented Feb 5, 2025

Closes #1315

Added a public useRender hook as an simpler adapter that uses the useComponentRenderer hook. I intentionally copied the types, so we make sure we don't break them if we ever change the internal useComponentRenderer hook.

The hooks receives the following settings:

  • className
  • render
  • state
  • props (passed as extraProps)
  • stateAttributesMap (alias for customStyleHookMapping)

Documentation page: https://deploy-preview-1418--base-ui.netlify.app/react/utils/use-render

Copy link

netlify bot commented Feb 5, 2025

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit ed6a7fb
🔍 Latest deploy log https://app.netlify.com/sites/base-ui/deploys/67b6e8de85ffa800084867d9
😎 Deploy Preview https://deploy-preview-1418--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@mj12albert mj12albert linked an issue Feb 5, 2025 that may be closed by this pull request
@mnajdova mnajdova marked this pull request as draft February 5, 2025 11:56
@mnajdova mnajdova added the new feature New feature or request label Feb 5, 2025
Copy link
Member

@mj12albert mj12albert left a comment

Choose a reason for hiding this comment

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

Nice ~ I imagine the use-case is something like this: https://codesandbox.io/p/sandbox/flamboyant-fog-9x266p?file=%2Fsrc%2FApp.tsx%3A29%2C63
(for some reason importing the hook in CSB doesn't work, this works fine locally in a playground though)

This feels more intuitive to me (biased though because I never used asChild extensively) to use than a Slot component

@mnajdova
Copy link
Member Author

mnajdova commented Feb 5, 2025

for some reason importing the hook in CSB doesn't work, this works fine locally in a playground though

I haven't updated the exports field, will fix it. Here is the updated sandbox: https://codesandbox.io/p/sandbox/exciting-paper-9x266p-use-renderer-forked-ry269y?file=%2Fpackage.json

@mj12albert
Copy link
Member

I removed only the customStyleHookMapping setting from the original hook

It may be useful to provide a (alternative) way to opt-out individual props placed in state from generating a corresponding data attribute

@mnajdova
Copy link
Member Author

mnajdova commented Feb 6, 2025

Thanks for the initial review, I updated the API to include:

@michaldudak
Copy link
Member

I'd leave customStyleHookMapping as in useComponentRenderer. It should be quite common to customize the data attributes (we do this often).

@mnajdova
Copy link
Member Author

mnajdova commented Feb 6, 2025

I'd leave customStyleHookMapping as in useComponentRenderer. It should be quite common to customize the data attributes (we do this often).

Yeah, fair enough, ok I was thinking initially to make the API simpler. I've changed it back to use the customStyleHookMapping and added a test for it.

type Size = 'small' | 'medium' | 'large';

type TextState = {
weight: Weight;
Copy link
Member

Choose a reason for hiding this comment

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

This isn't really the state of the component but its props, so someone might be confused about the purpose of State and style hooks. Can we introduce something else that doesn't come from props? For example, a character counter?

Copy link
Member Author

Choose a reason for hiding this comment

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

I added color inside this too, which is a state that changes when clicking the text. I am out of ideas what else to include, maybe we can have a demo with a different component, more complex to showcase these features. I felt character counter, although could be state, it's not really valuable while styling.

@mnajdova mnajdova changed the title [useRenderer] Add public hook [useRenderer] Add public hook & Slot component Feb 12, 2025
@mnajdova mnajdova changed the title [useRenderer] Add public hook & Slot component [useRenderer] Add public hook Feb 14, 2025
@mnajdova mnajdova requested a review from atomiks February 18, 2025 10:31
@colmtuite
Copy link
Contributor

Fine to me pending changing the name to useRender as per discussion. 🚀

@aarongarciah aarongarciah changed the title [useRenderer] Add public hook [useRender] Add public hook Feb 20, 2025
Comment on lines +26 to +27
<Text className={styles.Text}>Text component rendered as a paragraph tag</Text>
<Text className={styles.Text} render={<strong />}>
Copy link
Member

Choose a reason for hiding this comment

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

I'd be more realistic if the component set its own class name instead of passing a class name to each instance. This also means removing className from TextProps.

Suggested change
<Text className={styles.Text}>Text component rendered as a paragraph tag</Text>
<Text className={styles.Text} render={<strong />}>
<Text>Text component rendered as a paragraph tag</Text>
<Text render={<strong />}>

Copy link
Member Author

Choose a reason for hiding this comment

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

For sure 👍


### Supporting the `render` prop

This is an example of a Text component that provides the support for the render prop.
Copy link
Member

Choose a reason for hiding this comment

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

For consistency, since the heading before this paragraph uses backticks.

Suggested change
This is an example of a Text component that provides the support for the render prop.
This is an example of a Text component that provides the support for the `render` prop.

Comment on lines +79 to +88
.Text {
font-size: 0.875rem;
line-height: 1rem;
&[data-size-small] {
font-size: 0.75rem;
}
&[data-size-large] {
font-size: 1.25rem;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

I'd simplify this codeblock by removing unrelated code and nesting.

Suggested change
.Text {
font-size: 0.875rem;
line-height: 1rem;
&[data-size-small] {
font-size: 0.75rem;
}
&[data-size-large] {
font-size: 1.25rem;
}
}
.Text[data-size-small] {
font-size: 0.75rem;
}
.Text[data-size-large] {
font-size: 1.25rem;
}

font-size: 0.875rem;
line-height: 1rem;
color: var(--color-gray-900);
}
Copy link
Member

Choose a reason for hiding this comment

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

strong has font-weight: 700 by default, but it looks ugly. I'd use 500 or 600 instead.

Suggested change
}
strong& {
font-weight: 500;
}
}
before after
deploy-preview-1418--base-ui netlify app_react_utils_use-render (3) localhost_3005_react_utils_use-render

Comment on lines +52 to +54
This is an example of a Text component that provides the support for the render prop.

<Demo path="./demos/render" />
Copy link
Member

Choose a reason for hiding this comment

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

I think this example makes it look more complex than it really is, due to imports, types, styles, etc. I wonder if it'd be better to just have a code snippet and not a live example for this. Something like:

Suggested change
This is an example of a Text component that provides the support for the render prop.
<Demo path="./demos/render" />
This is an example on how to create your own `render` prop:
```jsx
import * as React from 'react';
import { useRender } from '@base-ui-components/react/use-render';
function Text({ render, ...otherProps }) {
const { render, ...otherProps } = props;
const { renderElement } = useRender({
render: render ?? <p />,
props: otherProps,
});
return renderElement();
}
```
It'd be used as follows:
```jsx
<Text>I'm a paragraph</Text>
<Text render={<span />}>I'm a span</Text>
```

The downside is not being able to open it on CodeSandbox, but not sure if it's that important.

Copy link
Member

@michaldudak michaldudak Feb 20, 2025

Choose a reason for hiding this comment

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

It could be an (another) argument for having the ability to select code to display in a demo preview. This way we could have the best of both - a concise code snippet that's runnable and can be opened in CSB. I could take a look at this next if you agree this could be handy.

The downside is not being able to open it on CodeSandbox, but not sure if it's that important.

An (IMO more serious) downside is that contrary to demos, code snippets are not verified in any way (not executed, linted, typechecked, etc.), so they could be invalid (or inadvertently become invalid in the future after changes to the API).

Copy link
Member

Choose a reason for hiding this comment

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

It could be an (another) argument for having the ability to select code to display in a demo preview

This would be perfect.

code snippets are not verified in any way (not executed, linted, typechecked, etc.), so they could be invalid (or inadvertently become invalid in the future after changes to the API)

Yeah, it's a trade-off. Sometimes live demos are an overkill and makes things harder to convey. I'll leave the live demo for now.


### Generating data attributes

In the following demo, the Text component provides more Base UI features, like adding data attributes and `className` callback where developers can have access to the internal state of the component.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
In the following demo, the Text component provides more Base UI features, like adding data attributes and `className` callback where developers can have access to the internal state of the component.
`useRender` can also be used to automatically add data attributes based on the component's internal state and support a callback in the `className` prop where developers can access the component's internal state.

It's weird that the paragraph talks about the className callback but the it's not present in the demo.

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe we can extend the demo on the usage to show className callback where a class is added fort he large variant. What do you think?

});
```
Then, developers can target the data attributes like this:
Copy link
Member

Choose a reason for hiding this comment

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

Seeing data-size-small might confuse users into thinking the generated data attributes have this shape by default (instead of data-size='small'). I'm not sure if this paragraph and the code block below are necessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

The demo is about showcasing that you can override the default behavior data-size='small', to generate data-size-small

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe we need a better description of what it is about.


<Demo path="./demos/render" />

### Generating data attributes
Copy link
Member

Choose a reason for hiding this comment

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

I think we could come up with a better demo. Clicking a text is not very common and it's hard to discover. We could also simplify it and only leave one "state" (color or size, but not both).

We could also use this demo only for data attributes and leave the className callback as paragraph + code snippet in a new section. This could also improve the discoverability of the className callback docs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, the thing I was trying to add here is that people can use internal state as well, I couldn't come up with an idea for internal state for Text component :) I am ok with simplifying it.

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Feb 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new feature New feature or request PR: out-of-date The pull request has merge conflicts and can't be merged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Slot component vs useRender hook [core] Could useComponentRenderer be exported as a util?
6 participants