Skip to content

Commit

Permalink
🪟 🎉 Improved rendering of variable input fields in source/destination…
Browse files Browse the repository at this point in the history
… and update custom transformations form. (#14514)

* Show field names instead of index in ArraySection items

* Update ArraySection to show description

* Update EditorRow to show description as tooltip
* Update ArrayOfObjectsEditor with render name and description props, fix base item interface

* Update ToolTip so that cursor only changes if specified, make inline

* Update form types from type to interface

* Update EditorRow style to match new design

* Update Add/Edit mode in ArrayOfObjectsEditor to use Modal

* Add bottom margin to content card title to ensure drop shadow does not overlap bottom content

* Cleanup spacing in Modal scss

* Add width and height settings to ArrayOfObjectEditor
Ensure that Transformation field edit modal and ArraySection edit modal have the right width and height

* Move buttons outside of ArrayOfObjects editor and let TransformationField and ArraySection handle it

* Move DBT command reference docs to links config

* Move ArrayOfObjectsEditor to css module

* Split variable input fields form into component, make editable via hidden field

* Removed unfinished flow usage from ArraySection

* Add button type to EditorRow buttons to prevent Formik warning

* Fix path prop in ArraySection to not include "hidden"

* Add validation to VariableInputFieldForm
* Move default field from FromGroup to FormItemBase
* Add Form validationSchema to Service Form Context
* Update path in ArraySection to use correct path
* Update VariableInputFieldForm to use form validatonSchema to determine if the data is valid or not
* Add default values to VariableInputFieldForm item

* Move EditorRow styles to scss and fix wrappig on small width

* Add styles for tooltip in ArraySection

* Update ArrayOfObjectsEditor component to match design
* Update edit and close button icons
* Fix spacing
* Show item count
* Show add / edit string on modal depending on mode

* Update 0 items to No items in en file

* Fix serviceForm 'should fill right values in array of objects field' test
* Add testId field to Modal
* Add testId to ArrayOfObjectsEditor modal
* Cleanup test

* Fix 'should fill all fields by right values' in serviceForm tests
* Update addPriceListItem to a utility
* Only use document.body for modal query

* Cleanup mocks in serviceForm test
* Update naming in DocumentationPanelContext

* Fix stylings in EditorRow and ContentCard

* Update EditorRow to always show a border between items regardless of having a description or not

* Update VariableInputField field name such that it can be removed from formik values when done or cancelled

* Update temp field name in variable input fields form and explain the reasons for it

* Update ArrayOfObjectEditor to render form as prop instead of children

Co-authored-by: Tim Roes <tim@airbyte.io>
  • Loading branch information
edmundito and timroes authored Aug 4, 2022
1 parent e04e661 commit 6b5e4fb
Show file tree
Hide file tree
Showing 22 changed files with 535 additions and 312 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use "../../scss/colors";
@use "../../scss/variables";

.container {
margin-bottom: variables.$spacing-xl;
}

.list {
background-color: colors.$grey-50;
border-radius: 4px;
}
Original file line number Diff line number Diff line change
@@ -1,111 +1,93 @@
import React from "react";
import { FormattedMessage } from "react-intl";
import styled from "styled-components";

import { Button } from "components";
import Modal, { ModalProps } from "components/Modal";

import { ConnectionFormMode } from "views/Connection/ConnectionForm/ConnectionForm";

import styles from "./ArrayOfObjectsEditor.module.scss";
import { EditorHeader } from "./components/EditorHeader";
import { EditorRow } from "./components/EditorRow";

const ItemsList = styled.div`
background: ${({ theme }) => theme.greyColor0};
border-radius: 4px;
`;

const ButtonContainer = styled.div`
display: flex;
justify-content: flex-end;
`;

const SmallButton = styled(Button)`
margin-left: 8px;
padding: 6px 8px 7px;
`;

const Content = styled.div`
margin-bottom: 20px;
`;
interface ItemBase {
name?: string;
description?: string;
}

export interface ArrayOfObjectsEditorProps<T extends { name: string }> {
export interface ArrayOfObjectsEditorProps<T extends ItemBase> {
items: T[];
editableItemIndex?: number | string | null;
children: (item?: T) => React.ReactNode;
mainTitle?: React.ReactNode;
addButtonText?: React.ReactNode;
renderItemName?: (item: T, index: number) => React.ReactNode | undefined;
renderItemDescription?: (item: T, index: number) => React.ReactNode | undefined;
renderItemEditorForm: (item?: T) => React.ReactNode;
onStartEdit: (n: number) => void;
onCancelEdit?: () => void;
onDone?: () => void;
onRemove: (index: number) => void;
mode?: ConnectionFormMode;
disabled?: boolean;
editModalSize?: ModalProps["size"];
}

export const ArrayOfObjectsEditor = <T extends { name: string } = { name: string }>({
export const ArrayOfObjectsEditor = <T extends ItemBase = ItemBase>({
onStartEdit,
onDone,
onRemove,
onCancelEdit,
renderItemName = (item) => item.name,
renderItemDescription = (item) => item.description,
renderItemEditorForm,
items,
editableItemIndex,
children,
mainTitle,
addButtonText,
mode,
disabled,
editModalSize,
}: ArrayOfObjectsEditorProps<T>): JSX.Element => {
const onAddItem = React.useCallback(() => onStartEdit(items.length), [onStartEdit, items]);

const isEditable = editableItemIndex !== null && editableItemIndex !== undefined;

if (mode !== "readonly" && isEditable) {
const renderEditModal = () => {
const item = typeof editableItemIndex === "number" ? items[editableItemIndex] : undefined;

return (
<Content>
{children(item)}
{onCancelEdit || onDone ? (
<ButtonContainer>
{onCancelEdit && (
<SmallButton onClick={onCancelEdit} type="button" secondary disabled={disabled}>
<FormattedMessage id="form.cancel" />
</SmallButton>
)}
{onDone && (
<SmallButton onClick={onDone} type="button" data-testid="done-button" disabled={disabled}>
<FormattedMessage id="form.done" />
</SmallButton>
)}
</ButtonContainer>
) : null}
</Content>
<Modal
title={<FormattedMessage id={item ? "form.edit" : "form.add"} />}
size={editModalSize}
testId="arrayOfObjects-editModal"
>
{renderItemEditorForm(item)}
</Modal>
);
}
};

return (
<Content>
<EditorHeader
itemsCount={items.length}
onAddItem={onAddItem}
mainTitle={mainTitle}
addButtonText={addButtonText}
mode={mode}
disabled={disabled}
/>
{items.length ? (
<ItemsList>
{items.map((item, key) => (
<EditorRow
key={`form-item-${key}`}
name={item.name}
id={key}
onEdit={onStartEdit}
onRemove={onRemove}
disabled={disabled}
/>
))}
</ItemsList>
) : null}
</Content>
<>
<div className={styles.container}>
<EditorHeader
itemsCount={items.length}
onAddItem={onAddItem}
mainTitle={mainTitle}
addButtonText={addButtonText}
mode={mode}
disabled={disabled}
/>
{items.length ? (
<div className={styles.list}>
{items.map((item, index) => (
<EditorRow
key={`form-item-${index}`}
name={renderItemName(item, index)}
description={renderItemDescription(item, index)}
id={index}
onEdit={onStartEdit}
onRemove={onRemove}
disabled={disabled}
/>
))}
</div>
) : null}
</div>
{mode !== "readonly" && isEditable && renderEditModal()}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Content = styled.div`
font-weight: 500;
font-size: 14px;
line-height: 17px;
margin: 5px 0;
margin: 5px 0 10px;
`;

interface EditorHeaderProps {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@use "../../../scss/colors";
@use "../../../scss/variables";

.container + .container {
border-top: 1px solid colors.$white;
}

.body {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
color: colors.$dark-blue;
font-weight: 400;
font-size: 12px;
line-height: 17px;
padding: variables.$spacing-md 8px;
gap: variables.$spacing-md;
}

.name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.actions {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
gap: variables.$spacing-md;
}
Original file line number Diff line number Diff line change
@@ -1,57 +1,50 @@
import { faTimes } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import styled from "styled-components";
import { useIntl } from "react-intl";

import { Button } from "components";
import { CrossIcon } from "components/icons/CrossIcon";
import { PencilIcon } from "components/icons/PencilIcon";
import ToolTip from "components/ToolTip";

const Content = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
color: ${({ theme }) => theme.textColor};
font-weight: 500;
font-size: 14px;
line-height: 17px;
padding: 5px 12px 6px 14px;
border-bottom: 1px solid ${({ theme }) => theme.greyColor20};
&:last-child {
border: none;
}
`;

const DeleteButton = styled(Button)`
margin-left: 7px;
`;
import styles from "./EditorRow.module.scss";

interface EditorRowProps {
name: string;
name?: React.ReactNode;
description?: React.ReactNode;
id: number;
onEdit: (id: number) => void;
onRemove: (id: number) => void;
disabled?: boolean;
}

const EditorRow: React.FC<EditorRowProps> = ({ name, id, onEdit, onRemove, disabled }) => {
export const EditorRow: React.FC<EditorRowProps> = ({ name, id, description, onEdit, onRemove, disabled }) => {
const { formatMessage } = useIntl();
const buttonLabel = formatMessage({ id: "form.delete" });

return (
<Content>
<div>{name || id}</div>
<div>
<Button secondary onClick={() => onEdit(id)} type="button" disabled={disabled}>
<FormattedMessage id="form.edit" />
const body = (
<div className={styles.body}>
<div className={styles.name}>{name || id}</div>
<div className={styles.actions}>
<Button
type="button"
iconOnly
arial-label={formatMessage({ id: "form.edit" })}
onClick={() => onEdit(id)}
disabled={disabled}
>
<PencilIcon />
</Button>
<Button
type="button"
iconOnly
aria-label={formatMessage({ id: "form.delete" })}
onClick={() => onRemove(id)}
disabled={disabled}
>
<CrossIcon />
</Button>
<DeleteButton iconOnly onClick={() => onRemove(id)} disabled={disabled} aria-label={buttonLabel}>
<FontAwesomeIcon icon={faTimes} />
</DeleteButton>
</div>
</Content>
</div>
);
};

export { EditorRow };
return <div className={styles.container}>{description ? <ToolTip control={body}>{description}</ToolTip> : body}</div>;
};
9 changes: 7 additions & 2 deletions airbyte-webapp/src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface ModalProps {
clear?: boolean;
closeOnBackground?: boolean;
size?: "sm" | "md" | "lg" | "xl";
testId?: string;
}

const cardStyleBySize = {
Expand All @@ -21,7 +22,7 @@ const cardStyleBySize = {
xl: styles.xl,
};

const Modal: React.FC<ModalProps> = ({ children, title, onClose, clear, closeOnBackground, size }) => {
const Modal: React.FC<ModalProps> = ({ children, title, onClose, clear, closeOnBackground, size, testId }) => {
const handleUserKeyPress = useCallback((event: KeyboardEvent, closeModal: () => void) => {
const { key } = event;
// Escape key
Expand All @@ -44,7 +45,11 @@ const Modal: React.FC<ModalProps> = ({ children, title, onClose, clear, closeOnB
}, [handleUserKeyPress, onClose]);

return createPortal(
<div className={styles.modal} onClick={() => (closeOnBackground && onClose ? onClose() : null)}>
<div
className={styles.modal}
onClick={() => (closeOnBackground && onClose ? onClose() : null)}
data-testid={testId}
>
{clear ? (
children
) : (
Expand Down
3 changes: 2 additions & 1 deletion airbyte-webapp/src/components/Modal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Modal from "./Modal";

export * from "./Modal";
export * from "./ModalBody";
export * from "./ModalFooter";

export default Modal;
export { Modal };
export default Modal;
4 changes: 2 additions & 2 deletions airbyte-webapp/src/components/ToolTip/ToolTip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ interface ToolTipProps {
}

const Control = styled.div<{ $cursor?: "pointer" | "help" | "not-allowed"; $showCursor?: boolean }>`
display: inline-block;
display: inline;
position: relative;
cursor: ${({ $cursor, $showCursor = true }) => ($showCursor && $cursor) ?? "pointer"};
${({ $cursor, $showCursor = true }) => ($showCursor && $cursor ? `cursor: ${$cursor}` : "")};
`;

const ToolTipView = styled.div<{ $disabled?: boolean }>`
Expand Down
12 changes: 12 additions & 0 deletions airbyte-webapp/src/components/icons/CrossIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
interface CrossIconProps {
color?: string;
}

export const CrossIcon = ({ color = "currentColor" }: CrossIconProps) => (
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
<path
d="M9.20495 0.71967C8.91206 0.426777 8.43718 0.426777 8.14429 0.71967L4.96234 3.90162L1.7804 0.719679C1.48751 0.426786 1.01263 0.426786 0.71974 0.719679C0.426847 1.01257 0.426847 1.48745 0.71974 1.78034L3.90168 4.96228L0.71967 8.14429C0.426777 8.43718 0.426777 8.91206 0.71967 9.20495C1.01256 9.49784 1.48744 9.49784 1.78033 9.20495L4.96234 6.02294L8.14436 9.20496C8.43725 9.49785 8.91213 9.49785 9.20502 9.20496C9.49791 8.91207 9.49791 8.43719 9.20502 8.1443L6.023 4.96228L9.20495 1.78033C9.49784 1.48744 9.49784 1.01256 9.20495 0.71967Z"
fill={color}
/>
</svg>
);
12 changes: 12 additions & 0 deletions airbyte-webapp/src/components/icons/PencilIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
interface PencilIconProps {
color?: string;
}

export const PencilIcon = ({ color = "currentColor" }: PencilIconProps) => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path
d="M7.675 3.14356L10.8565 6.32581L3.4315 13.7501H0.25V10.5678L7.675 3.14281V3.14356ZM8.7355 2.08306L10.3262 0.491563C10.4669 0.350959 10.6576 0.271973 10.8565 0.271973C11.0554 0.271973 11.2461 0.350959 11.3868 0.491563L13.5085 2.61331C13.6491 2.75396 13.7281 2.94469 13.7281 3.14356C13.7281 3.34244 13.6491 3.53317 13.5085 3.67381L11.917 5.26456L8.7355 2.08306Z"
fill={color}
/>
</svg>
);
1 change: 1 addition & 0 deletions airbyte-webapp/src/config/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
const BASE_DOCS_LINK = "https://docs.airbyte.com";

export const links = {
dbtCommandsReference: "https://docs.getdbt.com/reference/dbt-commands",
technicalSupport: `${BASE_DOCS_LINK}/troubleshooting/on-deploying`,
termsLink: "https://airbyte.com/terms",
privacyLink: "https://airbyte.com/privacy-policy",
Expand Down
Loading

0 comments on commit 6b5e4fb

Please sign in to comment.