Skip to content

Commit

Permalink
Update plugin components (#4927)
Browse files Browse the repository at this point in the history
  • Loading branch information
lanzhenw authored Oct 22, 2024
1 parent adf51c9 commit 36faaac
Show file tree
Hide file tree
Showing 20 changed files with 1,221 additions and 63 deletions.
29 changes: 29 additions & 0 deletions app/packages/components/src/components/Loading/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";

import { CircularProgress } from "@mui/material";

const LoadingSpinner = ({
color = "base",
size = "medium",
}: {
color?: string;
size?: string;
}) => {
const COLORS: { [key: string]: string } = {
base: "#FFC59B",
primary: "primary",
secondary: "secondary",
error: "error",
warning: "warning",
info: "info",
success: "success",
};
const SIZES: { [key: string]: string } = {
small: "1rem",
medium: "2rem",
large: "3rem",
};
return <CircularProgress sx={{ color: COLORS[color] }} size={SIZES[size]} />;
};

export default LoadingSpinner;
88 changes: 88 additions & 0 deletions app/packages/components/src/components/ModalBase/DisplayTags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useState } from "react";
import { Box, Chip, TextField, IconButton } from "@mui/material";
import AddIcon from "@mui/icons-material/Add";

// Define the props interface for the DisplayTags component
interface DisplayTagsProps {
saveTags: (tags: string[]) => void; // saveTags is a function that accepts an array of strings and returns void
}

const DisplayTags: React.FC<DisplayTagsProps> = ({ saveTags }) => {
const [chips, setChips] = useState<string[]>([]); // chips is an array of strings
const [inputValue, setInputValue] = useState<string>(""); // inputValue is a string

const handleKeyPress = (event: React.KeyboardEvent) => {
if (event.key === "Enter") {
event.preventDefault();
handleAddChip();
}
};

const handleAddChip = () => {
if (inputValue.trim() !== "") {
const updatedChips = [...chips, inputValue];
setChips(updatedChips);
setInputValue("");
saveTags(updatedChips); // Call the saveTags function to save the new list of chips
}
};

const handleDeleteChip = (chipToDelete: string) => {
const updatedChips = chips.filter((chip) => chip !== chipToDelete);
setChips(updatedChips);
saveTags(updatedChips); // Call the saveTags function to save the updated list of chips
};

return (
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 2,
alignItems: "start",
paddingTop: 1,
paddingBottom: 1,
width: "100%", // Ensure the box takes full width
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 1,
width: "100%", // Ensure the inner box takes full width
}}
>
<TextField
variant="outlined"
label="Enter tag"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyPress}
fullWidth // Make TextField take up the remaining width
/>
<IconButton onClick={handleAddChip} color="primary">
<AddIcon />
</IconButton>
</Box>

<Box
sx={{
display: "flex",
gap: 1,
flexWrap: "wrap",
}}
>
{chips.map((chip, index) => (
<Chip
key={index}
label={chip}
onDelete={() => handleDeleteChip(chip)}
/>
))}
</Box>
</Box>
);
};

export default DisplayTags;
270 changes: 270 additions & 0 deletions app/packages/components/src/components/ModalBase/ModalBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import React, { useCallback, useEffect, useState } from "react";
import ButtonView from "@fiftyone/core/src/plugins/SchemaIO/components/ButtonView";
import { Box, Modal, Typography } from "@mui/material";
import DisplayTags from "./DisplayTags";
import { MuiIconFont } from "../index";

interface ModalBaseProps {
modal: {
icon?: string;
iconVariant?: "outlined" | "filled" | "rounded" | "sharp" | undefined;
title: string;
subtitle: string;
body: string;
textAlign?: string | { [key: string]: string };
};
primaryButton?: {
href?: any;
prompt?: any;
params?: any;
operator?: any;
align?: string;
width?: string;
onClick?: any;
disabled?: boolean;
primaryText: string;
primaryColor: string;
};
secondaryButton?: {
href?: any;
prompt?: any;
params?: any;
operator?: any;
align?: string;
width?: string;
onClick?: any;
disabled?: boolean;
secondaryText: string;
secondaryColor: string;
};
functionality?: string;
primaryCallback?: () => void;
secondaryCallback?: () => void;
props: any;
}

interface ModalButtonView {
variant: string;
label: string;
icon?: string;
iconPosition?: string;
componentsProps: any;
}

