From 6f3c99e16678d50725fa0edcfb1f05c9c8cee5d9 Mon Sep 17 00:00:00 2001
From: Alexander Polyankin <alexander.polyankin@metabase.com>
Date: Wed, 15 Mar 2023 18:19:32 +0200
Subject: [PATCH] Tweak boolean fields in basic action forms (#29165)

---
 .../FormCheckBox/FormCheckBox.stories.tsx     | 34 +++++++++++++++
 .../FormDateInput/FormDateInput.stories.tsx   | 34 +++++++++++++++
 .../components/FormField/FormField.styled.tsx | 24 +++++------
 .../core/components/FormField/FormField.tsx   | 17 ++++++--
 .../FormFileInput/FormFileInput.stores.tsx    | 34 +++++++++++++++
 .../FormInput/FormInput.stories.tsx           | 34 +++++++++++++++
 .../FormNumericInput.stories.tsx              | 34 +++++++++++++++
 .../FormRadio/FormRadio.stories.tsx           | 40 ++++++++++++++++++
 .../FormSelect/FormSelect.stories.tsx         | 42 +++++++++++++++++++
 .../FormTextArea/FormTextArea.stories.tsx     | 34 +++++++++++++++
 .../FormToggle/FormToggle.stories.tsx         | 34 +++++++++++++++
 .../ActionSidebar/ActionSidebar.tsx           |  2 +-
 12 files changed, 345 insertions(+), 18 deletions(-)
 create mode 100644 frontend/src/metabase/core/components/FormCheckBox/FormCheckBox.stories.tsx
 create mode 100644 frontend/src/metabase/core/components/FormDateInput/FormDateInput.stories.tsx
 create mode 100644 frontend/src/metabase/core/components/FormFileInput/FormFileInput.stores.tsx
 create mode 100644 frontend/src/metabase/core/components/FormInput/FormInput.stories.tsx
 create mode 100644 frontend/src/metabase/core/components/FormNumericInput/FormNumericInput.stories.tsx
 create mode 100644 frontend/src/metabase/core/components/FormRadio/FormRadio.stories.tsx
 create mode 100644 frontend/src/metabase/core/components/FormSelect/FormSelect.stories.tsx
 create mode 100644 frontend/src/metabase/core/components/FormTextArea/FormTextArea.stories.tsx
 create mode 100644 frontend/src/metabase/core/components/FormToggle/FormToggle.stories.tsx

diff --git a/frontend/src/metabase/core/components/FormCheckBox/FormCheckBox.stories.tsx b/frontend/src/metabase/core/components/FormCheckBox/FormCheckBox.stories.tsx
new file mode 100644
index 00000000000..93dfcfb278e
--- /dev/null
+++ b/frontend/src/metabase/core/components/FormCheckBox/FormCheckBox.stories.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import type { ComponentStory } from "@storybook/react";
+import Form from "../Form";
+import FormProvider from "../FormProvider";
+import FormCheckBox from "./FormCheckBox";
+
+export default {
+  title: "Core/FormCheckBox",
+  component: FormCheckBox,
+};
+
+const Template: ComponentStory<typeof FormCheckBox> = args => {
+  const initialValues = { value: false };
+  const handleSubmit = () => undefined;
+
+  return (
+    <FormProvider initialValues={initialValues} onSubmit={handleSubmit}>
+      <Form>
+        <FormCheckBox {...args} name="value" />
+      </Form>
+    </FormProvider>
+  );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+  title: "Title",
+};
+
+export const WithDescription = Template.bind({});
+WithDescription.args = {
+  title: "Title",
+  description: "Description",
+};
diff --git a/frontend/src/metabase/core/components/FormDateInput/FormDateInput.stories.tsx b/frontend/src/metabase/core/components/FormDateInput/FormDateInput.stories.tsx
new file mode 100644
index 00000000000..208fa1b0ece
--- /dev/null
+++ b/frontend/src/metabase/core/components/FormDateInput/FormDateInput.stories.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import type { ComponentStory } from "@storybook/react";
+import Form from "../Form";
+import FormProvider from "../FormProvider";
+import FormDateInput from "./FormDateInput";
+
+export default {
+  title: "Core/FormDateInput",
+  component: FormDateInput,
+};
+
+const Template: ComponentStory<typeof FormDateInput> = args => {
+  const initialValues = { value: undefined };
+  const handleSubmit = () => undefined;
+
+  return (
+    <FormProvider initialValues={initialValues} onSubmit={handleSubmit}>
+      <Form>
+        <FormDateInput {...args} name="value" />
+      </Form>
+    </FormProvider>
+  );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+  title: "Title",
+};
+
+export const WithDescription = Template.bind({});
+WithDescription.args = {
+  title: "Title",
+  description: "Description",
+};
diff --git a/frontend/src/metabase/core/components/FormField/FormField.styled.tsx b/frontend/src/metabase/core/components/FormField/FormField.styled.tsx
index 3957153fba2..1e898c039fd 100644
--- a/frontend/src/metabase/core/components/FormField/FormField.styled.tsx
+++ b/frontend/src/metabase/core/components/FormField/FormField.styled.tsx
@@ -1,5 +1,4 @@
 import styled from "@emotion/styled";
-import React from "react";
 import { color } from "metabase/lib/colors";
 import Icon from "metabase/components/Icon";
 import { FieldAlignment, FieldOrientation } from "./types";
@@ -7,9 +6,12 @@ import { FieldAlignment, FieldOrientation } from "./types";
 export interface FormCaptionProps {
   alignment: FieldAlignment;
   orientation: FieldOrientation;
+  hasDescription: boolean;
 }
 
 export const FieldCaption = styled.div<FormCaptionProps>`
+  align-self: ${props =>
+    props.orientation !== "vertical" && !props.hasDescription ? "center" : ""};
   margin-left: ${props =>
     props.orientation === "horizontal" &&
     props.alignment === "start" &&
@@ -38,10 +40,16 @@ export const OptionalTag = styled.span`
   margin-left: 0.25rem;
 `;
 
-export const FieldLabelContainer = styled.div`
+interface FieldLabelContainerProps {
+  orientation: FieldOrientation;
+  hasDescription: boolean;
+}
+
+export const FieldLabelContainer = styled.div<FieldLabelContainerProps>`
   display: flex;
   align-items: center;
-  margin-bottom: 0.5em;
+  margin-bottom: ${props =>
+    props.orientation === "vertical" || props.hasDescription ? "0.5em" : ""};
 `;
 
 export const FieldLabelError = styled.span`
@@ -94,13 +102,3 @@ export const FieldRoot = styled.div<FieldRootProps>`
     }
   }
 `;
-
-export const FieldLabelWithContainer = ({
-  children,
-}: {
-  children: React.ReactNode;
-}) => (
-  <FieldLabelContainer>
-    <FieldLabel hasError={false}>{children}</FieldLabel>
-  </FieldLabelContainer>
-);
diff --git a/frontend/src/metabase/core/components/FormField/FormField.tsx b/frontend/src/metabase/core/components/FormField/FormField.tsx
index 8397867d014..3348eddb6cc 100644
--- a/frontend/src/metabase/core/components/FormField/FormField.tsx
+++ b/frontend/src/metabase/core/components/FormField/FormField.tsx
@@ -42,6 +42,8 @@ const FormField = forwardRef(function FormField(
   }: FormFieldProps,
   ref: Ref<HTMLDivElement>,
 ) {
+  const hasTitle = Boolean(title);
+  const hasDescription = Boolean(description);
   const hasError = Boolean(error);
 
   return (
@@ -52,10 +54,17 @@ const FormField = forwardRef(function FormField(
       orientation={orientation}
     >
       {alignment === "start" && children}
-      {(title || description) && (
-        <FieldCaption alignment={alignment} orientation={orientation}>
-          <FieldLabelContainer>
-            {title && (
+      {(hasTitle || hasDescription) && (
+        <FieldCaption
+          alignment={alignment}
+          orientation={orientation}
+          hasDescription={hasDescription}
+        >
+          <FieldLabelContainer
+            orientation={orientation}
+            hasDescription={hasDescription}
+          >
+            {hasTitle && (
               <FieldLabel hasError={hasError} htmlFor={htmlFor}>
                 {title}
                 {hasError && <FieldLabelError>: {error}</FieldLabelError>}
diff --git a/frontend/src/metabase/core/components/FormFileInput/FormFileInput.stores.tsx b/frontend/src/metabase/core/components/FormFileInput/FormFileInput.stores.tsx
new file mode 100644
index 00000000000..53c6fe0e220
--- /dev/null
+++ b/frontend/src/metabase/core/components/FormFileInput/FormFileInput.stores.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import type { ComponentStory } from "@storybook/react";
+import Form from "../Form";
+import FormProvider from "../FormProvider";
+import FormFileInput from "./FormFileInput";
+
+export default {
+  title: "Core/FormFileInput",
+  component: FormFileInput,
+};
+
+const Template: ComponentStory<typeof FormFileInput> = args => {
+  const initialValues = { value: undefined };
+  const handleSubmit = () => undefined;
+
+  return (
+    <FormProvider initialValues={initialValues} onSubmit={handleSubmit}>
+      <Form>
+        <FormFileInput {...args} name="value" />
+      </Form>
+    </FormProvider>
+  );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+  title: "Title",
+};
+
+export const WithDescription = Template.bind({});
+WithDescription.args = {
+  title: "Title",
+  description: "Description",
+};
diff --git a/frontend/src/metabase/core/components/FormInput/FormInput.stories.tsx b/frontend/src/metabase/core/components/FormInput/FormInput.stories.tsx
new file mode 100644
index 00000000000..506d9f51e5c
--- /dev/null
+++ b/frontend/src/metabase/core/components/FormInput/FormInput.stories.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import type { ComponentStory } from "@storybook/react";
+import Form from "../Form";
+import FormProvider from "../FormProvider";
+import FormInput from "./FormInput";
+
+export default {
+  title: "Core/FormInput",
+  component: FormInput,
+};
+
+const Template: ComponentStory<typeof FormInput> = args => {
+  const initialValues = { value: false };
+  const handleSubmit = () => undefined;
+
+  return (
+    <FormProvider initialValues={initialValues} onSubmit={handleSubmit}>
+      <Form>
+        <FormInput {...args} name="value" />
+      </Form>
+    </FormProvider>
+  );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+  title: "Title",
+};
+
+export const WithDescription = Template.bind({});
+WithDescription.args = {
+  title: "Title",
+  description: "Description",
+};
diff --git a/frontend/src/metabase/core/components/FormNumericInput/FormNumericInput.stories.tsx b/frontend/src/metabase/core/components/FormNumericInput/FormNumericInput.stories.tsx
new file mode 100644
index 00000000000..c79447058f7
--- /dev/null
+++ b/frontend/src/metabase/core/components/FormNumericInput/FormNumericInput.stories.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import type { ComponentStory } from "@storybook/react";
+import Form from "../Form";
+import FormProvider from "../FormProvider";
+import FormNumericInput from "./FormNumericInput";
+
+export default {
+  title: "Core/FormNumericInput",
+  component: FormNumericInput,
+};
+
+const Template: ComponentStory<typeof FormNumericInput> = args => {
+  const initialValues = { value: undefined };
+  const handleSubmit = () => undefined;
+
+  return (
+    <FormProvider initialValues={initialValues} onSubmit={handleSubmit}>
+      <Form>
+        <FormNumericInput {...args} name="value" />
+      </Form>
+    </FormProvider>
+  );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+  title: "Title",
+};
+
+export const WithDescription = Template.bind({});
+WithDescription.args = {
+  title: "Title",
+  description: "Description",
+};
diff --git a/frontend/src/metabase/core/components/FormRadio/FormRadio.stories.tsx b/frontend/src/metabase/core/components/FormRadio/FormRadio.stories.tsx
new file mode 100644
index 00000000000..451959ed049
--- /dev/null
+++ b/frontend/src/metabase/core/components/FormRadio/FormRadio.stories.tsx
@@ -0,0 +1,40 @@
+import React from "react";
+import type { ComponentStory } from "@storybook/react";
+import Form from "../Form";
+import FormProvider from "../FormProvider";
+import FormRadio from "./FormRadio";
+
+const TEST_OPTIONS = [
+  { name: "Line", value: "line" },
+  { name: "Area", value: "area" },
+  { name: "Bar", value: "bar" },
+];
+
+export default {
+  title: "Core/FormRadio",
+  component: FormRadio,
+};
+
+const Template: ComponentStory<typeof FormRadio> = args => {
+  const initialValues = { value: undefined };
+  const handleSubmit = () => undefined;
+
+  return (
+    <FormProvider initialValues={initialValues} onSubmit={handleSubmit}>
+      <Form>
+        <FormRadio {...args} name="value" options={TEST_OPTIONS} />
+      </Form>
+    </FormProvider>
+  );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+  title: "Title",
+};
+
+export const WithDescription = Template.bind({});
+WithDescription.args = {
+  title: "Title",
+  description: "Description",
+};
diff --git a/frontend/src/metabase/core/components/FormSelect/FormSelect.stories.tsx b/frontend/src/metabase/core/components/FormSelect/FormSelect.stories.tsx
new file mode 100644
index 00000000000..2206cb82f69
--- /dev/null
+++ b/frontend/src/metabase/core/components/FormSelect/FormSelect.stories.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import type { ComponentStory } from "@storybook/react";
+import Form from "../Form";
+import FormProvider from "../FormProvider";
+import FormSelect from "./FormSelect";
+
+const TEST_OPTIONS = [
+  { name: "Line", value: "line" },
+  { name: "Area", value: "area" },
+  { name: "Bar", value: "bar" },
+];
+
+export default {
+  title: "Core/FormSelect",
+  component: FormSelect,
+};
+
+const Template: ComponentStory<typeof FormSelect> = args => {
+  const initialValues = { value: undefined };
+  const handleSubmit = () => undefined;
+
+  return (
+    <FormProvider initialValues={initialValues} onSubmit={handleSubmit}>
+      <Form>
+        <FormSelect {...args} name="value" options={TEST_OPTIONS} />
+      </Form>
+    </FormProvider>
+  );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+  title: "Title",
+  placeholder: "Use default",
+};
+
+export const WithDescription = Template.bind({});
+WithDescription.args = {
+  title: "Title",
+  placeholder: "Use default",
+  description: "Description",
+};
diff --git a/frontend/src/metabase/core/components/FormTextArea/FormTextArea.stories.tsx b/frontend/src/metabase/core/components/FormTextArea/FormTextArea.stories.tsx
new file mode 100644
index 00000000000..ffc2e26d35e
--- /dev/null
+++ b/frontend/src/metabase/core/components/FormTextArea/FormTextArea.stories.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import type { ComponentStory } from "@storybook/react";
+import Form from "../Form";
+import FormProvider from "../FormProvider";
+import FormTextArea from "./FormTextArea";
+
+export default {
+  title: "Core/FormTextArea",
+  component: FormTextArea,
+};
+
+const Template: ComponentStory<typeof FormTextArea> = args => {
+  const initialValues = { value: false };
+  const handleSubmit = () => undefined;
+
+  return (
+    <FormProvider initialValues={initialValues} onSubmit={handleSubmit}>
+      <Form>
+        <FormTextArea {...args} name="value" />
+      </Form>
+    </FormProvider>
+  );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+  title: "Title",
+};
+
+export const WithDescription = Template.bind({});
+WithDescription.args = {
+  title: "Title",
+  description: "Description",
+};
diff --git a/frontend/src/metabase/core/components/FormToggle/FormToggle.stories.tsx b/frontend/src/metabase/core/components/FormToggle/FormToggle.stories.tsx
new file mode 100644
index 00000000000..32de935bf39
--- /dev/null
+++ b/frontend/src/metabase/core/components/FormToggle/FormToggle.stories.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import type { ComponentStory } from "@storybook/react";
+import Form from "../Form";
+import FormProvider from "../FormProvider";
+import FormToggle from "./FormToggle";
+
+export default {
+  title: "Core/FormToggle",
+  component: FormToggle,
+};
+
+const Template: ComponentStory<typeof FormToggle> = args => {
+  const initialValues = { value: false };
+  const handleSubmit = () => undefined;
+
+  return (
+    <FormProvider initialValues={initialValues} onSubmit={handleSubmit}>
+      <Form>
+        <FormToggle {...args} name="value" />
+      </Form>
+    </FormProvider>
+  );
+};
+
+export const Default = Template.bind({});
+Default.args = {
+  title: "Title",
+};
+
+export const WithDescription = Template.bind({});
+WithDescription.args = {
+  title: "Title",
+  description: "Description",
+};
diff --git a/frontend/src/metabase/dashboard/components/ActionSidebar/ActionSidebar.tsx b/frontend/src/metabase/dashboard/components/ActionSidebar/ActionSidebar.tsx
index 7238071f079..4cbc72d4f9a 100644
--- a/frontend/src/metabase/dashboard/components/ActionSidebar/ActionSidebar.tsx
+++ b/frontend/src/metabase/dashboard/components/ActionSidebar/ActionSidebar.tsx
@@ -109,7 +109,7 @@ export function ActionSidebarFn({
             />
           </Form>
         </FormProvider>
-        <FieldLabelContainer>
+        <FieldLabelContainer orientation="vertical" hasDescription={false}>
           <FieldLabel hasError={false}>{t`Action`}</FieldLabel>
         </FieldLabelContainer>
 
-- 
GitLab