antd#Anchor TypeScript Examples

The following examples show how to use antd#Anchor. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: DendronTOC.tsx    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
DendronTOC = ({
  note,
  ...rest
}: {
  note: NoteProps;
} & ComponentProps<typeof Anchor>) => {
  return (
    <>
      <Anchor style={{ zIndex: 1 }} className="dendron-toc" {...rest}>
        {Object.entries(note?.anchors).map(([key, entry]) =>
          entry?.type === "header" ? (
            <Link
              key={key}
              href={`#${key}`}
              // `anchor.text` contains clean, user displayable text for
              // headings. It should always exist for exported notes, but we
              // have this fallback just in case.
              title={entry?.text ?? unslug(entry?.value)}
            />
          ) : (
            <></>
          )
        )}
      </Anchor>
    </>
  );
}
Example #2
Source File: DendronTOC.tsx    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
Link = Anchor.Link
Example #3
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
{ Link } = Anchor
Example #4
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
DetailsPanel = (props: IProps) => {
  const { baseInfoConf, linkList, anchorContainer, children } = props;

  const _baseInfoConf = {
    ...baseInfoConf,
    titleProps: {
      title: baseInfoConf?.title,
      level: 2,
      ...baseInfoConf?.titleProps,
    },
  };

  const container = React.useRef(anchorContainer || (document.getElementById('main') as HTMLDivElement));

  return (
    <div className="details-panel-template">
      <IF check={!isEmpty(baseInfoConf)}>
        <div className="base-info mb-3">
          <Content {...(_baseInfoConf as IContentProps)} />
        </div>
      </IF>
      {children}
      <IF check={!isEmpty(linkList)}>
        <Anchor getContainer={() => container?.current || document.body}>
          {map(linkList, (item) => {
            const { linkProps, key } = item;
            const { icon, title } = linkProps;
            const href = `#${key}`;
            return (
              <Link
                href={href}
                title={
                  <div className="anchor-link-title-icon">
                    {icon}
                    <span className="flex items-center">{title}</span>
                  </div>
                }
                key={href}
              />
            );
          })}
        </Anchor>

        {map(linkList, (item) => {
          const { linkProps, crossLine, titleProps, showTitle, panelProps, getComp, key } = item;
          const { icon, title } = linkProps;
          const _titleProps = {
            title,
            level: 2,
            icon: <div className="title-icon">{icon}</div>,
            ...titleProps,
          };
          return (
            <div key={key} id={key} className="pt-3">
              <Content
                crossLine={crossLine}
                titleProps={_titleProps}
                panelProps={panelProps}
                getComp={getComp}
                showTitle={showTitle}
              />
            </div>
          );
        })}
      </IF>
    </div>
  );
}
Example #5
Source File: header-renderer.tsx    From electron-playground with MIT License 5 votes vote down vote up
{ Link } = Anchor
Example #6
Source File: header-renderer.tsx    From electron-playground with MIT License 5 votes vote down vote up
AnchorRender = () => {
  const [show, setShow] = useState<boolean>(false)
  useEffect(() => {
    return () => {
      TOC = []
    }
  }, [])

  const handleAnchor = (
    e: React.MouseEvent<HTMLElement>,
    link: { title: React.ReactNode; href: string },
  ) => {
    const dom = document.getElementById(link.href)
    if (dom) {
      dom.scrollIntoView({ behavior: 'smooth' })
      dom.scrollTop -= 60
    }
    e.preventDefault()
  }

  return (
    <>
      <div className={style.contents} onMouseOver={() => setShow(true)}>
        <LeftOutlined className={style.icon} />
        目录
      </div>

      <Drawer title={null} placement='right' closable={false} mask={false} visible={show}>
        <div onMouseLeave={() => setShow(false)} style={{height:'100%'}}>
          <Anchor onClick={handleAnchor} bounds={0}>
            {TOC.map(({ content, level }) => (
              <div key={content} style={{ marginLeft: (level - 1) * 10 }}>
                <Link href={content} title={content} key={content} />
              </div>
            ))}
          </Anchor>
        </div>
      </Drawer>
    </>
  )
}
Example #7
Source File: index.tsx    From amiya with MIT License 5 votes vote down vote up
{ Link } = Anchor
Example #8
Source File: GeneralAnchor.spec.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
describe("GeneralAnchor", () => {
  it("should work", () => {
    const { Link } = Anchor;
    const wrapper = mount(<GeneralAnchor anchorList={linkList} />);
    expect(wrapper.find(Anchor).length).toBe(1);
    expect(wrapper.find(Anchor).prop("offsetTop")).toBe(56);
    wrapper.setProps({
      type: "radio",
    });

    wrapper.update();
    expect(wrapper.find("div.anchorWrapper").length).toBe(1);
    expect(wrapper.find("div.anchorContainer").length).toBe(1);
    expect(wrapper.find("div.anchorLinkContainer").length).toBe(1);
    expect(wrapper.find(Link).length).toBe(3);
  });
  it("should work and type ", () => {
    const linkListWidthChild = [
      {
        title: "应用资源",
        href: "https://192.168.100.162/next/resource-monitor#saas-monitor",
        children: [
          {
            title: "平台资源",
            href: "https://192.168.100.162/next/resource-monitor#paas-monitor",
          },
        ],
      },
      {
        title: "基础设施",
        href: "https://192.168.100.162/next/resource-monitor#iaas-monitor",
      },
    ];
    const { Link } = Anchor;
    const wrapper = mount(
      <GeneralAnchor anchorList={linkListWidthChild} type="default" />
    );
    expect(wrapper.find(Link).length).toBe(3);
  });
  it("should work and hasExtraSlot ", () => {
    const extraBrick = {
      useBrick: [
        {
          brick: "span",
          properties: {
            textContent: "测试",
          },
        },
      ],
    };
    const wrapper = shallow(
      <GeneralAnchor anchorList={linkList} extraBrick={extraBrick} />
    );
    expect(wrapper.find(".extraContainer").length).toBe(1);
    wrapper.setProps({
      configProps: {
        affix: true,
      },
    });
    wrapper.update();
    expect(wrapper.find(Anchor).prop("affix")).toBe(true);
  });

  it("handleClick and handleChange should work", () => {
    const handleChange = jest.fn();
    let result;
    const handleClick = (
      _e: React.MouseEvent<HTMLElement, MouseEvent>,
      detail: Record<string, any>
    ) => {
      result = detail;
    };

    const linkListWidthChild = [
      {
        title: "应用资源",
        href: "https://192.168.100.162/next/resource-monitor#saas-monitor",
        balabala: 123,
      },
      {
        title: "基础设施",
        href: "https://192.168.100.162/next/resource-monitor#iaas-monitor",
      },
    ];
    const { Link } = Anchor;
    const wrapper = mount(
      <GeneralAnchor
        anchorList={linkListWidthChild}
        type="default"
        handleClick={handleClick}
        handleChange={handleChange}
      />
    );

    wrapper.find(Link).at(0).simulate("click");

    expect(handleChange).toBeCalled();
    expect(result).toEqual({
      balabala: 123,
      href: "https://192.168.100.162/next/resource-monitor#saas-monitor",
      title: "应用资源",
    });
  });
});
Example #9
Source File: GeneralAnchor.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function GeneralAnchor(props: GeneralAnchorProps): React.ReactElement {
  const { configProps, type, extraBrick, handleClick, handleChange } = props;
  const { Link } = Anchor;

  const getHref = (hash: string) => {
    const isHash = (hash || "").startsWith("#");
    if (!isHash) {
      return hash;
    }
    const history = getHistory();
    return history.createHref({
      ...history.location,
      hash,
    });
  };

  const activeLink = useRef(getHref(location.hash));
  const [anchorList, setAnchorList] = useState([]);
  const renderAnchorList = (
    anchorList: AnchorListType[],
    type?: "default" | "radio"
  ) => {
    return anchorList.map((item: AnchorListType, i: number) => {
      return (
        <span key={i} onClick={(e) => handleClick(e, item)}>
          <Link
            title={item.title}
            href={item.href}
            target={item.target}
            key={i}
          >
            {type === "default" &&
              item.children?.length &&
              renderAnchorList(item.children, type)}
          </Link>
        </span>
      );
    });
  };

  useEffect(() => {
    /* TODO(astrid): 初始锚点无法滚动到对应位置 */
    const sharpMatcherRegx = /#([\S ]+)$/;
    const initHash =
      props?.anchorList.find((item) => item.href.includes(location.hash))
        ?.href || "";

    if (initHash) {
      const sharpLinkMatch = sharpMatcherRegx.exec(initHash.toString());
      const target = document.getElementById(sharpLinkMatch[1]);

      if (target) {
        setTimeout(() => {
          window.scrollTo({
            top: target.offsetTop - (configProps?.offsetTop || 56),
          });
        });
        handleChange(initHash);
      }
    }
  }, []);

  useEffect(() => {
    if (props?.anchorList?.length) {
      const anchorList = props.anchorList.map((anchor) => ({
        ...anchor,
        href: getHref(anchor.href),
      }));
      setAnchorList([...anchorList]);
    }
  }, [props?.anchorList]);

  return (
    <Anchor
      offsetTop={56}
      {...configProps}
      className={classnames([
        {
          [styles.anchorWrapper]: type !== "default",
        },
      ])}
      onChange={handleChange}
      getCurrentAnchor={() => activeLink.current}
    >
      {type === "default" ? (
        renderAnchorList(anchorList, type)
      ) : (
        <div className={styles.anchorContainer}>
          <div className={styles.anchorLinkContainer}>
            {renderAnchorList(anchorList, type)}
          </div>
          {extraBrick?.useBrick && (
            <div className={styles.extraContainer}>
              <BrickAsComponent useBrick={extraBrick.useBrick} />
            </div>
          )}
        </div>
      )}
    </Anchor>
  );
}
Example #10
Source File: issue-drawer.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
IssueDrawer = (props: IProps) => {
  const {
    className = '',
    canCreate = false,
    canDelete = false,
    children,
    editMode,
    shareLink,
    loading = false,
    visible,
    onClose,
    onDelete,
    confirmCloseTip,
    handleCopy,
    maskClosable,
    data,
    issueType,
    projectId,
    setData,
    header = IssueDrawer.Empty,
    footer = IssueDrawer.Empty,
    extraHeaderOp = null,
    detailTitle,
    ...rest
  } = props;
  const [
    formField = IssueDrawer.Empty,
    descField = IssueDrawer.Empty,
    workflowField = IssueDrawer.Empty,
    inclusionField = IssueDrawer.Empty,
    relationField = IssueDrawer.Empty,
    logField = IssueDrawer.Empty,
  ] = React.Children.toArray(children);

  const customFieldDetail = issueStore.useStore((s) => s.customFieldDetail);
  const escStack = layoutStore.useStore((s) => s.escStack);
  const [copyTitle, setCopyTitle] = React.useState('');
  const [isChanged, setIsChanged] = React.useState(false);
  const [showCopy, setShowCopy] = React.useState(false);
  const [showAnchor, setShowAnchor] = React.useState(false);
  const preDataRef = React.useRef(data);
  const preData = preDataRef.current;

  const escClose = React.useCallback(
    (e) => {
      if (e.key === 'Escape') {
        if (escStack.length) {
          return;
        }
        if (isChanged && confirmCloseTip) {
          Modal.confirm({
            title: confirmCloseTip,
            onOk() {
              onClose(e);
            },
          });
        } else {
          onClose(e);
        }
      }
    },
    [confirmCloseTip, escStack, isChanged, onClose],
  );

  useEvent('keydown', escClose);

  React.useEffect(() => {
    const isIssueDrawerChanged = (initData: CreateDrawerData, currentData: CreateDrawerData) => {
      setIsChanged(false);

      Object.keys(currentData).forEach((key) => {
        if (key in initData) {
          if (!isEqual(initData[key], currentData[key])) {
            setIsChanged(true);
          }
        } else {
          const defaultValue = find(customFieldDetail?.property, { propertyName: key })?.values;

          // Determine whether the field has changed. When the value is the following conditions, the field has not changed
          const notChange =
            isEqual(defaultValue, currentData[key]) ||
            currentData[key] === undefined ||
            currentData[key] === '' ||
            isEqual(currentData[key], []) ||
            isEqual(currentData[key], { estimateTime: 0, remainingTime: 0 });

          if (!notChange) {
            setIsChanged(true);
          }
        }
      });
    };
    isIssueDrawerChanged(preData, data);
  }, [customFieldDetail?.property, data, preData]);

  const mainEle = React.useRef<HTMLDivElement>(null);
  const { y } = useScroll(mainEle);

  React.useLayoutEffect(() => {
    let timer: NodeJS.Timeout;
    if (mainEle.current) {
      // wait for layout stable
      timer = setTimeout(() => {
        setShowAnchor(true);
      }, 1000);
    }
    return () => timer && clearTimeout(timer);
  }, []);

  return (
    <Modal
      wrapClassName="issue-drawer-modal"
      className={`task-drawer ${className}`}
      width="calc(100% - 80px)"
      closable={false}
      visible={visible}
      onCancel={onClose}
      footer={null}
      maskClosable={maskClosable || !isChanged}
      keyboard={false}
      forceRender // useScroll will not take effect if mainRel is lazy mounted
      {...rest}
    >
      <Spin spinning={loading}>
        <div className="relative flex flex-col" style={{ height: 'calc(100vh - 80px)' }}>
          <If condition={header !== IssueDrawer.Empty}>
            <div className={`task-drawer-header ${y > 2 ? 'shadow-card' : ''}`}>
              <div className="flex justify-between items-center">
                <div className="flex-1 nowrap">{typeof header === 'function' ? header(y) : header}</div>
                <div className="task-drawer-op flex items-center">
                  {extraHeaderOp}
                  <SubscribersSelector
                    subscribers={data.subscribers}
                    issueID={customFieldDetail?.issueID}
                    issueType={issueType}
                    projectId={projectId}
                    setData={setData}
                    data={data}
                  />
                  <If condition={editMode && shareLink}>
                    <Copy selector=".copy-share-link" tipName={i18n.t('dop:link-share')} />
                    <ErdaIcon
                      type="lianjie"
                      className="cursor-copy hover-active copy-share-link ml-4 text-default-6"
                      size="20"
                      data-clipboard-text={shareLink}
                    />
                  </If>
                  <If condition={editMode}>
                    <WithAuth pass={canCreate}>
                      <Popover
                        title={i18n.t('dop:Copy issue')}
                        visible={showCopy}
                        onVisibleChange={(v) => setShowCopy(v)}
                        content={
                          <>
                            <Input
                              placeholder={i18n.t('dop:Please enter the issue title')}
                              style={{ width: 400 }}
                              value={copyTitle}
                              onChange={(e) => setCopyTitle(e.target.value)}
                            />
                            <div className="flex items-center flex-wrap justify-end mt-2">
                              <Button
                                className="mr-2"
                                onClick={() => {
                                  setCopyTitle('');
                                  setShowCopy(false);
                                }}
                              >
                                {i18n.t('Cancel')}
                              </Button>
                              <Button
                                onClick={() => {
                                  if (copyTitle === '') {
                                    message.error(i18n.t('dop:The title can not be empty'));
                                    return;
                                  }
                                  handleCopy && handleCopy(true, copyTitle);
                                  setCopyTitle('');
                                  setShowCopy(false);
                                }}
                                type="primary"
                              >
                                {i18n.t('Copy')}
                              </Button>
                            </div>
                          </>
                        }
                        placement="leftTop"
                        trigger="click"
                      >
                        <ErdaIcon type="fuzhi" className="hover-active ml-4 text-default-6" size="20" />
                      </Popover>
                    </WithAuth>
                  </If>
                  {onDelete ? (
                    <WithAuth pass={canDelete}>
                      <Popconfirm
                        title={`${i18n.t('common:confirm to delete')}?`}
                        placement="bottomRight"
                        onConfirm={onDelete}
                      >
                        <ErdaIcon type="shanchu-4d7l02mb" className="hover-active ml-4 text-default-6" size="20" />
                      </Popconfirm>
                    </WithAuth>
                  ) : null}
                  {isChanged && confirmCloseTip ? (
                    <Popconfirm title={confirmCloseTip} placement="bottomRight" onConfirm={onClose}>
                      <ErdaIcon type="guanbi" className="ml-4 hover-active text-default-6" size="20" />
                    </Popconfirm>
                  ) : (
                    <ErdaIcon type="guanbi" className="ml-4 hover-active text-default-6" size="20" onClick={onClose} />
                  )}
                </div>
              </div>
            </div>
          </If>
          <div
            ref={mainEle}
            className="relative flex-1 overflow-x-hidden overflow-y-auto"
            style={footer !== IssueDrawer.Empty ? { padding: '0 12% 60px' } : { padding: '0 12%' }}
          >
            <If condition={editMode && showAnchor}>
              <div className="absolute">
                <Anchor offsetTop={16} getContainer={() => mainEle.current || document.body}>
                  {formField !== IssueDrawer.Empty && <Anchor.Link href="#field" title={i18n.t('dop:Basic')} />}
                  {descField !== IssueDrawer.Empty && <Anchor.Link href="#desc" title={i18n.t('dop:Content')} />}
                  {workflowField !== IssueDrawer.Empty && (
                    <Anchor.Link href="#workflow" title={i18n.t('dop:workflow')} />
                  )}
                  {inclusionField !== IssueDrawer.Empty && (
                    <Anchor.Link href="#inclusion" title={i18n.t('dop:Contain')} />
                  )}
                  {relationField !== IssueDrawer.Empty && (
                    <Anchor.Link href="#relation" title={i18n.t('dop:Reference')} />
                  )}
                  {logField !== IssueDrawer.Empty && <Anchor.Link href="#log" title={i18n.t('dop:Log')} />}
                </Anchor>
              </div>
            </If>
            <If condition={formField !== IssueDrawer.Empty}>
              <div id="field">{formField}</div>
            </If>
            <If condition={descField !== IssueDrawer.Empty}>
              <div id="desc" className="h-px bg-default-08 my-4" />
              {descField}
            </If>
            <If condition={workflowField !== IssueDrawer.Empty}>
              <div id="workflow" className="mt-6">
                {workflowField}
              </div>
            </If>
            <If condition={inclusionField !== IssueDrawer.Empty}>
              <div id="inclusion" className="mt-6">
                {inclusionField}
              </div>
            </If>
            <If condition={relationField !== IssueDrawer.Empty}>
              <div id="relation" className="mt-6">
                {relationField}
              </div>
            </If>
            <If condition={logField !== IssueDrawer.Empty}>
              <div id="log" className="mt-6">
                {logField}
              </div>
            </If>
          </div>
          <If condition={footer !== IssueDrawer.Empty}>
            <div className="task-drawer-footer">
              {typeof footer === 'function' ? footer(isChanged, confirmCloseTip) : footer}
            </div>
          </If>
        </div>
      </Spin>
    </Modal>
  );
}
Example #11
Source File: index.tsx    From gant-design with MIT License 4 votes vote down vote up
GantAnchor = (props: GantAnchorProps) => {
  let {
    list = [],
    content,
    fixedTop = 0,
    layout = 'vertical',
    onLayoutChange,
    minHeight = 400,
    className,
    style,
    getContainer = defaultContainer,
    ...nextProps
  } = props;
  const [stateMode, setStateMode] = useState(layout);
  const [currentId, setId] = useState(list.length ? list[0].id : ''); //当前选中id,进入页面默认选中第一
  const [leftArrowsShow, setLeftArrowsShow] = useState(false); //左侧箭头
  const [rightArrowsShow, setRightArrowsShow] = useState(false); //右侧箭头
  const [menuArrowsShow, setMenuArrowsShow] = useState(true);
  const [silderIdWidth, setSilderIdWidth] = useState(0); //List外层宽度
  const [contentWidth, setContentWidth] = useState(0); //List实际宽度
  const [isClickScroll, setIsClickScroll] = useState(false); //页面滚动判断是点击后的滚动还是手动的滚动
  let scrollHeight = 0; // 滚动的值
  let m2 = 0; // 对比时间的值
  let timer = null;
  const Data = useCallback(
    e => {
      m2 = getContainerScrollHeight(getContainer);
      if (m2 == scrollHeight) {
        if (isClickScroll) {
          setIsClickScroll(false);
        }
      }
    },
    [isClickScroll, setIsClickScroll, getContainer],
  );

  //   //滚动时触发
  const handleScroll = useCallback(
    e => {
      const fixedEle = document.getElementById('anchorBoxId'); //定位元素
      const fixedEleParent = document.querySelector('.gant-anchor-horAnchorOut'); //定位元素的父元素

      if (fixedEle && stateMode == 'horizontal') {
        clearTimeout(timer);
        timer = setTimeout(Data, 300);
        const menuboxhor = document.querySelector('.gant-submenu-menuboxhor'); //anchor的外层card
        const extraheight = menuboxhor ? menuboxhor['offsetHeight'] : 0;
        const container = getContainer();
        const isWindow = container == window;
        // 容器距离浏览器顶部的距离
        const containerScrollTop = isWindow
          ? 0
          : (container as Element)?.getBoundingClientRect?.()?.top;
        // 滚动条滚动的距离
        scrollHeight = getContainerScrollHeight(getContainer);
        if (fixedEle) {
          if (scrollHeight >= FIXED_HEIGHT + fixedTop + extraheight) {
            fixedEle.classList.add('gant-anchor-activeScroll');
            const active = document.querySelector('.gant-anchor-activeScroll');
            active['style'].top = `${fixedTop + containerScrollTop}px`;
            active['style'].width = `${fixedEleParent['offsetWidth']}px`;
          } else {
            fixedEle.classList.remove('gant-anchor-activeScroll');
          }
        }

        if (!isClickScroll) {
          //水平方向锚点跟随页面滚动高亮
          list.map(item => {
            if (!item.isInvalid) {
              const id = document.getElementById(item.id);
              let common = fixedTop + extraheight + FIXED_HEIGHT + containerScrollTop;
              if (id && id.getBoundingClientRect()) {
                let { top, height } = id.getBoundingClientRect();
                if (top <= common && top >= common - height) {
                  //这里的44是水平锚点条高度
                  setId(item.id);
                }
              }
            }
          });
        }
      }
    },
    [stateMode, setId, isClickScroll, list, getContainer],
  );

  //   //点击左右箭头
  const handleMobileTabs = useCallback(
    e => {
      const contentId = document.getElementById('contentId');
      const left = contentId.offsetLeft;
      const right = contentWidth - silderIdWidth + left;
      if (e == 'left') {
        if (left < 0 && left > -500) {
          contentId.style.left = '0px';
        } else if (left < -500) {
          contentId.style.left = `${left + 500}` + 'px';
        }
        const newLeft = contentId.offsetLeft;
        setLeftArrowsShow(newLeft == 0 ? false : true);
        setRightArrowsShow(true);
      } else {
        if (right > 0 && right < 500) {
          contentId.style.left = `${left - right}` + 'px';
        } else if (right >= 0 && right > 500) {
          contentId.style.left = `${left - 500}` + 'px';
        }
        const newRight = contentWidth - silderIdWidth + contentId.offsetLeft;
        setLeftArrowsShow(true);
        setRightArrowsShow(newRight == 0 ? false : true);
      }
    },
    [contentWidth, silderIdWidth, setLeftArrowsShow, setRightArrowsShow],
  );

  useEffect(() => {
    setStateMode(stateMode);
  }, [setStateMode]);

  useEffect(() => {
    const container = getContainer();
    container.addEventListener('scroll', handleScroll); //监听滚动
    return function cleanup() {
      container.removeEventListener('scroll', handleScroll);
    };
  }, [handleScroll, setIsClickScroll, getContainer]);

  useEffect(() => {
    const silderId = document.getElementById('silderId');
    const contentId = document.getElementById('contentId');
    const silderIdWidth = silderId ? silderId.offsetWidth : 0;
    const contentWidth = contentId ? contentId.offsetWidth : 0;
    setSilderIdWidth(silderIdWidth);
    setContentWidth(contentWidth);
    setRightArrowsShow(
      stateMode == 'horizontal' ? (silderIdWidth < contentWidth ? true : false) : false,
    );
    setMenuArrowsShow(
      stateMode == 'horizontal' ? (silderIdWidth < contentWidth ? true : false) : false,
    );
  }, [stateMode, setSilderIdWidth, setContentWidth, setRightArrowsShow, setMenuArrowsShow]);

  const getOffsetTop = useCallback((element: HTMLElement, container: any): number => {
    if (!element.getClientRects().length) {
      return 0;
    }

    const rect = element.getBoundingClientRect();

    if (rect.width || rect.height) {
      if (container === window) {
        container = element.ownerDocument!.documentElement!;
        return rect.top - container.clientTop;
      }
      return rect.top - (container as HTMLElement).getBoundingClientRect().top;
    }

    return rect.top;
  }, []);

  //水平方向锚点事件
  const scrollToAnchor = useCallback(
    anchorName => {
      if (anchorName) {
        const container = getContainer();
        let anchorElement = document.getElementById(anchorName);
        if (anchorElement) {
          const scrollTop = getScroll(container, true);
          const eleOffsetTop = getOffsetTop(anchorElement, container);
          let y = scrollTop + eleOffsetTop - FIXED_HEIGHT - fixedTop;
          scrollTo(y, {
            getContainer,
          });
        }
        setId(anchorName);
        setIsClickScroll(true);
      }
    },
    [setId, setIsClickScroll, fixedTop, getContainer],
  );

  //水平方向锚点menu内容
  const menu = useMemo(() => {
    return (
      <Menu selectedKeys={[currentId]}>
        {list.map(item => {
          return (
            <Menu.Item
              key={item.id}
              onClick={() => scrollToAnchor(item.id)}
              disabled={item.isInvalid ? true : false}
            >
              {item.title}
            </Menu.Item>
          );
        })}
      </Menu>
    );
  }, [currentId, list]);

  //锚点方向切换
  const onSwitchClick = useCallback(
    e => {
      let newStateMode: layout = stateMode === 'vertical' ? 'horizontal' : 'vertical';
      if (layout === 'vertical') {
        // 去掉磁吸效果
        const fixedEle = document.getElementById('anchorBoxId'); //定位元素
        fixedEle && fixedEle.classList.remove(`${prefixCls}-activeScroll`);
      }
      setStateMode(newStateMode);
      onLayoutChange && onLayoutChange(newStateMode);
    },
    [stateMode, setStateMode, onLayoutChange],
  );

  return (
    <div className={classnames(className, `${prefixCls}-wrapper`)} style={{ ...style }}>
      <div
        style={{ width: stateMode === 'horizontal' ? '100%' : 'calc(100% - 150px)', float: 'left' }}
      >
        <div
          className={classnames(`${prefixCls}-horAnchorOut`, {
            [`${prefixCls}-horAnchorOut__hidden`]: stateMode === 'vertical',
          })}
        >
          <div className={`gant-anchor`} id="anchorBoxId">
            <Icon
              type="left"
              style={{ display: leftArrowsShow ? 'block' : 'none', float: 'left' }}
              className={`${prefixCls}-iconCss`}
              onClick={() => handleMobileTabs('left')}
            />
            <div className={`${prefixCls}-silderCss`} id="silderId">
              <div className={`${prefixCls}-contentCss`} id="contentId">
                {list.map(item => {
                  let nowCss = item.id == currentId ? 'activeCss' : '';
                  if (item.isInvalid) {
                    return <div className={`${prefixCls}-isInvalid`}>{item.title}</div>;
                  }
                  return (
                    <a
                      className={`${prefixCls}-aCss`}
                      key={item.id}
                      onClick={() => scrollToAnchor(item.id)}
                    >
                      <span className={`${prefixCls}-${nowCss}`}>
                        {
                          <>
                            {item.title}
                            {item.complete ? (
                              <Icon
                                type="check-circle"
                                theme="twoTone"
                                twoToneColor="#52c41a"
                                style={{ paddingLeft: '5px' }}
                              />
                            ) : null}
                          </>
                        }
                      </span>
                    </a>
                  );
                })}
              </div>
            </div>
            <Icon
              type="switcher"
              onClick={onSwitchClick}
              className={`${prefixCls}-iconCss`}
              style={{ float: 'right' }}
            />
            <Dropdown
              overlay={menu}
              // style={{ display: menuArrowsShow ? 'block' : 'none' }}
              placement="bottomRight"
            >
              <Icon type="down" className={`${prefixCls}-iconCss`} style={{ float: 'right' }} />
            </Dropdown>
            <Icon
              type="right"
              style={{ display: rightArrowsShow ? 'block' : 'none', float: 'right' }}
              className={`${prefixCls}-iconCss`}
              onClick={() => handleMobileTabs('right')}
            />
          </div>
        </div>

        <div className="gant-anchor-content" style={{ padding: '0px', minHeight }}>
          {content}
        </div>
      </div>
      <div
        className={classnames(`${prefixCls}-verticalbox`, {
          [`${prefixCls}-verticalbox__hidden`]: stateMode === 'horizontal',
        })}
        style={{
          width: 150,
          paddingLeft: '10px',
          paddingTop: '10px',
          float: stateMode === 'horizontal' ? 'none' : 'left',
        }}
      >
        <Anchor
          offsetTop={fixedTop}
          onClick={e => {
            e.preventDefault();
          }}
          {...nextProps}
          getContainer={getContainer}
        >
          <Icon
            type="switcher"
            onClick={onSwitchClick}
            style={{ width: '100%', paddingRight: '10px', textAlign: 'right' }}
          />
          {list.map(item => {
            const nullCss = {};
            return (
              <div
                key={item?.id}
                style={item.isInvalid ? { opacity: 0.5, cursor: 'not-allowed' } : nullCss}
              >
                <div style={item.isInvalid ? { pointerEvents: 'none' } : nullCss}>
                  <Anchor.Link
                    key={item.key || item.title}
                    href={`#${item.id || item.title}`}
                    title={
                      <>
                        <Tooltip title={item.title} placement="left">
                          {item.title}
                        </Tooltip>
                        {item.complete ? (
                          <Icon
                            type="check-circle"
                            theme="twoTone"
                            twoToneColor="#52c41a"
                            style={{ paddingLeft: '5px' }}
                          />
                        ) : null}
                      </>
                    }
                  />
                </div>
              </div>
            );
          })}
        </Anchor>
      </div>
    </div>
  );
}
Example #12
Source File: index.tsx    From jetlinks-ui-antd with MIT License 4 votes vote down vote up
FieldAccess: React.FC<Props> = props => {
  const initState: State = {
    currentItem: props.data,
    tempFieldAccess: [],
  };
  const [currentItem, setCurrentItem] = useState(initState.currentItem);
  const [tempFieldAccess, setTempFieldAccess] = useState(initState.tempFieldAccess);
  useEffect(() => {
    setCurrentItem(props.data);
    const tempKey: string[] = [];
    ((props.data.current || {}).dataAccesses || []).forEach(e =>
      e.config.fields.forEach((i: string) => tempKey.push(e.action + '-' + i)),
    );
    const temp = (props.data.optionalFields || []).map(field => {
      const actions = (props.data.actions || []).map(a => {
        let key = a.action + '-' + field.name;
        return { checked: tempKey.find(i => i === key) ? false : true, ...a };
      });
      return { actions, ...field } as TempField;
    });
    setTempFieldAccess(temp);
  }, [props.data]);

  const buildAccess = () => {
    let tempAccess = new Map();
    tempFieldAccess.forEach(item => {
      item.actions.forEach(ac => {
        if (ac.checked) return;
        //获取不到就创建
        let fieldAccess =
          tempAccess.get(ac.action) ||
          tempAccess
            .set(ac.action, {
              action: ac.action,
              type: 'DENY_FILEDS',
              config: {
                fields: [],
              },
            })
            .get(ac.action);
        fieldAccess.config.fields.push(item.name);
      });
    });
    currentItem.dataAccesses = [...tempAccess.values()];
    props.save(currentItem);
    message.success('保存成功');
  };

  const checkItem = (field: string, action: string) => {
    const temp = tempFieldAccess.map(item => {
      if (item.name === field) {
        item.actions.map(ac => {
          if (ac.action === action) {
            ac.checked = !ac.checked;
          }
          return ac;
        });
      }
      return item;
    });
    setTempFieldAccess(temp);
  };

  return (
    <Modal
      title={`设置${currentItem.name}字段权限`}
      visible
      width={800}
      onCancel={() => {
        props.close();
        setCurrentItem({});
      }}
      onOk={() => {
        buildAccess();
      }}
    >
      <Alert
        message='描述: 如果角色对当前对象设置了 "新建" 或 "导入" 权限,带*号的必填字段默认设置为“读写”,不可设置为“只读”或“不可见”,否则会造成新建/导入不成功'
        type="info"
      />

      <Divider type="horizontal" />

      <Card bordered={false} id="field-access-card" style={{ height: 300, overflow: 'auto' }}>
        <Col span={20}>
          {tempFieldAccess.map(data => {
            return (
              <Row style={{ width: '100%', marginTop: 20 }} key={data.name} id={data.name}>
                <Col span={4} style={{ fontWeight: 700 }}>
                  <span style={{ fontSize: '14px', color: '#f5222d' }}>* </span>
                  {data.describe}
                </Col>
                <Col span={20}>
                  <Checkbox.Group
                    style={{ width: '100%' }}
                    value={data.actions.filter(e => e.checked === true).map(e => e.action)}
                  >
                    {data.actions.map(item => (
                      <Col key={item.action} span={5}>
                        <Checkbox
                          value={item.action}
                          onClick={() => {
                            checkItem(data.name, item.action);
                          }}
                        >
                          {item.name}
                        </Checkbox>
                      </Col>
                    ))}
                  </Checkbox.Group>
                </Col>
              </Row>
            );
          })}
        </Col>

        <Col span={3} push={1} style={{ height: 600, overflow: 'auto' }}>
          <Affix>
            <Anchor getContainer={() => document.getElementById('field-access-card') || window}>
              {(props.data.optionalFields || []).map(data => (
                <Anchor.Link href={'#' + data.name} title={data.describe} key={data.name} />
              ))}
            </Anchor>
          </Affix>
        </Col>
      </Card>
    </Modal>
  );
}
Example #13
Source File: index.tsx    From surveyo with Apache License 2.0 4 votes vote down vote up
function FormCreator() {
  const [questions, setQuestions] = useState<any>([]);
  const [formSubmitted, setFormSubmitState] = useState(false);
  const [formURL, setFormURL] = useState('');
  const [surveyTitle, setSurveyTitle] = useState('');
  const [formHook] = useForm();
  const {user} = useAuth0();

  const [sendToClient] = useMutation(ADD_FORM);

  const getCard = (i: number) => {
    const question = questions[i];
    const params = {
      question: question,
      updateQuestion: (question: any) =>
        setQuestions(update(questions, {$splice: [[i, 1, question]]})),
      deleteQuestion: () =>
        setQuestions(update(questions, {$splice: [[i, 1]]})),
    };
    return <QuestionCard {...params} />;
  };
  const menu = (
    <Menu onClick={e => setQuestions(questions.concat({type: e.key}))}>
      <Menu.Item key="Text">Short Answer</Menu.Item>
      <Menu.Item key="SingleChoice">Multiple Choice</Menu.Item>
      <Menu.Item key="Date">Date</Menu.Item>
      <Menu.Item key="Rating">Rating</Menu.Item>
      <Menu.Item key="NetPromoterScore">Net Promoter Score</Menu.Item>
    </Menu>
  );

  if (formSubmitted) {
    return (
      <Card type="inner">
        <Result
          status="success"
          title="Thank you!"
          subTitle={
            <Anchor>
              <Anchor.Link href={formURL} title="Your form is live." />
            </Anchor>
          }
        />
      </Card>
    );
  } else
    return (
      <PageHeader ghost={true} title="Create a survey">
        <Form form={formHook}>
          <Card
            actions={[
              <Dropdown overlay={menu}>
                <Button>
                  Add Question <DownOutlined />
                </Button>
              </Dropdown>,
              <Button
                type="primary"
                onClick={async () => {
                  const values = await formHook.validateFields();
                  console.log('validation ' + values.name);
                  for (let index = 0; index < questions.length; index++) {
                    if ('options' in questions[index]) {
                      let newOptions = questions[index].options.map(
                        (value: any, index: any) => {
                          return {order: index, title: value};
                        }
                      );
                      questions[index].options = newOptions;
                    }
                    questions[index].order = index;
                    if (!('required' in questions[index])) {
                      questions[index].required = false;
                    }
                  }
                  var form = {
                    title: surveyTitle,
                    fields: questions,
                    creator: {email: user.email},
                  };

                  console.log('Form: ', form);

                  try {
                    var result = await sendToClient({
                      variables: {
                        form: form,
                      },
                    });

                    console.log(result);
                    let id = result.data.addForm.form[0].id;
                    let url =
                      window.location.href.replace('/create', '') +
                      '/form/' +
                      id;
                    setFormURL(url);
                    setFormSubmitState(true);
                  } catch (error) {
                    console.log(error);
                  }
                }}
              >
                Create
              </Button>,
            ]}
          >
            <Form.Item
              label="Survey Title"
              name="survey title"
              rules={[{required: true, message: 'Please input Survey title'}]}
            >
              <Input
                placeholder="Enter your survey title"
                onChange={e => {
                  setSurveyTitle(e.target.value);
                }}
              />
            </Form.Item>
            {questions.map((question: any, index: number) => (
              <div key={index}>
                <Card>{getCard(index)}</Card>
              </div>
            ))}
          </Card>
        </Form>
      </PageHeader>
    );
}
Example #14
Source File: index.tsx    From amiya with MIT License 4 votes vote down vote up
export default function Demo() {
  // 提交的数据
  const [submitValues, setSubmitValues] = useState<AnyKeyProps>({})
  // 当前 sku 表格数据
  const [skuData, setSkuData] = useState<Array<Record>>([])
  // sku 对应的图片对象
  const [skuImageMap, setSkuImageMap] = useState<Record>({})
  const formRef = useRef<any>()

  /**
   * 更新上传图片
   * @param name 当前 sku 值
   * @param value 新图片
   */
  const handleChange = (name: string, value: string) => {
    let newMap = { ...skuImageMap }
    newMap[name] = value
    setSkuImageMap(newMap)
  }

  // 改变 sku 表格数据后,删除多余的 key,保留已经选中的 key
  useEffect(() => {
    let newMap = { ...skuImageMap }
    let names = skuData.map(item => item.name)
    for (let key in newMap) {
      if (!names.includes(key)) {
        delete newMap[key]
      }
    }
    setSkuImageMap(newMap)
  }, [skuData])

  const handleSubmit = (values: FormValues) => {
    // TODO 必填校验
    setSubmitValues({ ...values, skuMap: skuImageMap })
  }

  const fillData = () => {
    formRef.current.setFieldsValue(data)
    setSkuImageMap(data.skuMap)
  }

  return (
    <Card>
      <div style={{ position: 'relative' }}>
        <AyForm
          ref={formRef}
          style={{ marginRight: 100 }}
          formLayout="vertical"
          onConfirm={values => handleSubmit(values)}
          gutter={12}
        >
          <AyFields>
            <AyField title="基础信息" key="__base" id="base" type="card" collapsible>
              <AyField
                title="店铺"
                type="select"
                key="shopId"
                required
                span={12}
                options={[
                  { label: '选项A', value: 1 },
                  { label: '选项B', value: 2 }
                ]}
              />
              <AyField title="商店名称" key="name" required span={12} maxLength={120} showCount />
              <AyField
                title="主营类目"
                type="select"
                key="category"
                required
                span={12}
                options={[
                  { label: '选项A', value: 1 },
                  { label: '选项B', value: 2 }
                ]}
              />
              <AyField
                title="商品保存状态"
                type="select"
                key="saveStatus"
                span={12}
                options={[
                  { label: '选项A', value: 1 },
                  { label: '选项B', value: 2 }
                ]}
              />
              <AyField title="商品描述" type="textarea" required key="remark" rows={5} maxLength={3000} showCount />
            </AyField>
            <AyField title="商品信息" key="__goods" id="goods" type="card" collapsible>
              <AyField key="sku" type="custom" renderContent={() => <SkuEdit onDataChange={setSkuData} />} />
            </AyField>
            <AyField title="媒体管理" key="__images" type="card" id="images" collapsible>
              <AyField
                title="商品图片"
                key="goodsImage"
                type="custom"
                required
                help="*最多可以上传 9 张"
                defaultValue={[]}
                multiple
                renderContent={() => <UploadImage multiple max={9} />}
              />
              <AyField
                title="SKU 图片"
                key="__skuImage"
                type="custom"
                hidden={skuData.length === 0}
                renderContent={() => (
                  <Space>
                    {skuData.map(sku => (
                      <div key={sku.name}>
                        <UploadImage
                          value={skuImageMap[sku.name]}
                          onChange={value => handleChange(sku.name, value as string)}
                        />
                        <div style={{ textAlign: 'center', transform: 'translateX(-4px)' }}>{sku.name}</div>
                      </div>
                    ))}
                  </Space>
                )}
              />
            </AyField>
            <AyField title="运费信息" key="__freight" id="freight" type="card" collapsible>
              <AyField key="__freightTitle" render={() => <h3>包裹尺寸</h3>} />
              <AyField title="长" key="__long" type="input-group" span={8}>
                <AyField key="long" type="number" />
                <AyField key="longUnit" type="select" options={unitOptions} defaultValue={1} readonly />
              </AyField>
              <AyField title="宽" key="__wide" type="input-group" span={8}>
                <AyField key="wide" type="number" />
                <AyField key="wideUnit" type="select" options={unitOptions} defaultValue={1} readonly />
              </AyField>
              <AyField title="高" key="__height" type="input-group" span={8}>
                <AyField key="height" type="number" />
                <AyField key="heightUnit" type="select" options={unitOptions} defaultValue={1} readonly />
              </AyField>
              <AyField title="重量" key="__weight" type="input-group" span={8}>
                <AyField key="weight" type="number" />
                <AyField
                  key="weightUnit"
                  type="select"
                  options={[
                    { label: 'kg', value: 1 },
                    { label: 'g', value: 2 }
                  ]}
                  readonly
                  defaultValue={1}
                />
              </AyField>
            </AyField>
          </AyFields>
          <Space>
            <AyButton htmlType="submit" type="primary">
              提交
            </AyButton>
            <AyButton onClick={fillData}>填充数据</AyButton>
          </Space>
        </AyForm>
        <div style={{ position: 'absolute', right: 0, top: 0 }}>
          <Anchor offsetTop={90}>
            <Link href="#base" title="基础信息" />
            <Link href="#goods" title="商品信息" />
            <Link href="#images" title="媒体管理" />
            <Link href="#freight" title="运费信息" />
          </Anchor>
        </div>
      </div>
      {submitValues.name && <pre>{JSON.stringify(submitValues, null, 2)}</pre>}
    </Card>
  )
}