Skip to content

Commit

Permalink
[core] Fix proptypes generation when multiple components per file (#4…
Browse files Browse the repository at this point in the history
  • Loading branch information
Janpot authored Oct 10, 2024
1 parent 93f0d6a commit f776273
Show file tree
Hide file tree
Showing 9 changed files with 577 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ function createBabelPlugin({
let importName = '';
let needImport = false;
let alreadyImported = false;
let originalPropTypesPath: null | babel.NodePath = null;
const previousPropTypesSource = new Map<string, string>();
const originalPropTypesPaths = new Map<string, babel.NodePath>();
const previousPropTypesSources = new Map<string, Map<string, string>>();

function injectPropTypes(injectOptions: {
path: babel.NodePath;
Expand All @@ -184,6 +184,9 @@ function createBabelPlugin({
}) {
const { path, props, usedProps, nodeName } = injectOptions;

const previousPropTypesSource =
previousPropTypesSources.get(nodeName) || new Map<string, string>();

const source = generatePropTypes(props, {
...otherOptions,
importedName: importName,
Expand All @@ -201,8 +204,10 @@ function createBabelPlugin({

mapOfPropTypes.set(placeholder, source);

const originalPropTypesPath = originalPropTypesPaths.get(nodeName);

// `Component.propTypes` already exists
if (originalPropTypesPath !== null) {
if (originalPropTypesPath) {
originalPropTypesPath.replaceWith(babel.template.ast(placeholder) as babel.Node);
} else if (!emptyPropTypes && babelTypes.isExportNamedDeclaration(path.parent)) {
// in:
Expand Down Expand Up @@ -258,7 +263,12 @@ function createBabelPlugin({
babelTypes.isMemberExpression(node.expression.left) &&
babelTypes.isIdentifier(node.expression.left.property, { name: 'propTypes' })
) {
originalPropTypesPath = nodePath as babel.NodePath;
babelTypes.assertIdentifier(node.expression.left.object);
const componentName = node.expression.left.object.name;
originalPropTypesPaths.set(componentName, nodePath);

const previousPropTypesSource = new Map<string, string>();
previousPropTypesSources.set(componentName, previousPropTypesSource);

let maybeObjectExpression = node.expression.right;
// Component.propTypes = {} as any;
Expand Down
1 change: 1 addition & 0 deletions packages/mui-base/src/MenuItem/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const InnerMenuItem = React.memo(
*
* - [MenuItem API](https://mui.com/base-ui/react-menu/components-api/#menu-item)
*/

const MenuItem = React.forwardRef(function MenuItem(
props: MenuItemProps,
ref: React.ForwardedRef<Element>,
Expand Down
139 changes: 85 additions & 54 deletions packages/mui-base/src/Option/Option.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,60 +20,90 @@ function useUtilityClasses<OptionValue>(ownerState: OptionOwnerState<OptionValue
return composeClasses(slots, useClassNamesOverride(getOptionUtilityClass));
}

const InnerOption = React.memo(
React.forwardRef<Element, OptionProps<unknown>>(function Option<
OptionValue,
RootComponentType extends React.ElementType,
>(props: OptionProps<OptionValue, RootComponentType>, forwardedRef: React.ForwardedRef<Element>) {
const {
children,
disabled = false,
label,
slotProps = {},
slots = {},
value,
...other
} = props;

const Root = slots.root ?? 'li';

const optionRef = React.useRef<HTMLElement>(null);
const combinedRef = useForkRef(optionRef, forwardedRef);

// If `label` is not explicitly provided, the `children` are used for convenience.
// This is used to populate the select's trigger with the selected option's label.
const computedLabel =
label ?? (typeof children === 'string' ? children : optionRef.current?.textContent?.trim());

const { getRootProps, selected, highlighted, index } = useOption({
disabled,
label: computedLabel,
rootRef: combinedRef,
value,
});

const ownerState: OptionOwnerState<OptionValue> = {
...props,
disabled,
highlighted,
index,
selected,
};

const classes = useUtilityClasses(ownerState);

const rootProps: WithOptionalOwnerState<OptionRootSlotProps<OptionValue>> = useSlotProps({
getSlotProps: getRootProps,
elementType: Root,
externalSlotProps: slotProps.root,
externalForwardedProps: other,
className: classes.root,
ownerState,
});

return <Root {...rootProps}>{children}</Root>;
const InnerOption = React.forwardRef<Element, OptionProps<unknown>>(function InnerOption<
OptionValue,
RootComponentType extends React.ElementType,
>(props: OptionProps<OptionValue, RootComponentType>, forwardedRef: React.ForwardedRef<Element>) {
const { children, disabled = false, label, slotProps = {}, slots = {}, value, ...other } = props;

const Root = slots.root ?? 'li';

const optionRef = React.useRef<HTMLElement>(null);
const combinedRef = useForkRef(optionRef, forwardedRef);

// If `label` is not explicitly provided, the `children` are used for convenience.
// This is used to populate the select's trigger with the selected option's label.
const computedLabel =
label ?? (typeof children === 'string' ? children : optionRef.current?.textContent?.trim());

const { getRootProps, selected, highlighted, index } = useOption({
disabled,
label: computedLabel,
rootRef: combinedRef,
value,
});

const ownerState: OptionOwnerState<OptionValue> = {
...props,
disabled,
highlighted,
index,
selected,
};

const classes = useUtilityClasses(ownerState);

const rootProps: WithOptionalOwnerState<OptionRootSlotProps<OptionValue>> = useSlotProps({
getSlotProps: getRootProps,
elementType: Root,
externalSlotProps: slotProps.root,
externalForwardedProps: other,
className: classes.root,
ownerState,
});

return <Root {...rootProps}>{children}</Root>;
});

InnerOption.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
children: PropTypes.node,
className: PropTypes.string,
/**
* If `true`, the option will be disabled.
* @default false
*/
disabled: PropTypes.bool,
/**
* A text representation of the option's content.
* Used for keyboard text navigation matching.
*/
label: PropTypes.string,
/**
* The props used for each slot inside the Option.
* @default {}
*/
slotProps: PropTypes.shape({
root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
}),
);
/**
* The components used for each slot inside the Option.
* Either a string to use a HTML element or a component.
* @default {}
*/
slots: PropTypes.shape({
root: PropTypes.elementType,
}),
/**
* The value of the option.
*/
value: PropTypes.any.isRequired,
} as any;

const InnerOptionMemo = React.memo(InnerOption);

/**
* An unstyled option to be used within a Select.
Expand All @@ -86,6 +116,7 @@ const InnerOption = React.memo(
*
* - [Option API](https://mui.com/base-ui/react-select/components-api/#option)
*/

const Option = React.forwardRef(function Option<OptionValue>(
props: OptionProps<OptionValue>,
ref: React.ForwardedRef<Element>,
Expand All @@ -100,7 +131,7 @@ const Option = React.forwardRef(function Option<OptionValue>(

return (
<ListContext.Provider value={contextValue}>
<InnerOption {...props} ref={ref} />
<InnerOptionMemo {...props} ref={ref} />
</ListContext.Provider>
);
}) as OptionType;
Expand Down
Loading

0 comments on commit f776273

Please sign in to comment.