Skip to content

Commit

Permalink
Global functions explorer and header (#2690)
Browse files Browse the repository at this point in the history
  • Loading branch information
apedroferreira authored Feb 28, 2024
1 parent 2664c1e commit 513812e
Show file tree
Hide file tree
Showing 7 changed files with 643 additions and 41 deletions.
23 changes: 17 additions & 6 deletions packages/toolpad-app/src/components/EditableTreeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { TreeItem, TreeItemProps } from '@mui/x-tree-view';
import {
Alert,
CircularProgress,
InputBase,
InputBaseProps,
Popover,
Expand All @@ -17,6 +18,7 @@ export interface EditableTreeItemProps extends Omit<TreeItemProps, 'label'> {
isEditing?: boolean;
onEdit?: (newItemName: string) => void | Promise<void>;
onCancel?: () => void | Promise<void>;
isLoading?: boolean;
validateItemName?: (newItemName: string) => { isValid: boolean; errorMessage?: string };
inputProps?: Omit<
InputBaseProps,
Expand All @@ -34,6 +36,7 @@ export default function EditableTreeItem({
isEditing: isExternalEditing = false,
onEdit,
onCancel,
isLoading = false,
inputProps,
sx,
...rest
Expand Down Expand Up @@ -76,20 +79,20 @@ export default function EditableTreeItem({
}
}, [onCancel, suggestedNewItemName]);

const handleConfirm = React.useCallback(() => {
if (!itemNameInput || !newItemValidationResult.isValid) {
const handleConfirm = React.useCallback(async () => {
if (!itemNameInput || !newItemValidationResult.isValid || isLoading) {
handleCancel();
return;
}

if (onEdit) {
onEdit(itemNameInput);
await onEdit(itemNameInput);
}
setIsInternalEditing(false);
}, [handleCancel, itemNameInput, newItemValidationResult.isValid, onEdit]);
}, [handleCancel, isLoading, itemNameInput, newItemValidationResult.isValid, onEdit]);

const handleChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setItemNameInput(event.target.value);
setItemNameInput(event.target.value.replaceAll(/[^a-zA-Z0-9]/g, ''));
}, []);

const handleFocus = React.useCallback((event: React.FocusEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -138,6 +141,8 @@ export default function EditableTreeItem({
onBlur={handleBlur}
onKeyDown={handleKeyDown}
fullWidth
disabled={isLoading}
endAdornment={isLoading ? <CircularProgress size={14} /> : null}
sx={{
...(inputProps?.sx || {}),
...labelTextSx,
Expand Down Expand Up @@ -165,7 +170,13 @@ export default function EditableTreeItem({
) : (
<Typography
variant="body2"
sx={{ fontWeight: 'inherit', flexGrow: 1, ...labelTextSx }}
sx={{
fontWeight: 'inherit',
flexGrow: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
...labelTextSx,
}}
noWrap
>
{labelText}
Expand Down
104 changes: 92 additions & 12 deletions packages/toolpad-app/src/toolpad/AppEditor/ExplorerHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import * as React from 'react';
import { IconButton, Tooltip, Stack, Typography, styled } from '@mui/material';
import {
IconButton,
Tooltip,
Stack,
Typography,
styled,
InputAdornment,
TextField,
} from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import SearchIcon from '@mui/icons-material/Search';
// import ClearIcon from '@mui/icons-material/Clear';

interface ExplorerHeaderProps {
headerText: string;
headerIcon?: React.ReactNode;
createLabelText?: string;
onCreate?: React.MouseEventHandler<HTMLButtonElement>;
searchLabelText?: string;
onSearch?: (searchTerm: string) => void | Promise<void>;
hasPersistentSearch?: boolean;
}

const ExplorerHeaderContainer = styled(Stack)(({ theme }) => ({
Expand All @@ -25,25 +38,92 @@ export default function ExplorerHeader({
headerText,
headerIcon,
onCreate,
onSearch,
createLabelText,
searchLabelText = 'Search',
hasPersistentSearch = false,
}: ExplorerHeaderProps) {
const [isSearching, setIsSearching] = React.useState(false || hasPersistentSearch);
const [searchTerm, setSearchTerm] = React.useState('');

const handleSearchClick = React.useCallback(() => {
if (isSearching && searchTerm) {
setSearchTerm('');
}

setIsSearching((previousIsSearching) => !previousIsSearching);
}, [isSearching, searchTerm]);

const handleSearchChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
}, []);

// const handleClearSearch = React.useCallback(() => {
// setSearchTerm('');
// }, []);

React.useEffect(() => {
if (onSearch) {
onSearch(searchTerm);
}
}, [onSearch, searchTerm]);

return (
<ExplorerHeaderContainer
direction="row"
alignItems="center"
justifyContent="space-between"
sx={{ pl: 2.5 }}
sx={{ pl: isSearching ? 2 : 2.5 }}
>
{headerIcon}
<ExplorerHeaderTitle
variant="body2"
sx={{
mx: 0.5,
my: 0.5,
}}
>
{headerText}
</ExplorerHeaderTitle>
{isSearching ? (
<TextField
hiddenLabel
value={searchTerm}
onChange={handleSearchChange}
InputProps={{
startAdornment: hasPersistentSearch ? (
<InputAdornment position="start">
<SearchIcon fontSize="small" sx={{ mt: '-4px' }} />
</InputAdornment>
) : null,
// endAdornment: searchTerm ? (
// <InputAdornment position="end">
// <IconButton onClick={handleClearSearch} edge="end">
// <ClearIcon fontSize="small" />
// </IconButton>
// </InputAdornment>
// ) : null,
sx: {
fontSize: 14,
borderRadius: 0,
},
}}
variant="standard"
fullWidth
size="small"
placeholder={`${searchLabelText}…`}
/>
) : (
<React.Fragment>
{headerIcon}
<ExplorerHeaderTitle
variant="body2"
sx={{
mx: 0.5,
my: 0.5,
}}
>
{headerText}
</ExplorerHeaderTitle>
</React.Fragment>
)}
{onSearch && searchLabelText && !hasPersistentSearch ? (
<Tooltip title={searchLabelText}>
<IconButton aria-label={searchLabelText} size="medium" onClick={handleSearchClick}>
<SearchIcon color={isSearching ? 'primary' : 'inherit'} />
</IconButton>
</Tooltip>
) : null}
{onCreate && createLabelText ? (
<Tooltip title={createLabelText}>
<IconButton aria-label={createLabelText} size="medium" onClick={onCreate}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Box, Typography } from '@mui/material';
import { TreeView, TreeItem, TreeItemProps } from '@mui/x-tree-view';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import useBoolean from '@mui/toolpad-utils/hooks/useBoolean';
import * as appDom from '@mui/toolpad-core/appDom';
import { useAppState, useDomApi, useAppStateApi } from '../../AppState';
import { ComponentIcon } from '../PageEditor/ComponentCatalog/ComponentCatalogItem';
Expand All @@ -22,12 +23,9 @@ function CustomTreeItem(props: CustomTreeItemProps) {
const { dom } = useAppState();
const appStateApi = useAppStateApi();

const [domNodeEditable, setDomNodeEditable] = React.useState(false);
const { label, node, ...other } = props;

const handleStopEditing = React.useCallback(() => {
setDomNodeEditable(false);
}, []);
const { value: domNodeEditing, setFalse: stopDomNodeEditing } = useBoolean(false);

const existingNames = React.useMemo(() => appDom.getExistingNamesForNode(dom, node), [dom, node]);

Expand Down Expand Up @@ -84,10 +82,10 @@ function CustomTreeItem(props: CustomTreeItemProps) {
{children}
</Box>
)}
isEditing={domNodeEditable}
isEditing={domNodeEditing}
onEdit={handleNameSave}
suggestedNewItemName={node.name}
onCancel={handleStopEditing}
onCancel={stopDomNodeEditing}
validateItemName={validateEditableNodeName}
{...other}
/>
Expand Down
Loading

0 comments on commit 513812e

Please sign in to comment.