airbnb/caravel

View on GitHub
superset-frontend/src/explore/components/controls/ColumnConfigControl/ControlForm/index.tsx

Summary

Maintainability
B
4 hrs
Test Coverage
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import {
  Children,
  cloneElement,
  FunctionComponentElement,
  useMemo,
} from 'react';
import {
  FAST_DEBOUNCE,
  JsonObject,
  JsonValue,
  useTheme,
} from '@superset-ui/core';
import { debounce } from 'lodash';
import { ControlFormItemNode } from './ControlFormItem';

export * from './ControlFormItem';

export type ControlFormRowProps = {
  children: ControlFormItemNode | ControlFormItemNode[];
};

export function ControlFormRow({ children }: ControlFormRowProps) {
  const { gridUnit } = useTheme();
  return (
    <div
      css={{
        display: 'flex',
        flexWrap: 'nowrap',
        marginBottom: gridUnit,
        maxWidth: '100%',
      }}
    >
      {children}
    </div>
  );
}

type ControlFormRowNode = FunctionComponentElement<ControlFormRowProps>;

export type ControlFormProps = {
  /**
   * Form field values dict.
   */
  value?: JsonObject;
  onChange: (value: JsonObject) => void;
  children: ControlFormRowNode | ControlFormRowNode[];
};

/**
 * Light weight form for control panel.
 */
export default function ControlForm({
  onChange,
  value,
  children,
}: ControlFormProps) {
  const theme = useTheme();
  const debouncedOnChange = useMemo(
    () =>
      ({
        0: onChange,
        [FAST_DEBOUNCE]: debounce(onChange, FAST_DEBOUNCE),
      }) as Record<number, typeof onChange>,
    [onChange],
  );

  const updatedChildren = Children.map(children, row => {
    if ('children' in row.props) {
      const defaultWidth = Array.isArray(row.props.children)
        ? `${100 / row.props.children.length}%`
        : undefined;
      return cloneElement(row, {
        children: Children.map(row.props.children, item => {
          const {
            name,
            width,
            debounceDelay = FAST_DEBOUNCE,
            onChange: onItemValueChange,
          } = item.props;
          return cloneElement(item, {
            width: width || defaultWidth,
            value: value?.[name],
            // remove `debounceDelay` from rendered control item props
            // so React DevTools don't throw a `invalid prop` warning.
            debounceDelay: undefined,
            onChange(fieldValue: JsonValue) {
              // call `onChange` on each FormItem
              if (onItemValueChange) {
                onItemValueChange(fieldValue);
              }
              // propagate to the form
              if (!(debounceDelay in debouncedOnChange)) {
                debouncedOnChange[debounceDelay] = debounce(
                  onChange,
                  debounceDelay,
                );
              }
              debouncedOnChange[debounceDelay]({
                ...value,
                [name]: fieldValue,
              });
            },
          });
        }),
      });
    }
    return row;
  });
  return (
    <div
      css={{
        label: {
          color: theme.colors.text.label,
          fontSize: theme.typography.sizes.s,
        },
      }}
    >
      {updatedChildren}
    </div>
  );
}