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

[core] Remove @mui/utils and @mui/types dependencies #827

Merged
merged 12 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"@mdx-js/mdx": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@mui/system": "6.1.8",
"@mui/utils": "6.1.8",
"@next/mdx": "^15.0.2",
"@react-spring/web": "^9.7.5",
"@stefanprobst/rehype-extract-toc": "^2.2.0",
Expand Down
3 changes: 1 addition & 2 deletions docs/src/app/experiments/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
import * as React from 'react';
import clsx from 'clsx';
import { Dialog } from '@base-ui-components/react/dialog';
// eslint-disable-next-line no-restricted-imports
import { useTransitionStatus } from '@base-ui-components/react/utils/useTransitionStatus';
import { useTransitionStatus } from '@base-ui-components/react/utils';
import {
animated as springAnimated,
useSpring,
Expand Down
2 changes: 1 addition & 1 deletion docs/src/blocks/GoogleAnalytics.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';
import * as React from 'react';
import { useMediaQuery } from '@base-ui-components/react/use-media-query';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import { useEnhancedEffect } from '@base-ui-components/react/utils';

let boundDataGaListener = false;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';
// eslint-disable-next-line no-restricted-imports
import { useEnhancedEffect } from '@base-ui-components/react/utils/useEnhancedEffect';
import { useEnhancedEffect } from '@base-ui-components/react/utils';
import * as React from 'react';

export interface PackageManagerSnippetContext {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use client';
import * as React from 'react';
import { Tabs } from '@base-ui-components/react/tabs';
// eslint-disable-next-line no-restricted-imports
import { useEnhancedEffect } from '@base-ui-components/react/utils/useEnhancedEffect';
import { useEnhancedEffect } from '@base-ui-components/react/utils';
import { usePackageManagerSnippetContext } from './PackageManagerSnippetProvider';

export function PackageManagerSnippetRoot(props: PackageManagerSnippetRoot.Props) {
Expand Down
3 changes: 1 addition & 2 deletions docs/src/components/demo/DemoVariantSelectorProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';
import * as React from 'react';
// eslint-disable-next-line no-restricted-imports
import { useEnhancedEffect } from '@base-ui-components/react/utils/useEnhancedEffect';
import { useEnhancedEffect } from '@base-ui-components/react/utils';

export interface DemoVariantSelectorContext {
selectedVariant: string;
Expand Down
3 changes: 1 addition & 2 deletions docs/src/design-system/ToggleButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';
import clsx from 'clsx';
// eslint-disable-next-line no-restricted-imports
import { useControlled } from '@base-ui-components/react/utils/useControlled';
import { useControlled } from '@base-ui-components/react/utils';
import classes from './ToggleButtonGroup.module.css';

export interface ToggleButtonGroupProps<Option extends { value: string; label: string }>
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@
"@mui/internal-scripts": "^1.0.27",
"@mui/internal-test-utils": "https://pkg.csb.dev/mui/material-ui/commit/92c23999/@mui/internal-test-utils",
"@mui/monorepo": "github:mui/material-ui#v6.1.7",
"@mui/utils": "6.1.8",
"@next/eslint-plugin-next": "^14.2.17",
"@octokit/rest": "^20.1.1",
"@playwright/test": "1.49.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
".": {
"import": "./src/index.ts"
},
"./utils": {
"import": "./src/utils/index.ts"
},
"./*": {
"import": "./src/*/index.ts"
}
Expand All @@ -54,8 +57,6 @@
"@floating-ui/react": "^0.26.28",
"@floating-ui/react-dom": "^2.1.2",
"@floating-ui/utils": "^0.2.8",
"@mui/types": "^7.2.19",
"@mui/utils": "^6.1.8",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"use-sync-external-store": "^1.2.2"
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/menu/trigger/useMenuTrigger.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';
import * as React from 'react';
import { unstable_useForkRef as useForkRef } from '@mui/utils';
import { FloatingEvents } from '@floating-ui/react';
import { useButton } from '../../use-button/useButton';
import { useForkRef } from '../../utils/useForkRef';
import { GenericHTMLProps } from '../../utils/types';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { ownerDocument } from '../../utils/owner';
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/no-ssr/NoSsr.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import { exactProp, unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils';
import { useEnhancedEffect } from '../utils/useEnhancedEffect';
import { exactProp } from '../utils/proptypes';
import { NoSsrProps } from './NoSsr.types';

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useScrollAreaRootContext } from '../root/ScrollAreaRootContext';
import { useEventCallback } from '../../utils/useEventCallback';
import { useEnhancedEffect } from '../../utils/useEnhancedEffect';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { clamp } from '../../utils/clamp';
import { MIN_THUMB_SIZE } from '../constants';

export function useScrollAreaViewport(params: useScrollAreaViewport.Parameters) {
Expand Down Expand Up @@ -98,9 +99,6 @@ export function useScrollAreaViewport(params: useScrollAreaViewport.Parameters)
scrollbarXEl.offsetWidth - clampedNextWidth - (paddingLeft + paddingRight);
const scrollRatioX = scrollLeft / (scrollableContentWidth - viewportWidth);

const clamp = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);

// In Safari, don't allow it to go negative or too far as `scrollLeft` considers the rubber
// band effect.
const thumbOffsetX =
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/switch/root/SwitchRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import refType from '@mui/utils/refType';
import { useSwitchRoot } from './useSwitchRoot';
import { SwitchRootContext } from './SwitchRootContext';
import { styleHookMapping } from '../styleHooks';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
import type { FieldRoot } from '../../field/root/FieldRoot';
import { useFieldRootContext } from '../../field/root/FieldRootContext';
import type { BaseUIComponentProps } from '../../utils/types';
import { refType } from '../../utils/proptypes';

/**
* The foundation for building custom-styled switches.
Expand Down
10 changes: 10 additions & 0 deletions packages/react/src/utils/clamp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { expect } from 'chai';
import { clamp } from './clamp';

describe('clamp', () => {
it('clamps a value based on min and max', () => {
expect(clamp(1, 2, 4)).to.equal(2);
expect(clamp(5, 2, 4)).to.equal(4);
expect(clamp(-5, -1, 5)).to.equal(-1);
});
});
8 changes: 7 additions & 1 deletion packages/react/src/utils/clamp.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export { default as clamp } from '@mui/utils/clamp';
export function clamp(
val: number,
min: number = Number.MIN_SAFE_INTEGER,
max: number = Number.MAX_SAFE_INTEGER,
): number {
return Math.max(min, Math.min(val, max));
}
39 changes: 39 additions & 0 deletions packages/react/src/utils/getReactElementRef.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from 'react';
import { expect } from 'chai';
import { getReactElementRef } from '@base-ui-components/react/utils';

describe('getReactElementRef', () => {
it('should return undefined when not used correctly', () => {
// @ts-expect-error
expect(getReactElementRef(false)).to.equal(null);
// @ts-expect-error
expect(getReactElementRef()).to.equal(null);
// @ts-expect-error
expect(getReactElementRef(1)).to.equal(null);

const children = [<div key="1" />, <div key="2" />];
// @ts-expect-error
expect(getReactElementRef(children)).to.equal(null);
});

it('should return the ref of a React element', () => {
const ref = React.createRef<HTMLDivElement>();
const element = <div ref={ref} />;
expect(getReactElementRef(element)).to.equal(ref);
});

it('should return null for a fragment', () => {
const element = (
<React.Fragment>
<p>Hello</p>
<p>Hello</p>
</React.Fragment>
);
expect(getReactElementRef(element)).to.equal(null);
});

it('should return null for element with no ref', () => {
const element = <div />;
expect(getReactElementRef(element)).to.equal(null);
});
});
18 changes: 18 additions & 0 deletions packages/react/src/utils/getReactElementRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';

/**
* Returns the ref of a React element handling differences between React 19 and older versions.
* It will throw runtime error if the element is not a valid React element.
*
* @param element React.ReactElement
* @returns React.Ref<any> | null
*/
export function getReactElementRef(element: React.ReactElement): React.Ref<any> | null {
// 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in older versions
if (parseInt(React.version, 10) >= 19) {
return (element?.props as any)?.ref || null;
}
// @ts-expect-error element.ref is not included in the ReactElement type
// https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/70189
return element?.ref || null;
}
6 changes: 5 additions & 1 deletion packages/react/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Public utils

export * from './prepareForSlot';
export * from './getReactElementRef';
export * from './MuiCancellableEvent';
export * from './useControlled';
export * from './useEnhancedEffect';
export * from './useForkRef';
export * from './useId';
export * from './useScrollLock';
export * from './useTransitionStatus';
export * from './visuallyHidden';
10 changes: 8 additions & 2 deletions packages/react/src/utils/owner.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
export { default as ownerDocument } from '@mui/utils/ownerDocument';
export { default as ownerWindow } from '@mui/utils/ownerWindow';
export function ownerDocument(node: Node | null | undefined): Document {
return (node && node.ownerDocument) || document;
}

export function ownerWindow(node: Node | undefined): Window {
const doc = ownerDocument(node);
return doc.defaultView || window;
}
28 changes: 0 additions & 28 deletions packages/react/src/utils/prepareForSlot.spec.tsx

This file was deleted.

13 changes: 0 additions & 13 deletions packages/react/src/utils/prepareForSlot.tsx

This file was deleted.

35 changes: 35 additions & 0 deletions packages/react/src/utils/proptypes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { expect } from 'chai';
import PropTypes from 'prop-types';
import { exactProp } from './proptypes';

describe('exactProp()', () => {
beforeEach(() => {
PropTypes.resetWarningCache();
});

it('should return null for supported props', () => {
const props = {
bar: false,
};
const propTypes = {
bar: PropTypes.bool,
};

expect(() => {
PropTypes.checkPropTypes(exactProp(propTypes), props, 'props', 'Component');
}).not.toErrorDev();
});

it('should return an error for unsupported props', () => {
const props = {
foo: false,
};
const propTypes = {
bar: PropTypes.bool,
};

expect(() => {
PropTypes.checkPropTypes(exactProp(propTypes), props, 'props', 'Component');
}).toErrorDev('The following props are not supported: `foo`. Please remove them');
});
});
59 changes: 57 additions & 2 deletions packages/react/src/utils/proptypes.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,57 @@
export { default as refType } from '@mui/utils/refType';
export { default as HTMLElementType } from '@mui/utils/HTMLElementType';
import PropTypes, { ValidationMap } from 'prop-types';

export const refType = PropTypes.oneOfType([PropTypes.func, PropTypes.object]);

export function HTMLElementType(
props: { [key: string]: unknown },
propName: string,
componentName: string,
location: string,
propFullName: string,
): Error | null {
if (process.env.NODE_ENV === 'production') {
return null;
}

const propValue = props[propName];
const safePropName = propFullName || propName;

if (propValue == null) {
return null;
}

if (propValue && (propValue as any).nodeType !== 1) {
return new Error(
`Invalid ${location} \`${safePropName}\` supplied to \`${componentName}\`. ` +
`Expected an HTMLElement.`,
);
}

return null;
}

const specialProperty = 'exact-prop: \u200b';

// This module is based on https://github.com/airbnb/prop-types-exact repository.
// However, in order to reduce the number of dependencies and to remove some extra safe checks
// the module was forked.
export function exactProp<T>(propTypes: ValidationMap<T>): ValidationMap<T> {
if (process.env.NODE_ENV === 'production') {
return propTypes;
}

return {
...propTypes,
[specialProperty]: (props: { [key: string]: unknown }) => {
const unsupportedProps = Object.keys(props).filter((prop) => !propTypes.hasOwnProperty(prop));
if (unsupportedProps.length > 0) {
return new Error(
`The following props are not supported: ${unsupportedProps
.map((prop) => `\`${prop}\``)
.join(', ')}. Please remove them.`,
);
}
return null;
},
};
}
6 changes: 6 additions & 0 deletions packages/react/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ export type BaseUIComponentProps<
| ComponentRenderFn<RenderFunctionProps, State>
| React.ReactElement<Record<string, unknown>>;
};

/**
* Simplifies the display of a type (without modifying it).
* Taken from https://effectivetypescript.com/2022/02/25/gentips-4-display/
*/
export type Simplify<T> = T extends Function ? T : { [K in keyof T]: T[K] };
Loading