├── .dumi ├── global.less ├── pages │ └── index │ │ ├── index.scss │ │ └── index.tsx ├── theme │ ├── builtins │ │ ├── Details.tsx │ │ └── index.less │ ├── common │ │ └── Loading.tsx │ ├── hooks │ │ └── index.tsx │ ├── layouts │ │ ├── DocLayout.scss │ │ └── DocLayout.tsx │ ├── slots │ │ ├── Footer │ │ │ ├── index.scss │ │ │ └── index.tsx │ │ ├── Header │ │ │ ├── index.scss │ │ │ └── index.tsx │ │ ├── Sidebar │ │ │ ├── index.scss │ │ │ └── index.tsx │ │ └── Toc │ │ │ ├── index.scss │ │ │ └── index.tsx │ └── style │ │ └── const.scss └── tsconfig.json ├── .dumirc.ts ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .fatherrc.ts ├── .github ├── ISSUE_TEMPLATE │ ├── 1_bug_report.yaml │ └── 2_feature_request.yaml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── CD.yml │ └── CI.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierignore ├── .prettierrc.js ├── .stylelintignore ├── .stylelintrc ├── CHANGELOG.md ├── LICENSE ├── README-zh_CN.md ├── README.md ├── docs ├── guide │ ├── CONTRIBUTING.md │ ├── componentDev.md │ ├── index.md │ ├── migration-v4.md │ └── updateLog.md └── index.md ├── jest.config.js ├── package.json ├── pnpm-lock.yaml ├── public └── logo.png ├── scripts └── release.sh ├── src ├── blockHeader │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── blockHeader.test.tsx.snap │ │ └── blockHeader.test.tsx │ ├── demos │ │ ├── addonAfter.tsx │ │ ├── addonBefore.tsx │ │ ├── basic.tsx │ │ └── expand.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── catalogue │ ├── components │ │ ├── index.ts │ │ ├── tree │ │ │ ├── components │ │ │ │ ├── header │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.scss │ │ │ │ └── index.ts │ │ │ ├── helpers │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── style.scss │ │ └── treeSelect │ │ │ ├── index.tsx │ │ │ └── style.scss │ ├── consts.scss │ ├── demos │ │ ├── tree │ │ │ ├── DefaultTree │ │ │ │ └── index.tsx │ │ │ ├── NoHeaderTree │ │ │ │ └── index.tsx │ │ │ ├── SmallTree │ │ │ │ └── index.tsx │ │ │ ├── WithBtnSlotTree │ │ │ │ └── index.tsx │ │ │ ├── WithCheckboxTree │ │ │ │ └── index.tsx │ │ │ ├── WithTabsTree │ │ │ │ └── index.tsx │ │ │ └── data.ts │ │ └── treeSelect │ │ │ ├── NormalTreeSelect │ │ │ └── index.tsx │ │ │ └── data.ts │ ├── index.md │ └── index.tsx ├── chat │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ ├── button.test.tsx.snap │ │ │ ├── codeBlock.test.tsx.snap │ │ │ ├── content.test.tsx.snap │ │ │ ├── icon.test.tsx.snap │ │ │ ├── input.test.tsx.snap │ │ │ ├── loading.test.tsx.snap │ │ │ ├── markdown.test.tsx.snap │ │ │ ├── message.test.tsx.snap │ │ │ ├── pagination.test.tsx.snap │ │ │ ├── prompt.test.tsx.snap │ │ │ ├── tag.test.tsx.snap │ │ │ └── welcome.test.tsx.snap │ │ ├── button.test.tsx │ │ ├── codeBlock.test.tsx │ │ ├── content.test.tsx │ │ ├── icon.test.tsx │ │ ├── input.test.tsx │ │ ├── loading.test.tsx │ │ ├── markdown.test.tsx │ │ ├── message.test.tsx │ │ ├── pagination.test.tsx │ │ ├── prompt.test.tsx │ │ ├── tag.test.tsx │ │ ├── useChat.test.ts │ │ └── welcome.test.tsx │ ├── button │ │ ├── index.scss │ │ └── index.tsx │ ├── codeBlock │ │ ├── index.scss │ │ └── index.tsx │ ├── content │ │ ├── index.scss │ │ └── index.tsx │ ├── demos │ │ ├── basic.tsx │ │ ├── button.tsx │ │ ├── codeBlock-convert.tsx │ │ ├── codeBlock.tsx │ │ ├── global-state │ │ │ └── index.tsx │ │ ├── input.tsx │ │ ├── loading.tsx │ │ ├── markdown.tsx │ │ ├── message-lazyRendered.tsx │ │ ├── message.tsx │ │ ├── mockSSE.ts │ │ ├── prompt.tsx │ │ └── tag.tsx │ ├── entity.ts │ ├── icon │ │ ├── index.scss │ │ └── index.tsx │ ├── index.$tab-button.md │ ├── index.$tab-codeBlock.md │ ├── index.$tab-input.md │ ├── index.$tab-loading.md │ ├── index.$tab-markdown.md │ ├── index.$tab-message.md │ ├── index.$tab-prompt.md │ ├── index.$tab-tag.md │ ├── index.md │ ├── index.tsx │ ├── input │ │ ├── index.scss │ │ └── index.tsx │ ├── loading │ │ ├── index.scss │ │ └── index.tsx │ ├── markdown │ │ ├── index.scss │ │ └── index.tsx │ ├── message │ │ ├── index.scss │ │ └── index.tsx │ ├── pagination │ │ ├── index.scss │ │ └── index.tsx │ ├── prompt │ │ ├── index.scss │ │ └── index.tsx │ ├── tag │ │ ├── index.scss │ │ └── index.tsx │ ├── useChat.ts │ ├── useContext.ts │ └── welcome │ │ ├── index.scss │ │ └── index.tsx ├── collapsibleActionItems │ ├── __tests__ │ │ └── index.test.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── components │ └── icon.tsx ├── contentLayout │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── contentLayout.test.tsx.snap │ │ └── contentLayout.test.tsx │ ├── components.tsx │ ├── demos │ │ └── basic.tsx │ ├── index.md │ ├── index.scss │ └── index.tsx ├── contextMenu │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── contextMenu.test.tsx.snap │ │ └── contextMenu.test.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── copy │ ├── __tests__ │ │ └── copy.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ └── custom.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── descriptions │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── descriptions.test.tsx.snap │ │ └── descriptions.test.tsx │ ├── demos │ │ └── basic.tsx │ ├── index.md │ ├── index.scss │ └── index.tsx ├── drawer │ ├── __tests │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ └── index.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ ├── basicBanner.tsx │ │ ├── basicBannerProps.tsx │ │ ├── basicSize.tsx │ │ ├── basic_top.tsx │ │ ├── basic_two.tsx │ │ ├── basic_type.tsx │ │ ├── customTitle.tsx │ │ ├── footer.tsx │ │ ├── tabs.tsx │ │ └── tabsControl.tsx │ ├── index.md │ ├── index.tsx │ ├── motion.ts │ └── style.scss ├── dropdown │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── dropdown.test.tsx.snap │ │ └── dropdown.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ ├── submit.tsx │ │ └── virtual.tsx │ ├── index.md │ ├── index.tsx │ ├── select.tsx │ └── style.scss ├── ellipsisText │ ├── __tests__ │ │ └── ellipsisText.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ ├── flex.tsx │ │ ├── inlineElement.tsx │ │ ├── maxWidth.tsx │ │ ├── multiple.tsx │ │ ├── valueType.tsx │ │ └── watchParent.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── empty │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── empty.test.tsx.snap │ │ └── empty.test.tsx │ ├── emptyImg │ │ ├── empty_chart.png │ │ ├── empty_default.png │ │ ├── empty_overview.png │ │ ├── empty_permission.png │ │ ├── empty_project.png │ │ └── empty_search.png │ ├── index.md │ ├── index.tsx │ └── style.scss ├── errorBoundary │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── errorBoundary.test.tsx.snap │ │ └── errorBoundary.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ └── customErrorPage.tsx │ ├── index.md │ ├── index.tsx │ └── loadError.tsx ├── filterRules │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── filterRules.test.tsx.snap │ │ └── filterRules.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ ├── basicCheck.tsx │ │ ├── basicMaxSize.tsx │ │ ├── basicMore.tsx │ │ ├── basicUnController.tsx │ │ ├── constants.ts │ │ └── editCheck.tsx │ ├── index.md │ ├── index.tsx │ └── ruleController │ │ ├── index.scss │ │ └── index.tsx ├── flex │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── flex.test.tsx.snap │ │ └── flex.test.tsx │ ├── demos │ │ ├── align.tsx │ │ └── basic.tsx │ ├── index.md │ ├── index.scss │ └── index.tsx ├── form │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── table.test.tsx.snap │ │ └── table.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ ├── check.tsx │ │ ├── data.ts │ │ ├── footer.tsx │ │ ├── related.tsx │ │ └── rules.tsx │ ├── index.md │ ├── index.scss │ ├── index.tsx │ └── table.tsx ├── fullscreen │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ └── index.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ ├── custom.tsx │ │ ├── local.tsx │ │ └── theme.tsx │ ├── icon.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── globalLoading │ ├── __tests__ │ │ └── globalLoading.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ └── default.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── image │ ├── __test__ │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ └── index.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ └── lazy.tsx │ ├── index.md │ └── index.tsx ├── index.ts ├── input │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── match.test.tsx.snap │ │ └── match.test.tsx │ ├── demos │ │ ├── input │ │ │ └── basic.tsx │ │ └── match │ │ │ ├── basic.tsx │ │ │ └── filterOptions.tsx │ ├── icons.tsx │ ├── index.$tab-match.md │ ├── index.md │ ├── index.tsx │ ├── internalInput.tsx │ ├── match.scss │ └── match.tsx ├── keyEventListener │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ └── index.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ └── customKey.tsx │ ├── index.md │ ├── index.tsx │ └── listener.tsx ├── markdownRender │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── markdownRender.test.tsx.snap │ │ └── markdownRender.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ ├── dark.tsx │ │ └── sql.tsx │ ├── extensions │ │ └── index.ts │ ├── index.md │ ├── index.tsx │ ├── style.scss │ └── theme │ │ ├── vs-dark.scss │ │ └── vs.scss ├── modal │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── modal.test.tsx.snap │ │ └── modal.test.tsx │ ├── demos │ │ ├── banner.tsx │ │ ├── bannerProps.tsx │ │ ├── basic.tsx │ │ └── size.tsx │ ├── index.md │ ├── index.scss │ ├── index.tsx │ └── modal.tsx ├── notFound │ ├── __tests__ │ │ └── notFound.test.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── progressBar │ ├── __test__ │ │ └── progressBar.test.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── progressLine │ ├── __test__ │ │ └── progressLine.test.tsx │ ├── demos │ │ ├── color.tsx │ │ ├── percent.tsx │ │ ├── title.tsx │ │ └── width.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── resize │ ├── __tests__ │ │ └── index.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ └── observerEle.tsx │ ├── index.md │ └── index.tsx ├── spreadSheet │ ├── __tests__ │ │ └── index.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ └── changeData.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── statusTag │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ └── index.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ ├── icon.tsx │ │ ├── loading.tsx │ │ ├── noIcon.tsx │ │ └── status.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── table │ ├── __tests__ │ │ └── table.test.tsx │ ├── demos │ │ └── basic.tsx │ ├── index.md │ ├── index.scss │ └── index.tsx ├── tinyTag │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── tinyTag.test.tsx.snap │ │ └── tinyTag.test.tsx │ ├── demos │ │ ├── basic.tsx │ │ ├── style.scss │ │ └── table.tsx │ ├── index.md │ ├── index.tsx │ └── style.scss ├── useCookieListener │ ├── __tests__ │ │ └── useCookieListener.test.tsx │ ├── demos │ │ ├── advanced.tsx │ │ └── basic.tsx │ ├── index.md │ └── index.tsx ├── useDebounce │ ├── __tests__ │ │ └── useDebounce.test.tsx │ ├── demos │ │ └── basic.tsx │ ├── index.md │ └── index.tsx ├── useIntersectionObserver │ ├── __tests__ │ │ └── useIntersectionObserver.test.ts │ ├── demos │ │ ├── basic.tsx │ │ └── imgLazy.tsx │ ├── index.md │ └── index.ts ├── useList │ ├── __tests__ │ │ └── useList.test.ts │ ├── demos │ │ ├── basic.tsx │ │ ├── data.ts │ │ ├── mutate.tsx │ │ ├── mutateOptions.tsx │ │ ├── options.tsx │ │ ├── query.tsx │ │ └── sort.tsx │ ├── index.md │ └── index.ts ├── useMeasure │ ├── __tests__ │ │ └── useMeasure.test.ts │ ├── demos │ │ └── basic.tsx │ ├── index.md │ └── index.ts ├── useModal │ ├── __tests__ │ │ └── useModal.test.ts │ ├── demos │ │ └── basic.tsx │ ├── index.md │ └── index.ts ├── useTyping │ ├── __tests__ │ │ └── useTyping.test.ts │ ├── demos │ │ └── basic.tsx │ ├── index.md │ └── index.ts ├── useWindowSwitchListener │ ├── __tests__ │ │ └── switchWindow.test.tsx │ ├── demos │ │ └── basic.tsx │ ├── index.md │ └── index.tsx └── utils │ └── index.ts ├── tests ├── fileTransformer.js ├── setupTests.js └── styleMock.js └── tsconfig.json /.dumi/global.less: -------------------------------------------------------------------------------- 1 | @import "antd/dist/antd.css"; 2 | -------------------------------------------------------------------------------- /.dumi/pages/index/index.scss: -------------------------------------------------------------------------------- 1 | @import "../../theme/style/const.scss"; 2 | 3 | .dtc-homepage { 4 | background-color: #EBF5FD; 5 | height: calc(100vh - 64px); 6 | display: flex; 7 | flex-direction: column; 8 | justify-content: center; 9 | text-align: center; 10 | .dtc-homepage-title { 11 | font-size: 70px; 12 | color: $basic-black-color; 13 | user-select: none; 14 | margin-bottom: 12px; 15 | line-height: 1.15; 16 | } 17 | .dtc-homepage-badges { 18 | margin-bottom: 12px; 19 | img:not(:last-child) { 20 | margin-inline-end: 20px; 21 | } 22 | } 23 | .dtc-homepage-description { 24 | color: $basic-black-color; 25 | font-size: 30px; 26 | } 27 | .dtc-homepage-btnGroups { 28 | margin: 12px auto 0; 29 | display: grid; 30 | grid-gap: 18px; 31 | grid-template-columns: 1fr 1fr; 32 | > .ant-btn { 33 | font-size: 20px; 34 | height: 40px; 35 | line-height: 40px; 36 | } 37 | } 38 | } 39 | 40 | @media only screen and (max-width: 767px) { 41 | .dtc-homepage { 42 | height: calc(100vh - 52px); 43 | .dtc-homepage-title { 44 | font-size: 60px; 45 | } 46 | .dtc-homepage-description { 47 | font-size: 26px; 48 | } 49 | .dtc-homepage-btnGroups { 50 | > .ant-btn { 51 | font-size: 16px; 52 | height: 32px; 53 | line-height: 32px; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.dumi/theme/builtins/index.less: -------------------------------------------------------------------------------- 1 | .dumi-builtins-details { 2 | background: #ECF4FF; 3 | padding: 16px; 4 | font-size: 14px; 5 | color: #3367AF; 6 | border-radius: 4px; 7 | margin: 12px 0; 8 | &[data-open="true"] { 9 | > summary::before { 10 | transform: rotate(90deg); 11 | } 12 | } 13 | > summary { 14 | cursor: pointer; 15 | list-style: none; 16 | padding-left: 1em; 17 | line-height: 1.5em; 18 | position: relative; 19 | &::before { 20 | border-color: transparent transparent transparent #3367AF; 21 | border-style: solid; 22 | border-width: 0.38em; 23 | content: ""; 24 | left: 0; 25 | position: absolute; 26 | top: 0.4em; 27 | transform: rotate(0); 28 | transform-origin: 3px 50%; 29 | transition: transform 0.5s; 30 | } 31 | } 32 | &-content { 33 | transition: height 0.3s; 34 | > :last-child { 35 | margin-bottom: 0; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.dumi/theme/common/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Skeleton } from 'antd'; 3 | 4 | export default function Loading() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /.dumi/theme/hooks/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | const RESPONSIVE_MOBILE = 768; 4 | 5 | export function useMobile() { 6 | const [isMobile] = useState(window.innerWidth < RESPONSIVE_MOBILE); 7 | return isMobile; 8 | } 9 | -------------------------------------------------------------------------------- /.dumi/theme/layouts/DocLayout.scss: -------------------------------------------------------------------------------- 1 | .dumi-default-doc-layout { 2 | background: #EBF5FD; 3 | } 4 | 5 | .dumi-default-doc-layout > main { 6 | max-width: 100vw; 7 | } 8 | 9 | @media only screen and (min-width: 767px) { 10 | .dumi-default-doc-layout > main { 11 | padding: 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.dumi/theme/layouts/DocLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DumiDocLayout from 'dumi/theme-default/layouts/DocLayout'; 3 | 4 | import 'dumi/theme-default/layouts/DocLayout/index.less'; 5 | import './DocLayout.scss'; 6 | 7 | export default function DocLayout() { 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /.dumi/theme/slots/Footer/index.scss: -------------------------------------------------------------------------------- 1 | @import "../../style/const.scss"; 2 | 3 | .dtc-footer { 4 | margin-top: 16px; 5 | background: rgb(250, 250, 252); 6 | .dtc-footer-links { 7 | padding-top: 20px; 8 | display: flex; 9 | flex-wrap: wrap; 10 | justify-content: center; 11 | .dtc-footer-rows { 12 | width: 200px; 13 | flex-shrink: 0; 14 | display: flex; 15 | flex-direction: column; 16 | .dtc-footer-rows-title { 17 | line-height: 28px; 18 | font-size: 14px; 19 | color: #8B8FA8; 20 | } 21 | .dtc-footer-col { 22 | line-height: 28px; 23 | > a { 24 | color: $basic-black-color; 25 | } 26 | } 27 | } 28 | } 29 | .dtc-footer-divider { 30 | margin: 0 48px; 31 | } 32 | .dtc-footer-copyright { 33 | text-align: center; 34 | font-size: 14px; 35 | padding-bottom: 20px; 36 | } 37 | } 38 | 39 | @media only screen and (max-width: 767px) { 40 | .dtc-footer { 41 | .dtc-footer-links { 42 | .dtc-footer-rows { 43 | width: 100px; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.dumi/theme/slots/Toc/index.scss: -------------------------------------------------------------------------------- 1 | @media only screen and (min-width: 767px) { 2 | #root { 3 | .dumi-default-doc-layout-toc-wrapper > h4 { 4 | display: none; 5 | } 6 | .dumi-default-toc { 7 | border-inline-start-width: 2px; 8 | > li > a.active { 9 | margin-inline-start: -2px; 10 | border-inline-start-width: 2px; 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.dumi/theme/slots/Toc/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DumiToc from 'dumi/theme-default/slots/Toc'; 3 | 4 | import 'dumi/theme-default/slots/Toc/index.less'; 5 | import './index.scss'; 6 | 7 | export default function Toc() { 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /.dumi/theme/style/const.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #1D78FF; 2 | $basic-black-color: #3D446E; 3 | $navbar-font-size: 16px; 4 | $sidebar-font-size: 14px; 5 | -------------------------------------------------------------------------------- /.dumi/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": ["**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /.dumirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'dumi'; 2 | import pkg from './package.json'; 3 | 4 | export default defineConfig({ 5 | outputPath: 'docs-dist', 6 | themeConfig: { 7 | name: 'dt-react-component', 8 | footer: `dt-react-component ${pkg.version} · Made by dtstack`, 9 | }, 10 | base: '/dt-react-component/', 11 | publicPath: '/dt-react-component/', 12 | exportStatic: {}, 13 | // TODO 先关闭,后面再看 14 | ssr: false, 15 | logo: '/dt-react-component/logo.png', 16 | favicons: ['/dt-react-component/logo.png'], 17 | extraBabelPlugins: [ 18 | [ 19 | 'import', 20 | { 21 | libraryName: 'antd', 22 | style: true, 23 | }, 24 | ], 25 | ], 26 | clickToComponent: {}, 27 | }); 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | .dumi/tmp 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve('ko-lint-config/.eslintrc')], 3 | }; 4 | -------------------------------------------------------------------------------- /.fatherrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'father'; 2 | 3 | export default defineConfig({ 4 | // more father config: https://github.com/umijs/father/blob/master/docs/config.md 5 | esm: { output: 'esm', ignores: ['**/demos/**'], transformer: 'babel' }, 6 | cjs: { output: 'lib', ignores: ['**/demos/**'], transformer: 'babel' }, 7 | }); 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: '[Bug]: ' 4 | labels: [] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 请填写该错误报告。 10 | 11 | - type: input 12 | id: component 13 | attributes: 14 | label: 组件 15 | description: 哪个组件有问题? 16 | placeholder: 请输入组件名字 17 | validations: 18 | required: true 19 | 20 | - type: dropdown 21 | id: version 22 | attributes: 23 | label: 版本 24 | description: 该问题存在于什么版本? 25 | multiple: true 26 | options: 27 | - 3.x 28 | - 4.x 29 | - 5.x 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | id: expect 35 | attributes: 36 | label: 期望的结果 37 | description: 期望的结果是什么? 38 | 39 | - type: textarea 40 | id: current 41 | attributes: 42 | label: 实际的结果 43 | description: 实际的结果是什么? 44 | 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2_feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | title: '[Feature Request]: ' 4 | labels: [] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 为了有效地审核功能请求,请填写此表格。 10 | 11 | - type: dropdown 12 | id: feature 13 | attributes: 14 | label: 功能 15 | options: 16 | - 添加新的组件 17 | - 增强现有功能 18 | - 修改文档 19 | - 补全单测 20 | - 其他 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: description 26 | attributes: 27 | label: 详细描述 28 | description: 请描述您的功能请求 29 | validations: 30 | required: true 31 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### 变更类型 2 | 3 | 请选择以下选项以描述 PR 的类型: 4 | 5 | - [ ] Bug 修复(修复现有问题) 6 | - [ ] 新功能(添加了一个功能) 7 | - [ ] 代码优化(性能改进、代码重构) 8 | - [ ] 文档更新 9 | - [ ] 单测新增或修改 10 | - [ ] 其他(请说明): 11 | 12 | #### 相关问题 13 | 14 | 15 | #### 变更内容 16 | 17 | 18 | #### 详细描述 19 | 20 | 21 | #### 对应 Previewer 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Triggers the workflow on push or pull request events but only for the master branch 6 | on: 7 | # push: 8 | # branches: "*" 9 | pull_request: 10 | branches: "*" 11 | 12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 13 | jobs: 14 | setup: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v3 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 14.x 24 | 25 | - name: Install pnpm 26 | uses: pnpm/action-setup@v2 27 | with: 28 | version: 6.32.12 29 | run_install: false 30 | 31 | - name: Get pnpm store directory 32 | id: pnpm-cache 33 | shell: bash 34 | run: | 35 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 36 | 37 | - uses: actions/cache@v3 38 | name: Setup pnpm cache 39 | with: 40 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 41 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 42 | restore-keys: | 43 | ${{ runner.os }}-pnpm-store- 44 | 45 | - name: Install dependencies 46 | run: pnpm install 47 | 48 | - name: Lint code 49 | run: pnpm lint 50 | 51 | - name: Run Build 52 | run: pnpm build 53 | 54 | - name: Run tests 55 | run: pnpm test 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /esm 3 | /lib 4 | .dumi/tmp 5 | .dumi/tmp-test 6 | .dumi/tmp-production 7 | .DS_Store 8 | /docs-dist 9 | 10 | /coverage -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit "${1}" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist 2 | *.yaml 3 | .dumi/tmp -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const prettier = require('ko-lint-config/.prettierrc'); 2 | 3 | module.exports = { 4 | ...prettier, 5 | }; 6 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | .dumi/tmp -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "ko-lint-config/.stylelintrc" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) dtinsight UED 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/guide/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 贡献指南 3 | group: 研发 4 | order: 5 5 | --- 6 | 7 | 欢迎大家参与贡献,本文将告诉你如何贡献一份自己的力量,在提 issue 或者 pull request 之前,请花几分钟来阅读这篇指南。 8 | 9 | **请 fork 本仓库,修改后提 PR** 10 | 11 | ## 分支管理 12 | 13 | 我们在 master 分支维护版本,在创建分支前,请先了解一下分支规范。 14 | 15 | - **master**: 主干分支,用于 RC 组件的 npm 发布 16 | 17 | - **feat**: 新特性分支 18 | 19 | - **beta**: 即将发布的功能 20 | 21 | ## 新增功能 22 | 23 | 如需开发新组件,请遵循以下流程: 24 | 25 | 1、fork 本仓库,在自己的仓库提交代码 26 | 27 | 2、请从 **master** 分支中新建 **feat** 分支进行开发,分支命名用下划线加上版本号,如:**feat\_功能** 28 | 29 | 3、**feat** 分支开发完毕后请向相应负责人提 PR,PR 要求附上自己仓库的文档预览地址,待相应负责人 review 代码后和入 **master** 分支 30 | 31 | ## 修复功能 32 | 33 | 我们使用 GitHub issues 来做 bug 追踪。 34 | 35 | 如果你在使用中发现了 bug,请给我们提 issue。如果你想自行修复这个问题,请遵循以下流程: 36 | 37 | 1、fork 本仓库,在自己的仓库提交代码 38 | 39 | 2、请从 **master** 分支中新建 **fix** 分支进行开发,分支命名用下划线加上版本号,如:**fix\_功能** 40 | 41 | 3、**fix** 分支开发完毕后请向相应负责人提 PR,PR 要求附上自己仓库的文档预览地址,待相应负责人 review 代码后和入 **master** 分支 42 | 43 | ## 如何生成自己的文档地址 44 | 45 | 本地查看文档效果:`npm run dev` 46 | 47 | 代码修改完毕后在本地执行 `npm run deploy`,等待执行完成后,访问 `https://github-username.github.io/dt-react-component/`,提 PR 需要附上文档预览地址。 48 | 49 | ## 第一次贡献 50 | 51 | 如果你还不清楚怎么在 GitHub 上提 Pull Request ,可以阅读下面这篇文章来学习: 52 | 53 | [如何优雅地在 GitHub 上贡献代码](https://segmentfault.com/a/1190000000736629) 54 | 55 | 如果你打算开始处理一个 issue,请先检查一下 issue 下面的留言以确保没有别人正在处理这个 issue。如果当前没有人在处理的话你可以留言告知其他人你将会处理这个 issue,以免别人重复劳动。 56 | -------------------------------------------------------------------------------- /docs/guide/updateLog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 更新日志 3 | group: 研发 4 | order: 7 5 | --- 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hero: 3 | title: dt-react-component 4 | description: React UI component library based on antd package 5 | actions: 6 | - text: Quick Start 7 | link: /guide 8 | --- 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'jsdom', 5 | setupFilesAfterEnv: ['./tests/setupTests.js'], 6 | transform: { 7 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 8 | '/tests/fileTransformer.js', 9 | }, 10 | moduleNameMapper: { 11 | '\\.(css|scss|less)$': '/tests/styleMock.js', 12 | // make tests access in handsontable@6.22.0 13 | '@babel/polyfill/lib/noConflict': '/tests/styleMock.js', 14 | '^lodash-es$': 'lodash', 15 | 'react-markdown': '/node_modules/react-markdown/react-markdown.min.js', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTStack/dt-react-component/07e27b960943e385fa9e65310221a4c48597afb6/public/logo.png -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while [[ "$#" > 0 ]]; do case $1 in 4 | -r|--release) release="$2"; shift;; 5 | -b|--branch) branch="$2"; shift;; 6 | *) echo "Unknown parameter passed: $1"; exit 1;; 7 | esac; shift; done 8 | 9 | # Default as minor, the argument major, minor or patch: 10 | if [ -z "$release" ]; then 11 | release="minor"; 12 | fi 13 | 14 | # Default release branch is master 15 | if [ -z "$branch" ] ; then 16 | branch="master"; 17 | fi; 18 | 19 | 20 | echo "Branch is $branch" 21 | echo "Release as $release" 22 | 23 | # Tag prefix 24 | prefix="v" 25 | git pull origin $branch 26 | echo "Current pull origin $branch." 27 | 28 | # Auto generate version number and tag 29 | standard-version -r $release --tag-prefix $prefix --infile CHANGELOG.md 30 | 31 | git push --follow-tags origin $branch 32 | 33 | echo "Release finished." -------------------------------------------------------------------------------- /src/blockHeader/demos/addonAfter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, Space } from 'antd'; 3 | import { BlockHeader } from 'dt-react-component'; 4 | 5 | export default () => ( 6 | 7 | 8 | } 12 | tooltip={{ title: '这里展示问号提示' }} 13 | /> 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /src/blockHeader/demos/addonBefore.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PauseCircleOutlined, PieChartOutlined } from '@ant-design/icons'; 3 | import { BlockHeader } from 'dt-react-component'; 4 | 5 | export default () => { 6 | return ( 7 | <> 8 | 9 | 10 | } /> 11 | } /> 12 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /src/blockHeader/demos/expand.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { BlockHeader } from 'dt-react-component'; 3 | 4 | export default () => { 5 | const [expand, setExpand] = useState(false); 6 | return ( 7 | <> 8 | console.log(expand)} 13 | > 14 | Hello World! 15 | 16 | 17 | setExpand(expand)} 21 | hasBottom 22 | > 23 | Hello World! 24 | 25 | 26 | Hello World! 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/catalogue/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Tree } from './tree'; 2 | export { type ITabsStatus, type ITreeDataItem } from './tree'; 3 | export { default as TreeSelect } from './treeSelect'; 4 | -------------------------------------------------------------------------------- /src/catalogue/components/tree/components/header/style.scss: -------------------------------------------------------------------------------- 1 | .dtTreeHeaderWrapper { 2 | display: flex; 3 | align-items: center; 4 | justify-content: space-between; 5 | padding: 12px 12px 0; 6 | margin-bottom: 8px; 7 | &-left { 8 | display: flex; 9 | align-items: center; 10 | &__catalogueIcon { 11 | font-size: 20px; 12 | margin-right: 4px; 13 | color: #1D78FF; 14 | } 15 | &__title { 16 | font-size: 14px; 17 | } 18 | } 19 | &-right { 20 | display: flex; 21 | align-items: center; 22 | } 23 | &-menuFold { 24 | font-size: 20px; 25 | color: #AAA; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/catalogue/components/tree/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Header } from './header'; 2 | -------------------------------------------------------------------------------- /src/catalogue/components/treeSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { TreeSelectProps } from 'antd'; 3 | import { TreeSelect } from 'antd'; 4 | 5 | export interface IProps extends TreeSelectProps {} 6 | 7 | export default (props: IProps) => ; 8 | -------------------------------------------------------------------------------- /src/catalogue/components/treeSelect/style.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTStack/dt-react-component/07e27b960943e385fa9e65310221a4c48597afb6/src/catalogue/components/treeSelect/style.scss -------------------------------------------------------------------------------- /src/catalogue/demos/treeSelect/NormalTreeSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, useState } from 'react'; 2 | import { TreeSelectProps } from 'antd'; 3 | import { Catalogue } from 'dt-react-component'; 4 | 5 | import { initTreeSelectData } from '../data'; 6 | 7 | export const NormalTreeSelect = () => { 8 | const [dataSource] = useState(initTreeSelectData); 9 | const [selectedLabelList, setSelectedLabelList] = useState([]); 10 | const handleChange: TreeSelectProps['onChange'] = (value, labelList, _) => { 11 | setSelectedLabelList(labelList); 12 | }; 13 | console.log(selectedLabelList, '--selectedLabelList'); 14 | return ( 15 |
16 | 23 |

32 |

33 | {selectedLabelList?.length 34 | ? `选中了 ${selectedLabelList?.join?.('、')}` 35 | : '未选中'} 36 |

37 |

38 |
39 | ); 40 | }; 41 | 42 | export default NormalTreeSelect; 43 | -------------------------------------------------------------------------------- /src/catalogue/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import type { ITreeProps } from './components/tree'; 4 | import { Tree, TreeSelect } from './components'; 5 | 6 | export type { ITreeDataItem, ITreeProps } from './components/tree'; 7 | export type { ITreeHeaderProps } from './components/tree/components/header'; 8 | export { getExpendKeysByQuery, getIcon, loopTree } from './components/tree/helpers'; 9 | 10 | function Catalogue(props: ITreeProps) { 11 | return ; 12 | } 13 | 14 | Catalogue.Tree = Tree; 15 | Catalogue.TreeSelect = TreeSelect; 16 | 17 | export default Catalogue; 18 | -------------------------------------------------------------------------------- /src/chat/__tests__/__snapshots__/content.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test Chat Content Match snapshot: normal 1`] = ` 4 | 5 |
8 |
11 |
14 |         {"data":{"id":"1","createdAt":1736479532239,"title":"What is your name?","messages":[{"id":"1","createdAt":1736479532239,"content":"My Name is dt-react-component","status":0}]}}
15 |       
16 |
19 |
20 |           {"prompt":{"id":"1","createdAt":1736479532239,"title":"What is your name?","messages":[{"id":"1","createdAt":1736479532239,"content":"My Name is dt-react-component","status":0}]},"data":[{"id":"1","createdAt":1736479532239,"content":"My Name is dt-react-component","status":0}],"regenerate":true}
21 |         
22 | 27 | 32 | 37 |
38 |
39 |
40 |
41 | `; 42 | 43 | exports[`Test Chat Content Match snapshot: placeholder 1`] = ` 44 | 45 |
48 |
49 | placeholder 50 |
51 |
52 |
53 | `; 54 | -------------------------------------------------------------------------------- /src/chat/__tests__/__snapshots__/prompt.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test Chat Prompt Match Snapshots: default 1`] = ` 4 | 5 |
8 |
11 |
14 |
17 |

18 | What is your name? 19 |

20 |
21 |
22 |
23 |
24 |
25 | `; 26 | 27 | exports[`Test Chat Prompt Match Snapshots: empty 1`] = ` 28 | 29 |
32 |
35 |
38 |
41 |
42 |
43 |
44 |
45 | `; 46 | -------------------------------------------------------------------------------- /src/chat/__tests__/button.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { cleanup, render } from '@testing-library/react'; 3 | 4 | import Button from '../button'; 5 | 6 | describe('Test Chat Button', () => { 7 | beforeEach(() => cleanup()); 8 | 9 | it('Match the snapshot', () => { 10 | expect(render().asFragment()).toMatchSnapshot( 11 | 'default button' 12 | ); 13 | 14 | expect(render().asFragment()).toMatchSnapshot( 15 | 'primary button' 16 | ); 17 | 18 | expect(render().asFragment()).toMatchSnapshot( 19 | 'secondary button' 20 | ); 21 | }); 22 | 23 | it('expect ONLY one global gradient div', () => { 24 | render( 25 | <> 26 | 27 | 28 | 29 | 30 | ); 31 | const nodeList = document.querySelectorAll( 32 | '.dtc__aigc__button__global-gradient' 33 | ); 34 | expect(nodeList.length).toBe(1); 35 | expect(nodeList[0].style.getPropertyValue('width')).toBe('0px'); 36 | expect(nodeList[0].style.getPropertyValue('height')).toBe('0px'); 37 | expect(nodeList[0].innerHTML).toMatchSnapshot('global gradient'); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/chat/__tests__/icon.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { cleanup, render } from '@testing-library/react'; 3 | 4 | import { CopyIcon, GradientDotIcon, PauseIcon, ReloadIcon, SendIcon, ShiningIcon } from '../icon'; 5 | 6 | describe('Test Chat Icon', () => { 7 | beforeEach(cleanup); 8 | 9 | it('Match Snapshots', () => { 10 | expect(render().asFragment()).toMatchSnapshot('ReloadIcon'); 11 | expect(render().asFragment()).toMatchSnapshot('PauseIcon'); 12 | expect(render().asFragment()).toMatchSnapshot('SendIcon'); 13 | expect(render().asFragment()).toMatchSnapshot( 14 | 'SendIcon with gradient' 15 | ); 16 | expect(render().asFragment()).toMatchSnapshot('CopyIcon'); 17 | expect(render().asFragment()).toMatchSnapshot('GradientDotIcon'); 18 | expect(render().asFragment()).toMatchSnapshot('ShiningIcon'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/chat/__tests__/loading.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import Loading from '../loading'; 5 | 6 | describe('Test Chat Loading', () => { 7 | it('Match Snapshots', () => { 8 | expect(render(test)).toMatchSnapshot('loading'); 9 | expect(render(test)).toMatchSnapshot('not loading'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/chat/__tests__/pagination.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { act } from 'react-dom/test-utils'; 3 | import { fireEvent, render } from '@testing-library/react'; 4 | 5 | import Pagination from '../pagination'; 6 | 7 | describe('Test Chat Pagination', () => { 8 | it('Match Snapshots', () => { 9 | expect( 10 | render().asFragment() 11 | ).toMatchSnapshot('default'); 12 | 13 | expect( 14 | render().asFragment() 15 | ).toMatchSnapshot('disabled'); 16 | }); 17 | 18 | it('Should call onChange when click left/right arrow', () => { 19 | const onChange = jest.fn(); 20 | const { container, rerender } = render( 21 | 22 | ); 23 | 24 | act(() => { 25 | fireEvent.click(container.querySelector('.anticon-left')!); 26 | }); 27 | expect(onChange).not.toBeCalled(); 28 | 29 | act(() => { 30 | fireEvent.click(container.querySelector('.anticon-right')!); 31 | }); 32 | expect(onChange).toBeCalledWith(2); 33 | 34 | rerender(); 35 | 36 | act(() => { 37 | fireEvent.click(container.querySelector('.anticon-left')!); 38 | }); 39 | expect(onChange).toBeCalledWith(4); 40 | 41 | onChange.mockRestore(); 42 | act(() => { 43 | fireEvent.click(container.querySelector('.anticon-right')!); 44 | }); 45 | expect(onChange).not.toBeCalled(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/chat/__tests__/tag.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import Tag from '../tag'; 5 | 6 | describe('Test Chat Tag', () => { 7 | it('Match Snapshots', () => { 8 | expect(render(Tag).asFragment()).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/chat/content/index.scss: -------------------------------------------------------------------------------- 1 | .dtc__aigc__content { 2 | &__container { 3 | position: relative; 4 | width: 100%; 5 | height: 100%; 6 | overflow: auto; 7 | &--disabled { 8 | pointer-events: none; 9 | } 10 | } 11 | &__inner__holder { 12 | display: flex; 13 | flex-direction: column; 14 | gap: 16px; 15 | max-width: 800px; 16 | margin: 0 auto; 17 | padding: 8px 8px 48px; 18 | box-sizing: content-box; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/chat/demos/codeBlock-convert.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * background: '#f6f7f9' 3 | */ 4 | import React from 'react'; 5 | import { Chat } from 'dt-react-component'; 6 | 7 | const children = ` 8 | \`\`\` sql 9 | SELECT * FROM table_name; 10 | \`\`\` 11 | `; 12 | export default function CodeBlock() { 13 | return ( 14 | {children}; 18 | }, 19 | }} 20 | > 21 | {children} 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/chat/demos/codeBlock.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * background: '#f6f7f9' 3 | */ 4 | import React from 'react'; 5 | import { PlusCircleOutlined } from '@ant-design/icons'; 6 | import { Chat } from 'dt-react-component'; 7 | 8 | const children = ` 9 | \`\`\` sql 10 | SELECT * FROM table_name; 11 | \`\`\` 12 | `; 13 | export default function CodeBlock() { 14 | return ( 15 | }> 20 | {children} 21 | 22 | ); 23 | }, 24 | }} 25 | > 26 | {children} 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/chat/demos/input.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Chat } from 'dt-react-component'; 3 | 4 | export default function () { 5 | const [value, setValue] = useState(''); 6 | return ( 7 | console.log('value:', value)} 15 | /> 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/chat/demos/loading.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Chat } from 'dt-react-component'; 4 | 5 | export default function () { 6 | const [loading, setLoading] = useState(true); 7 | return ( 8 | <> 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/chat/demos/markdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chat } from 'dt-react-component'; 3 | 4 | const children = ` 5 | # 大标题 6 | ## 小标题 7 | ### 标题 8 | #### 标题 9 | ##### 标题 10 | ###### 标题 11 | 正文 12 | **加粗** 13 | *斜体* 14 | ***加粗斜体*** 15 | ~~删除线~~ 16 | \`行内代码\` 17 | \`\`\` sql 18 | SELECT * FROM table_name; 19 | \`\`\` 20 | [链接](https://www.baidu.com) 21 | - 无序列表 22 | - 无序列表 23 | - 无序列表 24 | 1. 有序列表 25 | 2. 有序列表 26 | 3. 有序列表 27 | 28 | --- 29 | 30 | > 引用 31 | 32 | | 表头 | 表头 | 表头 | 33 | | ---- | ---- | ---- | 34 | | 单元格 | 单元格 | 单元格 | 35 | | 单元格 | 单元格 | 单元格 | 36 | | 单元格 | 单元格 | 单元格 | 37 | `; 38 | 39 | export default function () { 40 | return {children}; 41 | } 42 | -------------------------------------------------------------------------------- /src/chat/demos/message-lazyRendered.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { Chat } from 'dt-react-component'; 3 | import { MessageStatus } from 'dt-react-component/chat/entity'; 4 | 5 | export default function () { 6 | const data = useMemo(() => { 7 | return [ 8 | { 9 | id: new Date().valueOf().toString(), 10 | content: '# 大标题', 11 | status: MessageStatus.DONE, 12 | } as any, 13 | ]; 14 | }, []); 15 | 16 | return ( 17 |
18 |
往下拉
19 | { 23 | console.log('beforeRender', window.performance.now()); 24 | cb().then(() => { 25 | console.log('rendered', window.performance.now()); 26 | }); 27 | }} 28 | /> 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/chat/demos/prompt.tsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer, useRef, useState } from 'react'; 2 | import { Space } from 'antd'; 3 | import { Chat } from 'dt-react-component'; 4 | import { Prompt } from 'dt-react-component/chat/entity'; 5 | 6 | const updateReducer = (num: number): number => (num + 1) % 1_000_000; 7 | 8 | export default function () { 9 | const [value, setValue] = useState(''); 10 | const [, update] = useReducer(updateReducer, 0); 11 | const prompt = useRef(new (class extends Prompt {})({ id: '1', title: 'test' })); 12 | 13 | function setContent(data: string) { 14 | prompt.current.title = data; 15 | update(); 16 | } 17 | 18 | return ( 19 | 20 | 21 | 22 | value?.trim() && setContent(value.trim())} 30 | /> 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/chat/demos/tag.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Space } from 'antd'; 3 | import { Chat } from 'dt-react-component'; 4 | 5 | export default function () { 6 | return ( 7 | 8 | 如何对FLinkSQL进行SQL优化 9 | 如何对FLinkSQL进行性能优化 10 | 如何对FLinkSQL进行SQL优化 11 | 如何对FLinkSQL进行SQL优化 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/chat/icon/index.scss: -------------------------------------------------------------------------------- 1 | .dtc__icon { 2 | display: inline-flex; 3 | align-items: center; 4 | color: inherit; 5 | font-style: normal; 6 | line-height: 0; 7 | text-align: center; 8 | text-transform: none; 9 | vertical-align: -0.125em; 10 | text-rendering: optimizelegibility; 11 | -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; 13 | > * { 14 | line-height: 1; 15 | } 16 | svg { 17 | display: inline-block; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/chat/index.$tab-button.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Button 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # Button 10 | 11 | ## 何时使用 12 | 13 | Button 组件用以中响应 AI 交互中用户的点击行为。 14 | 15 | ## 示例 16 | 17 | 18 | 19 | ## API 20 | 21 | | 参数 | 说明 | 类型 | 默认值 | 22 | | ---- | ---- | -------------------------- | ------ | 23 | | type | 类型 | `'primary' \| 'secondary'` | - | 24 | -------------------------------------------------------------------------------- /src/chat/index.$tab-codeBlock.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CodeBlock 3 | group: 组件 4 | toc: content 5 | --- 6 | 7 | # CodeBlock 8 | 9 | ## 何时使用 10 | 11 | CodeBlock 组件用以展示代码块 12 | 13 | ## 示例 14 | 15 | 16 | 17 | 18 | ## API 19 | 20 | | 参数 | 说明 | 类型 | 默认值 | 21 | | --------- | ---------------- | -------------------------------------------- | ------ | 22 | | copy | 是否支持复制功能 | `boolean \| ICopyProps` | `true` | 23 | | className | 类名 | `string` | - | 24 | | style | 样式 | `CSSProperties` | - | 25 | | convert | 反色模式 | `boolean` | - | 26 | | toolbars | 渲染工具位 | `React.ReactNode \| (() => React.ReactNode)` | - | 27 | | options | 配置项 | `SyntaxHighlighterProps` | - | 28 | | children | 文案 | `React.ReactNode & React.ReactNode[]` | - | 29 | -------------------------------------------------------------------------------- /src/chat/index.$tab-input.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Input 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # Input 10 | 11 | ## 何时使用 12 | 13 | Input 组件用以解决 AI 交互中的输入框 14 | 15 | ## 示例 16 | 17 | 18 | 19 | ## API 20 | 21 | | 参数 | 说明 | 类型 | 默认值 | 22 | | ----------------- | ------------------------- | ---------------------- | ------ | 23 | | onChange | 值发生变化时的回调 | `(str:string) => void` | - | 24 | | onPressShiftEnter | 输入 Shift + Enter 的回调 | `Function` | - | 25 | -------------------------------------------------------------------------------- /src/chat/index.$tab-loading.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Loading 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # Loading 10 | 11 | ## 何时使用 12 | 13 | Loading 组件用以解决 AI 交互中的 loading 状态 14 | 15 | ## 示例 16 | 17 | 18 | 19 | ## API 20 | 21 | | 参数 | 说明 | 类型 | 默认值 | 22 | | ------- | ---------- | --------- | ------ | 23 | | loading | 是否加载中 | `boolean` | - | 24 | -------------------------------------------------------------------------------- /src/chat/index.$tab-markdown.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown 3 | group: 组件 4 | toc: content 5 | --- 6 | 7 | # Markdown 8 | 9 | ## 何时使用 10 | 11 | Markdown 组件用以渲染 markdown 内容 12 | 13 | :::warning 14 | Markdown 组件自带 memo 用于性能优化,只有 `typing`、`children`、`className` 属性修改才会引起重渲染。 15 | ::: 16 | 17 | ## 示例 18 | 19 | 20 | 21 | ## API 22 | 23 | | 参数 | 说明 | 类型 | 默认值 | 24 | | --------- | ------------------- | ------------ | ------ | 25 | | typing | 是否输入中 | `boolean` | - | 26 | | className | 类名 | `string` | - | 27 | | children | 文案 | `string` | - | 28 | | onMount | didMount 的回调函数 | `() => void` | - | 29 | 30 | 其余属性参考 `react-markdown@~8.0.6` 31 | -------------------------------------------------------------------------------- /src/chat/index.$tab-message.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Message 3 | group: 组件 4 | toc: content 5 | --- 6 | 7 | # Message 8 | 9 | ## 何时使用 10 | 11 | Message 组件用以渲染回答框 12 | 13 | ## 示例 14 | 15 | 16 | 17 | 18 | ## API 19 | 20 | | 参数 | 说明 | 类型 | 默认值 | 21 | | -------------- | ---------------------- | ----------------------------------- | ------- | 22 | | data | 数据 | `Message[]` | - | 23 | | regenerate | 是否支持重新生成 | `boolean` | `false` | 24 | | copy | 是否支持复制功能 | `boolean \| CopyOptions` | `true` | 25 | | onRegenerate | 点击重新生成的回调函数 | `(data: Message) => void` | - | 26 | | onStop | 点击停止问答的回调函数 | `(data: Message) => void` | - | 27 | | onLazyRendered | 懒加载的回调函数 | `(cb: () => Promise) => void` | - | 28 | -------------------------------------------------------------------------------- /src/chat/index.$tab-prompt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prompt 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # Prompt 10 | 11 | ## 何时使用 12 | 13 | Prompt 组件用以渲染 AI 中的提问框 14 | 15 | ## 示例 16 | 17 | 18 | 19 | ## API 20 | 21 | | 参数 | 说明 | 类型 | 默认值 | 22 | | --------- | ---- | -------- | ------ | 23 | | content | 文案 | `string` | - | 24 | | className | 类名 | `string` | - | 25 | -------------------------------------------------------------------------------- /src/chat/index.$tab-tag.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tag 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # Tag 10 | 11 | ## 何时使用 12 | 13 | Tag 组件作为提示集合提供给用户快捷操作 14 | 15 | ## 示例 16 | 17 | 18 | 19 | ## API 20 | 21 | | 参数 | 说明 | 类型 | 默认值 | 22 | | -------- | ---- | ----------------- | ------ | 23 | | children | | `React.ReactNode` | - | 24 | -------------------------------------------------------------------------------- /src/chat/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Chat 智能问答 3 | group: 组件 4 | toc: content 5 | --- 6 | 7 | # 智能问答 8 | 9 | ## 组件概述 10 | 11 | Chat 规范由多个组件复合使用实现落地场景,其中: 12 | 13 | - `useChat` 是旨在解决 Chat 的数据问题的 hook 14 | - `Chat` 组件是作为 Chat 组件的集合及 Provider 15 | - `Loading` 组件是回答框在等待生成时的加载组件 16 | - `Button` 组件是符合 AI 规范的按钮组件 17 | - `CodeBlock` 组件是符合 AI 规范的代码块组件 18 | - `Input` 组件是符合 AI 规范的输入框组件 19 | - `Markdown` 组件是符合 AI 规范的渲染 markdown 的组件 20 | - `Pagination` 组件是符合 AI 规范的分页器 21 | - `Message` 组件是符合 AI 规范的回答框 22 | - `Prompt` 组件是符合 AI 规范的提问框 23 | - `Content` 组件是符合 AI 规范的正文内容 24 | 25 | ## 何时使用 26 | 27 | 涉及到 AI 相关功能时。 28 | 29 | ## 示例 30 | 31 | 32 | 33 | 34 | ## API 35 | -------------------------------------------------------------------------------- /src/chat/input/index.scss: -------------------------------------------------------------------------------- 1 | .dtc__chat__textarea__container { 2 | width: 100%; 3 | border-radius: 8px; 4 | border: 1px solid #D8DAE2; 5 | background: #FFF; 6 | position: relative; 7 | max-width: 800px; 8 | margin: 0 auto; 9 | .dtc__chat__textarea { 10 | border: none; 11 | background-color: transparent; 12 | color: #3D446E; 13 | font-size: 12px; 14 | font-style: normal; 15 | font-weight: 400; 16 | line-height: 20px; 17 | padding: 8px 36px 8px 16px; 18 | &:focus { 19 | box-shadow: none; 20 | } 21 | } 22 | .dtc__chat__textarea__send { 23 | font-size: 24px; 24 | position: absolute; 25 | bottom: 8px; 26 | right: 8px; 27 | &:hover:not(&--disabled) { 28 | &.dtc__icon path { 29 | fill: url(#paint0_linear_3878_6538_hover); 30 | } 31 | } 32 | &--disabled { 33 | cursor: not-allowed; 34 | color: #B1B4C5; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/chat/loading/index.scss: -------------------------------------------------------------------------------- 1 | .dtc__aigc__loading { 2 | display: flex; 3 | gap: 2px; 4 | width: 100%; 5 | padding: 10px 0; 6 | align-items: center; 7 | justify-content: center; 8 | height: 28px; 9 | > div { 10 | width: 4px; 11 | height: 4px; 12 | border-radius: 50%; 13 | background-color: #E8F1FF; 14 | &:nth-child(1) { 15 | animation: 0.6s linear 0s infinite alternate loading; 16 | } 17 | &:nth-child(2) { 18 | animation: 0.6s linear 0.2s infinite alternate loading; 19 | } 20 | &:nth-child(3) { 21 | animation: 0.6s linear 0.4s infinite alternate loading; 22 | } 23 | } 24 | } 25 | 26 | @keyframes loading { 27 | 0% { 28 | width: 4px; 29 | height: 4px; 30 | background-color: #FFF; 31 | } 32 | 50% { 33 | width: 5px; 34 | height: 5px; 35 | background-color: #BBD6FF; 36 | } 37 | 100% { 38 | width: 6px; 39 | height: 6px; 40 | background-color: #1D78FF; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/chat/loading/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from 'react'; 2 | 3 | import './index.scss'; 4 | 5 | interface ILoadingProps { 6 | loading: boolean; 7 | } 8 | 9 | export default function Loading(props: PropsWithChildren) { 10 | return props.loading ? ( 11 |
12 |
13 |
14 |
15 |
16 | ) : ( 17 | <>{props.children} 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/chat/pagination/index.scss: -------------------------------------------------------------------------------- 1 | .dtc-aigc-pagination { 2 | display: flex; 3 | align-items: center; 4 | gap: 4px; 5 | & > section { 6 | display: flex; 7 | align-items: center; 8 | gap: 4px; 9 | & > span { 10 | color: #3D446E; 11 | font-size: 12px; 12 | font-style: normal; 13 | font-weight: 400; 14 | line-height: 20px; 15 | } 16 | } 17 | .anticon { 18 | height: 12px; 19 | color: #B1B4C5; 20 | cursor: pointer; 21 | } 22 | .anticon:hover { 23 | color: #1D78FF; 24 | } 25 | .anticon.arrow-disabled { 26 | cursor: not-allowed; 27 | } 28 | .anticon.arrow-disabled:hover { 29 | color: #B1B4C5; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/chat/pagination/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { LeftOutlined, RightOutlined } from '@ant-design/icons'; 3 | 4 | import './index.scss'; 5 | 6 | interface IPaginationProps { 7 | current: number; 8 | total: number; 9 | onChange: (current: number) => void; 10 | disabled?: boolean; 11 | style?: React.CSSProperties; 12 | } 13 | 14 | export default function Pagination({ 15 | current, 16 | total, 17 | onChange, 18 | style, 19 | disabled, 20 | }: IPaginationProps) { 21 | const disableLeft = current === 1 || disabled; 22 | const disableRight = current === total || disabled; 23 | 24 | return ( 25 |
26 | { 29 | if (disableLeft) return; 30 | onChange(current - 1); 31 | }} 32 | /> 33 |
34 | {current} 35 | / 36 | {total} 37 |
38 | { 41 | if (disableRight) return; 42 | onChange(current + 1); 43 | }} 44 | /> 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/chat/prompt/index.scss: -------------------------------------------------------------------------------- 1 | .dtc__prompt { 2 | &__container { 3 | align-self: end; 4 | max-width: calc(100% - 120px); 5 | } 6 | &__wrapper { 7 | width: 100%; 8 | overflow: hidden; 9 | } 10 | &__content { 11 | border-radius: 8px; 12 | padding: 8px; 13 | background: #E8F1FF; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/chat/prompt/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { Components } from 'react-markdown'; 3 | import classNames from 'classnames'; 4 | 5 | import type { Prompt as PromptEntity } from '../entity'; 6 | import Markdown from '../markdown'; 7 | import { useContext } from '../useContext'; 8 | import './index.scss'; 9 | 10 | type IPromptProps = { 11 | data?: PromptEntity; 12 | className?: string; 13 | }; 14 | 15 | export default function Prompt({ data, className }: IPromptProps) { 16 | const { components = {}, codeBlock } = useContext(); 17 | 18 | const composedComponents = useMemo(() => { 19 | return Object.keys(components).reduce((acc, cur) => { 20 | const original = components[cur as keyof Components]; 21 | (acc as any)[cur] = (...args: any[]) => { 22 | return typeof original === 'function' 23 | ? (original as Function)(...args, { promptId: data?.id }) 24 | : original; 25 | }; 26 | 27 | return acc; 28 | }, {}); 29 | }, [components, data?.id]); 30 | 31 | return ( 32 |
33 |
34 |
35 | 36 | {data?.title || ''} 37 | 38 |
39 |
40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/chat/tag/index.scss: -------------------------------------------------------------------------------- 1 | .dtc__tag { 2 | display: inline-flex; 3 | align-items: center; 4 | justify-content: center; 5 | padding: 4px 8px; 6 | border: 1px solid transparent; 7 | border-radius: 4px; 8 | background-image: 9 | linear-gradient(white, white), 10 | linear-gradient( 11 | 110deg, 12 | rgba(0, 186, 198, 0.1) 0%, 13 | rgba(0, 103, 255, 0.1) 50%, 14 | rgba(69, 15, 222, 0.1) 100% 15 | ); 16 | background-origin: border-box; 17 | background-clip: padding-box, border-box; 18 | font-size: 12px; 19 | color: #3D446E; 20 | cursor: pointer; 21 | transition: all 0.3s ease; 22 | white-space: nowrap; 23 | &__icon { 24 | margin-right: 8px; 25 | font-size: 6px; 26 | } 27 | &:hover, 28 | &:focus { 29 | background-image: 30 | linear-gradient(white, white), 31 | linear-gradient(110deg, #08C4FF 0%, #4892FF 50%, #8A61FF 100%); 32 | } 33 | &:active { 34 | transform: scale(0.98); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/chat/tag/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | import { GradientDotIcon } from '../icon'; 5 | import './index.scss'; 6 | 7 | export interface ChatTagProps extends React.HTMLAttributes { 8 | className?: string; 9 | children?: React.ReactNode; 10 | onClick?: () => void; 11 | } 12 | 13 | const Tag: React.FC = function Tag(props) { 14 | const { className, children, ...rest } = props; 15 | return ( 16 |
17 | 18 | {children} 19 |
20 | ); 21 | }; 22 | 23 | export default Tag; 24 | -------------------------------------------------------------------------------- /src/chat/useContext.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { type Components } from 'react-markdown'; 3 | import { type ReactMarkdownOptions } from 'react-markdown/lib/react-markdown'; 4 | 5 | import { type ICopyProps } from '../copy'; 6 | import { type ICodeBlockProps } from './codeBlock'; 7 | import type { Message, Prompt } from './entity'; 8 | import type useChat from './useChat'; 9 | 10 | export type CopyOptions = Partial & { 11 | formatText?: (content?: string) => string; 12 | }; 13 | 14 | export interface IChatContext { 15 | /** 16 | * Chat 实例 17 | */ 18 | chat: ReturnType; 19 | /** 20 | * markdown 自定义的组件 21 | */ 22 | components?: Components; 23 | /** 24 | * 重新回答的最大次数 25 | */ 26 | maxRegenerateCount: number; 27 | /** 28 | * 是否支持重新生成 29 | */ 30 | regenerate?: boolean | ((prompt: Prompt, index: number, array: Prompt[]) => boolean); 31 | /** 32 | * 透传给 CodeBlock 的属性 33 | */ 34 | codeBlock?: Omit; 35 | copy?: boolean | CopyOptions; 36 | messageIcons?: React.ReactNode | ((record: Message, prompt: Prompt) => React.ReactNode); 37 | rehypePlugins?: ReactMarkdownOptions['rehypePlugins']; 38 | remarkPlugins?: ReactMarkdownOptions['remarkPlugins']; 39 | } 40 | 41 | export const context = React.createContext({ 42 | chat: undefined, 43 | maxRegenerateCount: 5, 44 | } as any); 45 | 46 | export function useContext() { 47 | return React.useContext(context); 48 | } 49 | -------------------------------------------------------------------------------- /src/chat/welcome/index.scss: -------------------------------------------------------------------------------- 1 | $primaryGradient: #00BAC6 0%, #0067FF 50%, #450FDE 100%; 2 | 3 | .dtc__welcome { 4 | overflow: hidden; 5 | color: #FFF; 6 | background: linear-gradient(110deg, $primaryGradient) border-box; 7 | border: 1px solid transparent; 8 | &__content { 9 | padding: 12px; 10 | position: relative; 11 | z-index: 2; 12 | background: 13 | linear-gradient( 14 | 110deg, 15 | rgba(0, 186, 198, 0.05) 0%, 16 | rgba(0, 103, 255, 0.05) 50%, 17 | rgba(69, 15, 222, 0.05) 100% 18 | ), 19 | #FFF; 20 | } 21 | &__title { 22 | color: #3D446E; 23 | font-size: 20px; 24 | font-weight: 500; 25 | line-height: 28px; 26 | } 27 | &__description { 28 | color: #64698B; 29 | font-size: 12px; 30 | font-weight: 400; 31 | line-height: 20px; 32 | margin-top: 4px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/chat/welcome/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties } from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | import Flex from '../../flex'; 5 | import { AIAvatar } from '../icon'; 6 | import './index.scss'; 7 | 8 | export interface IWelcomeProps { 9 | title: React.ReactNode; 10 | description?: React.ReactNode; 11 | icon?: React.ReactNode; 12 | className?: string; 13 | style?: CSSProperties; 14 | } 15 | 16 | export default function Welcome({ 17 | icon = , 18 | title, 19 | description, 20 | className, 21 | style, 22 | }: IWelcomeProps) { 23 | return ( 24 |
25 |
26 | 27 | {icon} 28 | {title} 29 | 30 |
{description}
31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/collapsibleActionItems/style.scss: -------------------------------------------------------------------------------- 1 | $card_prefix: "dtc-collapsibleActionItems"; 2 | 3 | .#{$card_prefix} { 4 | &__icon { 5 | font-size: 16px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/contentLayout/components.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Table, TableProps } from 'antd'; 3 | import classNames from 'classnames'; 4 | 5 | import { NAME } from '.'; 6 | 7 | interface ContentLayoutChildProps { 8 | ref?: React.Ref; 9 | children?: React.ReactNode; 10 | } 11 | 12 | type ITableProps = { 13 | height?: string; 14 | style?: React.CSSProperties; 15 | className?: string; 16 | } & TableProps & 17 | Omit; 18 | 19 | export const TableLayout = ({ height, size, className, ...otherProps }: ITableProps) => { 20 | let lineHeight = size === 'small' ? 36 : 44; 21 | 22 | if (otherProps.footer) { 23 | let footerHeight = 44; 24 | if (className?.includes('dt-pagination-small')) footerHeight = 36; 25 | lineHeight = lineHeight + footerHeight; 26 | } 27 | 28 | const scroll: TableProps['scroll'] = { 29 | y: `calc(${height} - ${lineHeight}px)`, 30 | ...otherProps.scroll, 31 | }; 32 | 33 | return ; 34 | }; 35 | 36 | interface HeaderProps extends ContentLayoutChildProps { 37 | style?: React.CSSProperties; 38 | className?: string; 39 | } 40 | 41 | export const Header = React.forwardRef( 42 | ({ className, style, children }, ref) => { 43 | return ( 44 |
45 | {children} 46 |
47 | ); 48 | } 49 | ); 50 | -------------------------------------------------------------------------------- /src/contentLayout/index.scss: -------------------------------------------------------------------------------- 1 | .dtc-content-layout { 2 | display: flex; 3 | flex-direction: column; 4 | overflow: hidden; 5 | &__header { 6 | padding: 16px; 7 | border-bottom: 1px solid #EBECF0; 8 | background-color: #FFF; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/contextMenu/style.scss: -------------------------------------------------------------------------------- 1 | .dtc-contextMenu-menu { 2 | min-width: 90px; 3 | max-width: 160px; 4 | } 5 | -------------------------------------------------------------------------------- /src/copy/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Space } from 'antd'; 3 | import { BlockHeader, Copy } from 'dt-react-component'; 4 | 5 | const text = 6 | '基于 ant-design 的 React UI 组件库。 主要用于中,后台产品。我们的目标是满足更具体的业务场景组件。 当然,我们也有基于原生 javascript 实现的业务组件,例如ContextMenu,KeyEventListener等.'; 7 | 8 | export default () => { 9 | return ( 10 | 11 |
12 | 13 | 14 |

{text}

15 |
16 |
17 | 18 | 19 |

{text}

20 |
21 |
22 | React.ReactNode`} background={false} size="small" /> 23 | `使用 () => React.ReactNode,复制该文本`} /> 24 |

{text}

25 |
26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/copy/demos/custom.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Copy } from 'dt-react-component'; 3 | 4 | const text = 5 | '基于 ant-design 的 React UI 组件库。 主要用于中,后台产品。我们的目标是满足更具体的业务场景组件。 当然,我们也有基于原生 javascript 实现的业务组件,例如ContextMenu,KeyEventListener等.'; 6 | 7 | export default () => { 8 | return ( 9 | <> 10 |
11 | 复制文本} /> 12 |

{text}

13 |
14 |
15 | 复制文本} tooltip={false} /> 16 |

{text}

17 |
18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/copy/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Copy 复制文本 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 1 7 | --- 8 | 9 | # Copy 复制文本 10 | 11 | ## 何时使用 12 | 13 | 复制文本到粘贴版 14 | 15 | ## 示例 16 | 17 | 18 | 19 | 20 | ### API 21 | 22 | | 参数 | 说明 | 类型 | 默认值 | 23 | | --------- | ---------------- | --------------------------------------- | ----------------------------------- | 24 | | text | 需要复制的文本 | `string` | - | 25 | | tooltip | 配置提示信息 | `TooltipProps['title'] \| TooltipProps` | `复制` | 26 | | button | 自定义按钮 | `React.ReactNode` | `` | 27 | | style | 样式 | `React.CSSProperties` | -- | 28 | | className | 样式 | `string` | -- | 29 | | onCopy | 复制后的回调函数 | `(text: string) => void` | `() => message.success('复制成功')` | 30 | -------------------------------------------------------------------------------- /src/copy/style.scss: -------------------------------------------------------------------------------- 1 | .dtc-copy { 2 | display: inline-block; 3 | cursor: pointer; 4 | &__default-icon { 5 | color: #1D78FF; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/descriptions/__tests__/descriptions.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import Descriptions from '..'; 5 | 6 | const TestDescriptions = ({ fixed }: { fixed?: boolean }) => { 7 | return ( 8 | 9 | 我是 dt-react-component 组件库 10 | 11 |
20 | 很长很长的描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述,长度甚至超过了一行 21 |
22 |
23 |
24 | ); 25 | }; 26 | 27 | describe('Test Descriptions', () => { 28 | it('match snapshot', () => { 29 | expect(render().asFragment()).toMatchSnapshot( 30 | "As same as antd's Descriptions" 31 | ); 32 | 33 | expect(render().asFragment()).toMatchSnapshot( 34 | 'Fixed table layout' 35 | ); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/descriptions/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Space, Switch } from 'antd'; 3 | import { Descriptions, EllipsisText } from 'dt-react-component'; 4 | 5 | export default function Basic() { 6 | const [fixed, setFixed] = useState(true); 7 | return ( 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 |
29 | 很长很长的描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述,长度甚至超过了一行 30 |
31 |
32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/descriptions/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Descriptions 描述列表 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 1 7 | --- 8 | 9 | # Descriptions 描述列表 10 | 11 | ## 何时使用 12 | 13 | 展示多个只读字段的组合。(支持设置 table-layout 为 fixed) 14 | 15 | ## 示例 16 | 17 | 18 | 19 | ### API 20 | 21 | | 参数 | 说明 | 类型 | 默认值 | 22 | | ----------- | ---- | ------------------- | ------ | 23 | | tableLayout | - | `'auto' \| 'fixed'` | 'auto' | 24 | 25 | ## FAQ 26 | 27 | ### 为什么要把 table-layout 设置为 fixed? 28 | 29 | 参考 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/table-layout#fixed) 中关于 `table-layout` 的相关描述可以看出,当我们的需求是用 `Descriptions` 组件用固定比例展示字段信息的时候,相比 `auto` 的属性,更好地是 `fixed` 属性 30 | -------------------------------------------------------------------------------- /src/descriptions/index.scss: -------------------------------------------------------------------------------- 1 | .dtc-descriptions { 2 | &__fixed { 3 | .ant-descriptions-view > table { 4 | table-layout: fixed; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/descriptions/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Descriptions as AntdDescriptions, DescriptionsProps as AntdDescriptionsProps } from 'antd'; 3 | import classNames from 'classnames'; 4 | 5 | import './index.scss'; 6 | 7 | interface DescriptionsProps extends AntdDescriptionsProps { 8 | tableLayout?: 'auto' | 'fixed'; 9 | } 10 | 11 | function Descriptions({ className, tableLayout, ...rest }: DescriptionsProps) { 12 | return ( 13 | 17 | ); 18 | } 19 | 20 | Descriptions.Item = AntdDescriptions.Item; 21 | 22 | export default Descriptions; 23 | -------------------------------------------------------------------------------- /src/drawer/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Slider } from 'antd'; 3 | import { Drawer } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [visible, setVisible] = useState(false); 7 | const [loading, setLoading] = useState(false); 8 | const [width, setWidth] = useState(80); 9 | 10 | return ( 11 | <> 12 | setWidth(value)} 17 | /> 18 | width:{width}% 19 | 31 | setVisible(false)} 36 | title="title" 37 | > 38 |
hello world
39 |
40 | 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /src/drawer/demos/basicBanner.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Drawer } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [visible, setVisible] = useState(false); 7 | 8 | return ( 9 | <> 10 | 13 | setVisible(false)} 17 | title="title" 18 | > 19 |
hello world
20 |
21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/drawer/demos/basicBannerProps.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Drawer } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [visible, setVisible] = useState(false); 7 | 8 | return ( 9 | <> 10 | 13 | setVisible(false)} 21 | title="title" 22 | > 23 |
hello world
24 |
25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/drawer/demos/basicSize.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Space } from 'antd'; 3 | import { Drawer } from 'dt-react-component'; 4 | import { DrawerProps } from 'dt-react-component/drawer'; 5 | 6 | export default () => { 7 | const [visible, setVisible] = useState(false); 8 | const [size, setSize] = useState['size']>('default'); 9 | 10 | return ( 11 | <> 12 | 13 | 22 | 31 | 40 | 41 | setVisible(false)} title="title"> 42 |
hello world
43 |
44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /src/drawer/demos/basic_top.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Alert, Button } from 'antd'; 3 | import { Drawer } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [visible, setVisible] = useState(false); 7 | 8 | return ( 9 | <> 10 | 11 | 19 | setVisible(false)} 22 | title="title" 23 | rootStyle={{ top: 64 }} 24 | > 25 |
hello world
26 |
27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/drawer/demos/basic_two.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Drawer } from 'dt-react-component'; 4 | import { DrawerType } from 'dt-react-component/drawer'; 5 | 6 | export default () => { 7 | const [firstVisible, setFirstVisible] = useState(false); 8 | const [secondVisible, setSecondVisible] = useState(false); 9 | 10 | return ( 11 | <> 12 | 20 | setFirstVisible(false)} title="一级弹窗"> 21 |
一级弹窗
22 | 23 | setSecondVisible(false)} 26 | title="二级弹窗" 27 | type={DrawerType.Form} 28 | > 29 |
一级弹窗
30 |
31 |
32 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/drawer/demos/customTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Drawer } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [visible, setVisible] = useState(false); 7 | 8 | return ( 9 | <> 10 | 11 | setVisible(false)} 18 | title={ 19 |
20 |
增信方式
21 |
22 | 创建时间: xxxx 23 | 更新时间: xxxx 24 |
25 |
26 | } 27 | > 28 |
hello world
29 |
30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /src/drawer/demos/footer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Space } from 'antd'; 3 | import { Drawer } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [visible, setVisible] = useState(false); 7 | 8 | return ( 9 | <> 10 | 11 | setVisible(false)} 14 | title="Title" 15 | footer={ 16 | 17 | 18 | 19 | 20 | } 21 | > 22 |
超长可滚动
23 |
24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/drawer/demos/tabs.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Drawer } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [visible, setVisible] = useState(false); 7 | 8 | return ( 9 | <> 10 | 11 | setVisible(false)} 14 | title={'Title'} 15 | tabs={ 16 | [ 17 | { 18 | key: 'basicInfo', 19 | title: '基本信息', 20 | }, 21 | { 22 | key: 'changelog', 23 | title: '变更记录', 24 | }, 25 | ] as const 26 | } 27 | defaultKey="changelog" 28 | onChange={(key) => console.log('currentKey', key)} 29 | > 30 | {(key) => { 31 | switch (key) { 32 | case 'basicInfo': 33 | return
基本信息
; 34 | case 'changelog': 35 | return
变更记录
; 36 | default: 37 | break; 38 | } 39 | }} 40 |
41 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/drawer/demos/tabsControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Drawer } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [visible, setVisible] = useState(false); 7 | const [activeKey, setActiveKey] = useState<'basicInfo' | 'changelog'>('changelog'); 8 | 9 | return ( 10 | <> 11 | 12 | setVisible(false)} 15 | title={'Title'} 16 | tabs={ 17 | [ 18 | { 19 | key: 'basicInfo', 20 | title: '基本信息', 21 | }, 22 | { 23 | key: 'changelog', 24 | title: '变更记录', 25 | }, 26 | ] as const 27 | } 28 | activeKey={activeKey} 29 | onChange={(key) => setActiveKey(key)} 30 | > 31 | {(key) => { 32 | switch (key) { 33 | case 'basicInfo': 34 | return
基本信息
; 35 | case 'changelog': 36 | return
变更记录
; 37 | default: 38 | break; 39 | } 40 | }} 41 |
42 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /src/drawer/motion.ts: -------------------------------------------------------------------------------- 1 | import type { DrawerProps } from 'rc-drawer'; 2 | 3 | export const maskMotion: DrawerProps['maskMotion'] = { 4 | motionAppear: true, 5 | motionName: 'mask-motion', 6 | onAppearEnd: console.warn, 7 | }; 8 | 9 | export const motion: DrawerProps['motion'] = (placement) => ({ 10 | motionAppear: true, 11 | motionName: `panel-motion-${placement}`, 12 | }); 13 | 14 | const motionProps: Partial = { 15 | maskMotion, 16 | motion, 17 | }; 18 | 19 | export default motionProps; 20 | -------------------------------------------------------------------------------- /src/dropdown/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Dropdown } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [selected, setSelected] = useState([2]); 7 | 8 | return ( 9 | setSelected(checked as number[])} 16 | > 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/dropdown/demos/submit.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Spin } from 'antd'; 3 | import { Dropdown } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [selected, setSelected] = useState([2]); 7 | const [fetching, setFetching] = useState(false); 8 | 9 | const fetchData = () => { 10 | setFetching(true); 11 | setTimeout(() => { 12 | setFetching(false); 13 | }, 150); 14 | }; 15 | 16 | return ( 17 | <> 18 | { 25 | setSelected(checked as number[]); 26 | fetchData(); 27 | }} 28 | > 29 | 30 | 31 | 32 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/dropdown/demos/virtual.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Dropdown } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [selected, setSelected] = useState([2, 1000, 2000]); 7 | 8 | return ( 9 | idx)} 12 | onChange={(val) => { 13 | console.log(val); 14 | setSelected(val as number[]); 15 | }} 16 | > 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/dropdown/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dropdown 下拉菜单 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # Dropdown 下拉菜单 10 | 11 | ## 何时使用 12 | 13 | 支持全选按钮的下拉菜单 14 | 15 | ## 示例 16 | 17 | 18 | 19 | 20 | 21 | ## API 22 | 23 | | 参数 | 说明 | 类型 | 默认值 | 24 | | ----------------- | ---------------------------------- | ------------------------------------------- | ------ | 25 | | value | 当前选中的值 | `(string \| number)[]` | - | 26 | | defaultValue | 初始值 | `(string \| number)[]` | - | 27 | | className | - | `string` | - | 28 | | options | Checkbox 指定可选项 | `(string \| number \| Option)[]` | `[]` | 29 | | getPopupContainer | 同 Dropdown 的 `getPopupContainer` | `(triggerNode: HTMLElement) => HTMLElement` | - | 30 | | onChange | 变化时的回调函数 | `(checkedValue) => void` | - | 31 | -------------------------------------------------------------------------------- /src/dropdown/index.tsx: -------------------------------------------------------------------------------- 1 | import { Dropdown } from 'antd'; 2 | 3 | import Select from './select'; 4 | 5 | type OriginalInterface = typeof Dropdown; 6 | interface DropdownInterface extends OriginalInterface { 7 | Select: typeof Select; 8 | } 9 | const WrapperDropdown = Dropdown; 10 | (WrapperDropdown as DropdownInterface).Select = Select; 11 | export default WrapperDropdown as DropdownInterface; 12 | -------------------------------------------------------------------------------- /src/dropdown/style.scss: -------------------------------------------------------------------------------- 1 | $gap: 8px; 2 | $item-size: 32px; 3 | 4 | .dtc-dropdown-select { 5 | &__container { 6 | background: #FFF; 7 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.1); 8 | border-radius: 2px; 9 | height: auto; 10 | padding: $gap 0; 11 | .ant-checkbox-group { 12 | width: 100%; 13 | display: block; 14 | } 15 | } 16 | &__shadow { 17 | position: absolute; 18 | top: 0; 19 | width: 100%; 20 | box-shadow: inset 0 10px 10px -10px #EBECF0; 21 | height: 10px; 22 | z-index: 999; 23 | opacity: 0; 24 | transform: opacity 0.3s; 25 | pointer-events: none; 26 | &.active { 27 | opacity: 1; 28 | } 29 | } 30 | &__col { 31 | height: $item-size; 32 | line-height: $item-size; 33 | padding: 0 $gap; 34 | cursor: pointer; 35 | transition: background-color 0.3s; 36 | .ant-checkbox-wrapper { 37 | width: 100%; 38 | } 39 | &:hover { 40 | background-color: #F9F9FA; 41 | } 42 | } 43 | &__btns { 44 | height: $item-size; 45 | line-height: $item-size; 46 | padding: 0 $gap; 47 | width: 100%; 48 | justify-content: end; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ellipsisText/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { EllipsisText } from 'dt-react-component'; 3 | 4 | export default () => { 5 | return ( 6 | 9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /src/ellipsisText/demos/flex.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Divider } from 'antd'; 3 | import { EllipsisText } from 'dt-react-component'; 4 | 5 | export default () => { 6 | return ( 7 | <> 8 |
13 | 18 | 我是来捣乱的 19 |
20 | 21 |
26 | 我是来捣乱的 27 | 32 |
33 | 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/ellipsisText/demos/inlineElement.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { EllipsisText } from 'dt-react-component'; 3 | 4 | export default () => { 5 | return ( 6 |
11 | 12 | 17 | 18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/ellipsisText/demos/maxWidth.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Divider, Radio, RadioChangeEvent } from 'antd'; 3 | import { EllipsisText } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [width, setWidth] = useState(200); 7 | 8 | const onChange = (e: RadioChangeEvent) => { 9 | setWidth(e.target.value); 10 | }; 11 | 12 | return ( 13 | <> 14 | 15 | 200px 16 | 300px 17 | 20% 18 | 50% 19 | 100% 20 | calc(100% - 100px)% 21 | 22 | 23 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/ellipsisText/demos/multiple.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { EllipsisText } from 'dt-react-component'; 3 | 4 | export default () => { 5 | return ( 6 |
11 | 17 | 23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/ellipsisText/demos/valueType.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, ReactNode } from 'react'; 2 | import { Divider } from 'antd'; 3 | import { EllipsisText } from 'dt-react-component'; 4 | 5 | const element = ( 6 | <> 7 | 我是很长长长长长长 8 | 13 | 长长长长长长长长长长长长长长 14 | 15 | 长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长的文本 16 | 17 | ); 18 | 19 | const useEllipsisText = () => element; 20 | 21 | class WithEllipsisText extends Component { 22 | render(): ReactNode { 23 | return element; 24 | } 25 | } 26 | 27 | export default () => { 28 | return ( 29 | <> 30 | Element 31 |
36 | 37 |
38 | Hooks 39 |
44 | 45 |
46 | Class 47 |
52 | } /> 53 |
54 | 55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /src/ellipsisText/demos/watchParent.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Divider, Radio, RadioChangeEvent } from 'antd'; 3 | import { EllipsisText } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [width, setWidth] = useState(200); 7 | const onChange = (e: RadioChangeEvent) => { 8 | setWidth(e.target.value); 9 | }; 10 | 11 | return ( 12 | <> 13 | 14 | 200px 15 | 500px 16 | 17 | 18 |
23 | 27 |
28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /src/ellipsisText/style.scss: -------------------------------------------------------------------------------- 1 | .dtc-ellipsis-text { 2 | display: inline-block; 3 | overflow: hidden; 4 | text-overflow: ellipsis; 5 | white-space: nowrap; 6 | vertical-align: bottom; 7 | } 8 | -------------------------------------------------------------------------------- /src/empty/__tests__/__snapshots__/empty.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Empty match snapshots 1`] = ` 4 | 5 |
8 |
11 | 14 |
15 |
18 | No Data 19 |
20 |
21 |
22 | `; 23 | 24 | exports[`Empty match snapshots 2`] = ` 25 | 26 |
29 |
32 | 35 |
36 |
39 | No Data 40 |
41 |
42 |
43 | `; 44 | -------------------------------------------------------------------------------- /src/empty/emptyImg/empty_chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTStack/dt-react-component/07e27b960943e385fa9e65310221a4c48597afb6/src/empty/emptyImg/empty_chart.png -------------------------------------------------------------------------------- /src/empty/emptyImg/empty_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTStack/dt-react-component/07e27b960943e385fa9e65310221a4c48597afb6/src/empty/emptyImg/empty_default.png -------------------------------------------------------------------------------- /src/empty/emptyImg/empty_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTStack/dt-react-component/07e27b960943e385fa9e65310221a4c48597afb6/src/empty/emptyImg/empty_overview.png -------------------------------------------------------------------------------- /src/empty/emptyImg/empty_permission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTStack/dt-react-component/07e27b960943e385fa9e65310221a4c48597afb6/src/empty/emptyImg/empty_permission.png -------------------------------------------------------------------------------- /src/empty/emptyImg/empty_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTStack/dt-react-component/07e27b960943e385fa9e65310221a4c48597afb6/src/empty/emptyImg/empty_project.png -------------------------------------------------------------------------------- /src/empty/emptyImg/empty_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DTStack/dt-react-component/07e27b960943e385fa9e65310221a4c48597afb6/src/empty/emptyImg/empty_search.png -------------------------------------------------------------------------------- /src/empty/style.scss: -------------------------------------------------------------------------------- 1 | $defaultSize: 80px; 2 | $largeSize: 100px; 3 | 4 | .ant-empty.dtc-empty { 5 | .ant-empty-image { 6 | display: flex; 7 | justify-content: center; 8 | width: $defaultSize; 9 | height: $defaultSize; 10 | font-size: $defaultSize; 11 | margin: 0 auto 8px; 12 | } 13 | .ant-empty-description { 14 | color: #8B8FA8; 15 | line-height: 20px; 16 | font-size: 14px; 17 | font-family: PingFangSC-Regular, "PingFang SC"; 18 | font-weight: 400; 19 | } 20 | &__active { 21 | .ant-empty-image { 22 | position: relative; 23 | } 24 | } 25 | .dtc-empty__search { 26 | line-height: 0; 27 | font-size: 100%; 28 | } 29 | .dtc-empty__loupe { 30 | line-height: 0; 31 | position: absolute; 32 | font-size: 38px; 33 | right: 10px; 34 | bottom: 2px; 35 | animation: 36 | animY 1s cubic-bezier(0.36, 0, 0.64, 1) 0.5s infinite alternate, 37 | animX 1s cubic-bezier(0.36, 0, 0.64, 1) -0s infinite alternate; 38 | } 39 | &__large { 40 | .ant-empty-image { 41 | width: $largeSize; 42 | height: $largeSize; 43 | font-size: $largeSize; 44 | } 45 | .dtc-empty__loupe { 46 | font-size: 48px; 47 | } 48 | } 49 | } 50 | 51 | @keyframes animX { 52 | 0% { 53 | right: 10px; 54 | } 55 | 100% { 56 | right: 25px; 57 | } 58 | } 59 | 60 | @keyframes animY { 61 | 0% { 62 | bottom: 2px; 63 | } 64 | 100% { 65 | bottom: 17px; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/errorBoundary/__tests__/__snapshots__/errorBoundary.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test ErrorBoundary should render children without error 1`] = ` 4 |
5 | children 6 |
7 | `; 8 | -------------------------------------------------------------------------------- /src/errorBoundary/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { message } from 'antd'; 3 | import { ErrorBoundary } from 'dt-react-component'; 4 | 5 | const ThrowError = () => { 6 | const [count, setCount] = useState(0); 7 | if (count % 2) throw new Error('test error'); 8 | return ( 9 |
18 | 30 |

hello, dt-react-component

31 |
32 | ); 33 | }; 34 | 35 | export default () => { 36 | return ( 37 | message.error('捕获到错误:' + err.message)}> 38 | 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /src/errorBoundary/demos/customErrorPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { message } from 'antd'; 3 | import { ErrorBoundary } from 'dt-react-component'; 4 | 5 | const ThrowError = () => { 6 | const [count, setCount] = useState(0); 7 | if (count % 2) throw new Error('test error'); 8 | return ( 9 |
18 | 30 |

hello, dt-react-component

31 |
32 | ); 33 | }; 34 | 35 | const ErrorPage = () => { 36 | return ( 37 |
38 |

这是自定义捕获异常页面

39 |
40 | ); 41 | }; 42 | 43 | export default () => { 44 | return ( 45 | message.error('捕获到错误:' + err.message)} 47 | errorPage={} 48 | > 49 | 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/errorBoundary/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ErrorBoundary 错误边界 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 1 7 | --- 8 | 9 | # ErrorBoundary 错误边界 10 | 11 | ## 何时使用 12 | 13 | 错误边界可以捕获子组件的渲染、生命周期函数以及构造函数内的错误,记录错误日志并在错误发生时, 展示 LoadError 页面,以避免因为局部组件错误而导致的整个组件树崩溃。 14 | 15 | ## 示例 16 | 17 | 基本使用 18 | 自定义错误页面 19 | 20 | ## API 21 | 22 | | 参数 | 说明 | 类型 | 默认值 | 23 | | --------- | ------------------ | ------------------------ | --------------- | 24 | | children | 子组件 | `React.ReactNode` | - | 25 | | errorPage | 自定义错误展示页面 | `React.ReactNode` | `` | 26 | | onError | 发生错误捕获时触发 | `(error: Error) => void` | - | 27 | -------------------------------------------------------------------------------- /src/errorBoundary/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, ErrorInfo, ReactNode } from 'react'; 2 | 3 | import LoadError from './loadError'; 4 | 5 | interface IErrorBoundaryProps { 6 | children?: ReactNode; 7 | errorPage?: ReactNode; 8 | onError?: (error: Error) => void; 9 | } 10 | 11 | interface IErrorBoundaryState { 12 | hasError: boolean; 13 | } 14 | export default class ErrorBoundary extends Component { 15 | state = { hasError: false }; 16 | static getDerivedStateFromError(_error: any) { 17 | // Update state so the next render will show the fallback UI. 18 | return { hasError: true }; 19 | } 20 | 21 | componentDidCatch(error: Error, errorInfo: ErrorInfo) { 22 | this.setState({ hasError: true }); 23 | this.props.onError?.(error); 24 | console.log(error); 25 | console.log(errorInfo); 26 | } 27 | 28 | render() { 29 | const { children, errorPage = } = this.props; 30 | const { hasError } = this.state; 31 | 32 | if (hasError) { 33 | return errorPage; 34 | } 35 | return children; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/errorBoundary/loadError.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LoadError: React.FC = function () { 4 | return ( 5 |
6 |
7 |  8 |

9 | 发现新版本,请 10 | { 12 | location.reload(); 13 | }} 14 | > 15 | 刷新 16 | 17 | 获取新版本。 18 |

19 |

若该提示长时间存在,请联系管理员。

20 |
21 |
22 | ); 23 | }; 24 | 25 | export default LoadError; 26 | -------------------------------------------------------------------------------- /src/filterRules/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Input } from 'antd'; 3 | import { FilterRules } from 'dt-react-component'; 4 | import { IComponentProps } from 'dt-react-component/filterRules'; 5 | 6 | import { INIT_DATA, INIT_ROW_VALUES, IRow } from './constants'; 7 | 8 | const MyInput = ({ rowValues: { input }, rowKey, onChange }: IComponentProps) => ( 9 | onChange?.(rowKey, { input: e.target.value })} /> 10 | ); 11 | 12 | export default () => { 13 | const [data, setData] = useState(INIT_DATA); 14 | 15 | return ( 16 | <> 17 | 18 | component={(props) => } 19 | value={data} 20 | onChange={(value: any) => setData(value)} 21 | initValues={INIT_ROW_VALUES} 22 | /> 23 | 24 | 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/filterRules/demos/basicCheck.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Button, Form, Input } from 'antd'; 3 | import { FilterRules } from 'dt-react-component'; 4 | import { IComponentProps } from 'dt-react-component/filterRules'; 5 | 6 | import { INIT_DATA, INIT_ROW_VALUES, IRow } from './constants'; 7 | 8 | const MyInput = ({ rowValues: { input }, rowKey, disabled, onChange }: IComponentProps) => ( 9 | onChange?.(rowKey, { input: e.target.value })} 12 | disabled={disabled} 13 | /> 14 | ); 15 | 16 | export default () => { 17 | const [form] = Form.useForm(); 18 | const [disabled, useDisabled] = useState(false); 19 | 20 | useEffect(() => { 21 | form.setFieldsValue({ condition: INIT_DATA }); 22 | }, []); 23 | 24 | return ( 25 |
26 | 29 | 30 | 31 | component={(props) => } 32 | disabled={disabled} 33 | initValues={INIT_ROW_VALUES} 34 | /> 35 | 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/filterRules/demos/basicMaxSize.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Input, InputNumber, Space } from 'antd'; 3 | import { FilterRules } from 'dt-react-component'; 4 | import { IComponentProps } from 'dt-react-component/filterRules'; 5 | 6 | import { INIT_DATA, INIT_ROW_VALUES, IRow } from './constants'; 7 | 8 | const MyInput = ({ rowValues: { input }, rowKey, onChange }: IComponentProps) => ( 9 | onChange?.(rowKey, { input: e.target.value })} /> 10 | ); 11 | 12 | export default () => { 13 | const [data, setData] = useState(INIT_DATA); 14 | const [maxSize, setMaxSize] = useState(2); 15 | 16 | return ( 17 | <> 18 | 19 | 最大子节点个数: 20 | setMaxSize(value ?? 0)} 25 | /> 26 | 27 | 28 | component={(props) => } 29 | value={data} 30 | maxSize={maxSize} 31 | onChange={(value: any) => setData(value)} 32 | initValues={INIT_ROW_VALUES} 33 | /> 34 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/filterRules/demos/basicMore.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Alert, Input } from 'antd'; 3 | import { FilterRules } from 'dt-react-component'; 4 | import { IComponentProps } from 'dt-react-component/filterRules'; 5 | 6 | import { INIT_ROW_VALUES, IRow, MORE_INIT_DATA } from './constants'; 7 | 8 | const MyInput = ({ rowValues: { input }, rowKey, onChange }: IComponentProps) => ( 9 | onChange?.(rowKey, { input: e.target.value })} /> 10 | ); 11 | 12 | export default () => { 13 | const [data, setData] = useState(MORE_INIT_DATA); 14 | 15 | return ( 16 | <> 17 | 21 | 22 | component={(props) => } 23 | value={data} 24 | onChange={(value: any) => setData(value)} 25 | initValues={INIT_ROW_VALUES} 26 | /> 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/filterRules/demos/basicUnController.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Form, Input } from 'antd'; 3 | import { FilterRules } from 'dt-react-component'; 4 | import { IComponentProps } from 'dt-react-component/filterRules'; 5 | 6 | import { INIT_DATA, INIT_ROW_VALUES, IRow } from './constants'; 7 | 8 | const MyInput = ({ name }: IComponentProps) => ( 9 | 10 | 11 | 12 | ); 13 | 14 | export default () => { 15 | const [form] = Form.useForm(); 16 | 17 | return ( 18 |
19 | 25 | 26 | 27 | component={(props) => } 28 | notEmpty={{ data: false }} 29 | initValues={INIT_ROW_VALUES} 30 | /> 31 | 32 | 35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/filterRules/demos/editCheck.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Alert, Input } from 'antd'; 3 | import { FilterRules } from 'dt-react-component'; 4 | import { IComponentProps } from 'dt-react-component/filterRules'; 5 | 6 | import { INIT_CHECK_DATA, INIT_ROW_VALUES, IRow } from './constants'; 7 | 8 | const MyInput = ({ rowValues: { input }, disabled, rowKey, onChange }: IComponentProps) => ( 9 | onChange?.(rowKey, { input: e.target.value })} 13 | /> 14 | ); 15 | 16 | export default () => { 17 | const [data, setData] = useState(INIT_CHECK_DATA); 18 | 19 | return ( 20 | <> 21 | 25 | 26 | component={(props) => } 27 | value={data} 28 | onChange={(value: any) => setData(value)} 29 | initValues={INIT_ROW_VALUES} 30 | /> 31 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/flex/__tests__/__snapshots__/flex.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test Flex Button Match the snapshot 1`] = ` 4 | 5 |
9 |
10 | 123 11 |
12 |
13 | 123 14 |
15 |
16 | 123 17 |
18 |
19 | 123 20 |
21 |
22 |
23 | `; 24 | 25 | exports[`Test Flex Button Match the snapshot: primary button 1`] = ` 26 | 27 |
31 |
32 | 123 33 |
34 |
35 | 123 36 |
37 |
38 | 123 39 |
40 |
41 | 123 42 |
43 |
44 |
45 | `; 46 | -------------------------------------------------------------------------------- /src/flex/__tests__/flex.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { cleanup, render } from '@testing-library/react'; 3 | 4 | import Flex from '..'; 5 | 6 | const children = ( 7 | <> 8 |
123
9 |
123
10 |
123
11 |
123
12 | 13 | ); 14 | 15 | describe('Test Flex Button', () => { 16 | beforeEach(() => cleanup()); 17 | 18 | it('Match the snapshot', () => { 19 | expect(render({children}).asFragment()).toMatchSnapshot(); 20 | 21 | expect(render({children}).asFragment()).toMatchSnapshot( 22 | 'primary button' 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/flex/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import { Flex } from 'dt-react-component'; 4 | 5 | export default () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /src/flex/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Flex 布局 3 | group: 组件 4 | toc: content 5 | --- 6 | 7 | # Flex 布局 8 | 9 | ## 何时使用 10 | 11 | - 需要 Flex 布局 12 | 13 | ## 示例 14 | 15 | 16 | 17 | 18 | ## API 19 | 20 | | 参数 | 说明 | 类型 | 默认值 | 21 | | -------- | ----------------------- | ----------------------------------------------------------------------------------- | -------- | 22 | | vertical | flex 主轴的方向是否垂直 | `boolean` | `false` | 23 | | wrap | 主轴换行 | [flex-wrap](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-wrap) | `nowrap` | 24 | | justify | `justify-content` | [justify-content](https://developer.mozilla.org/zh-CN/docs/Web/CSS/justify-content) | `normal` | 25 | | align | `align-items` | [align-items](https://developer.mozilla.org/zh-CN/docs/Web/CSS/align-items) | `normal` | 26 | | flex | `flex` | [flex](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex) | `normal` | 27 | | gap | `gap` | [gap](https://developer.mozilla.org/zh-CN/docs/Web/CSS/gap) | `0` | 28 | | children | 展示内容 | `React.ReactNode` | - | 29 | -------------------------------------------------------------------------------- /src/flex/index.scss: -------------------------------------------------------------------------------- 1 | .dtc-flex { 2 | display: flex; 3 | flex-direction: row; 4 | &__vertical { 5 | flex-direction: column; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/flex/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, forwardRef } from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | import './index.scss'; 5 | 6 | export interface IFlexProps extends React.DOMAttributes { 7 | vertical?: boolean; 8 | wrap?: React.CSSProperties['flexWrap']; 9 | justify?: React.CSSProperties['justifyContent']; 10 | align?: React.CSSProperties['alignItems']; 11 | flex?: React.CSSProperties['flex']; 12 | gap?: React.CSSProperties['gap']; 13 | children: React.ReactNode; 14 | className?: string; 15 | style?: CSSProperties; 16 | } 17 | 18 | /** 19 | * 简单版本的 Ant Design 的 Flex 组件 20 | */ 21 | export default forwardRef(function Flex( 22 | { 23 | className, 24 | vertical = false, 25 | wrap = 'nowrap', 26 | justify = 'normal', 27 | align = 'normal', 28 | flex = 'normal', 29 | gap = 0, 30 | style, 31 | children, 32 | ...rest 33 | }, 34 | ref 35 | ) { 36 | return ( 37 |
50 | {children} 51 |
52 | ); 53 | }); 54 | -------------------------------------------------------------------------------- /src/form/demos/data.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | 3 | export type MockData = { 4 | uuid: string; 5 | name: string; 6 | address: string; 7 | company: string; 8 | gender: string; 9 | weight: number; 10 | }; 11 | 12 | export default async function getMockData() { 13 | return new Promise((resolve) => { 14 | setTimeout(() => { 15 | const data = Array.from({ length: 5 }).map(() => { 16 | return { 17 | uuid: faker.datatype.uuid(), 18 | name: faker.internet.userName(), 19 | address: faker.address.cityName(), 20 | company: faker.company.bs(), 21 | gender: faker.name.sex(), 22 | weight: faker.datatype.number({ 23 | max: 200, 24 | min: 80, 25 | }), 26 | }; 27 | }); 28 | resolve(data); 29 | }, 150); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /src/form/index.scss: -------------------------------------------------------------------------------- 1 | $gap: 16px; 2 | $errorColor: #FF4D4F; 3 | 4 | .dtc-form { 5 | &__table { 6 | height: 100%; 7 | &__column { 8 | &--required { 9 | &::before { 10 | content: "*"; 11 | display: inline-block; 12 | margin-inline-end: 4px; 13 | font-size: 14px; 14 | line-height: 1; 15 | color: $errorColor; 16 | } 17 | vertical-align: -0.1em; 18 | } 19 | } 20 | .ant-table, 21 | .ant-table-container { 22 | height: 100%; 23 | } 24 | .ant-form-item { 25 | margin-bottom: 0; 26 | } 27 | table colgroup > col.ant-table-selection-col { 28 | width: 48px; 29 | } 30 | .ant-table-thead > tr > th.ant-table-selection-column { 31 | padding: 0 $gap; 32 | } 33 | .ant-table-tbody > tr > td.ant-table-selection-column { 34 | padding: 0 $gap; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/form/index.tsx: -------------------------------------------------------------------------------- 1 | import { Form } from 'antd'; 2 | 3 | import InternalTable from './table'; 4 | 5 | type OriginFormType = typeof Form; 6 | interface FormInterface extends OriginFormType { 7 | Table: typeof InternalTable; 8 | } 9 | 10 | const WrapperForm = Form; 11 | 12 | (WrapperForm as FormInterface).Table = InternalTable; 13 | 14 | export type { ColumnType, IFormTableProps } from './table'; 15 | export default WrapperForm as FormInterface; 16 | -------------------------------------------------------------------------------- /src/fullscreen/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Fullscreen } from 'dt-react-component'; 3 | 4 | export default () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/fullscreen/demos/custom.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ArrowsAltOutlined, ShrinkOutlined } from '@ant-design/icons'; 3 | import { Fullscreen } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const iconStyle = { 7 | width: 12, 8 | height: 12, 9 | marginRight: 5, 10 | }; 11 | 12 | return ( 13 | 17 | 18 | 全屏 19 | 20 | } 21 | exitFullIcon={ 22 |
23 | 24 | 退出全屏 25 |
26 | } 27 | /> 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/fullscreen/demos/local.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Card } from 'antd'; 3 | import { Fullscreen } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [targetKey, setTargetKey] = useState(''); 7 | 8 | useEffect(() => { 9 | setTargetKey('localContainer'); 10 | }, []); 11 | 12 | return ( 13 | 14 | 15 |
Fullscreen in this
16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/fullscreen/demos/theme.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Switch } from 'antd'; 3 | import { Fullscreen } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [themeDark, setThemeDark] = useState(false); 7 | const changeTheme = (value: boolean) => { 8 | setThemeDark(value); 9 | }; 10 | return ( 11 | <> 12 | 18 |
19 |
20 | 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/fullscreen/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fullscreen 全屏 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # Fullscreen 全屏 10 | 11 | ## 何时使用 12 | 13 | 全屏切换操作 14 | 15 | ## 代码演示 16 | 17 | 18 | 19 | 20 | 21 | 22 | ## API 23 | 24 | | 参数 | 说明 | 类型 | 默认值 | 25 | | ------------ | -------------------------- | ----------------- | ------- | 26 | | target | 全局操作作用于指定目标对象 | `string` | - | 27 | | themeDark | 是否使用暗黑主题模式 | `boolean` | `false` | 28 | | iconStyle | 图标元素样式 | `CSSProperties` | - | 29 | | fullIcon | 自定义全屏图标 | `React.ReactNode` | - | 30 | | exitFullIcon | 自定义退出全屏图标 | `React.ReactNode` | - | 31 | -------------------------------------------------------------------------------- /src/fullscreen/style.scss: -------------------------------------------------------------------------------- 1 | .dtc-fullscreen-icon { 2 | width: 16px; 3 | height: 16px; 4 | margin-right: 5px; 5 | margin-top: -4px; 6 | } 7 | -------------------------------------------------------------------------------- /src/globalLoading/__tests__/globalLoading.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | 5 | import GlobalLoading from '..'; 6 | 7 | describe('test GlobalLoading', () => { 8 | it('renders loading title', () => { 9 | const { getByText } = render(); 10 | const titleElement = getByText(/Test Title/i); 11 | expect(titleElement).toBeInTheDocument(); 12 | }); 13 | 14 | it('renders with custom class name', () => { 15 | const { container } = render(); 16 | expect(container.firstChild).toHaveClass('test-class'); 17 | }); 18 | 19 | it('renders with custom main background', () => { 20 | const { container } = render(); 21 | expect(container.firstChild).toHaveStyle('background: #000'); 22 | }); 23 | 24 | it('renders with custom circle background', () => { 25 | const { container } = render(); 26 | const circleElement = container.querySelector('.dtc-dot'); 27 | expect(circleElement).toHaveStyle('background: #000'); 28 | }); 29 | 30 | it('renders with custom title color', () => { 31 | const { container } = render(); 32 | const titleElement = container.querySelector('.dtc-global-loading-title'); 33 | expect(titleElement).toHaveStyle('color: #000'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/globalLoading/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GlobalLoading } from 'dt-react-component'; 3 | 4 | export default () => { 5 | return ( 6 |
7 | 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/globalLoading/demos/default.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GlobalLoading } from 'dt-react-component'; 3 | 4 | export default () => { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /src/globalLoading/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GlobalLoading 应用加载中 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # GlobalLoading 应用加载中 10 | 11 | ## 何时使用 12 | 13 | 应用等待加载时使用,可自定义样式。(PS:宽度和高度均为 100%,所以其大小基于其父级) 14 | 15 | ## 示例 16 | 17 | 18 | 19 | 20 | ## API 21 | 22 | | 参数 | 说明 | 类型 | 默认值 | 23 | | ---------------- | ---------------------- | -------- | -------------------- | 24 | | loadingTitle | 应用名称 | `string` | 应用加载中,请等候~ | 25 | | mainBackground | 整体背景色 | `string` | `#F2F7FA` | 26 | | titleColor | 文案字体颜色 | `string` | `#3D446E` | 27 | | circleBackground | 等待动画 circle 背景色 | `string` | `#1D78FF` | 28 | | className | 设置组件 className | `string` | - | 29 | -------------------------------------------------------------------------------- /src/image/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Image from '..'; 4 | 5 | export default function Component() { 6 | return ( 7 |
8 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/image/demos/lazy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Image from '..'; 4 | 5 | export default function Component() { 6 | return ( 7 |
8 |
占位
9 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/image/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Image 图片组件 3 | group: 组件 4 | toc: content 5 | --- 6 | 7 | # Image 8 | 9 | ## 何时使用 10 | 11 | 展示图片 12 | 13 | ## 示例 14 | 15 | 16 | 17 | 18 | ## API 19 | 20 | ### Props 21 | 22 | | 参数 | 说明 | 类型 | 默认值 | 23 | | --------- | ----------------------- | --------------------- | ---------- | 24 | | src | 图片资源 | `string` | - | 25 | | lazy | 是否开启图片懒加载 | `boolean` | - | 26 | | className | 图片样式名 | `string` | - | 27 | | style | 图片样式 | `React.CSSProperties` | - | 28 | | width | 图片宽度 | `number` | - | 29 | | height | 图片高度 | `number` | - | 30 | | loader | 图片 loading 时展示内容 | `JSX.Element \| null` | `` | 31 | -------------------------------------------------------------------------------- /src/input/demos/input/basic.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Space } from 'antd'; 3 | import { Input } from 'dt-react-component'; 4 | 5 | export default () => { 6 | return ( 7 | 8 | alert('触发')} 12 | /> 13 | alert('触发')} 17 | /> 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/input/demos/match/basic.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Radio } from 'antd'; 3 | import type { SizeType } from 'antd/es/config-provider/SizeContext'; 4 | import { Input } from 'dt-react-component'; 5 | 6 | export default () => { 7 | const [size, setSize] = useState('middle'); 8 | return ( 9 | <> 10 | setSize(e.target.value)} 19 | style={{ marginBottom: 16 }} 20 | /> 21 | console.log('e', e.target.value)} 25 | onTypeChange={(type) => console.log('onTypeChange:', type)} 26 | onSearch={(value, searchType) => console.log('onSearch:', value, searchType)} 27 | /> 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /src/input/demos/match/filterOptions.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from 'dt-react-component'; 3 | 4 | export default () => { 5 | return ( 6 | console.log('e', e.target.value)} 10 | onTypeChange={(type) => console.log('onTypeChange:', type)} 11 | onSearch={(value, searchType) => console.log('onSearch:', value, searchType)} 12 | /> 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /src/input/index.$tab-match.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Match 输入框 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # Match 输入框 10 | 11 | ## 何时使用 12 | 13 | 需要用户输入表单域内容时。 14 | 15 | ## 示例 16 | 17 | 18 | 19 | 20 | ## API 21 | 22 | ### 类型 23 | 24 | | 参数 | 说明 | 类型 | 默认值 | 25 | | ------------- | -------------------- | -------------------------------------------------- | -------------- | 26 | | searchType | 当前匹配项 | `SearchType` | 27 | | filterOptions | 过滤匹配项数组 | `SearchType[]` | `SearchType[]` | 28 | | onTypeChange | 匹配项修改的回调函数 | `(type: SearchType) => void;` | - | 29 | | onSearch | 搜索的回调函数 | `(value: string, searchType: SearchType) => void;` | - | 30 | 31 | ### SearchType 32 | 33 | `type SearchType = 'fuzzy' | 'caseSensitive' | 'precise' | 'front' | 'tail';` 34 | 35 | | 参数 | 说明 | 36 | | ------------- | ---------- | 37 | | fuzzy | 无选中状态 | 38 | | caseSensitive | 大小写敏感 | 39 | | precise | 精准查询 | 40 | | front | 头部匹配 | 41 | | tail | 尾部匹配 | 42 | -------------------------------------------------------------------------------- /src/input/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Input 输入框 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # Input 输入框 10 | 11 | ## 何时使用 12 | 13 | 需要用户输入内容时。 14 | 15 | ## 示例 16 | 17 | 18 | 19 | ## API 20 | 21 | ### 类型 22 | 23 | | 参数 | 说明 | 类型 | 默认值 | 24 | | ------------------ | ---------------------- | ---------- | ------ | 25 | | onPressEnter | 输入英文回车的回调函数 | `Function` | 26 | | onPressEnterNative | 输入任意回车的回调函数 | `Function` | | 27 | 28 | 其余属性均继承自 `Input` 组件,参考 [Input API](https://4x.ant.design/components/input-cn/#Input) 29 | -------------------------------------------------------------------------------- /src/input/index.tsx: -------------------------------------------------------------------------------- 1 | import InternalInput from './internalInput'; 2 | import InternalMatch from './match'; 3 | 4 | type OriginInputType = typeof InternalInput; 5 | interface InputInterface extends OriginInputType { 6 | Match: typeof InternalMatch; 7 | } 8 | 9 | const WrapperInput = InternalInput; 10 | 11 | (WrapperInput as InputInterface).Match = InternalMatch; 12 | 13 | export default WrapperInput as InputInterface; 14 | -------------------------------------------------------------------------------- /src/input/internalInput.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, type InputProps } from 'antd'; 3 | 4 | export interface IInternalInputProps extends InputProps { 5 | onPressEnterNative?: InputProps['onPressEnter']; 6 | } 7 | 8 | export default function InternalInput({ 9 | onPressEnterNative, 10 | onPressEnter, 11 | ...rest 12 | }: IInternalInputProps) { 13 | return ( 14 | { 17 | if (e.keyCode === 13) { 18 | onPressEnter?.(e); 19 | } 20 | onPressEnterNative?.(e); 21 | }} 22 | /> 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/input/match.scss: -------------------------------------------------------------------------------- 1 | .dtc-input-match { 2 | &-items { 3 | display: flex; 4 | align-items: center; 5 | gap: 8px; 6 | } 7 | &-suffixItem { 8 | cursor: pointer; 9 | transition: color 0.15s; 10 | color: #B1B4C5; 11 | outline: 1px solid transparent; 12 | outline-offset: -1px; 13 | padding: 2px; 14 | box-sizing: content-box; 15 | &:hover { 16 | color: #1D78FF; 17 | } 18 | &:active, 19 | &:focus { 20 | color: #005CE6; 21 | } 22 | &__active { 23 | color: #1D78FF; 24 | outline-color: #1D78FF; 25 | border-radius: 2px; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/keyEventListener/__tests__/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test KeyCombiner suite snapshot match 1`] = ` 4 | 5 | 6 |
7 | command+shift+f 8 |
9 |
10 |
11 | `; 12 | 13 | exports[`test KeyEventListener suite snapshot match 1`] = ` 14 | 15 | 任意键盘监听 16 | 17 | `; 18 | -------------------------------------------------------------------------------- /src/keyEventListener/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { KeyEventListener } from 'dt-react-component'; 3 | 4 | export default () => { 5 | return ( 6 | { 8 | console.log('onkeyDown', evt); 9 | }} 10 | > 11 | {
尝试按下任意键盘,看看控制台打印结果
} 12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /src/keyEventListener/demos/customKey.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { KeyEventListener } from 'dt-react-component'; 3 | 4 | const { KeyCombiner } = KeyEventListener; 5 | 6 | export default () => { 7 | return ( 8 | { 10 | evt.preventDefault(); 11 | console.log('command+shift+f action'); 12 | }} 13 | keyMap={{ 14 | 70: true, 15 | 91: true, 16 | 16: true, 17 | }} 18 | > 19 |
尝试按下 command+shift+f 看看控制台是否监听了键盘事件
20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/keyEventListener/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: KeyEventListener 键盘监听 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # KeyEventListener 键盘监听 10 | 11 | ## 何时使用 12 | 13 | 监听键盘事件 14 | 15 | ## 代码演示 16 | 17 | 18 | 19 | 20 | ## API 21 | 22 | ### KeyEventListener 23 | 24 | | 参数 | 说明 | 类型 | 默认值 | 25 | | --------- | ------------ | ----------------- | ------ | 26 | | onKeyDown | 键盘按下执行 | `function` | - | 27 | | onKeyUp | 键盘抬起执行 | `function` | - | 28 | | children | 子组件 | `React.ReactNode` | - | 29 | 30 | ### KeyEventListener.KeyCombiner 31 | 32 | | 参数 | 说明 | 类型 | 默认值 | 33 | | --------- | ------------------------------------------------------------------------------------------ | ----------------- | ------ | 34 | | keyMap | 监听的一组 key map,eg: { 70: true, 91: true, 16: true } 则表示监听 command+shift+f 组合键 | `object` | - | 35 | | onTrigger | 触发事件 | `function` | - | 36 | | children | 子组件 | `React.ReactNode` | - | 37 | -------------------------------------------------------------------------------- /src/keyEventListener/index.tsx: -------------------------------------------------------------------------------- 1 | import { ForwardRefExoticComponent, ReactNode, useCallback, useEffect } from 'react'; 2 | 3 | import KeyCombiner from './listener'; 4 | 5 | export interface IKeyEventListenerProps { 6 | onKeyDown?: (e: KeyboardEvent) => void; 7 | onKeyUp?: (e: KeyboardEvent) => void; 8 | children?: ReactNode; 9 | } 10 | interface CompoundedComponent extends ForwardRefExoticComponent { 11 | KeyCombiner: typeof KeyCombiner; 12 | } 13 | 14 | const InternalKeyEventListener = ({ onKeyDown, onKeyUp, children }: IKeyEventListenerProps) => { 15 | useEffect(() => { 16 | handleAddEventListener(); 17 | return () => { 18 | handleRemoveEventListener(); 19 | }; 20 | }, []); 21 | 22 | const handleAddEventListener = () => { 23 | addEventListener('keydown', bindEvent, false); 24 | addEventListener('keyup', bindEvent, false); 25 | }; 26 | const handleRemoveEventListener = () => { 27 | removeEventListener('keydown', bindEvent, false); 28 | removeEventListener('keyup', bindEvent, false); 29 | }; 30 | 31 | const bindEvent = useCallback( 32 | (target: KeyboardEvent) => { 33 | const isKeyDown = target.type === 'keydown'; 34 | if (isKeyDown) { 35 | onKeyDown?.(target); 36 | } else { 37 | onKeyUp?.(target); 38 | } 39 | }, 40 | [onKeyDown, onKeyUp] 41 | ); 42 | 43 | return children; 44 | }; 45 | 46 | const KeyEventListener = InternalKeyEventListener as CompoundedComponent; 47 | 48 | KeyEventListener.KeyCombiner = KeyCombiner; 49 | 50 | export default KeyEventListener; 51 | -------------------------------------------------------------------------------- /src/markdownRender/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { MarkdownRender } from 'dt-react-component'; 3 | 4 | export default () => { 5 | const [value, setValue] = useState(''); 6 | 7 | useEffect(() => { 8 | fetch('https://cdn.jsdelivr.net/npm/dt-react-component@3.0.8/CHANGELOG.md', { 9 | method: 'get', 10 | }) 11 | .then((res) => res.text()) 12 | .then(setValue) 13 | .catch((err) => { 14 | setValue(err.message); 15 | }); 16 | }, []); 17 | 18 | return ( 19 |
20 | 21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/markdownRender/demos/dark.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { MarkdownRender } from 'dt-react-component'; 3 | 4 | const md = ` 5 | 以下是一段 sql 语法 6 | 7 | \`\`\`sql 8 | select count(*) from a; 9 | -- name sqltest 10 | -- type sql 11 | -- create time 2022-11-09 16:13:45 12 | -- desc 13 | 14 | 15 | -- create table employees(name string); 16 | insert into employees values('1111'); 17 | 18 | 19 | select * from employees 20 | \`\`\` 21 | `; 22 | 23 | export default () => { 24 | const [value, setValue] = useState(''); 25 | 26 | useEffect(() => { 27 | setValue(md); 28 | }, []); 29 | 30 | return ( 31 |
32 | 33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/markdownRender/demos/sql.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { MarkdownRender } from 'dt-react-component'; 3 | 4 | const md = ` 5 | 以下是一段 sql 语法 6 | 7 | \`\`\`sql 8 | select count(*) from a; 9 | -- name sqltest 10 | -- type sql 11 | -- create time 2022-11-09 16:13:45 12 | -- desc 13 | 14 | 15 | -- create table employees(name string); 16 | insert into employees values('1111'); 17 | 18 | 19 | select * from employees 20 | \`\`\` 21 | `; 22 | 23 | export default () => { 24 | const [value, setValue] = useState(''); 25 | 26 | useEffect(() => { 27 | setValue(md); 28 | }, []); 29 | 30 | return ( 31 |
32 | 33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/markdownRender/extensions/index.ts: -------------------------------------------------------------------------------- 1 | import type * as Hljs from 'highlight.js'; 2 | import hljsCore from 'highlight.js/lib/core'; 3 | import sql from 'highlight.js/lib/languages/sql'; 4 | import showdown from 'showdown'; 5 | 6 | import 'highlight.js/styles/default.css'; 7 | import '../theme/vs.scss'; 8 | import '../theme/vs-dark.scss'; 9 | 10 | const hljs = hljsCore as typeof Hljs; 11 | 12 | hljs.registerLanguage('sql', sql); 13 | 14 | export default function sqlHighlightExtension(): showdown.ShowdownExtension { 15 | return { 16 | type: 'output', 17 | filter: function (text) { 18 | return showdown.helper.replaceRecursiveRegExp( 19 | text.replace(/>/g, '>').replace(/</g, '<'), 20 | (_: string, match: string, left: string, right: string) => { 21 | const lang = (left.match(/class=\"([^ \"]+)/) || [])[1]; 22 | 23 | // Append hljs class 24 | const prefix = left.slice(0, 18) + 'hljs ' + left.slice(18); 25 | if (lang && hljs.getLanguage(lang)) { 26 | return prefix + hljs.highlight(match, { language: lang }).value + right; 27 | } else { 28 | return prefix + hljs.highlightAuto(match).value + right; 29 | } 30 | }, 31 | '
]*>',
32 |                 '
', 33 | 'g' 34 | ); 35 | }, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/markdownRender/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: MarkdownRender 渲染 3 | group: 组件 4 | toc: content 5 | --- 6 | 7 | # MarkdownRender 渲染 8 | 9 | ## 何时使用 10 | 11 | 用于 Markdown 语法在 HTML 上展示,只负责渲染 12 | 13 | ## 示例 14 | 15 | 16 | 17 | 18 | 19 | ## API 20 | 21 | | 参数 | 说明 | 类型 | 默认值 | 22 | | --------- | ----------------- | --------- | ------ | 23 | | value | markdown 文本数据 | `string` | - | 24 | | dark | 暗黑主题 | `boolean` | - | 25 | | className | 类名 | `string` | - | 26 | -------------------------------------------------------------------------------- /src/markdownRender/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import classnames from 'classnames'; 3 | import showdown from 'showdown'; 4 | 5 | import sqlHighlightExtension from './extensions'; 6 | import './style.scss'; 7 | 8 | export interface IMarkdownRenderProps { 9 | /** 10 | * 当前渲染的值 11 | */ 12 | value?: string; 13 | style?: React.CSSProperties; 14 | className?: string; 15 | /** 16 | * 暗黑模式 17 | */ 18 | dark?: boolean; 19 | } 20 | 21 | export default function MarkdownRender({ 22 | value = '', 23 | className, 24 | style, 25 | dark, 26 | }: IMarkdownRenderProps) { 27 | const result = useMemo(() => { 28 | const converter = new showdown.Converter({ 29 | extensions: [sqlHighlightExtension], 30 | emoji: true, 31 | }); 32 | return converter.makeHtml(value); 33 | }, [value]); 34 | 35 | return ( 36 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /src/markdownRender/theme/vs.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Visual Studio-like style based on original C# coloring by Jason Diamond 4 | */ 5 | 6 | .dtc-vs { 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: white; 12 | color: black; 13 | } 14 | .hljs-comment, 15 | .hljs-quote, 16 | .hljs-variable { 17 | color: #008000; 18 | } 19 | .hljs-keyword, 20 | .hljs-selector-tag, 21 | .hljs-built_in, 22 | .hljs-name, 23 | .hljs-tag { 24 | color: #00F; 25 | } 26 | .hljs-string, 27 | .hljs-title, 28 | .hljs-section, 29 | .hljs-attribute, 30 | .hljs-literal, 31 | .hljs-template-tag, 32 | .hljs-template-variable, 33 | .hljs-type, 34 | .hljs-addition { 35 | color: #A31515; 36 | } 37 | .hljs-deletion, 38 | .hljs-selector-attr, 39 | .hljs-selector-pseudo, 40 | .hljs-meta { 41 | color: #2B91AF; 42 | } 43 | .hljs-doctag { 44 | color: #808080; 45 | } 46 | .hljs-attr { 47 | color: #F00; 48 | } 49 | .hljs-symbol, 50 | .hljs-bullet, 51 | .hljs-link { 52 | color: #00B0E8; 53 | } 54 | .hljs-emphasis { 55 | font-style: italic; 56 | } 57 | .hljs-strong { 58 | font-weight: bold; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/modal/demos/banner.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Modal } from 'dt-react-component'; 4 | 5 | export default function Banner() { 6 | const [visible, setVisible] = useState(false); 7 | 8 | return ( 9 | <> 10 | setVisible(false)} 15 | onOk={() => setVisible(false)} 16 | > 17 | testssss 18 | 19 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/modal/demos/bannerProps.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Modal } from 'dt-react-component'; 4 | 5 | export default function BannerProps() { 6 | const [visible, setVisible] = useState(false); 7 | 8 | return ( 9 | <> 10 | setVisible(false)} 18 | onOk={() => setVisible(false)} 19 | > 20 | testssss 21 | 22 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/modal/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from 'antd'; 3 | import { Modal } from 'dt-react-component'; 4 | 5 | export default function Basic() { 6 | const [visible, setVisible] = useState(false); 7 | 8 | return ( 9 | <> 10 | setVisible(false)} 14 | onOk={() => setVisible(false)} 15 | > 16 |
    17 | {Array.from({ length: 300 }).map((_, i) => ( 18 |
  • 19 | {i} 20 |
  • 21 | ))} 22 |
23 |
24 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/modal/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Modal 模态框 3 | group: 组件 4 | toc: content 5 | --- 6 | 7 | # Modal 模态框 8 | 9 | ## 何时使用 10 | 11 | - 使用模态框时,使用该组件替换 antd 的 Modal 12 | - 支持 size 属性来快速设置宽度;限制 Modal 的高度为 600px,超出内部滚动 13 | - 支持 banner 属性来快速实现 Modal 内部提示 14 | 15 | ## 示例 16 | 17 | 18 | 19 | 20 | 21 | 22 | ## API 23 | 24 | ### AlertProps 25 | 26 | [AlertProps](https://4x-ant-design.antgroup.com/components/alert-cn/#API) 27 | 28 | | 参数 | 说明 | 类型 | 默认值 | 29 | | ------ | ---- | --------------------------------------------- | --------- | 30 | | size | 尺寸 | `'small' \| 'default' \| 'middle' \| 'large'` | `default` | 31 | | banner | 提示 | `React.ReactNode \| AlertProps` | | 32 | 33 | :::info 34 | 其余参数继承 antd4.x 的 [Modal](https://4x.ant.design/components/modal-cn/#API) 35 | ::: 36 | -------------------------------------------------------------------------------- /src/modal/index.scss: -------------------------------------------------------------------------------- 1 | $modal-max-height: 80vh; 2 | 3 | .dtc-modal { 4 | .ant-modal-content { 5 | max-height: $modal-max-height; 6 | display: flex; 7 | flex-direction: column; 8 | .ant-modal-body { 9 | overflow: hidden; 10 | flex: 1; 11 | display: flex; 12 | flex-direction: column; 13 | .dtc-modal-body { 14 | flex: 1; 15 | padding: 16px 24px; 16 | overflow-y: auto; 17 | min-height: 0; 18 | } 19 | .dtc-modal-alert { 20 | border-radius: 6px; 21 | margin: 4px; 22 | } 23 | } 24 | .ant-modal-header, 25 | .ant-modal-footer { 26 | flex: 0 1 auto; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/modal/index.tsx: -------------------------------------------------------------------------------- 1 | import { Modal as AntdModal } from 'antd'; 2 | import { ModalStaticFunctions } from 'antd/lib/modal/confirm'; 3 | 4 | import InternalModal from './modal'; 5 | 6 | type ModalType = typeof InternalModal & 7 | ModalStaticFunctions & { 8 | useModal: typeof AntdModal.useModal; 9 | destroyAll: () => void; 10 | config: typeof AntdModal.config; 11 | }; 12 | 13 | const { useModal, info, success, error, warning, confirm, destroyAll, config } = AntdModal; 14 | 15 | const Modal = InternalModal as ModalType; 16 | 17 | Object.assign(Modal, { 18 | useModal, 19 | info, 20 | success, 21 | error, 22 | warning, 23 | confirm, 24 | destroyAll, 25 | config, 26 | }); 27 | 28 | export { IModalProps } from './modal'; 29 | 30 | export default Modal; 31 | -------------------------------------------------------------------------------- /src/notFound/__tests__/notFound.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { cleanup, render } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | 5 | import NotFound from '..'; 6 | 7 | describe('test NotFound', () => { 8 | afterEach(cleanup); 9 | 10 | test('renders not found message', () => { 11 | const { getByText } = render(); 12 | const element = getByText(/亲,是不是走错地方了?/i); 13 | expect(element).toBeInTheDocument(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/notFound/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NotFound 未匹配 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # NotFound 未匹配 10 | 11 | ## 何时使用 12 | 13 | 路由未匹配上的展示页 14 | 15 | ## 示例 16 | 17 | ```jsx 18 | /** 19 | * title: "基础使用" 20 | */ 21 | import React from 'react'; 22 | import { NotFound } from 'dt-react-component'; 23 | 24 | export default () => { 25 | return ; 26 | }; 27 | ``` 28 | -------------------------------------------------------------------------------- /src/notFound/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FrownOutlined } from '@ant-design/icons'; 3 | import classNames from 'classnames'; 4 | 5 | import './style.scss'; 6 | 7 | const NotFound: React.FC = function ({ 8 | className, 9 | style, 10 | }: { 11 | className?: string; 12 | style?: React.CSSProperties; 13 | }) { 14 | return ( 15 |
16 |

17 | 亲,是不是走错地方了? 18 |

19 |
20 | ); 21 | }; 22 | 23 | export default NotFound; 24 | -------------------------------------------------------------------------------- /src/notFound/style.scss: -------------------------------------------------------------------------------- 1 | .dtc-not-found { 2 | line-height: 200px; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /src/progressBar/__test__/progressBar.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect'; 2 | 3 | import ProgressBar from '..'; 4 | 5 | describe('test ProgressBar', () => { 6 | const progressBar = ProgressBar; 7 | 8 | test('show method should add progress bar and loading image to the DOM', () => { 9 | progressBar.show(); 10 | // show 方法内有延时 11 | setTimeout(() => { 12 | expect(progressBar.hasAdded()).toBe(true); 13 | }, 200); 14 | }); 15 | 16 | test('hide method should remove progress bar and loading image from the DOM', () => { 17 | progressBar.show(); 18 | progressBar.hide(); 19 | expect(progressBar.hasAdded()).toBe(false); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/progressBar/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ProgressBar 网络请求进度条 3 | group: 组件 4 | toc: content 5 | demo: 6 | cols: 2 7 | --- 8 | 9 | # ProgressBar 网络请求进度条 10 | 11 | ## 何时使用 12 | 13 | 网络请求过程提示进度条 14 | 15 | ## 示例 16 | 17 | ```jsx 18 | /** 19 | * iframe: "true" 20 | * title: "基础使用" 21 | */ 22 | import React from 'react'; 23 | import { ProgressBar } from 'dt-react-component'; 24 | import { Button } from 'antd'; 25 | 26 | export default () => { 27 | return ( 28 | <> 29 | 38 | 45 | 46 | ); 47 | }; 48 | ``` 49 | 50 | ## API 51 | 52 | | 参数 | 说明 | 类型 | 默认值 | 53 | | ---- | ---------- | ---------- | ------ | 54 | | show | 显示进度条 | `Function` | - | 55 | | hide | 隐藏进度条 | `Function` | - | 56 | -------------------------------------------------------------------------------- /src/progressBar/style.scss: -------------------------------------------------------------------------------- 1 | $prefix: "dtc-progress"; 2 | 3 | .#{$prefix} { 4 | &-bar { 5 | position: fixed; 6 | top: 0; 7 | left: -100%; 8 | z-index: 10000; 9 | width: 100%; 10 | height: 1px; 11 | background: #2491F7; 12 | box-shadow: 0 1px 3px #2491F7; 13 | animation: dtc-progress-loading 1s ease-in-out infinite; 14 | } 15 | &-img { 16 | position: fixed; 17 | top: 0; 18 | left: 0; 19 | width: 30px; 20 | height: 30px; 21 | z-index: 10000; 22 | } 23 | } 24 | 25 | @keyframes dtc-progress-loading { 26 | to { 27 | transform: translateX(200%); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/progressLine/__test__/progressLine.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | 5 | import ProgressLine from '..'; 6 | 7 | describe('test ProgressLine', () => { 8 | it('renders title and percent', () => { 9 | const { getByText } = render(); 10 | expect(getByText('Test Title')).toBeInTheDocument(); 11 | expect(getByText('50%')).toBeInTheDocument(); 12 | }); 13 | 14 | it('renders with custom color', () => { 15 | const { getByTestId } = render(); 16 | expect(getByTestId('progress-line')).toHaveStyle('background-color: #FF0000'); 17 | }); 18 | 19 | it('renders with custom width', () => { 20 | const { getByTestId } = render(); 21 | expect(getByTestId('progress-line-wrap')).toHaveStyle('width: 500px'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/progressLine/demos/color.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { ProgressLine } from 'dt-react-component'; 3 | 4 | export default () => { 5 | const [color, setColor] = useState('#e66465'); 6 | 7 | return ( 8 | <> 9 | setColor(e.target.value)} 13 | style={{ marginBottom: 16 }} 14 | /> 15 | 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/progressLine/demos/percent.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { InputNumber } from 'antd'; 3 | import { ProgressLine } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [num, setNum] = useState(60); 7 | 8 | return ( 9 | <> 10 | setNum(value)} 15 | style={{ marginBottom: 16 }} 16 | /> 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/progressLine/demos/title.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Input } from 'antd'; 3 | import { ProgressLine } from 'dt-react-component'; 4 | 5 | export default () => { 6 | const [title, setTitle] = useState('衍生标签:35'); 7 | 8 | return ( 9 | <> 10 | setTitle(e.target.value)} 13 | style={{ marginBottom: 16, width: 300 }} 14 | /> 15 | 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/progressLine/demos/width.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { ProgressLine } from 'dt-react-component'; 3 | 4 | export default () => { 5 | const [width, setWidth] = useState('200'); 6 | 7 | return ( 8 | <> 9 | setWidth(e.target.value)} 15 | style={{ marginBottom: 16 }} 16 | /> 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/progressLine/style.scss: -------------------------------------------------------------------------------- 1 | $prefix: "dtc-progress-line"; 2 | 3 | .#{$prefix} { 4 | width: 100%; 5 | &-title { 6 | display: flex; 7 | justify-content: space-between; 8 | margin-bottom: 6px; 9 | } 10 | &-content { 11 | height: 22px; 12 | font-size: 12px; 13 | font-family: PingFangSC-Regular, "PingFang SC"; 14 | color: #666; 15 | line-height: 22px; 16 | // title 超出省略 17 | &:nth-child(3n + 1) { 18 | white-space: nowrap; 19 | text-overflow: ellipsis; 20 | overflow: hidden; 21 | } 22 | } 23 | &-wrap { 24 | height: 6px; 25 | background: #E8E8E8; 26 | border-radius: 3px; 27 | } 28 | &-line { 29 | height: 6px; 30 | border-radius: 3px; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/resize/demos/basic.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | import { Resize } from 'dt-react-component'; 3 | 4 | export default () => { 5 | const [innerWidth, setInnerWidth] = useState(window.innerWidth); 6 | const [innerHeight, setInnerHeight] = useState(window.innerHeight); 7 | 8 | const onResize = useCallback(() => { 9 | setInnerWidth(window.innerWidth); 10 | setInnerHeight(window.innerHeight); 11 | }, []); 12 | 13 | return ( 14 | 15 |
16 |
window高度: {innerWidth}
17 |
window宽度: {innerHeight}
18 |
19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/resize/demos/observerEle.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useRef, useState } from 'react'; 2 | import { Resize } from 'dt-react-component'; 3 | 4 | export default () => { 5 | const [clientWidth, setWidth] = useState(0); 6 | const [clientHegiht, setHegiht] = useState(0); 7 | const textareaRef = useRef(null); 8 | 9 | const onResize = useCallback(() => { 10 | setWidth(textareaRef.current!.clientWidth); 11 | setHegiht(textareaRef.current!.clientHeight); 12 | }, []); 13 | 14 | return ( 15 | 16 |