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

[Slider] restrict slider type #1241

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

Conversation

seloner
Copy link

@seloner seloner commented Dec 28, 2024

Trying to fix
image

#1230


/**
* Groups all parts of the slider.
* Renders a `<div>` element.
*
* Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)
*/
const SliderRoot = React.forwardRef(function SliderRoot(
props: SliderRoot.Props,
const SliderRoot = fixedForwardRef(function SliderRoot<TValue extends number | number[]>(
Copy link
Author

Choose a reason for hiding this comment

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

ReactForwardRef does not work well with generics.
So we need to create a wrapper function to apply the generic.

Choose a reason for hiding this comment

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

@seloner I think we can remove generic SliderRoot component by removing generic in Props interfacce.

Check https://github.com/mui/base-ui/pull/1241/files#r1899037802, please

@@ -157,28 +158,25 @@ export namespace SliderRoot {
values: ReadonlyArray<number>;
}

export interface Props
export interface Props<TValue extends number | number[]>
Copy link
Author

Choose a reason for hiding this comment

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

My first approach here was to try to carry this generic all the way down in the slider components but it resulted to just pollute the code with generics.
Instead I simple just try to modify the top level component that the consumer will use.

Choose a reason for hiding this comment

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

@seloner
I believe we can define Props without using generics. By utilizing intersection types, we can potentially make the necessary changes internally without affecting the external API. This approach could simplify the type definitions and improve readability.

Here’s an example of how we can implement this:

@@ -158,7 +158,10 @@ export namespace SliderRoot {
     values: ReadonlyArray<number>;
   }
 
-  export interface Props<TValue extends number | number[]>
+  export type Props = PropsTemplate<number> &
+    PropsTemplate<number[]> &
+    PropsTemplate<number | number[]>;
+  export interface PropsTemplate<TValue extends number | number[]>
     extends Pick<
         useSliderRoot.Parameters,
         | 'disabled'

Please refer to mui/material-ui#44777 (comment)

@mui-bot
Copy link

mui-bot commented Dec 28, 2024

Netlify deploy preview

https://deploy-preview-1241--base-ui.netlify.app/

Generated by 🚫 dangerJS against 11792ab

}
}

export { SliderRoot };

// @ts-expect-error
Copy link
Author

Choose a reason for hiding this comment

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

I don't know how to handle this I am getting ts error for proptypes.
Tried the proptypes script but no luck.

Choose a reason for hiding this comment

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

if you remove fixedForwardRef and replace it with React.forwardRef, then // @ts-expect-error can be removed.

@@ -18,8 +18,8 @@ import { fixedForwardRef } from '../utils';
  *
  * Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)
  */
-const SliderRoot = fixedForwardRef(function SliderRoot<TValue extends number | number[]>(
-  props: SliderRoot.Props<TValue>,
+const SliderRoot = React.forwardRef(function SliderRoot(
+  props: SliderRoot.Props,
   forwardedRef: React.ForwardedRef<HTMLDivElement>,
 ) {
   const {

@seloner seloner changed the title [Slider]: restrict slider type [Slider] restrict slider type Dec 29, 2024
@seloner seloner marked this pull request as ready for review December 29, 2024 00:04
@oliviertassinari oliviertassinari added the component: slider This is the name of the generic UI component, not the React module! label Dec 29, 2024
@good-jinu
Copy link

good-jinu commented Dec 29, 2024

Nice work! @seloner

This issue originated from mui/material-ui#44777. Therefore, referring to mui/material-ui#44777 might be helpful.

IMHO, it would be better to run pnpm proptypes and fix the ts errors since the CI: test_static check has failed.

image
It has failed CI check

Copy link

@good-jinu good-jinu left a comment

Choose a reason for hiding this comment

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

I’d like to suggest the following tasks to address this issue:

  1. Convert SliderRoot.Props to an Intersection Type: Change the Props definition to use an intersection type, eliminating the need for generics.
@@ -158,7 +158,10 @@ export namespace SliderRoot {
     values: ReadonlyArray<number>;
   }
 
-  export interface Props<TValue extends number | number[]>
+  export type Props = PropsTemplate<number> &
+    PropsTemplate<number[]> &
+    PropsTemplate<number | number[]>;
+  export interface PropsTemplate<TValue extends number | number[]>
     extends Pick<
         useSliderRoot.Parameters,
         | 'disabled'
  1. Remove Generic Type from SliderRoot: Simplify SliderRoot by removing the generic type.
  2. Replace fixedForwardRef with React.forwardRef: Update the code to use React.forwardRef directly, which will allow us to remove the // @ts-expect-error.
@@ -18,8 +18,8 @@ import { fixedForwardRef } from '../utils';
  *
  * Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)
  */
-const SliderRoot = fixedForwardRef(function SliderRoot<TValue extends number | number[]>(
-  props: SliderRoot.Props<TValue>,
+const SliderRoot = React.forwardRef(function SliderRoot(
+  props: SliderRoot.Props,
   forwardedRef: React.ForwardedRef<HTMLDivElement>,
 ) {
   const {
  1. Remove // @ts-expect-error: With the changes above, the TypeScript error should be resolved, making the // @ts-expect-error unnecessary.

These changes should help resolve the type-related issues. Let me know your thoughts!

/**
* The component orientation.
* @default 'horizontal'
*/

Choose a reason for hiding this comment

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

@seloner
It seems like this should be removed.

@@ -157,28 +158,25 @@ export namespace SliderRoot {
values: ReadonlyArray<number>;
}

export interface Props
export interface Props<TValue extends number | number[]>

Choose a reason for hiding this comment

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

@seloner
I believe we can define Props without using generics. By utilizing intersection types, we can potentially make the necessary changes internally without affecting the external API. This approach could simplify the type definitions and improve readability.

Here’s an example of how we can implement this:

@@ -158,7 +158,10 @@ export namespace SliderRoot {
     values: ReadonlyArray<number>;
   }
 
-  export interface Props<TValue extends number | number[]>
+  export type Props = PropsTemplate<number> &
+    PropsTemplate<number[]> &
+    PropsTemplate<number | number[]>;
+  export interface PropsTemplate<TValue extends number | number[]>
     extends Pick<
         useSliderRoot.Parameters,
         | 'disabled'

Please refer to mui/material-ui#44777 (comment)

@@ -49,7 +49,7 @@ function TestSlider(props: SliderRoot.Props) {
);
}

function TestRangeSlider(props: SliderRoot.Props) {
function TestRangeSlider(props: SliderRoot.Props<number[]>) {

Choose a reason for hiding this comment

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

@seloner I think we can define Props without using generics.

Check https://github.com/mui/base-ui/pull/1241/files#r1899037802, please


/**
* Groups all parts of the slider.
* Renders a `<div>` element.
*
* Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)
*/
const SliderRoot = React.forwardRef(function SliderRoot(
props: SliderRoot.Props,
const SliderRoot = fixedForwardRef(function SliderRoot<TValue extends number | number[]>(

Choose a reason for hiding this comment

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

@seloner I think we can remove generic SliderRoot component by removing generic in Props interfacce.

Check https://github.com/mui/base-ui/pull/1241/files#r1899037802, please

}
}

export { SliderRoot };

// @ts-expect-error

Choose a reason for hiding this comment

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

if you remove fixedForwardRef and replace it with React.forwardRef, then // @ts-expect-error can be removed.

@@ -18,8 +18,8 @@ import { fixedForwardRef } from '../utils';
  *
  * Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)
  */
-const SliderRoot = fixedForwardRef(function SliderRoot<TValue extends number | number[]>(
-  props: SliderRoot.Props<TValue>,
+const SliderRoot = React.forwardRef(function SliderRoot(
+  props: SliderRoot.Props,
   forwardedRef: React.ForwardedRef<HTMLDivElement>,
 ) {
   const {

@seloner
Copy link
Author

seloner commented Dec 29, 2024

@good-jinu
I have applied your suggestions but I can't make this work 🤔
SlideRoot.test.tsx broke

image

@good-jinu
Copy link

@seloner My bad

An intersection of function types is treated as overloaded signatures in TypeScript.

So It seems like just a field like value doesn't work with it.
How about union type?

For example:

export type Props =  PropsTemplate<number> |
  PropsTemplate<number[]> |
  PropsTemplate<number | number[]>;

@seloner
Copy link
Author

seloner commented Dec 29, 2024

 PropsTemplate<number> |
  PropsTemplate<number[]> |
  PropsTemplate<number | number[]>;

It does not work
I think the generic is needed.
Another approach is to export two SliderRoots one SliderRoot and one RangeSliderRoot to support both types without generic?

@mj12albert
Copy link
Member

Another approach is to export two SliderRoots one SliderRoot and one RangeSliderRoot to support both types without generic?

@seloner Thanks for working on this, I'm afraid this isn't an option though

So It seems like just a field like value doesn't work with it.

@good-jinu Not sure if I understand you correctly, by "field like value" did you mean a single non-array value?

@seloner
Copy link
Author

seloner commented Dec 30, 2024

Another approach is to export two SliderRoots one SliderRoot and one RangeSliderRoot to support both types without generic?

@seloner Thanks for working on this, I'm afraid this isn't an option though

So It seems like just a field like value doesn't work with it.

@good-jinu Not sure if I understand you correctly, by "field like value" did you mean a single non-array value?

We can still do the solution with the generics

@mj12albert
Copy link
Member

We can still do the solution with the generics

I think this is ok as long as it doesn't affect the public API, just wonder why @good-jinu is against generics (I prefer to avoid them too, but I'm just curious here)

A while back we did decide that for components with values that could sometimes be an array (accordion, slider, toggle group), we'd make the value in callbacks always be an array so it's predictable, also curious to see what you all think about this!

@seloner
Copy link
Author

seloner commented Dec 30, 2024

We can still do the solution with the generics

I think this is ok as long as it doesn't affect the public API, just wonder why @good-jinu is against generics (I prefer to avoid them too, but I'm just curious here)

A while back we did decide that for components with values that could sometimes be an array (accordion, slider, toggle group), we'd make the value in callbacks always be an array so it's predictable, also curious to see what you all think about this!

We could use a default value so it does no really affect the public API compared to now .

type Props<T extends number | number[] = number | number[]> = {
  value: T;
  onValueChange: (value: T) => void;
};

const Component: <T extends number | number[]>(props: Props<T>) => {};

I like your suggestion to remove the number case and always go with an array.
It will work for this specific case.
The only issue I see that it tries to bypass the problem and not solve it.
In this case it will be fine but in general for a more complex case it will not work.
For example if there is a case with possible inputs string/number.

I am new to this project so I can't really answer though what is the best solution here.

@good-jinu
Copy link

I think this is ok as long as it doesn't affect the public API, just wonder why @good-jinu is against generics (I prefer to avoid them too, but I'm just curious here)

I actually agree with using generics; however, I wanted to avoid changing the public API. My main intention was to eliminate the @ts-expect-error from previous commits due to the TypeScript error.

I also think it's fine as long as it doesn't change the external API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: slider This is the name of the generic UI component, not the React module! typescript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants