mui / toolpad

Toolpad: Full stack components and low-code builder for dashboards and internal apps.
https://mui.com/toolpad/
MIT License
1.28k stars 283 forks source link

Can't load @mui/x-date-pickers as a custom component #419

Closed oliviertassinari closed 2 years ago

oliviertassinari commented 2 years ago

Duplicates

Latest version

Current behavior 😯

Screenshot 2022-05-16 at 01 05 33

Expected behavior 🤔

It renders like https://mui.com/x/react-date-pickers/date-picker/#basic-usage

Screenshot 2022-05-16 at 01 08 19

Steps to reproduce 🕹

Steps:

  1. Open https://master--toolpad.mui.com/_toolpad/app/cl1c2j0l512939zo6a43575en/editor/codeComponents/cl37w530600003f6agke0m1cv

or

  1. Create a custom component with:
import * as React from "react";
import { createComponent } from "@mui/toolpad-core";
import { TextField } from "@mui/material";
import {
  AdapterDateFns,
  LocalizationProvider,
  DatePicker,
} from "https://esm.sh/@mui/x-date-pickers@5.0.0-alpha.3";

export interface DatePickerRootProps {
  msg: string;
}

function DatePickerRoot({ msg }: DatePickerRootProps) {
  const [value, setValue] = React.useState<Date | null>(null);

  return (
    <LocalizationProvider dateAdapter={AdapterDateFns}>
      <DatePicker
        label="Basic example"
        value={value}
        onChange={(newValue) => {
          setValue(newValue);
        }}
        renderInput={(params) => <TextField {...params} />}
      />
    </LocalizationProvider>
  );
}

DatePickerRoot.defaultProps = {
  msg: "Hello world!",
};

export default createComponent(DatePickerRoot, {
  argTypes: {
    msg: { typeDef: { type: "string" } },
  },
});

Context 🔦

I need a date picker for my license key generator:

Screenshot 2022-05-16 at 01 06 47

https://master--toolpad.mui.com/_toolpad/app/cl1c2j0l512939zo6a43575en/editor/pages/cl2kfa28r00013f69amqv1prv

Your environment 🌎

No response

Janpot commented 2 years ago

Yep, this illustrates well what we discussed in our last retro. Components imported from urls will have to depend on react. In this case esm.sh rewrites the import to one that lives on the CDN itself (see https://cdn.esm.sh/v78/@mui/x-date-pickers@5.0.0-alpha.3/es2022/x-date-pickers.js). So it brings in its own instance of the react module. It's currently behaving as expected. We could look into what should be the behavior if your imported code tries to import a bare module identifier, but it's going to raise questions about versioning and backwards compatibility.

Regarding @mui/... packages, besides providing their components in the visual editor, I think we should also allow importing them using bare imports in custom components

edit:

another example:

import * as React from "react";
import { createComponent } from "@mui/toolpad-core";
import PhoneInput from "https://esm.sh/react-phone-number-input";

export interface ExternalProps {
  date: string;
}

function External({ date }: ExternalProps) {
  return <PhoneInput placeholder="Enter phone number" />;
}

export default createComponent(External, {
  argTypes: {},
});
oliviertassinari commented 2 years ago

@Janpot Great, thanks for the fix! Now, I need to figure out how to feed the value picked back to Toolpad. I will try:

https://github.com/mui/mui-toolpad/blob/18c7e7dc6fa1ac39a12b20fd883c7dd4a691c33f/packages/toolpad-components/src/TextField.tsx#L20-L27

Janpot commented 2 years ago

🤔 It could become a bit tricky to deal with these dayjs values through the toolpad interface. Perhaps it would make sense to create a wrapper prop that converts from a string/number and a handler that serializes to a string?

Janpot commented 2 years ago

@oliviertassinari Something like this seems to work for me:

import * as React from "react";
import { createComponent } from "@mui/toolpad-core";
import { Dayjs } from "dayjs";
import TextField from "@mui/material/TextField";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import dayjs from "dayjs";

interface BasicDatePickerProps {
  value: string;
  onChange: (newValue: string) => void;
}

function BasicDatePicker({ value, onChange }: BasicDatePickerProps) {
  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <DatePicker
        label="Basic example"
        value={value ? dayjs(value) : null}
        onChange={onChange}
        renderInput={(params) => <TextField {...params} />}
      />
    </LocalizationProvider>
  );
}

export default createComponent(BasicDatePicker, {
  argTypes: {
    value: {
      typeDef: { type: "string" },
      onChangeProp: "onChange",
      onChangeHandler: (newValue: dayjs.Dayjs) => newValue?.format(),
      defaultValue: "",
      defaultValueProp: "defaultValue",
    },
  },
});
oliviertassinari commented 2 years ago

@Janpot Thanks, I have used this version in the end:

import * as React from "react";
import { createComponent } from "@mui/toolpad-core";
import TextField from "@mui/material/TextField";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import dayjs from "dayjs";

interface BasicDatePickerProps {
  value: string;
  label: string;
  disabled: boolean;
  onChange: (newValue: string) => void;
}

function BasicDatePicker({
  value,
  label,
  disabled,
  onChange = () => {},
}: BasicDatePickerProps) {
  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <DatePicker
        label={label}
        disabled={disabled}
        value={value ? dayjs(value) : null}
        onChange={onChange}
        renderInput={(params) => (
          <TextField fullWidth size="small" {...params} />
        )}
      />
    </LocalizationProvider>
  );
}

export default createComponent(BasicDatePicker, {
  argTypes: {
    label: {
      typeDef: { type: "string" },
      defaultValue: "Label",
    },
    disabled: {
      typeDef: { type: "boolean" },
    },
    value: {
      typeDef: { type: "string" },
      onChangeProp: "onChange",
      onChangeHandler: (newValue: dayjs.Dayjs) => {
        return newValue?.toDate();
      },
      defaultValue: "",
      defaultValueProp: "defaultValue",
    },
  },
});

https://master--toolpad.mui.com/_toolpad/app/cl4hla83p01949xoizo5uxf2a/codeComponents/g823nzp