From 9a3144eca054fee43609b865e36806315a291cd5 Mon Sep 17 00:00:00 2001
From: Ryan Laurie <30528226+iethree@users.noreply.github.com>
Date: Wed, 14 Sep 2022 14:54:32 -0600
Subject: [PATCH] Action Creator 6: Allow user-defined options for select and
 radio inputs (#25404)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* allow user-defined select options

* Update frontend/src/metabase/writeback/components/ActionCreator/FormCreator/utils.ts

🤦
---
 .../FormCreator/FormCreator.styled.tsx        | 16 +++++++
 .../ActionCreator/FormCreator/FormCreator.tsx | 37 ++++++++++++++-
 .../FormCreator/OptionEditor.styled.tsx       | 28 ++++++++++++
 .../FormCreator/OptionEditor.tsx              | 45 +++++++++++++++++++
 .../ActionCreator/FormCreator/utils.ts        | 11 ++++-
 5 files changed, 134 insertions(+), 3 deletions(-)
 create mode 100644 frontend/src/metabase/writeback/components/ActionCreator/FormCreator/OptionEditor.styled.tsx
 create mode 100644 frontend/src/metabase/writeback/components/ActionCreator/FormCreator/OptionEditor.tsx

diff --git a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.styled.tsx b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.styled.tsx
index 6ec31bbcc09..43b1a3e784f 100644
--- a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.styled.tsx
+++ b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.styled.tsx
@@ -1,5 +1,6 @@
 import styled from "@emotion/styled";
 import InputBase from "metabase/core/components/Input";
+import Button from "metabase/core/components/Button";
 
 import { color } from "metabase/lib/colors";
 import { space } from "metabase/styled-components/theme";
@@ -40,3 +41,18 @@ export const EmptyFormPlaceholderWrapper = styled.div`
   max-width: 20rem;
   margin: 5rem auto;
 `;
+
+export const EditButton = styled(Button)`
+  color: ${color("brand")};
+  background-opacity: 0;
+  &:hover {
+    color: ${color("accent0-light")};
+  }
+`;
+
+export const FormSettingsPreviewContainer = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: ${space(1)};
+  min-width: 12rem;
+`;
diff --git a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.tsx b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.tsx
index d43b0c355f9..fe84275af26 100644
--- a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.tsx
+++ b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.tsx
@@ -7,6 +7,7 @@ import type { ActionFormSettings, FieldSettings } from "metabase-types/api";
 import { FieldSettingsPopover } from "./FieldSettingsPopover";
 import { getDefaultFormSettings, getDefaultFieldSettings } from "./utils";
 import { FormField } from "./FormField";
+import { OptionEditor } from "./OptionEditor";
 
 import {
   FormItemWrapper,
@@ -14,6 +15,8 @@ import {
   FormItemName,
   FormSettings,
   EmptyFormPlaceholderWrapper,
+  FormSettingsPreviewContainer,
+  EditButton,
 } from "./FormCreator.styled";
 
 export function FormCreator({
@@ -74,12 +77,44 @@ function FormItem({
   fieldSettings: FieldSettings;
   onChange: (fieldSettings: FieldSettings) => void;
 }) {
+  const [isEditingOptions, setIsEditingOptions] = useState(false);
   const name = tag.name;
+
+  const updateOptions = (newOptions: (string | number)[]) => {
+    onChange({
+      ...fieldSettings,
+      valueOptions: newOptions,
+    });
+    setIsEditingOptions(false);
+  };
+
+  const hasOptions =
+    fieldSettings.inputType === "dropdown" ||
+    fieldSettings.inputType === "inline-select";
+
   return (
     <FormItemWrapper>
       <FormItemName>{name}</FormItemName>
       <FormSettings>
-        <FormField tag={tag} fieldSettings={fieldSettings} />
+        <FormSettingsPreviewContainer>
+          {isEditingOptions && hasOptions ? (
+            <OptionEditor
+              options={fieldSettings.valueOptions ?? []}
+              onChange={updateOptions}
+            />
+          ) : (
+            <FormField tag={tag} fieldSettings={fieldSettings} />
+          )}
+          {!isEditingOptions && hasOptions && (
+            <EditButton
+              onClick={() => setIsEditingOptions(true)}
+              borderless
+              small
+            >
+              {t`Edit options`}
+            </EditButton>
+          )}
+        </FormSettingsPreviewContainer>
         <FieldSettingsPopover
           fieldSettings={fieldSettings}
           onChange={onChange}
diff --git a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/OptionEditor.styled.tsx b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/OptionEditor.styled.tsx
new file mode 100644
index 00000000000..358e4133b7f
--- /dev/null
+++ b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/OptionEditor.styled.tsx
@@ -0,0 +1,28 @@
+import styled from "@emotion/styled";
+import InputBase from "metabase/core/components/Input";
+
+import { color } from "metabase/lib/colors";
+import { space } from "metabase/styled-components/theme";
+
+export const OptionEditorContainer = styled.div`
+  display: flex;
+  flex-direction: column;
+`;
+
+export const AddMorePrompt = styled.div`
+  text-align: center;
+  font-size: 0.875rem;
+  margin: ${space(1)} 0;
+  height: 1.25rem;
+  color: ${color("text-light")};
+  transition: opacity 0.2s ease-in-out;
+`;
+
+export const StyledTextArea = styled.textarea`
+  resize: none;
+  border: none;
+  outline: 1px solid ${color("border")};
+  width: 20rem;
+  border-radius: ${space(1)};
+  padding: ${space(1)};
+`;
diff --git a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/OptionEditor.tsx b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/OptionEditor.tsx
new file mode 100644
index 00000000000..860a0e2710a
--- /dev/null
+++ b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/OptionEditor.tsx
@@ -0,0 +1,45 @@
+import React, { useState } from "react";
+import { t } from "ttag";
+
+import Button from "metabase/core/components/Button";
+
+import {
+  OptionEditorContainer,
+  AddMorePrompt,
+  StyledTextArea,
+} from "./OptionEditor.styled";
+
+type ValueOptions = (string | number)[];
+
+const optionsToText = (options: ValueOptions) => options.join("\n");
+const textToOptions = (text: string): ValueOptions =>
+  text.split("\n").map(option => option.trim());
+
+export const OptionEditor = ({
+  options,
+  onChange,
+}: {
+  options: ValueOptions;
+  onChange: (options: ValueOptions) => void;
+}) => {
+  const [text, setText] = useState(optionsToText(options));
+  const save = () => {
+    onChange(textToOptions(text));
+  };
+
+  return (
+    <OptionEditorContainer>
+      <StyledTextArea
+        value={text}
+        onChange={e => setText(e.target.value)}
+        placeholder={t`Enter one option per line`}
+      />
+      <AddMorePrompt style={{ opacity: text.length ? 1 : 0 }}>
+        {t`Press enter to add another option`}
+      </AddMorePrompt>
+      <Button onClick={save} small>
+        {t`Save`}
+      </Button>
+    </OptionEditorContainer>
+  );
+};
diff --git a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/utils.ts b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/utils.ts
index a7168563550..e0676b02f5d 100644
--- a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/utils.ts
+++ b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/utils.ts
@@ -40,6 +40,9 @@ const getSampleOptions = () => [
   { name: t`Option Three`, value: 3 },
 ];
 
+const getOptionsFromArray = (options: (number | string)[]) =>
+  options.map(o => ({ name: o, value: o }));
+
 const getParameterFieldProps = (fieldSettings: FieldSettings) => {
   switch (fieldSettings.inputType) {
     case "string":
@@ -56,12 +59,16 @@ const getParameterFieldProps = (fieldSettings: FieldSettings) => {
     case "dropdown":
       return {
         type: "select",
-        options: fieldSettings.valueOptions ?? getSampleOptions(),
+        options: fieldSettings.valueOptions?.length
+          ? getOptionsFromArray(fieldSettings.valueOptions)
+          : getSampleOptions(),
       };
     case "inline-select":
       return {
         type: "radio",
-        options: fieldSettings.valueOptions ?? getSampleOptions(),
+        options: fieldSettings.valueOptions?.length
+          ? getOptionsFromArray(fieldSettings.valueOptions)
+          : getSampleOptions(),
       };
     default:
       return { type: "input" };
-- 
GitLab