airbnb/caravel

View on GitHub
superset-frontend/src/components/TruncatedList/index.tsx

Summary

Maintainability
A
1 hr
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 { ReactNode, Key, useMemo } from 'react';

import { styled, t, useTruncation } from '@superset-ui/core';
import { Tooltip } from '../Tooltip';

export type TruncatedListProps<ListItemType> = {
  /**
   * Array of input items of type `ListItemType`.
   */
  items: ListItemType[];

  /**
   * Renderer for items not overflowed into the tooltip.
   * Required if `ListItemType` is not renderable by React.
   */
  renderVisibleItem?: (item: ListItemType) => ReactNode;

  /**
   * Renderer for items that are overflowed into the tooltip.
   * Required if `ListItemType` is not renderable by React.
   */
  renderTooltipItem?: (item: ListItemType) => ReactNode;

  /**
   * Returns the React key for an item.
   */
  getKey?: (item: ListItemType) => Key;

  /**
   * The max number of links that should appear in the tooltip.
   */
  maxLinks?: number;
};

const StyledTruncatedList = styled.div`
  & > span {
    width: 100%;
    display: flex;

    .ant-tooltip-open {
      display: inline;
    }
  }
`;

const StyledVisibleItems = styled.span`
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: inline-block;
  width: 100%;
  vertical-align: bottom;
`;

const StyledVisibleItem = styled.span`
  &:not(:last-child)::after {
    content: ', ';
  }
`;

const StyledTooltipItem = styled.div`
  .link {
    color: ${({ theme }) => theme.colors.grayscale.light5};
    display: block;
    text-decoration: underline;
  }
`;

const StyledPlus = styled.span`
  ${({ theme }) => `
  cursor: pointer;
  color: ${theme.colors.primary.dark1};
  font-weight: ${theme.typography.weights.normal};
  `}
`;

export default function TruncatedList<ListItemType>({
  items,
  renderVisibleItem = item => item,
  renderTooltipItem = item => item,
  getKey = item => item as unknown as Key,
  maxLinks = 20,
}: TruncatedListProps<ListItemType>) {
  const [itemsNotInTooltipRef, plusRef, elementsTruncated, hasHiddenElements] =
    useTruncation();

  const nMoreItems = useMemo(
    () => (items.length > maxLinks ? items.length - maxLinks : undefined),
    [items, maxLinks],
  );

  const itemsNotInTooltip = useMemo(
    () => (
      <StyledVisibleItems ref={itemsNotInTooltipRef} data-test="crosslinks">
        {items.map(item => (
          <StyledVisibleItem key={getKey(item)}>
            {renderVisibleItem(item)}
          </StyledVisibleItem>
        ))}
      </StyledVisibleItems>
    ),
    [getKey, items, renderVisibleItem],
  );

  const itemsInTooltip = useMemo(
    () =>
      items
        .slice(0, maxLinks)
        .map(item => (
          <StyledTooltipItem key={getKey(item)}>
            {renderTooltipItem(item)}
          </StyledTooltipItem>
        )),
    [getKey, items, maxLinks, renderTooltipItem],
  );

  return (
    <StyledTruncatedList>
      <Tooltip
        placement="top"
        title={
          elementsTruncated ? (
            <>
              {itemsInTooltip}
              {nMoreItems && <span>{t('+ %s more', nMoreItems)}</span>}
            </>
          ) : null
        }
      >
        {itemsNotInTooltip}
        {hasHiddenElements && (
          <StyledPlus ref={plusRef}>+{elementsTruncated}</StyledPlus>
        )}
      </Tooltip>
    </StyledTruncatedList>
  );
}