Create FloatingLabelInput for scenario variables (#68)
* Create FloatingLabelInput * Fix prettier * Simplify changes
This commit is contained in:
49
src/components/OutputsTable/FloatingLabelInput.tsx
Normal file
49
src/components/OutputsTable/FloatingLabelInput.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { FormLabel, FormControl, type TextareaProps } from "@chakra-ui/react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import AutoResizeTextArea from "../AutoResizeTextArea";
|
||||||
|
|
||||||
|
export const FloatingLabelInput = ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
...props
|
||||||
|
}: { label: string; value: string } & TextareaProps) => {
|
||||||
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl position="relative">
|
||||||
|
<FormLabel
|
||||||
|
position="absolute"
|
||||||
|
left="10px"
|
||||||
|
top={isFocused || !!value ? 0 : 3}
|
||||||
|
transform={isFocused || !!value ? "translateY(-50%)" : "translateY(0)"}
|
||||||
|
fontSize={isFocused || !!value ? "12px" : "16px"}
|
||||||
|
transition="all 0.15s"
|
||||||
|
zIndex="100"
|
||||||
|
bg="white"
|
||||||
|
px={1}
|
||||||
|
mt={0}
|
||||||
|
mb={2}
|
||||||
|
lineHeight="1"
|
||||||
|
pointerEvents="none"
|
||||||
|
color={isFocused ? "blue.500" : "gray.500"}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</FormLabel>
|
||||||
|
<AutoResizeTextArea
|
||||||
|
px={3}
|
||||||
|
pt={3}
|
||||||
|
pb={2}
|
||||||
|
onFocus={() => setIsFocused(true)}
|
||||||
|
onBlur={() => setIsFocused(false)}
|
||||||
|
borderRadius="md"
|
||||||
|
borderColor={isFocused ? "blue.500" : "gray.400"}
|
||||||
|
autoComplete="off"
|
||||||
|
value={value}
|
||||||
|
maxHeight={32}
|
||||||
|
overflowY="auto"
|
||||||
|
overflowX="hidden"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -9,7 +9,7 @@ import { Box, Button, Flex, HStack, Icon, Spinner, Stack, Tooltip, VStack } from
|
|||||||
import { cellPadding } from "../constants";
|
import { cellPadding } from "../constants";
|
||||||
import { BsX } from "react-icons/bs";
|
import { BsX } from "react-icons/bs";
|
||||||
import { RiDraggable } from "react-icons/ri";
|
import { RiDraggable } from "react-icons/ri";
|
||||||
import AutoResizeTextArea from "../AutoResizeTextArea";
|
import { FloatingLabelInput } from "./FloatingLabelInput";
|
||||||
|
|
||||||
export default function ScenarioEditor({
|
export default function ScenarioEditor({
|
||||||
scenario,
|
scenario,
|
||||||
@@ -77,6 +77,7 @@ export default function ScenarioEditor({
|
|||||||
pr={cellPadding.x}
|
pr={cellPadding.x}
|
||||||
py={cellPadding.y}
|
py={cellPadding.y}
|
||||||
pl={canModify ? 0 : cellPadding.x}
|
pl={canModify ? 0 : cellPadding.x}
|
||||||
|
spacing={0}
|
||||||
height="100%"
|
height="100%"
|
||||||
draggable={!variableInputHovered}
|
draggable={!variableInputHovered}
|
||||||
onDragStart={(e) => {
|
onDragStart={(e) => {
|
||||||
@@ -131,7 +132,7 @@ export default function ScenarioEditor({
|
|||||||
{variableLabels.length === 0 ? (
|
{variableLabels.length === 0 ? (
|
||||||
<Box color="gray.500">{vars.data ? "No scenario variables configured" : "Loading..."}</Box>
|
<Box color="gray.500">{vars.data ? "No scenario variables configured" : "Loading..."}</Box>
|
||||||
) : (
|
) : (
|
||||||
<VStack spacing={1}>
|
<VStack spacing={4} flex={1} py={2}>
|
||||||
{variableLabels.map((key) => {
|
{variableLabels.map((key) => {
|
||||||
const value = values[key] ?? "";
|
const value = values[key] ?? "";
|
||||||
const layoutDirection = value.length > 20 ? "column" : "row";
|
const layoutDirection = value.length > 20 ? "column" : "row";
|
||||||
@@ -143,31 +144,14 @@ export default function ScenarioEditor({
|
|||||||
flexWrap="wrap"
|
flexWrap="wrap"
|
||||||
width="full"
|
width="full"
|
||||||
>
|
>
|
||||||
<Box
|
<FloatingLabelInput
|
||||||
bgColor="blue.100"
|
label={key}
|
||||||
color="blue.600"
|
|
||||||
px={1}
|
|
||||||
my="3px"
|
|
||||||
fontSize="xs"
|
|
||||||
fontWeight="bold"
|
|
||||||
>
|
|
||||||
{key}
|
|
||||||
</Box>
|
|
||||||
<AutoResizeTextArea
|
|
||||||
px={2}
|
|
||||||
py={1}
|
|
||||||
placeholder="empty"
|
|
||||||
borderRadius="sm"
|
|
||||||
fontSize="sm"
|
|
||||||
lineHeight={1.2}
|
|
||||||
value={value}
|
|
||||||
isDisabled={!canModify}
|
isDisabled={!canModify}
|
||||||
_disabled={{ opacity: 1, cursor: "default" }}
|
style={{ width: "100%" }}
|
||||||
|
value={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setValues((prev) => ({ ...prev, [key]: e.target.value }));
|
setValues((prev) => ({ ...prev, [key]: e.target.value }));
|
||||||
}}
|
}}
|
||||||
maxH="32"
|
|
||||||
overflowY="auto"
|
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -175,12 +159,6 @@ export default function ScenarioEditor({
|
|||||||
onSave();
|
onSave();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
resize="none"
|
|
||||||
overflow="hidden"
|
|
||||||
flex={layoutDirection === "row" ? 1 : undefined}
|
|
||||||
borderColor={hasChanged ? "blue.300" : "transparent"}
|
|
||||||
_hover={{ borderColor: "gray.300" }}
|
|
||||||
_focus={{ borderColor: "blue.500", outline: "none", bg: "white" }}
|
|
||||||
onMouseEnter={() => setVariableInputHovered(true)}
|
onMouseEnter={() => setVariableInputHovered(true)}
|
||||||
onMouseLeave={() => setVariableInputHovered(false)}
|
onMouseLeave={() => setVariableInputHovered(false)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ import { type Session } from "next-auth";
|
|||||||
import { SessionProvider } from "next-auth/react";
|
import { SessionProvider } from "next-auth/react";
|
||||||
import { type AppType } from "next/app";
|
import { type AppType } from "next/app";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { ChakraProvider } from "@chakra-ui/react";
|
|
||||||
import theme from "~/utils/theme";
|
|
||||||
import Favicon from "~/components/Favicon";
|
import Favicon from "~/components/Favicon";
|
||||||
import "~/utils/analytics";
|
import "~/utils/analytics";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
import { ChakraThemeProvider } from "~/theme/ChakraThemeProvider";
|
||||||
|
|
||||||
const MyApp: AppType<{ session: Session | null }> = ({
|
const MyApp: AppType<{ session: Session | null }> = ({
|
||||||
Component,
|
Component,
|
||||||
@@ -22,9 +21,9 @@ const MyApp: AppType<{ session: Session | null }> = ({
|
|||||||
</Head>
|
</Head>
|
||||||
<SessionProvider session={session}>
|
<SessionProvider session={session}>
|
||||||
<Favicon />
|
<Favicon />
|
||||||
<ChakraProvider theme={theme}>
|
<ChakraThemeProvider>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</ChakraProvider>
|
</ChakraThemeProvider>
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { extendTheme } from "@chakra-ui/react";
|
import { extendTheme } from "@chakra-ui/react";
|
||||||
import "@fontsource/inconsolata";
|
import "@fontsource/inconsolata";
|
||||||
|
import { ChakraProvider } from "@chakra-ui/react";
|
||||||
|
|
||||||
const systemFont =
|
const systemFont =
|
||||||
'ui-sans-serif, -apple-system, "system-ui", "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"';
|
'ui-sans-serif, -apple-system, "system-ui", "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"';
|
||||||
@@ -34,4 +35,6 @@ const theme = extendTheme({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default theme;
|
export const ChakraThemeProvider = ({ children }: { children: JSX.Element }) => {
|
||||||
|
return <ChakraProvider theme={theme}>{children}</ChakraProvider>;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user