diff --git a/package.json b/package.json index 3e75ddff483..70bc9d9ffd9 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "monorepo:update": "tsx ./scripts/updateMonorepo.ts", "monorepo:canary": "tsx ./scripts/canaryMonorepo.ts", "check-changes": "git add -A && git diff --exit-code --staged", - "test:rest:start": "tsx ./scripts/restTestServer.ts" + "test:rest:start": "tsx ./scripts/restTestServer.ts", + "clean": "pnpm -r exec rm -rf build dist" }, "devDependencies": { "@argos-ci/core": "2.3.0", diff --git a/packages/toolpad-studio-components/package.json b/packages/toolpad-studio-components/package.json index d52e4468e12..a6c9ebcb7d1 100644 --- a/packages/toolpad-studio-components/package.json +++ b/packages/toolpad-studio-components/package.json @@ -33,7 +33,8 @@ "scripts": { "build": "tsup", "dev": "tsup --watch", - "check-types": "tsup && tsc --noEmit" + "check-types": "tsup && tsc --noEmit", + "build:types": "tsc --declaration --emitDeclarationOnly" }, "bugs": { "url": "https://github.com/mui/mui-toolpad/issues" diff --git a/packages/toolpad-studio-components/src/Autocomplete.tsx b/packages/toolpad-studio-components/src/Autocomplete.tsx index c4d70459027..065626eb7d0 100644 --- a/packages/toolpad-studio-components/src/Autocomplete.tsx +++ b/packages/toolpad-studio-components/src/Autocomplete.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { Autocomplete as MuiAutocomplete, AutocompleteProps as MuiAutocompleteProps, + styled, TextField, } from '@mui/material'; import createBuiltin from './createBuiltin'; @@ -13,6 +14,14 @@ import { withComponentForm, } from './Form'; +const ToolpadMuiAutocomplete = styled(MuiAutocomplete, { + shouldForwardProp: (prop) => prop !== 'hasWidth', +})<{ + hasWidth?: boolean; +}>(({ hasWidth }) => ({ + width: hasWidth ? 120 : '100%', +})); + type AutocompleteOption = string | { label?: string; value?: string }; type AutocompleteValue = string | null; @@ -85,8 +94,10 @@ function Autocomplete({ [getValue, onFormInputChange], ); + const hasWidth = !rest.fullWidth && !value; + return renderFormInput( - getValue(option) === getValue(selectedValue)} @@ -103,7 +114,8 @@ function Autocomplete({ })} /> )} - sx={{ ...(!rest.fullWidth && !value ? { width: 120 } : {}), ...sx }} + hasWidth={hasWidth} + sx={sx} {...rest} />, ); diff --git a/packages/toolpad-studio-components/src/Chart.tsx b/packages/toolpad-studio-components/src/Chart.tsx index 875776a2f37..a981362f3af 100644 --- a/packages/toolpad-studio-components/src/Chart.tsx +++ b/packages/toolpad-studio-components/src/Chart.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { CircularProgress, Box, BoxProps } from '@mui/material'; +import { CircularProgress, BoxProps, styled } from '@mui/material'; import { BarPlot, @@ -24,6 +24,12 @@ import createBuiltin from './createBuiltin'; import ErrorOverlay from './components/ErrorOverlay'; import { SX_PROP_HELPER_TEXT } from './constants'; +const ChartRoot = styled('div')({ + position: 'relative', + height: '100%', + width: '100%', +}); + type ChartDataSeriesKind = 'line' | 'bar' | 'area' | 'scatter'; export const CHART_DATA_SERIES_KINDS: ChartDataSeriesKind[] = ['line', 'bar', 'area', 'scatter']; @@ -170,7 +176,7 @@ function Chart({ data = [], loading, error, sx }: ChartProps) { const firstDataSeries = chartSeries[0]; return ( - + {displayError ? : null} {loading && !error ? (
) : null} - + ); } diff --git a/packages/toolpad-studio-components/src/Form.tsx b/packages/toolpad-studio-components/src/Form.tsx index ea026047205..22cdb2baf1c 100644 --- a/packages/toolpad-studio-components/src/Form.tsx +++ b/packages/toolpad-studio-components/src/Form.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Box, BoxProps, Stack } from '@mui/material'; +import { Box, BoxProps, Stack, styled } from '@mui/material'; import { LoadingButton } from '@mui/lab'; import { useNode } from '@toolpad/studio-runtime'; import { equalProperties } from '@toolpad/utils/collections'; @@ -14,6 +14,10 @@ import { import { SX_PROP_HELPER_TEXT } from './constants'; import createBuiltin, { BuiltinArgTypeDefinitions } from './createBuiltin'; +const FormRoot = styled('div')({ + width: '100%', +}); + export const FormContext = React.createContext<{ form: ReturnType | null; fieldValues: FieldValues; @@ -80,7 +84,7 @@ function Form({ return ( {hasChrome ? ( - +
{children} @@ -119,7 +123,7 @@ function Form({
-
+ ) : ( children )} diff --git a/packages/toolpad-studio-components/src/Image.tsx b/packages/toolpad-studio-components/src/Image.tsx index 6f7713d81dc..99a0abddda0 100644 --- a/packages/toolpad-studio-components/src/Image.tsx +++ b/packages/toolpad-studio-components/src/Image.tsx @@ -1,9 +1,17 @@ -import { Box, Skeleton, SxProps, styled } from '@mui/material'; +import { Skeleton, SxProps, styled } from '@mui/material'; import * as React from 'react'; import createBuiltin from './createBuiltin'; import { SX_PROP_HELPER_TEXT } from './constants'; import ErrorOverlay from './components/ErrorOverlay'; +const ImageRoot = styled('div')({ + maxWidth: '100%', + position: 'relative', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}); + export interface ImageProps { src: string; alt?: string; @@ -31,18 +39,7 @@ function Image({ error: errorProp, fit, }: ImageProps) { - const sx: SxProps = React.useMemo( - () => ({ - ...sxProp, - width, - height, - position: 'relative', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }), - [sxProp, width, height], - ); + const sx: SxProps = React.useMemo(() => ({ ...sxProp, width, height }), [sxProp, width, height]); const [imgError, setImgError] = React.useState(null); const [imgLoading, setImgLoading] = React.useState(false); @@ -64,7 +61,7 @@ function Image({ const loading = loadingProp || imgLoading; const error = errorProp || imgError; return ( - + {error ? : null} {loading && !error ? : null} - + ); } diff --git a/packages/toolpad-studio-components/src/List.tsx b/packages/toolpad-studio-components/src/List.tsx index 697137f54f1..4a6b09a72b9 100644 --- a/packages/toolpad-studio-components/src/List.tsx +++ b/packages/toolpad-studio-components/src/List.tsx @@ -1,9 +1,13 @@ import * as React from 'react'; import { TemplateRenderer } from '@toolpad/studio-runtime'; -import { Box, List as MuiList, ListItem, SxProps, Skeleton, Stack } from '@mui/material'; +import { Box, List as MuiList, ListItem, SxProps, Skeleton, Stack, styled } from '@mui/material'; import { SX_PROP_HELPER_TEXT } from './constants'; import createBuiltin from './createBuiltin'; +const ToolpadList = styled(MuiList)({ + width: '100%', +}); + export type ListProps = { itemCount: number; disablePadding?: boolean; @@ -14,7 +18,7 @@ export type ListProps = { function List({ itemCount, renderItem, disablePadding = false, sx, loading }: ListProps) { return ( - + {loading ? ( @@ -30,7 +34,7 @@ function List({ itemCount, renderItem, disablePadding = false, sx, loading }: Li )) )} - + ); } diff --git a/packages/toolpad-studio-components/src/Paper.tsx b/packages/toolpad-studio-components/src/Paper.tsx index 12134720a63..59e1aaee7c8 100644 --- a/packages/toolpad-studio-components/src/Paper.tsx +++ b/packages/toolpad-studio-components/src/Paper.tsx @@ -1,13 +1,18 @@ import * as React from 'react'; -import { Paper as MuiPaper, PaperProps as MuiPaperProps } from '@mui/material'; +import { Paper as MuiPaper, PaperProps as MuiPaperProps, styled } from '@mui/material'; import createBuiltin from './createBuiltin'; import { SX_PROP_HELPER_TEXT } from './constants'; +const PaperRoot = styled(MuiPaper)(({ theme }) => ({ + padding: theme.spacing(1), + width: '100%', +})); + function Paper({ children, sx, ...rest }: MuiPaperProps) { return ( - + {children} - + ); } diff --git a/packages/toolpad-studio-components/src/Select.tsx b/packages/toolpad-studio-components/src/Select.tsx index 62803ebb2c4..4ea5df9d1df 100644 --- a/packages/toolpad-studio-components/src/Select.tsx +++ b/packages/toolpad-studio-components/src/Select.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { TextFieldProps, MenuItem, TextField } from '@mui/material'; +import { TextFieldProps, MenuItem, TextField, styled } from '@mui/material'; import createBuiltin from './createBuiltin'; import { FORM_INPUT_ARG_TYPES, @@ -9,6 +9,10 @@ import { } from './Form'; import { SX_PROP_HELPER_TEXT } from './constants'; +const ToolpadTextField = styled(TextField)({ + minWidth: 120, +}); + export interface SelectOption { value: string; label?: string; @@ -66,20 +70,20 @@ function Select({ ); return renderFormInput( - {renderedOptions} - , + , ); } diff --git a/packages/toolpad-studio-components/src/Text.tsx b/packages/toolpad-studio-components/src/Text.tsx index 283bfbaba7c..08632e785ca 100644 --- a/packages/toolpad-studio-components/src/Text.tsx +++ b/packages/toolpad-studio-components/src/Text.tsx @@ -7,6 +7,7 @@ import { styled, TextareaAutosize, SxProps, + Typography, } from '@mui/material'; import { useNode } from '@toolpad/studio-runtime'; import ErrorIcon from '@mui/icons-material/Error'; @@ -14,6 +15,26 @@ import { errorFrom } from '@toolpad/utils/errors'; import createBuiltin from './createBuiltin'; import { SX_PROP_HELPER_TEXT } from './constants'; +const StaticTextRoot = styled(Typography)({ + // This will give it height, even when empty. + // REMARK: Does it make sense to put it in MUI core? + [`&:empty::before`]: { content: '""', display: 'inline-block' }, + outline: 'none', + whiteSpace: 'pre-wrap', + overflowWrap: 'anywhere', +}); + +const ToolpadLink = styled(MuiLink, { + shouldForwardProp: (prop) => prop !== 'hasMinWidth', +})<{ + hasMinWidth?: boolean; +}>(({ hasMinWidth }) => ({ + minWidth: hasMinWidth ? 150 : undefined, + // Same as Typography + [`&:empty::before`]: { content: '""', display: 'inline-block' }, + overflowWrap: 'anywhere', +})); + const Markdown = React.lazy(async () => import('markdown-to-jsx')); const StyledTextareaAutosize = styled(TextareaAutosize)(({ theme }) => ({ @@ -135,21 +156,18 @@ function LinkContent({ value, href, loading, sx, openInNewTab }: LinkContentProp return value; }, [value, loading]); + const hasMinWidth = loading || !value; + return ( - {content} - + ); } @@ -240,16 +258,8 @@ function TextContent({ value, loading, sx, variant }: TextContentProps) { className={`variant-${variant}`} /> ) : ( - { if (nodeRuntime) { @@ -262,7 +272,7 @@ function TextContent({ value, loading, sx, variant }: TextContentProps) { }} > {loading ? : input} - + ); } diff --git a/packages/toolpad-studio-components/tsup.config.ts b/packages/toolpad-studio-components/tsup.config.ts index 35c71f03c87..e6ad4b481bc 100644 --- a/packages/toolpad-studio-components/tsup.config.ts +++ b/packages/toolpad-studio-components/tsup.config.ts @@ -1,5 +1,5 @@ -import { spawnSync } from 'child_process'; import * as fs from 'fs/promises'; +import { $ } from 'execa'; import path from 'path'; import { defineConfig, Options } from 'tsup'; @@ -34,7 +34,9 @@ export default defineConfig((options) => ({ esbuildPlugins: [cleanFolderOnFailure(path.resolve(__dirname, 'dist'))], async onSuccess() { // eslint-disable-next-line no-console - console.log('build successful'); - spawnSync('tsc', ['--emitDeclarationOnly', '--declaration'], { shell: true, stdio: 'inherit' }); + console.log('build successful, generate typings...'); + await $`tsc --emitDeclarationOnly --declaration`; + // eslint-disable-next-line no-console + console.log('typings generated'); }, })); diff --git a/packages/toolpad-studio/src/components/CenteredSpinner.tsx b/packages/toolpad-studio/src/components/CenteredSpinner.tsx index 3301baa6223..37f4a27b1be 100644 --- a/packages/toolpad-studio/src/components/CenteredSpinner.tsx +++ b/packages/toolpad-studio/src/components/CenteredSpinner.tsx @@ -1,23 +1,22 @@ -import { CircularProgress, Box, SxProps } from '@mui/material'; +import { CircularProgress, SxProps, styled } from '@mui/material'; import * as React from 'react'; +const Root = styled('div')({ + width: '100%', + height: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}); + export interface CenteredSpinnerProps { sx?: SxProps; } export default function CenteredSpinner({ sx }: CenteredSpinnerProps) { return ( - + - + ); } diff --git a/packages/toolpad-studio/src/components/Devtools.tsx b/packages/toolpad-studio/src/components/Devtools.tsx index c88b790ccf5..32521e6c18c 100644 --- a/packages/toolpad-studio/src/components/Devtools.tsx +++ b/packages/toolpad-studio/src/components/Devtools.tsx @@ -7,6 +7,10 @@ import Console, { LogEntry } from './Console'; import lazyComponent from '../utils/lazyComponent'; import CenteredSpinner from './CenteredSpinner'; +const DevtoolsRoot = styled('div')({ + flexDirection: 'column', +}); + const HarViewer = lazyComponent(() => import('./HarViewer'), { fallback: , }); @@ -62,12 +66,7 @@ export default function Devtools({ sx, log, onLogClear, har, onHarClear }: Devto }, [activeTab, onHarClear, onLogClear]); return ( - + ) : null} - + ); } diff --git a/packages/toolpad-studio/src/components/EditableText.tsx b/packages/toolpad-studio/src/components/EditableText.tsx index 661b4fefbe9..487063dceff 100644 --- a/packages/toolpad-studio/src/components/EditableText.tsx +++ b/packages/toolpad-studio/src/components/EditableText.tsx @@ -6,9 +6,37 @@ import { SxProps, inputBaseClasses, inputClasses, + styled, } from '@mui/material'; import invariant from 'invariant'; +const EditableTextRoot = styled(TextField, { + shouldForwardProp: (prop) => prop !== 'editable' && prop !== 'readOnly', +})<{ + readOnly: boolean; + editable: boolean; +}>(({ theme, editable, readOnly }) => ({ + transition: theme.transitions.create(['border-bottom'], { + duration: theme.transitions.duration.short, + }), + [`.${inputClasses.root}.${inputBaseClasses.root}:before, .${inputClasses.root}.${inputBaseClasses.root}:not(${inputBaseClasses.disabled}):hover:before`]: + { + borderBottom: editable ? `initial` : 'none', + }, + [`& .${inputClasses.root}.${inputBaseClasses.root}::after`]: readOnly + ? { + transform: 'scaleX(0)', + borderBottom: 'none', + transition: theme.transitions.create(['transform'], { + duration: theme.transitions.duration.short, + }), + } + : { + transform: 'scaleX(1)', + borderBottom: '2px solid primary', + }, +})); + interface EditableTextProps { defaultValue?: string; disabled?: boolean; @@ -114,7 +142,7 @@ const EditableText = React.forwardRef( ); return ( - ( onBlur={handleBlur} onChange={handleChange} size={size ?? 'small'} - sx={{ - ...sx, - transition: (theme: Theme) => - theme.transitions.create(['border-bottom'], { - duration: theme.transitions.duration.short, - }), - [`.${inputClasses.root}.${inputBaseClasses.root}:before, .${inputClasses.root}.${inputBaseClasses.root}:not(${inputBaseClasses.disabled}):hover:before`]: - { - borderBottom: editable ? `initial` : 'none', - }, - [`& .${inputClasses.root}.${inputBaseClasses.root}::after`]: readOnly - ? { - transform: 'scaleX(0)', - borderBottom: 'none', - transition: (theme: Theme) => - theme.transitions.create(['transform'], { - duration: theme.transitions.duration.short, - }), - } - : { - transform: 'scaleX(1)', - borderBottom: '2px solid primary', - }, - }} + readOnly={readOnly} + editable={!!editable} + sx={sx} value={value} - variant={'standard'} + variant="standard" /> ); }, diff --git a/packages/toolpad-studio/src/components/EditableTreeItem.tsx b/packages/toolpad-studio/src/components/EditableTreeItem.tsx index a31dec8df4f..782282477b3 100644 --- a/packages/toolpad-studio/src/components/EditableTreeItem.tsx +++ b/packages/toolpad-studio/src/components/EditableTreeItem.tsx @@ -8,9 +8,22 @@ import { Popover, Typography, alpha, - useTheme, + styled, } from '@mui/material'; +const EditableTreeItemRoot = styled(TreeItem, { + shouldForwardProp: (prop) => prop !== 'isEditing', +})<{ + isEditing: boolean; +}>(({ theme, isEditing }) => ({ + paddingLeft: theme.spacing(0.5), + '> .MuiTreeItem-content': { + padding: theme.spacing(0, 0.5), + gap: theme.spacing(0.5), + backgroundColor: isEditing ? alpha(theme.palette.primary.main, 0.2) : undefined, + }, +})); + export interface EditableTreeItemProps extends Omit { labelText?: string; renderLabel?: (children: React.ReactNode) => React.ReactNode; @@ -41,8 +54,6 @@ export default function EditableTreeItem({ sx, ...rest }: EditableTreeItemProps) { - const theme = useTheme(); - const inputRef = React.useRef(null); const [itemNameInput, setItemNameInput] = React.useState(suggestedNewItemName); @@ -125,7 +136,7 @@ export default function EditableTreeItem({ }; return ( - ), )} - sx={{ - ...sx, - paddingLeft: theme.spacing(0.5), - '> .MuiTreeItem-content': { - padding: theme.spacing(0, 0.5), - gap: theme.spacing(0.5), - backgroundColor: isEditing ? alpha(theme.palette.primary.main, 0.2) : undefined, - }, - }} + isEditing={isEditing} + sx={sx} /> ); } diff --git a/packages/toolpad-studio/src/components/HelpTooltipIcon.tsx b/packages/toolpad-studio/src/components/HelpTooltipIcon.tsx index 565ec86c91c..279b2693b0f 100644 --- a/packages/toolpad-studio/src/components/HelpTooltipIcon.tsx +++ b/packages/toolpad-studio/src/components/HelpTooltipIcon.tsx @@ -1,15 +1,19 @@ import * as React from 'react'; -import { SvgIconProps, Tooltip } from '@mui/material'; +import { styled, SvgIconProps, Tooltip } from '@mui/material'; import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; export interface HelpTooltipIconProps extends SvgIconProps { helpText: React.ReactNode; } -export default function HelpTooltipIcon({ helpText, sx, ...props }: HelpTooltipIconProps) { +const HelpIcon = styled(HelpOutlineIcon)(({ theme }) => ({ + color: theme.palette.text.secondary, +})); + +export default function HelpTooltipIcon({ helpText, ...props }: HelpTooltipIconProps) { return ( - + ); } diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/QueryIcon.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/QueryIcon.tsx index 7772cf7ba68..184a51def7e 100644 --- a/packages/toolpad-studio/src/toolpad/AppEditor/QueryIcon.tsx +++ b/packages/toolpad-studio/src/toolpad/AppEditor/QueryIcon.tsx @@ -4,7 +4,7 @@ import JavascriptIcon from '@mui/icons-material/Javascript'; import AdsClickIcon from '@mui/icons-material/AdsClick'; import AutoModeIcon from '@mui/icons-material/AutoMode'; import { SvgIconProps } from '@mui/material/SvgIcon'; -import { SxProps } from '@mui/system'; +import { SxProps } from '@mui/material'; import { styled } from '@mui/material/styles'; const dataSourceIconMap = new Map>([ diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/UpgradeNotification.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/UpgradeNotification.tsx index 6e5e50e8a7b..d932b577c04 100644 --- a/packages/toolpad-studio/src/toolpad/AppEditor/UpgradeNotification.tsx +++ b/packages/toolpad-studio/src/toolpad/AppEditor/UpgradeNotification.tsx @@ -3,7 +3,7 @@ import Alert, { AlertColor } from '@mui/material/Alert'; import Button from '@mui/material/Button'; import Chip from '@mui/material/Chip'; import Tooltip from '@mui/material/Tooltip'; -import { SxProps } from '@mui/system'; +import { SxProps } from '@mui/material'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import { UPGRADE_URL } from '../../constants'; diff --git a/packages/toolpad-studio/src/toolpad/propertyControls/DataProviderSelector.tsx b/packages/toolpad-studio/src/toolpad/propertyControls/DataProviderSelector.tsx index 42afb86ca9d..1e6a800a203 100644 --- a/packages/toolpad-studio/src/toolpad/propertyControls/DataProviderSelector.tsx +++ b/packages/toolpad-studio/src/toolpad/propertyControls/DataProviderSelector.tsx @@ -18,6 +18,7 @@ import { FormControlLabel, Radio, FormHelperText, + Stack, } from '@mui/material'; import { errorFrom } from '@toolpad/utils/errors'; import AddIcon from '@mui/icons-material/Add'; @@ -25,7 +26,6 @@ import { useMutation } from '@tanstack/react-query'; import { LoadingButton } from '@mui/lab'; import { generateUniqueString } from '@toolpad/utils/strings'; import { PaginationMode } from '@toolpad/studio-runtime'; -import { Stack } from '@mui/system'; import { EditorProps } from '../../types'; import { useProjectApi } from '../../projectApi'; import type { diff --git a/packages/toolpad-studio/src/toolpadDataSources/QueryPreview.tsx b/packages/toolpad-studio/src/toolpadDataSources/QueryPreview.tsx index 4ded81ed31d..5a9279dfbb6 100644 --- a/packages/toolpad-studio/src/toolpadDataSources/QueryPreview.tsx +++ b/packages/toolpad-studio/src/toolpadDataSources/QueryPreview.tsx @@ -1,5 +1,4 @@ -import { LinearProgress } from '@mui/material'; -import { Box } from '@mui/system'; +import { LinearProgress, Box } from '@mui/material'; import * as React from 'react'; import ErrorAlert from '../toolpad/AppEditor/PageEditor/ErrorAlert'; diff --git a/packages/toolpad-utils/tsup.config.ts b/packages/toolpad-utils/tsup.config.ts index 3c04e787976..4d109aced48 100644 --- a/packages/toolpad-utils/tsup.config.ts +++ b/packages/toolpad-utils/tsup.config.ts @@ -1,6 +1,6 @@ import * as fs from 'fs/promises'; -import path from 'path'; -import { spawnSync } from 'child_process'; +import { $ } from 'execa'; +import * as path from 'path'; import { defineConfig, Options } from 'tsup'; type EsbuildPlugin = NonNullable[number]; @@ -32,7 +32,9 @@ export default defineConfig((options) => ({ esbuildPlugins: [cleanFolderOnFailure(path.resolve(__dirname, 'dist'))], async onSuccess() { // eslint-disable-next-line no-console - console.log('build successful'); - spawnSync('tsc', ['--emitDeclarationOnly', '--declaration'], { shell: true }); + console.log('build successful, generate typings...'); + await $`tsc --emitDeclarationOnly --declaration`; + // eslint-disable-next-line no-console + console.log('typings generated'); }, }));