const ModalBase: React.FC<ModalBaseProps> = ({
modal,
primaryButton,
secondaryButton,
primaryCallback,
secondaryCallback,
functionality = "none",
props,
}) => {
const { title, subtitle, body } = modal;

const defaultAlign = "left";

let titleAlign = defaultAlign;
let subtitleAlign = defaultAlign;
let bodyAlign = defaultAlign;

const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => {
if (!secondaryCallback) {
setOpen(false);
}
};

if (typeof modal?.textAlign === "string") {
titleAlign = subtitleAlign = bodyAlign = modal.textAlign;
} else {
titleAlign = modal?.textAlign?.title ?? defaultAlign;
subtitleAlign = modal?.textAlign?.subtitle ?? defaultAlign;
bodyAlign = modal?.textAlign?.body ?? defaultAlign;
}

const modalStyle = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 600,
bgcolor: "background.paper",
border: "2px solid #000",
boxShadow: 24,
p: 6, // Padding for inner spacing
display: "flex",
flexDirection: "column", // Stack items vertically
justifyContent: "center", // Vertically center the content
};

const modalButtonView: ModalButtonView = {
variant: props?.variant || "outlined",
label: props?.label || "Open Modal",
componentsProps: {
button: {
sx: {
height: props?.height || "100%",
width: props?.width || "100%",
padding: 1,
},
},
},
};

if (Object.keys(props).includes("icon")) {
modalButtonView["icon"] = props["icon"];
modalButtonView["iconPosition"] = props?.iconPosition || "left";
}

const [primaryButtonView, setPrimaryButtonView] = useState({
variant: "contained",
color: primaryButton?.primaryColor,
label: primaryButton?.primaryText,
onClick: primaryButton?.onClick,
operator: primaryCallback || primaryButton?.operator,
params: primaryButton?.params,
href: primaryButton?.href,
prompt: primaryButton?.prompt,
disabled: primaryButton?.disabled,
componentsProps: {
button: {
sx: {
width: primaryButton?.width || "100%",
justifyContent: primaryButton?.align || "center",
...primaryButton,
},
},
},
});

const [secondaryButtonView, setSecondaryButtonView] = useState({
variant: "outlined",
color: secondaryButton?.secondaryColor,
label: secondaryButton?.secondaryText,
onClick: secondaryButton?.onClick,
operator: secondaryCallback || secondaryButton?.operator,
params: secondaryButton?.params,
href: secondaryButton?.href,
prompt: secondaryButton?.prompt,
disabled: secondaryButton?.disabled,
componentsProps: {
button: {
sx: {
width: primaryButton?.width || "100%",
justifyContent: primaryButton?.align || "center",
...secondaryButton,
},
},
},
});

// State options for functionality based on user input

{
/* TAGGING FUNCTIONALITY */
}
useEffect(() => {
if (
(functionality === "tagging" || functionality === "Tagging") &&
(!primaryButtonView.params ||
!primaryButtonView.params.tags ||
primaryButtonView.params.tags.length === 0)
) {
setPrimaryButtonView({ ...primaryButtonView, disabled: true });
} else {
setPrimaryButtonView({ ...primaryButtonView, disabled: false });
}
}, [primaryButtonView.params]);

const handleSaveTags = useCallback((tags: string[]) => {
setPrimaryButtonView((prevButtonView) => ({
...prevButtonView,
params: { ...prevButtonView.params, tags }, // Add tags to existing params
}));
}, []);

return (
<>
<Box>
<ButtonView
onClick={handleOpen}
schema={{
view: modalButtonView,
}}
/>
</Box>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-title"
aria-describedby="modal-subtitle"
>
<Box sx={modalStyle}>
<Typography
id="modal-title"
variant="h5"
component="h5"
sx={{ textAlign: titleAlign }}
>
{modal?.icon && (
<MuiIconFont
variant={modal?.iconVariant || "filled"}
sx={{ verticalAlign: "middle" }}
name={modal.icon}
>
{modal.icon}
</MuiIconFont>
)}{" "}
{title}
</Typography>
<Typography
id="modal-subtitle"
variant="h6"
component="h6"
sx={{ mt: 4, textAlign: subtitleAlign }}
>
{subtitle}
</Typography>
<Typography id="modal-body" sx={{ my: 1, textAlign: bodyAlign }}>
{body}
</Typography>
{(functionality === "tagging" || functionality === "Tagging") && (
<DisplayTags saveTags={handleSaveTags} />
)}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
mt: 2,
gap: 3,
}}
>
{secondaryButton && (
<Box sx={{ flexGrow: 1 }}>
<ButtonView
onClick={handleClose}
schema={{
view: secondaryButtonView,
}}
/>
</Box>
)}
{primaryButton && (
<Box sx={{ flexGrow: 1 }}>
<ButtonView
onClick={handleClose}
schema={{
view: primaryButtonView,
}}
/>
</Box>
)}
</Box>
</Box>
</Modal>
</>
);
};
export default ModalBase;
1 change: 1 addition & 0 deletions app/packages/components/src/components/ModalBase/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ModalBase } from "./ModalBase";
Loading

0 comments on commit 36faaac

Please sign in to comment.