From e864e206ecf8583ff9cbbdda83dab80e4e6277b0 Mon Sep 17 00:00:00 2001 From: Lake Mossman Date: Thu, 15 Dec 2022 10:30:27 -0800 Subject: [PATCH] [Connector Builder] Update view styles and add stream delete button (#20464) * style view titles * save progress on adding stream delete functionality * fix delete behavior * remove remaining console logs * consolidate shared styles into BuilderConfigView component --- .../Builder/Builder.module.scss | 3 - .../Builder/BuilderConfigView.module.scss | 13 ++ .../Builder/BuilderConfigView.tsx | 20 ++ .../Builder/BuilderTitle.module.scss | 51 +++++ .../connectorBuilder/Builder/BuilderTitle.tsx | 44 ++++ .../Builder/GlobalConfigView.tsx | 19 +- .../Builder/StreamConfigView.module.scss | 27 +++ .../Builder/StreamConfigView.tsx | 88 ++++++-- .../src/components/ui/Input/Input.tsx | 213 +++++++++--------- airbyte-webapp/src/locales/en.json | 4 + 10 files changed, 341 insertions(+), 141 deletions(-) create mode 100644 airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.module.scss create mode 100644 airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.tsx create mode 100644 airbyte-webapp/src/components/connectorBuilder/Builder/BuilderTitle.module.scss create mode 100644 airbyte-webapp/src/components/connectorBuilder/Builder/BuilderTitle.tsx create mode 100644 airbyte-webapp/src/components/connectorBuilder/Builder/StreamConfigView.module.scss diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/Builder.module.scss b/airbyte-webapp/src/components/connectorBuilder/Builder/Builder.module.scss index 108a100d0dfb4..4c361af3aa4e5 100644 --- a/airbyte-webapp/src/components/connectorBuilder/Builder/Builder.module.scss +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/Builder.module.scss @@ -18,7 +18,4 @@ .form { flex: 1; padding: variables.$spacing-xl; - display: flex; - flex-direction: column; - gap: variables.$spacing-xl; } diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.module.scss b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.module.scss new file mode 100644 index 0000000000000..ff2cbdd5450d7 --- /dev/null +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.module.scss @@ -0,0 +1,13 @@ +@use "scss/variables"; +@use "scss/colors"; + +.container { + display: flex; + flex-direction: column; + gap: variables.$spacing-md; +} + +.heading { + text-align: center; + font-size: 14px; +} diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.tsx new file mode 100644 index 0000000000000..e064a9e1bbded --- /dev/null +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +import { Heading } from "components/ui/Heading"; + +import styles from "./BuilderConfigView.module.scss"; + +interface BuilderConfigViewProps { + heading: string; +} + +export const BuilderConfigView: React.FC> = ({ children, heading }) => { + return ( +
+ + {heading} + + {children} +
+ ); +}; diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderTitle.module.scss b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderTitle.module.scss new file mode 100644 index 0000000000000..a05e43af0b0ad --- /dev/null +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderTitle.module.scss @@ -0,0 +1,51 @@ +@use "scss/variables"; +@use "scss/colors"; + +.container { + display: flex; + flex-direction: column; + align-items: center; + gap: variables.$spacing-sm; +} + +.label { + color: colors.$grey; +} + +.inputContainer { + max-width: 500px; + width: 70%; + background-color: colors.$grey-100; + border-color: colors.$grey-200; + border-radius: variables.$border-radius-md; + + &:not(.disabled, .focused):hover { + border-color: colors.$blue; + } + + &.md { + height: 43px; + } + + &.lg { + height: 50px; + } +} + +.input { + text-align: center; + height: 100%; + font-weight: 700; + + &.md { + font-size: 19px; + } + + &.lg { + font-size: 24px; + } +} + +.error { + color: colors.$red; +} diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderTitle.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderTitle.tsx new file mode 100644 index 0000000000000..85524dbd8b22a --- /dev/null +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderTitle.tsx @@ -0,0 +1,44 @@ +import classNames from "classnames"; +import { useField } from "formik"; +import { FormattedMessage } from "react-intl"; + +import { Input } from "components/ui/Input"; +import { Text } from "components/ui/Text"; + +import styles from "./BuilderTitle.module.scss"; + +interface BuilderTitleProps { + // path to the location in the Connector Manifest schema which should be set by this component + path: string; + label: string; + size: "md" | "lg"; +} + +export const BuilderTitle: React.FC = ({ path, label, size }) => { + const [field, meta] = useField(path); + const hasError = !!meta.error && meta.touched; + + return ( +
+ + {label} + + + {hasError && ( + + + + )} +
+ ); +}; diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/GlobalConfigView.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/GlobalConfigView.tsx index 1d1a40a945180..71e998d944b8d 100644 --- a/airbyte-webapp/src/components/connectorBuilder/Builder/GlobalConfigView.tsx +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/GlobalConfigView.tsx @@ -1,21 +1,20 @@ +import { useIntl } from "react-intl"; + import { BuilderCard } from "./BuilderCard"; +import { BuilderConfigView } from "./BuilderConfigView"; import { BuilderField } from "./BuilderField"; +import { BuilderTitle } from "./BuilderTitle"; export const GlobalConfigView: React.FC = () => { + const { formatMessage } = useIntl(); + return ( - <> + {/* Not using intl for the labels and tooltips in this component in order to keep maintainence simple */} - - - + - + ); }; diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/StreamConfigView.module.scss b/airbyte-webapp/src/components/connectorBuilder/Builder/StreamConfigView.module.scss new file mode 100644 index 0000000000000..a0a3c22d9b679 --- /dev/null +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/StreamConfigView.module.scss @@ -0,0 +1,27 @@ +@use "scss/variables"; +@use "scss/colors"; + +$deleteButtonWidth: 24px; + +.controls { + display: flex; + justify-content: center; +} + +.deleteButton { + border: none; + background-color: transparent; + color: colors.$grey; + border-radius: variables.$border-radius-xs; + width: $deleteButtonWidth; + height: $deleteButtonWidth; + padding: variables.$spacing-xs; + padding: 0; + cursor: pointer; + transition: variables.$transition; + + &:hover { + color: colors.$white; + background-color: colors.$red; + } +} diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/StreamConfigView.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/StreamConfigView.tsx index a59b23b3b533c..93800396997ae 100644 --- a/airbyte-webapp/src/components/connectorBuilder/Builder/StreamConfigView.tsx +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/StreamConfigView.tsx @@ -1,36 +1,78 @@ +import { faTrashCan } from "@fortawesome/free-regular-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useField } from "formik"; +import { useIntl } from "react-intl"; + +import { useConfirmationModalService } from "hooks/services/ConfirmationModal"; +import { BuilderView, useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService"; + +import { BuilderStream } from "../types"; import { BuilderCard } from "./BuilderCard"; +import { BuilderConfigView } from "./BuilderConfigView"; import { BuilderField } from "./BuilderField"; +import { BuilderTitle } from "./BuilderTitle"; +import styles from "./StreamConfigView.module.scss"; interface StreamConfigViewProps { streamNum: number; } export const StreamConfigView: React.FC = ({ streamNum }) => { - const streamPath = (path: string) => `streams[${streamNum}].${path}`; + const { formatMessage } = useIntl(); + const [field, , helpers] = useField("streams"); + const { openConfirmationModal, closeConfirmationModal } = useConfirmationModalService(); + const { setSelectedView, setTestStreamIndex } = useConnectorBuilderState(); + + const streamPath = `streams[${streamNum}]`; + const streamFieldPath = (fieldPath: string) => `${streamPath}.${fieldPath}`; + + const handleDelete = () => { + openConfirmationModal({ + text: "connectorBuilder.deleteStreamModal.text", + title: "connectorBuilder.deleteStreamModal.title", + submitButtonText: "connectorBuilder.deleteStreamModal.submitButton", + onSubmit: () => { + const updatedStreams = field.value.filter((_, index) => index !== streamNum); + const streamToSelect = streamNum >= updatedStreams.length ? updatedStreams.length - 1 : streamNum; + const viewToSelect: BuilderView = updatedStreams.length === 0 ? "global" : streamToSelect; + helpers.setValue(updatedStreams); + setSelectedView(viewToSelect); + setTestStreamIndex(streamToSelect); + closeConfirmationModal(); + }, + }); + }; return ( - + {/* Not using intl for the labels and tooltips in this component in order to keep maintainence simple */} - - - - - + +
+ +
+ + + + + + ); }; diff --git a/airbyte-webapp/src/components/ui/Input/Input.tsx b/airbyte-webapp/src/components/ui/Input/Input.tsx index 5906caf6a223f..5b8c45d81a261 100644 --- a/airbyte-webapp/src/components/ui/Input/Input.tsx +++ b/airbyte-webapp/src/components/ui/Input/Input.tsx @@ -11,111 +11,114 @@ import styles from "./Input.module.scss"; export interface InputProps extends React.InputHTMLAttributes { error?: boolean; light?: boolean; + containerClassName?: string; } -export const Input = React.forwardRef(({ light, error, ...props }, ref) => { - const { formatMessage } = useIntl(); - - const inputRef = useRef(null); - const buttonRef = useRef(null); - const inputSelectionStartRef = useRef(null); - - // Necessary to bind a ref passed from the parent in to our internal inputRef - useImperativeHandle(ref, () => inputRef.current as HTMLInputElement); - - const [isContentVisible, toggleIsContentVisible] = useToggle(false); - const [focused, setFocused] = useState(false); - - const isPassword = props.type === "password"; - const isVisibilityButtonVisible = isPassword && !props.disabled; - const type = isPassword ? (isContentVisible ? "text" : "password") : props.type; - - const focusOnInputElement = useCallback(() => { - if (!inputRef.current) { - return; - } - - const { current: element } = inputRef; - const selectionStart = inputSelectionStartRef.current ?? inputRef.current?.value.length; - - element.focus(); - - if (selectionStart) { - // Update input cursor position to where it was before - window.setTimeout(() => { - element.setSelectionRange(selectionStart, selectionStart); - }, 0); - } - }, []); - - const onContainerFocus: React.FocusEventHandler = () => { - setFocused(true); - }; - - const onContainerBlur: React.FocusEventHandler = (event) => { - if (isVisibilityButtonVisible && event.target === inputRef.current) { - // Save the previous selection - inputSelectionStartRef.current = inputRef.current.selectionStart; - } - - setFocused(false); - - if (isPassword) { - window.setTimeout(() => { - if (document.activeElement !== inputRef.current && document.activeElement !== buttonRef.current) { - toggleIsContentVisible(false); - inputSelectionStartRef.current = null; - } - }, 0); - } - }; - - return ( -
- - {isVisibilityButtonVisible ? ( -
+ ); + } +); diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index 9ef7150327a90..5cd36830f8ee9 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -646,6 +646,9 @@ "connectorBuilder.addStreamModal.streamNameTooltip": "Name of the new stream", "connectorBuilder.addStreamModal.urlPathLabel": "URL path", "connectorBuilder.addStreamModal.urlPathTooltip": "URL path of the endpoint for this stream", + "connectorBuilder.deleteStreamModal.title": "Delete stream", + "connectorBuilder.deleteStreamModal.text": "Are you sure you want to delete this stream?", + "connectorBuilder.deleteStreamModal.submitButton": "Delete", "connectorBuilder.resetModal.text": "This will erase all streams and values that are currently set. Are you sure you want to continue?", "connectorBuilder.resetModal.title": "Reset Connector Builder", "connectorBuilder.resetModal.submitButton": "Reset", @@ -658,6 +661,7 @@ "connectorBuilder.configErrorsDownload": "Cannot download while there are form errors", "connectorBuilder.configErrorsTest": "Cannot test while there are form errors", "connectorBuilder.connectorImgAlt": "Connector Image", + "connectorBuilder.stream": "Stream", "connectorBuilder.uiYamlToggle.ui": "UI", "connectorBuilder.uiYamlToggle.yaml": "YAML", "connectorBuilder.resetAll": "Reset all",