diff --git a/.dumi/global.ts b/.dumi/global.ts new file mode 100644 index 000000000..53535b0aa --- /dev/null +++ b/.dumi/global.ts @@ -0,0 +1,3 @@ +import { webVitals } from './vitals'; + +webVitals({ debug: true }); diff --git a/.dumi/hooks/useLayoutState.ts b/.dumi/hooks/useLayoutState.ts index e69486fcc..9c79d31af 100644 --- a/.dumi/hooks/useLayoutState.ts +++ b/.dumi/hooks/useLayoutState.ts @@ -1,13 +1,11 @@ import { startTransition, useState } from 'react'; -const useLayoutState = ( - ...args: Parameters> -): ReturnType> => { - const [state, setState] = useState(...args); +const useLayoutState = (args: S): ReturnType> => { + const [state, setState] = useState(args); - const setLayoutState: typeof setState = (...setStateArgs) => { + const setLayoutState: typeof setState = setStateArgs => { startTransition(() => { - setState(...setStateArgs); + setState(setStateArgs); }); }; diff --git a/.dumi/theme/SiteThemeProvider.tsx b/.dumi/theme/SiteThemeProvider.tsx index 69ae0aa3d..39053b94b 100644 --- a/.dumi/theme/SiteThemeProvider.tsx +++ b/.dumi/theme/SiteThemeProvider.tsx @@ -1,4 +1,5 @@ import { ConfigProvider, token } from '@oceanbase/design'; +import type { ThemeConfig } from '@oceanbase/design'; import { ChartProvider } from '@oceanbase/charts'; import type { ThemeProviderProps } from 'antd-style'; import { ThemeProvider } from 'antd-style'; @@ -8,7 +9,13 @@ import useLocale from '../hooks/useLocale'; import SiteContext from './slots/SiteContext'; import zhCN from '../../packages/design/src/locale/zh-CN'; -const SiteThemeProvider: FC = ({ children, theme, ...rest }) => { +const SiteThemeProvider: FC< + ThemeProviderProps & { + theme: ThemeConfig & { + isDark?: boolean; + }; + } +> = ({ children, theme, ...rest }) => { const { getPrefixCls, iconPrefixCls } = useContext(ConfigProvider.ConfigContext); const rootPrefixCls = getPrefixCls(); @@ -37,7 +44,7 @@ const SiteThemeProvider: FC = ({ children, theme, ...rest }) > diff --git a/.dumi/theme/common/DirectionIcon.tsx b/.dumi/theme/common/DirectionIcon.tsx index a19f25f8a..ccb839494 100644 --- a/.dumi/theme/common/DirectionIcon.tsx +++ b/.dumi/theme/common/DirectionIcon.tsx @@ -1,13 +1,14 @@ import Icon from '@oceanbase/icons'; import type { DirectionType } from '@oceanbase/design/es/config-provider'; import React from 'react'; +import { Interpolation, Theme } from '@emotion/react'; const ltrD = 'M448 64l512 0 0 128-128 0 0 768-128 0 0-768-128 0 0 768-128 0 0-448c-123.712 0-224-100.288-224-224s100.288-224 224-224zM64 448l256 224-256 224z'; const rtlD = 'M256 64l512 0 0 128-128 0 0 768-128 0 0-768-128 0 0 768-128 0 0-448c-123.712 0-224-100.288-224-224s100.288-224 224-224zM960 896l-256-224 256-224z'; -const DirectionIcon: React.FC<{ direction: DirectionType }> = props => ( +const DirectionIcon: React.FC<{ direction: DirectionType; css: Interpolation }> = props => ( diff --git a/.dumi/theme/common/PrevAndNext.tsx b/.dumi/theme/common/PrevAndNext.tsx index e55df009e..6cb357f9f 100644 --- a/.dumi/theme/common/PrevAndNext.tsx +++ b/.dumi/theme/common/PrevAndNext.tsx @@ -1,7 +1,7 @@ import { LeftOutlined, RightOutlined } from '@oceanbase/icons'; import { css } from '@emotion/react'; import type { MenuProps } from '@oceanbase/design'; -import type { MenuItemType } from '@oceanbase/design/es/menu/hooks/useItems'; +import type { MenuItemType } from '@oceanbase/design/es/menu'; import React, { useMemo } from 'react'; import useMenu from '../../hooks/useMenu'; import useSiteToken from '../../hooks/useSiteToken'; diff --git a/.dumi/theme/layouts/GlobalLayout.tsx b/.dumi/theme/layouts/GlobalLayout.tsx index b10d46199..faca35068 100644 --- a/.dumi/theme/layouts/GlobalLayout.tsx +++ b/.dumi/theme/layouts/GlobalLayout.tsx @@ -8,7 +8,9 @@ import { import { App, theme as obTheme } from '@oceanbase/design'; import type { DirectionType } from '@oceanbase/design/es/config-provider'; import { usePrefersColor, createSearchParams, useOutlet, useSearchParams } from 'dumi'; +import { IColorValue } from 'dumi/dist/client/theme-api/usePrefersColor'; import React, { useCallback, useEffect, useMemo } from 'react'; +import { Analytics } from '@vercel/analytics/react'; import useLayoutState from '../../hooks/useLayoutState'; import SiteThemeProvider from '../SiteThemeProvider'; import useLocation from '../../hooks/useLocation'; @@ -69,9 +71,9 @@ const GlobalLayout: React.FC = () => { nextSearchParams = createSearchParams({ ...nextSearchParams, theme: value.filter(t => t !== 'light'), - }); + } as URLSearchParams & any); // Set theme of dumi site - setPrefersColor(value?.filter(t => t === 'dark' || t === 'light')?.[0]); + setPrefersColor(value?.filter(t => t === 'dark' || t === 'light')?.[0] as IColorValue); } }); @@ -92,7 +94,7 @@ const GlobalLayout: React.FC = () => { setSiteState({ theme: _theme, direction: _direction === 'rtl' ? 'rtl' : 'ltr' }); // Set theme of dumi site - setPrefersColor(_theme?.filter(t => t === 'dark' || t === 'light')?.[0]); + setPrefersColor(_theme?.filter(t => t === 'dark' || t === 'light')?.[0] as IColorValue); // Handle isMobile updateMobileMode(); @@ -135,6 +137,7 @@ const GlobalLayout: React.FC = () => { onChange={nextTheme => updateSiteConfig({ theme: nextTheme })} /> )} + diff --git a/.dumi/vitals.ts b/.dumi/vitals.ts new file mode 100644 index 000000000..7cc71d50d --- /dev/null +++ b/.dumi/vitals.ts @@ -0,0 +1,56 @@ +/* ref: https://vercel.com/docs/speed-insights/api */ +import { getCLS, getFCP, getFID, getLCP, getTTFB } from 'web-vitals'; + +const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals'; + +function getConnectionSpeed() { + return 'connection' in navigator && + navigator['connection'] && + 'effectiveType' in (navigator as any)['connection'] + ? navigator['connection']['effectiveType'] + : ''; +} + +function sendToAnalytics(metric, options) { + const analyticsId = process.env.VERCEL_ANALYTICS_ID; + + const body = { + dsn: analyticsId, + id: metric.id, + page: window.location.pathname, + href: window.location.href, + event_name: metric.name, + value: metric.value.toString(), + speed: getConnectionSpeed(), + }; + + if (options.debug) { + console.log('[Analytics]', metric.name, JSON.stringify(body, null, 2)); + } + + const blob = new Blob([new URLSearchParams(body).toString()], { + // This content type is necessary for `sendBeacon` + type: 'application/x-www-form-urlencoded', + }); + if (navigator.sendBeacon) { + navigator.sendBeacon(vitalsUrl, blob); + } else + fetch(vitalsUrl, { + body: blob, + method: 'POST', + credentials: 'omit', + keepalive: true, + }); +} + +export function webVitals(options) { + try { + getFID(metric => sendToAnalytics(metric, options)); + getTTFB(metric => sendToAnalytics(metric, options)); + getLCP(metric => sendToAnalytics(metric, options)); + getCLS(metric => sendToAnalytics(metric, options)); + getFCP(metric => sendToAnalytics(metric, options)); + } catch (err) { + console.error('[Analytics]', err); + } +} diff --git a/.dumirc.ts b/.dumirc.ts index 31a999814..77c2e5b70 100644 --- a/.dumirc.ts +++ b/.dumirc.ts @@ -145,6 +145,7 @@ export default defineConfig({ children: [ { title: 'BasicLayout 导航和布局', link: '/biz-components/basic-layout' }, { title: 'PageContainer 页容器', link: '/biz-components/page-container' }, + { title: 'Login 登录页', link: '/biz-components/login' }, // { title: 'NavMenu', link: '/biz-components/nav-menu' }, { title: 'Welcome 欢迎页', link: '/biz-components/welcome' }, ], @@ -160,6 +161,10 @@ export default defineConfig({ title: '其他', children: [ { title: 'Action 操作项', link: '/biz-components/action' }, + { + title: 'ContentWithQuestion 问号旁提示', + link: '/biz-components/content-with-question', + }, { title: 'ContentWithIcon 文字旁提示', link: '/biz-components/content-with-icon', diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 44d9ca742..7e3c1a39b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,3 +14,5 @@ updates: - dependency-name: "@antv/g6" # execa latest 7.x works only in ES module - dependency-name: "execa" + # execa latest 5.x works only in ES module + - dependency-name: "chalk" diff --git a/package.json b/package.json index 1b47baa81..3c50b07be 100644 --- a/package.json +++ b/package.json @@ -39,10 +39,10 @@ }, "devDependencies": { "@ant-design/colors": "^7.0.0", - "@ant-design/cssinjs": "^1.17.0", + "@ant-design/cssinjs": "^1.17.2", "@ant-design/icons": "^5.2.6", - "@babel/cli": "^7.22.15", - "@babel/preset-env": "^7.22.20", + "@babel/cli": "^7.23.0", + "@babel/preset-env": "^7.23.2", "@ctrl/tinycolor": "^4.0.2", "@emotion/babel-preset-css-prop": "^11.11.0", "@emotion/css": "^11.11.2", @@ -51,40 +51,42 @@ "@chenshuai2144/less2cssinjs": "^1.0.7", "@qixian.cs/github-contributors-list": "^1.1.0", "@stackblitz/sdk": "^1.9.0", - "@testing-library/dom": "^9.3.1", - "@testing-library/jest-dom": "^6.1.2", + "@testing-library/dom": "^9.3.3", + "@testing-library/jest-dom": "^6.1.4", "@testing-library/react": "^14.0.0", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.5.1", - "@types/jest": "^29.5.4", - "@types/node": "^20.6.2", - "@types/react": "^18.2.21", + "@types/jest": "^29.5.5", + "@types/lodash": "^4.14.199", + "@types/node": "^20.8.6", + "@types/react": "^18.2.28", "@umijs/fabric": "^4.0.1", - "@umijs/test": "^4.0.79", - "antd": "^5.9.0", + "@umijs/test": "^4.0.86", + "@vercel/analytics": "^1.1.1", + "antd": "^5.10.1", "antd-style": "^3.5.0", "antd-token-previewer": "^1.1.0", - "babel-jest": "^29.6.4", + "babel-jest": "^29.7.0", "classnames": "^2.3.2", "copy-to-clipboard": "^3.3.3", "cross-env": "^7.0.3", - "dayjs": "^1.11.9", - "dumi": "^2.2.10", - "eslint-plugin-jest": "^27.2.3", + "dayjs": "^1.11.10", + "dumi": "^2.2.13", + "eslint-plugin-jest": "^27.4.2", "execa": "^5.1.1", - "father": "^4.3.1", + "father": "^4.3.5", "fs-extra": "^11.1.1", - "gh-pages": "^5.0.0", + "gh-pages": "^6.0.0", "html2sketch": "^1.0.2", "identity-obj-proxy": "^3.0.0", - "jest": "^29.6.4", + "jest": "^29.7.0", "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^29.7.0", "jest-fetch-mock": "^3.0.3", "jest-svg-transformer": "^1.0.0", "jsonml-to-react-element": "^1.1.11", "jsonml.js": "^0.1.0", - "lerna": "^7.2.0", + "lerna": "^7.3.1", "lint-staged": "^14.0.1", "lodash": "^4.17.21", "lz-string": "^1.5.0", @@ -92,24 +94,25 @@ "prettier": "^3.0.3", "prismjs": "^1.29.0", "rc-checkbox": "^3.1.0", - "rc-drawer": "^6.4.1", + "rc-drawer": "^6.5.2", "rc-footer": "^0.6.8", - "rc-tabs": "^12.12.1", - "rc-util": "^5.37.0", + "rc-tabs": "^12.13.1", + "rc-util": "^5.38.0", "react": "^18.2.0", "react-color": "^2.19.3", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.2.0", - "react-fast-marquee": "^1.6.0", - "react-intl": "^6.4.7", - "react-router-dom": "^6.15.0", + "react-fast-marquee": "^1.6.2", + "react-intl": "^6.5.0", + "react-router-dom": "^6.17.0", "runscript": "^1.5.3", "svg-jest": "^1.0.1", "sylvanas": "^0.6.1", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "typescript": "^5.2.2", - "vanilla-jsoneditor": "^0.18.3", + "vanilla-jsoneditor": "^0.18.9", + "web-vitals": "^3.5.0", "yorkie": "^2.0.0" }, "packageManager": "pnpm@8.6.0", diff --git a/packages/charts/package.json b/packages/charts/package.json index a784cafad..f3df73724 100644 --- a/packages/charts/package.json +++ b/packages/charts/package.json @@ -1,6 +1,6 @@ { "name": "@oceanbase/charts", - "version": "0.2.10", + "version": "0.2.14", "description": "The Chart library for OceanBase", "homepage": "https://github.com/oceanbase/oceanbase-design/packages/charts", "repository": { @@ -31,14 +31,15 @@ "@oceanbase/util": "workspace:^", "classnames": "^2.3.2", "lodash": "^4.17.21", + "rc-util": "^5.38.0", "tinycolor2": "^1.6.0", "use-resize-observer": "^9.1.0" }, "devDependencies": { - "@types/tinycolor2": "^1.4.3" + "@types/tinycolor2": "^1.4.4" }, "peerDependencies": { - "react": "^16.8.4", - "react-dom": "^16.8.4" + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } } diff --git a/packages/charts/src/Area/__tests__/__snapshots__/ref.test.tsx.snap b/packages/charts/src/Area/__tests__/__snapshots__/ref.test.tsx.snap new file mode 100644 index 000000000..5594283d9 --- /dev/null +++ b/packages/charts/src/Area/__tests__/__snapshots__/ref.test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Area ref ref 1`] = ` +
+
+ +
+ + +`; diff --git a/packages/charts/src/Area/__tests__/ref.test.tsx b/packages/charts/src/Area/__tests__/ref.test.tsx new file mode 100644 index 000000000..aed64fa01 --- /dev/null +++ b/packages/charts/src/Area/__tests__/ref.test.tsx @@ -0,0 +1,23 @@ +import React, { useEffect, useRef } from 'react'; +import { render } from '@testing-library/react'; +import { Area } from '@oceanbase/charts'; + +describe('Area ref', () => { + it('ref', () => { + const DemoArea = () => { + const ref = useRef(null); + useEffect(() => { + expect(ref.current).not.toBeNull(); + expect(typeof ref.current?.getChart).toBe('function'); + }, []); + const config = { + data: [], + xField: 'x', + yField: 'y', + }; + return ; + }; + const { asFragment } = render(); + expect(asFragment().firstChild).toMatchSnapshot(); + }); +}); diff --git a/packages/charts/src/Area/index.tsx b/packages/charts/src/Area/index.tsx index 096667aeb..7f4e13456 100644 --- a/packages/charts/src/Area/index.tsx +++ b/packages/charts/src/Area/index.tsx @@ -1,8 +1,8 @@ -import React, { forwardRef } from 'react'; +import React, { useRef, forwardRef } from 'react'; import type { AreaConfig as AntAreaConfig } from '@ant-design/charts'; import { Area as AntArea } from '@ant-design/charts'; import { sortByMoment } from '@oceanbase/util'; -import useResizeObserver from 'use-resize-observer'; +import { composeRef } from 'rc-util/es/ref'; import type { Tooltip } from '../hooks/useTooltipScrollable'; import useTooltipScrollable from '../hooks/useTooltipScrollable'; import { useTheme } from '../theme'; @@ -14,17 +14,19 @@ export interface AreaConfig extends AntAreaConfig { theme?: Theme; } -const Area: React.FC = forwardRef( +const Area = forwardRef( ( { data, line, xField, xAxis, yAxis, tooltip, legend, interactions, theme, ...restConfig }, ref ) => { const themeConfig = useTheme(theme); - const { ref: containerRef, height: containerHeight } = useResizeObserver({ - // 包含 padding 和 border - box: 'border-box', - }); - const tooltipConfig = useTooltipScrollable(tooltip, containerHeight); + + const chartRef = useRef(null); + const mergedRef = composeRef(ref, chartRef); + const tooltipConfig = useTooltipScrollable( + tooltip, + chartRef.current?.getChart()?.chart?.height + ); const newConfig: AreaConfig = { data: @@ -91,11 +93,7 @@ const Area: React.FC = forwardRef( theme: themeConfig.theme, ...restConfig, }; - return ( -
- -
- ); + return ; } ); diff --git a/packages/charts/src/Bar/index.tsx b/packages/charts/src/Bar/index.tsx index 91f0f2891..a88e1d90b 100644 --- a/packages/charts/src/Bar/index.tsx +++ b/packages/charts/src/Bar/index.tsx @@ -17,7 +17,7 @@ export interface BarConfig extends AntBarConfig { theme?: Theme; } -const Bar: React.FC = forwardRef( +const Bar = forwardRef( ( { data, diff --git a/packages/charts/src/Column/index.tsx b/packages/charts/src/Column/index.tsx index acdd6dc3d..0c476b056 100644 --- a/packages/charts/src/Column/index.tsx +++ b/packages/charts/src/Column/index.tsx @@ -10,7 +10,7 @@ export interface ColumnConfig extends AntColumnConfig { theme?: Theme; } -const Column: React.FC = forwardRef( +const Column = forwardRef( ( { data, isStack, isGroup, isRange, seriesField, label, xAxis, legend, theme, ...restConfig }, ref diff --git a/packages/charts/src/DualAxes/__tests__/__snapshots__/ref.test.tsx.snap b/packages/charts/src/DualAxes/__tests__/__snapshots__/ref.test.tsx.snap new file mode 100644 index 000000000..62365b71f --- /dev/null +++ b/packages/charts/src/DualAxes/__tests__/__snapshots__/ref.test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DualAxes ref ref 1`] = ` +
+
+ +
+ + +`; diff --git a/packages/charts/src/DualAxes/__tests__/ref.test.tsx b/packages/charts/src/DualAxes/__tests__/ref.test.tsx new file mode 100644 index 000000000..abd4e82e5 --- /dev/null +++ b/packages/charts/src/DualAxes/__tests__/ref.test.tsx @@ -0,0 +1,23 @@ +import React, { useEffect, useRef } from 'react'; +import { render } from '@testing-library/react'; +import { DualAxes } from '@oceanbase/charts'; + +describe('DualAxes ref', () => { + it('ref', () => { + const DemoDualAxes = () => { + const ref = useRef(null); + useEffect(() => { + expect(ref.current).not.toBeNull(); + expect(typeof ref.current?.getChart).toBe('function'); + }, []); + const config = { + data: [[], []], + xField: 'x', + yField: ['y1', 'y2'], + }; + return ; + }; + const { asFragment } = render(); + expect(asFragment().firstChild).toMatchSnapshot(); + }); +}); diff --git a/packages/charts/src/DualAxes/index.tsx b/packages/charts/src/DualAxes/index.tsx index b748de1fa..d2346ac01 100644 --- a/packages/charts/src/DualAxes/index.tsx +++ b/packages/charts/src/DualAxes/index.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef } from 'react'; +import React, { useRef, forwardRef } from 'react'; import type { DualAxesConfig as AntDualAxesConfig } from '@ant-design/charts'; import { DualAxes as AntDualAxes } from '@ant-design/charts'; // @ts-ignore @@ -6,7 +6,7 @@ import type { GeometryColumnOption } from '@antv/g2plot/esm/plots/dual-axes/type // @ts-ignore import type { Axis } from '@antv/g2plot/esm/types/axis'; import { sortByMoment } from '@oceanbase/util'; -import useResizeObserver from 'use-resize-observer'; +import { composeRef } from 'rc-util/es/ref'; import type { Tooltip } from '../hooks/useTooltipScrollable'; import useTooltipScrollable from '../hooks/useTooltipScrollable'; import { useTheme } from '../theme'; @@ -20,22 +20,23 @@ export interface DualAxesConfig extends Omit { theme?: Theme; } -const DualAxes: React.FC = forwardRef( +const DualAxes = forwardRef( ( { data, xField, yField, xAxis, yAxis, tooltip, legend, geometryOptions, theme, ...restConfig }, ref ) => { const themeConfig = useTheme(theme); + const chartRef = useRef(null); + const mergedRef = composeRef(ref, chartRef); + const tooltipConfig = useTooltipScrollable( + tooltip, + chartRef.current?.getChart()?.chart?.height + ); + const yField1 = yField?.[0]; const yField2 = yField?.[1]; - const { ref: containerRef, height: containerHeight } = useResizeObserver({ - // 包含 padding 和 border - box: 'border-box', - }); - const tooltipConfig = useTooltipScrollable(tooltip, containerHeight); - const newConfig: DualAxesConfig = { data: // xAxis.type 为时间轴时,需要对 data 进行排序 @@ -140,11 +141,7 @@ const DualAxes: React.FC = forwardRef( theme: themeConfig.theme, ...restConfig, }; - return ( -
- -
- ); + return ; } ); diff --git a/packages/charts/src/Gauge/index.tsx b/packages/charts/src/Gauge/index.tsx index 7e728b23d..7a5898c58 100644 --- a/packages/charts/src/Gauge/index.tsx +++ b/packages/charts/src/Gauge/index.tsx @@ -10,7 +10,7 @@ export interface GaugeConfig extends AntGaugeConfig { theme?: Theme; } -const Gauge: React.FC = forwardRef( +const Gauge = forwardRef( ({ percent, range, axis, indicator, statistic, theme, ...restConfig }, ref) => { const themeConfig = useTheme(theme); diff --git a/packages/charts/src/Histogram/index.tsx b/packages/charts/src/Histogram/index.tsx index de16b2b6e..e8f03d272 100644 --- a/packages/charts/src/Histogram/index.tsx +++ b/packages/charts/src/Histogram/index.tsx @@ -9,7 +9,7 @@ export interface HistogramConfig extends AntHistogramConfig { theme?: Theme; } -const Histogram: React.FC = forwardRef( +const Histogram = forwardRef( ({ binWidth, columnStyle, meta, xAxis, legend, theme, ...restConfig }, ref) => { const themeConfig = useTheme(theme); diff --git a/packages/charts/src/Line/__tests__/__snapshots__/ref.test.tsx.snap b/packages/charts/src/Line/__tests__/__snapshots__/ref.test.tsx.snap new file mode 100644 index 000000000..472492495 --- /dev/null +++ b/packages/charts/src/Line/__tests__/__snapshots__/ref.test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Line ref ref 1`] = ` +
+
+ +
+ + +`; diff --git a/packages/charts/src/Line/__tests__/ref.test.tsx b/packages/charts/src/Line/__tests__/ref.test.tsx new file mode 100644 index 000000000..1f9f917e8 --- /dev/null +++ b/packages/charts/src/Line/__tests__/ref.test.tsx @@ -0,0 +1,23 @@ +import React, { useEffect, useRef } from 'react'; +import { render } from '@testing-library/react'; +import { Line } from '@oceanbase/charts'; + +describe('Line ref', () => { + it('ref', () => { + const DemoLine = () => { + const ref = useRef(null); + useEffect(() => { + expect(ref.current).not.toBeNull(); + expect(typeof ref.current?.getChart).toBe('function'); + }, []); + const config = { + data: [], + xField: 'x', + yField: 'y', + }; + return ; + }; + const { asFragment } = render(); + expect(asFragment().firstChild).toMatchSnapshot(); + }); +}); diff --git a/packages/charts/src/Line/demo/auto-fit.tsx b/packages/charts/src/Line/demo/auto-fit.tsx new file mode 100644 index 000000000..81b1eca78 --- /dev/null +++ b/packages/charts/src/Line/demo/auto-fit.tsx @@ -0,0 +1,36 @@ +import React, { useState, useEffect } from 'react'; +import { Line } from '@oceanbase/charts'; + +export default () => { + const [data, setData] = useState([]); + const asyncFetch = () => { + fetch('https://gw.alipayobjects.com/os/bmw-prod/55424a73-7cb8-4f79-b60d-3ab627ac5698.json') + .then(response => response.json()) + .then(json => setData(json)) + .catch(error => { + console.log('fetch data failed', error); + }); + }; + useEffect(() => { + asyncFetch(); + }, []); + const config = { + data, + xField: 'year', + yField: 'value', + seriesField: 'category', + xAxis: { + type: 'time', + }, + yAxis: { + label: { + // 数值格式化为千分位 + formatter: v => `${v}`.replace(/\d{1,3}(?=(\d{3})+$)/g, s => `${s},`), + }, + }, + style: { + height: '50vh', + }, + }; + return ; +}; diff --git a/packages/charts/src/Line/index.md b/packages/charts/src/Line/index.md index 857d6bce5..149dc8eed 100644 --- a/packages/charts/src/Line/index.md +++ b/packages/charts/src/Line/index.md @@ -13,6 +13,8 @@ nav: + + ## API diff --git a/packages/charts/src/Line/index.tsx b/packages/charts/src/Line/index.tsx index e0f3894e2..2cc1104a7 100644 --- a/packages/charts/src/Line/index.tsx +++ b/packages/charts/src/Line/index.tsx @@ -1,8 +1,8 @@ -import React, { forwardRef } from 'react'; +import React, { forwardRef, useRef } from 'react'; import { sortByMoment } from '@oceanbase/util'; import type { LineConfig as AntLineConfig } from '@ant-design/charts'; import { Line as AntLine } from '@ant-design/charts'; -import useResizeObserver from 'use-resize-observer'; +import { composeRef } from 'rc-util/es/ref'; import type { Tooltip } from '../hooks/useTooltipScrollable'; import useTooltipScrollable from '../hooks/useTooltipScrollable'; import { useTheme } from '../theme'; @@ -14,18 +14,19 @@ export interface LineConfig extends AntLineConfig { theme?: Theme; } -const Line: React.FC = forwardRef( +const Line = forwardRef( ( { data, stepType, xField, xAxis, yAxis, tooltip, legend, interactions, theme, ...restConfig }, ref ) => { const themeConfig = useTheme(theme); - const { ref: containerRef, height: containerHeight } = useResizeObserver({ - // 包含 padding 和 border - box: 'border-box', - }); - const tooltipConfig = useTooltipScrollable(tooltip, containerHeight); + const chartRef = useRef(null); + const mergedRef = composeRef(ref, chartRef); + const tooltipConfig = useTooltipScrollable( + tooltip, + chartRef.current?.getChart()?.chart?.height + ); const newConfig: LineConfig = { data: @@ -86,11 +87,7 @@ const Line: React.FC = forwardRef( theme: themeConfig.theme, ...restConfig, }; - return ( -
- -
- ); + return ; } ); diff --git a/packages/charts/src/Liquid/index.tsx b/packages/charts/src/Liquid/index.tsx index ba07157ce..a9828ea0c 100644 --- a/packages/charts/src/Liquid/index.tsx +++ b/packages/charts/src/Liquid/index.tsx @@ -39,7 +39,7 @@ export interface LiquidConfig extends AntLiquidConfig { theme?: Theme; } -const Liquid: React.FC = forwardRef( +const Liquid = forwardRef( ( { height = 400, diff --git a/packages/charts/src/Pie/demo/state-change.tsx b/packages/charts/src/Pie/demo/state-change.tsx index 82d4ae050..a2c00c6ff 100644 --- a/packages/charts/src/Pie/demo/state-change.tsx +++ b/packages/charts/src/Pie/demo/state-change.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState } from 'react'; import { Pie } from '@oceanbase/charts'; const Demo: React.FC = () => { @@ -30,16 +30,13 @@ const Demo: React.FC = () => { }, ]); - // memo function to avoid re-render - const onReady = useCallback(plot => { - console.log(plot); - }, []); - const config = { data, angleField: 'value', colorField: 'type', - onReady, + onReady: plot => { + console.log(plot); + }, }; return ( @@ -50,7 +47,7 @@ const Demo: React.FC = () => { setCount(count + 1); }} style={{ - marginLeft: 16, + marginLeft: 8, }} > 外部状态改变不会重新渲染 @@ -73,7 +70,7 @@ const Demo: React.FC = () => { ]); }} style={{ - marginLeft: 16, + marginLeft: 8, }} > 数据改变重新渲染 diff --git a/packages/charts/src/Pie/index.tsx b/packages/charts/src/Pie/index.tsx index 110d2ba09..4fc33905a 100644 --- a/packages/charts/src/Pie/index.tsx +++ b/packages/charts/src/Pie/index.tsx @@ -55,7 +55,7 @@ export interface PieConfig extends AntPieConfig { theme?: Theme; } -const Pie: React.FC = forwardRef( +const Pie = forwardRef( ( { data, diff --git a/packages/charts/src/Tiny/Progress.tsx b/packages/charts/src/Tiny/Progress.tsx index f6d52f1ae..6f8d5c087 100644 --- a/packages/charts/src/Tiny/Progress.tsx +++ b/packages/charts/src/Tiny/Progress.tsx @@ -26,7 +26,7 @@ export interface ProgressConfig extends AntProgressConfig { theme?: Theme; } -const Progress: React.FC = forwardRef( +const Progress = forwardRef( ( { compact = false, diff --git a/packages/charts/src/Tiny/TinyColumn.tsx b/packages/charts/src/Tiny/TinyColumn.tsx index f068535a3..c94d218e1 100644 --- a/packages/charts/src/Tiny/TinyColumn.tsx +++ b/packages/charts/src/Tiny/TinyColumn.tsx @@ -14,7 +14,7 @@ export interface TinyColumnConfig extends AntTinyColumnConfig { minColumnWidth?: number; } -const TinyColumn: React.FC = forwardRef( +const TinyColumn = forwardRef( ({ height = 60, columnStyle, label, theme, ...restConfig }, ref) => { const themeConfig = useTheme(theme); diff --git a/packages/charts/src/Tiny/TinyLine.tsx b/packages/charts/src/Tiny/TinyLine.tsx index dcb8ac043..bc0179726 100644 --- a/packages/charts/src/Tiny/TinyLine.tsx +++ b/packages/charts/src/Tiny/TinyLine.tsx @@ -9,7 +9,7 @@ export interface TinyLineConfig extends AntTinyLineConfig { theme?: Theme; } -const TinyLine: React.FC = forwardRef( +const TinyLine = forwardRef( ({ height = 60, color, lineStyle, point, theme, ...restConfig }, ref) => { const themeConfig = useTheme(theme); diff --git a/packages/charts/src/util/__tests__/custom-memo.test.ts b/packages/charts/src/util/__tests__/custom-memo.test.ts new file mode 100644 index 000000000..8a0e0a7af --- /dev/null +++ b/packages/charts/src/util/__tests__/custom-memo.test.ts @@ -0,0 +1,99 @@ +import { isEqualWithFunction } from '../custom-memo'; + +describe('custom-memo', () => { + it('isEqualWithFunction', () => { + expect( + isEqualWithFunction( + { + a: 1, + b: 2, + data: [{ x: 1, y: 2 }], + }, + { + a: 1, + b: 2, + data: [{ x: 1, y: 2 }], + } + ) + ).toEqual(true); + expect( + isEqualWithFunction( + { + a: 1, + b: 2, + data: [{ x: 1, y: 2 }], + // arrow function + fn: (x, y) => x + y, + }, + { + a: 1, + b: 2, + data: [{ x: 1, y: 2 }], + fn: (x, y) => x + y, + } + ) + ).toEqual(true); + expect( + isEqualWithFunction( + { + a: 1, + b: 2, + data: [{ x: 1, y: 2 }], + // normal function + fn: function (x, y) { + return x + y; + }, + }, + { + a: 1, + b: 2, + data: [{ x: 1, y: 2 }], + fn: function (x, y) { + return x + y; + }, + } + ) + ).toEqual(true); + expect( + isEqualWithFunction( + { + a: 1, + b: 2, + data: [{ x: 1, y: 2 }], + // normal function abbreviation + fn(x, y) { + return x + y; + }, + }, + { + a: 1, + b: 2, + data: [{ x: 1, y: 2 }], + fn(x, y) { + return x + y; + }, + } + ) + ).toEqual(true); + expect( + isEqualWithFunction( + { + a: 1, + b: 2, + data: [{ x: 1, y: 2 }], + fn: function (x, y) { + return x + y; + }, + }, + { + a: 1, + b: 2, + data: [{ x: 1, y: 2 }], + fn(x, y) { + return x + y; + }, + } + ) + ).toEqual(false); + }); +}); diff --git a/packages/charts/src/util/custom-memo.ts b/packages/charts/src/util/custom-memo.ts index 251c4a9cb..e8d5c9ed7 100644 --- a/packages/charts/src/util/custom-memo.ts +++ b/packages/charts/src/util/custom-memo.ts @@ -1,13 +1,28 @@ import { memo } from 'react'; import type { FunctionComponent } from 'react'; -import { isEqual } from 'lodash'; +import { isFunction, isEqualWith, isEqual, isPlainObject, maxBy } from 'lodash'; import type { BaseConfig, AllBaseConfig } from '@ant-design/charts'; +export function isEqualWithFunction(v1: any, v2: any) { + return isEqualWith(v1, v2, (value1, value2) => { + // function equals + if (isFunction(value1) && isFunction(value2)) { + return value1.toString() === value2.toString(); + } else if (isPlainObject(value1) && isPlainObject(value2)) { + const keys1 = Object.keys(value1); + const keys2 = Object.keys(value2); + const keys = maxBy([keys1, keys2], o => o.length); + return keys.every(key => isEqualWithFunction(value1[key], value2[key])); + } + return isEqual(value1, value2); + }); +} + export function customMemo

>( Component: FunctionComponent

, propsAreEqual?: (prevProps: Readonly

, nextProps: Readonly

) => boolean ) { return memo(Component, (prevProps, nextProps) => - propsAreEqual ? propsAreEqual(prevProps, nextProps) : isEqual(prevProps, nextProps) + propsAreEqual ? propsAreEqual(prevProps, nextProps) : isEqualWithFunction(prevProps, nextProps) ); } diff --git a/packages/codemod/README.md b/packages/codemod/README.md index 99e2c739b..16d5efc11 100644 --- a/packages/codemod/README.md +++ b/packages/codemod/README.md @@ -17,7 +17,7 @@ npx -p @oceanbase/codemod codemod src ### `antd-to-oceanbase-design` -import components and typs from `antd` and `@alipay/bigfish/antd` to `@oceanbase/design`. +import components and types from `antd` and `@alipay/bigfish/antd` to `@oceanbase/design`. ```diff import React from 'react'; @@ -43,7 +43,7 @@ import components and typs from `antd` and `@alipay/bigfish/antd` to `@oceanbase ### `obui-to-oceanbase-design-and-ui` -import components and typs from `antd` to `@oceanbase/design` and `@oceanbase/ui`. +import components and types from `antd` to `@oceanbase/design` and `@oceanbase/ui`. ```diff import React from 'react'; @@ -86,7 +86,7 @@ import `PageContainer` from `@alipay/tech-ui` to `@ant-design/pro-components` an ### `antd-and-ob-charts-to-oceanbase-charts` -import components and typs from `@ant-design/charts` and `@alipay/ob-charts` to `@oceanbase/charts`. +import components and types from `@ant-design/charts` and `@alipay/ob-charts` to `@oceanbase/charts`. ```diff import React from 'react'; @@ -118,15 +118,17 @@ import components and typs from `@ant-design/charts` and `@alipay/ob-charts` to ### `obutil-to-oceanbase-util` -import components and typs from `@alipay/ob-util` to `@oceanbase/util`. +import utils and hooks from `@alipay/ob-util` to `@oceanbase/util`. Additionally, it will rename `getTableData` to `useTableData` to follow hooks naming conventions. ```diff import React from 'react'; -- import { isNullValue, sortByNumber } from '@alipay/ob-util'; -+ import { isNullValue, sortByNumber } from '@oceanbase/util'; +- import { isNullValue, sortByNumber, getTableData } from '@alipay/ob-util'; ++ import { isNullValue, sortByNumber, useTableData } from '@oceanbase/util'; const Demo = () => { - return

; +- const { tableProps } = getTableData(fn, {}); ++ const { tableProps } = useTableData(fn, {}); + return
; }; export default Demo; diff --git a/packages/codemod/package.json b/packages/codemod/package.json index e51818283..309a6953e 100644 --- a/packages/codemod/package.json +++ b/packages/codemod/package.json @@ -1,6 +1,6 @@ { "name": "@oceanbase/codemod", - "version": "0.2.1", + "version": "0.2.5", "description": "Codemod for OceanBase Design upgrade", "keywords": [ "oceanbase", @@ -27,21 +27,21 @@ "command-exists": "^1.2.9", "execa": "^5.1.1", "find-up": "^4.1.0", - "glob": "^8.0.3", + "glob": "^10.3.10", "is-directory": "^0.3.1", "is-git-clean": "^1.1.0", "jscodeshift": "^0.15.0", - "lodash": "^4.17.15", + "lodash": "^4.17.21", "prettier": "^3.0.3", "read-pkg-up": "^9.1.0", - "semver": "^7.1.3", + "semver": "^7.5.4", "update-check": "^1.5.4", "yargs-parser": "^21.1.1" }, "devDependencies": { - "@types/jest": "^29.2.3", - "@types/jscodeshift": "^0.11.5", - "enzyme": "^3.0.0", + "@types/jest": "^29.5.5", + "@types/jscodeshift": "^0.11.7", + "enzyme": "^3.11.0", "enzyme-to-json": "^3.6.2" } } diff --git a/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/locale.input.js b/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/locale.input.js new file mode 100644 index 000000000..703989c0c --- /dev/null +++ b/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/locale.input.js @@ -0,0 +1,8 @@ +import enUS from '@alipay/ob-ui/es/locale/en-US'; +import zhCN from '@alipay/ob-ui/es/locale/zh-CN'; + +const Demo = () => { + return
; +}; + +export default Demo; diff --git a/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/locale.output.js b/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/locale.output.js new file mode 100644 index 000000000..b38747701 --- /dev/null +++ b/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/locale.output.js @@ -0,0 +1,8 @@ +import enUS from '@oceanbase/ui/es/locale/en-US'; +import zhCN from '@oceanbase/ui/es/locale/zh-CN'; + +const Demo = () => { + return
; +}; + +export default Demo; \ No newline at end of file diff --git a/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/obui.input.js b/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/obui.input.js index 94a892ead..060137c00 100644 --- a/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/obui.input.js +++ b/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/obui.input.js @@ -1,5 +1,6 @@ import React from 'react'; -import { Alert, Button, BasicLayout, Login, PageContainer } from '@alipay/ob-ui'; +import { Alert, Button, BackgroundTaskManager, BackgroundTaskManagerConstants, BasicLayout, Boundary, ConfigProvider, Login, PageContainer, theme } from '@alipay/ob-ui'; +import type { BackgroundTaskManagerRef, ITaskMgrPreset, ITaskMgrQueue, TaskMgrID } from '@alipay/ob-ui'; import type { BasicLayoutProps } from '@alipay/ob-ui/es/BasicLayout'; import type { LoginProps } from '@alipay/ob-ui/es/Login'; import type { PageContainerProps } from '@alipay/ob-ui/es/PageContainer'; diff --git a/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/obui.output.js b/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/obui.output.js index af90259f7..fa517be15 100644 --- a/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/obui.output.js +++ b/packages/codemod/transforms/__testfixtures__/obui-to-oceanbase-design-and-ui/obui.output.js @@ -1,6 +1,7 @@ import React from 'react'; -import { Alert, Button } from '@oceanbase/design'; -import { BasicLayout, Login, PageContainer } from '@oceanbase/ui'; +import { Alert, Button, ConfigProvider, theme } from '@oceanbase/design'; +import { BackgroundTaskManager, BackgroundTaskManagerConstants, BasicLayout, Boundary, Login, PageContainer } from '@oceanbase/ui'; +import type { BackgroundTaskManagerRef, ITaskMgrPreset, ITaskMgrQueue, TaskMgrID } from '@oceanbase/ui'; import type { BasicLayoutProps } from '@oceanbase/ui/es/BasicLayout'; import type { LoginProps } from '@oceanbase/ui/es/Login'; import type { PageContainerProps } from '@oceanbase/ui/es/PageContainer'; diff --git a/packages/codemod/transforms/__testfixtures__/obutil-to-oceanbase-util/getTableData.input.js b/packages/codemod/transforms/__testfixtures__/obutil-to-oceanbase-util/getTableData.input.js new file mode 100644 index 000000000..f6b106efa --- /dev/null +++ b/packages/codemod/transforms/__testfixtures__/obutil-to-oceanbase-util/getTableData.input.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { getTableData } from '@alipay/ob-util'; + +const Demo = () => { + const { tableProps, refresh } = getTableData({ + fn: getClusters, + params: { + configName, + }, + refreshDeps: [], + options: { + pagePropName: 'pageNo', + sizePropName: 'pageSize', + formatResult: (res: any) => { + const { data, pageNo, pageSize, total } = res || {}; + return { + list: data || [], + current: pageNo, + pageSize, + total, + }; + }, + }, + }); + return
; +}; + +export default Demo; diff --git a/packages/codemod/transforms/__testfixtures__/obutil-to-oceanbase-util/getTableData.output.js b/packages/codemod/transforms/__testfixtures__/obutil-to-oceanbase-util/getTableData.output.js new file mode 100644 index 000000000..837c159c2 --- /dev/null +++ b/packages/codemod/transforms/__testfixtures__/obutil-to-oceanbase-util/getTableData.output.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { useTableData } from '@oceanbase/util'; + +const Demo = () => { + const { tableProps, refresh } = useTableData({ + fn: getClusters, + params: { + configName, + }, + refreshDeps: [], + options: { + pagePropName: 'pageNo', + sizePropName: 'pageSize', + formatResult: (res: any) => { + const { data, pageNo, pageSize, total } = res || {}; + return { + list: data || [], + current: pageNo, + pageSize, + total, + }; + }, + }, + }); + return
; +}; + +export default Demo; diff --git a/packages/codemod/transforms/__tests__/obui-to-oceanbase-design-and-ui.test.ts b/packages/codemod/transforms/__tests__/obui-to-oceanbase-design-and-ui.test.ts index 57b847561..cf20501d0 100644 --- a/packages/codemod/transforms/__tests__/obui-to-oceanbase-design-and-ui.test.ts +++ b/packages/codemod/transforms/__tests__/obui-to-oceanbase-design-and-ui.test.ts @@ -1,7 +1,7 @@ import { defineTest } from 'jscodeshift/src/testUtils'; const testUnit = 'obui-to-oceanbase-design-and-ui'; -const tests = ['obui']; +const tests = ['obui', 'locale']; describe(testUnit, () => { tests.forEach(test => diff --git a/packages/codemod/transforms/__tests__/obutil-to-oceanbase-util.test.ts b/packages/codemod/transforms/__tests__/obutil-to-oceanbase-util.test.ts index 0221437c2..7afa170cf 100644 --- a/packages/codemod/transforms/__tests__/obutil-to-oceanbase-util.test.ts +++ b/packages/codemod/transforms/__tests__/obutil-to-oceanbase-util.test.ts @@ -1,7 +1,7 @@ import { defineTest } from 'jscodeshift/src/testUtils'; const testUnit = 'obutil-to-oceanbase-util'; -const tests = ['obutil']; +const tests = ['obutil', 'getTableData']; describe(testUnit, () => { tests.forEach(test => diff --git a/packages/codemod/transforms/obui-to-oceanbase-design-and-ui.js b/packages/codemod/transforms/obui-to-oceanbase-design-and-ui.js index 88ebe775f..b2635b46a 100644 --- a/packages/codemod/transforms/obui-to-oceanbase-design-and-ui.js +++ b/packages/codemod/transforms/obui-to-oceanbase-design-and-ui.js @@ -12,6 +12,7 @@ module.exports = (file, api, options) => { 'PageContainer', 'Action', 'BackgroundTaskManager', + 'BackgroundTaskManagerConstants', 'BasicLayout', 'BatchOperationBar', 'Boundary', @@ -27,6 +28,10 @@ module.exports = (file, api, options) => { 'PageContainerProps', 'ActionProps', 'BackgroundTaskManagerProps', + 'BackgroundTaskManagerRef', + 'ITaskMgrPreset', + 'ITaskMgrQueue', + 'TaskMgrID', 'BasicLayoutProps', 'BatchOperationBarProps', 'BoundaryProps', @@ -38,6 +43,7 @@ module.exports = (file, api, options) => { 'LottieProps', 'NavMenuProps', ], + paths: ['/locale/', '/locale/'], }, { name: '@oceanbase/design', diff --git a/packages/codemod/transforms/obutil-to-oceanbase-util.js b/packages/codemod/transforms/obutil-to-oceanbase-util.js index 65317fb36..2ced5a722 100644 --- a/packages/codemod/transforms/obutil-to-oceanbase-util.js +++ b/packages/codemod/transforms/obutil-to-oceanbase-util.js @@ -5,6 +5,15 @@ module.exports = (file, api, options) => { ...options, fromPkgNames: '@alipay/ob-util', toPkgList: [ + { + name: '@oceanbase/util', + components: [ + { + // rename + getTableData: 'useTableData', + }, + ], + }, { name: '@oceanbase/util', }, diff --git a/packages/codemod/transforms/utils/config.js b/packages/codemod/transforms/utils/config.js index 3e6a1fb7d..aef82d443 100644 --- a/packages/codemod/transforms/utils/config.js +++ b/packages/codemod/transforms/utils/config.js @@ -1,6 +1,7 @@ // https://github.com/benjamn/recast/blob/master/lib/options.ts const printOptions = { quote: 'single', + wrapColumn: 500, }; module.exports = { diff --git a/packages/codemod/transforms/utils/import-component.js b/packages/codemod/transforms/utils/import-component.js index 4d82cf1ec..ee5400d40 100644 --- a/packages/codemod/transforms/utils/import-component.js +++ b/packages/codemod/transforms/utils/import-component.js @@ -1,6 +1,9 @@ const { addSubmoduleImport, removeEmptyModuleImport, parseStrToArray } = require('./index'); const { markDependency } = require('./marker'); const { printOptions } = require('./config'); +const { isPlainObject } = require('lodash'); +const { flatten } = require('lodash'); +const { some } = require('lodash'); function importComponent(j, root, options) { const { fromPkgNames, toPkgList } = options; @@ -15,13 +18,20 @@ function importComponent(j, root, options) { ); if (fromPkgName) { path.value.specifiers.forEach(specifier => { - const toPkgByComponents = toPkgList.find(toPkg => - toPkg.components?.includes(specifier.imported.name) + const toPkgByComponents = toPkgList.find( + toPkg => + toPkg.components?.includes(specifier.imported?.name) || + toPkg.components?.find(component => component[specifier.imported?.name]) ); - const toPkgByTypes = toPkgList.find(toPkg => - toPkg.types?.includes(specifier.imported.name) + const toPkgByTypes = toPkgList.find( + toPkg => + toPkg.types?.includes(specifier.imported?.name) || + toPkg.types?.find(type => type[specifier.imported?.name]) ); - const toPkg = toPkgByComponents || toPkgByTypes; + const toPkgByPaths = toPkgList.find(toPkg => + some(toPkg.paths, pathString => path.value.source.value?.includes(pathString)) + ); + const toPkg = toPkgByComponents || toPkgByTypes || toPkgByPaths; if (toPkg) { // replace to toPkg for xxx/es/xxx、xxx/lib/xxx if (new RegExp(`${fromPkgName}/(es|lib|locale)/`).test(path.value.source.value)) { @@ -29,15 +39,30 @@ function importComponent(j, root, options) { } else { // remove old imports path.value.specifiers = path.value.specifiers.filter( - item => !item.imported || item.imported.name !== specifier.imported.name + item => !item.imported || item.imported?.name !== specifier.imported?.name + ); + const renameComponent = toPkg.components?.find( + component => component[specifier.imported?.name] ); - // add new imports + const renameType = toPkg.types?.find(type => type[specifier.imported?.name]); + const rename = renameComponent || renameType; + // add and rename new imports addSubmoduleImport(j, root, { moduleName: toPkg.name, - importedName: specifier.imported.name, + importedName: rename ? rename[specifier.imported?.name] : specifier.imported?.name, importKind: toPkgByTypes ? 'type' : 'value', after: fromPkgName, }); + // rename used part + if (rename) { + root + .find(j.Identifier, { + name: specifier.imported?.name, + }) + .forEach(path => { + path.node.name = rename[specifier.imported?.name]; + }); + } } markDependency(toPkg.name); } diff --git a/packages/codemod/transforms/utils/index.js b/packages/codemod/transforms/utils/index.js index 122fcf410..5f12c0eaf 100644 --- a/packages/codemod/transforms/utils/index.js +++ b/packages/codemod/transforms/utils/index.js @@ -138,7 +138,9 @@ function addModuleImport(j, root, { pkgName, importSpecifier, importKind, before return a.imported.name.localeCompare(b.imported.name); }); - return j.importDeclaration(mergedImportSpecifiers, j.literal(pkgName)); + const importStatement = j.importDeclaration(mergedImportSpecifiers, j.literal(pkgName)); + importStatement.importKind = importKind; + return importStatement; }); return true; } diff --git a/packages/design/package.json b/packages/design/package.json index 5955e4728..a612c2570 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@oceanbase/design", - "version": "0.2.18", + "version": "0.2.22", "description": "The Design System of OceanBase", "keywords": [ "oceanbase", @@ -35,24 +35,25 @@ "postbuild": "cp src/style/reset.css dist/" }, "dependencies": { - "@ant-design/cssinjs": "^1.17.0", + "@ant-design/cssinjs": "^1.17.2", "@oceanbase/icons": "workspace:^", "@oceanbase/util": "workspace:^", "ahooks": "^2.10.14", - "antd": "^5.9.0", + "antd": "^5.10.1", "classnames": "^2.3.2", "lodash": "^4.17.21", "lottie-web": "^5.12.2", - "rc-util": "^5.37.0", + "prop-types": "^15.8.1", + "rc-util": "^5.38.0", "react-sticky-mouse-tooltip": "^0.0.1" }, "devDependencies": { - "@babel/cli": "^7.22.15", - "@babel/preset-env": "^7.22.20", + "@babel/cli": "^7.23.0", + "@babel/preset-env": "^7.23.2", "antd-token-previewer": "^1.1.0" }, "peerDependencies": { - "react": "^16.9.0", - "react-dom": "^16.9.0" + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } } diff --git a/packages/design/src/_util/genComponentStyleHook.ts b/packages/design/src/_util/genComponentStyleHook.ts index e7af8008b..c629522ff 100644 --- a/packages/design/src/_util/genComponentStyleHook.ts +++ b/packages/design/src/_util/genComponentStyleHook.ts @@ -4,6 +4,8 @@ import type { FullToken, GenerateStyle } from 'antd/es/theme/internal'; import { genComponentStyleHook as antGenComponentStyleHook } from 'antd/es/theme/internal'; import type { GlobalToken } from 'antd/es/theme/interface'; import type { OverrideTokenWithoutDerivative } from 'antd/es/theme/util/genComponentStyleHook'; +import { useContext } from 'react'; +import theme from '../theme'; export type ComponentName = keyof ComponentTokenMap; @@ -22,7 +24,10 @@ export function genComponentStyleHook( }, getDefaultToken ); - const [wrapSSR, hashId] = useStyle(prefixCls); + const [wrapSSR] = useStyle(prefixCls); + // use hashId from useToken, as hashId is '' when hashed is false + // ref: https://github.com/ant-design/ant-design/blob/master/components/theme/useToken.ts#L80 + const { hashId } = theme.useToken(); return { wrapSSR, hashId, diff --git a/packages/design/src/alert/style/index.ts b/packages/design/src/alert/style/index.ts index 2de2ebb3c..d88377560 100644 --- a/packages/design/src/alert/style/index.ts +++ b/packages/design/src/alert/style/index.ts @@ -68,7 +68,7 @@ export const genAlertStyle: GenerateStyle = (token: AlertToken): CSS return { [`${componentCls}`]: { // vertical align to flex-start - alignItems: 'flex-start', + alignItems: 'flex-start !important', [`${componentCls}-icon`]: { height, }, diff --git a/packages/design/src/badge/__tests__/__snapshots__/index.test.tsx.snap b/packages/design/src/badge/__tests__/__snapshots__/index.test.tsx.snap index 2a05582b9..6cf09b5a1 100644 --- a/packages/design/src/badge/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/design/src/badge/__tests__/__snapshots__/index.test.tsx.snap @@ -2,7 +2,7 @@ exports[`Badge status custom icon 1`] = ` ( diff --git a/packages/design/src/card/index.tsx b/packages/design/src/card/index.tsx index 370fc40d7..6d67c7c33 100644 --- a/packages/design/src/card/index.tsx +++ b/packages/design/src/card/index.tsx @@ -5,9 +5,8 @@ import type { CardTabListType as AntCardTabListType, } from 'antd/es/card'; import classNames from 'classnames'; -import React, { useContext, useRef, useState } from 'react'; +import React, { useContext } from 'react'; import ConfigProvider from '../config-provider'; -import useInkBar from '../tabs/hooks/useInkBar'; import useStyle from './style'; export * from 'antd/es/card/Card'; @@ -27,10 +26,6 @@ const Card = ({ children, divided = true, tabList, - activeTabKey, - defaultActiveTabKey, - onTabChange, - tabProps, prefixCls: customizePrefixCls, className, ...restProps @@ -46,8 +41,6 @@ const Card = ({ className ); - const ref = useRef(); - const newTabList = tabList?.map(item => { if (!isNullValue(item.tag)) { return { @@ -65,34 +58,8 @@ const Card = ({ return item; }); - const [activeKey, setActiveKey] = useState( - activeTabKey || defaultActiveTabKey || newTabList?.[0]?.key - ); - - useInkBar({ - prefixCls: tabsPrefixCls, - activeKey, - size: tabProps?.size, - type: tabProps?.type, - tabPosition: tabProps?.tabPosition, - containerRef: ref, - }); - return wrapSSR( - { - setActiveKey(key); - onTabChange?.(key); - }} - tabProps={tabProps} - prefixCls={customizePrefixCls} - className={cardCls} - {...restProps} - > + {children} ); diff --git a/packages/design/src/config-provider/index.tsx b/packages/design/src/config-provider/index.tsx index dd6fba6bd..eb400d514 100644 --- a/packages/design/src/config-provider/index.tsx +++ b/packages/design/src/config-provider/index.tsx @@ -28,6 +28,8 @@ export type SpinConfig = ComponentStyleConfig & { }; export interface ConfigConsumerProps extends AntConfigConsumerProps { + theme?: ThemeConfig; + navigate?: NavigateFunction; spin?: SpinConfig; } @@ -50,7 +52,14 @@ const ExtendedConfigContext = React.createContext({ const { defaultSeed, components } = defaultTheme; -const ConfigProvider = ({ children, theme, navigate, spin, ...restProps }: ConfigProviderProps) => { +const ConfigProvider = ({ + children, + theme, + navigate, + spin, + tabs, + ...restProps +}: ConfigProviderProps) => { // inherit from parent ConfigProvider const parentContext = React.useContext(AntConfigProvider.ConfigContext); const parentExtendedContext = @@ -58,6 +67,13 @@ const ConfigProvider = ({ children, theme, navigate, spin, ...restProps }: Confi return ( (origin >= 24 ? origin - 16 : origin), + }, + parentContext.tabs, + tabs + )} theme={merge( { // Only set seed token for dark theme diff --git a/packages/design/src/index.ts b/packages/design/src/index.ts index 81aca8f2e..780e5dabc 100644 --- a/packages/design/src/index.ts +++ b/packages/design/src/index.ts @@ -1,5 +1,3 @@ -import { theme } from 'antd'; - // 引入 antd/dist/reset.css,以重置基本样式,保证原生元素遵从 antd 规范样式 // ref: https://ant.design/docs/react/migration-v5-cn#%E6%8A%80%E6%9C%AF%E8%B0%83%E6%95%B4 import 'antd/dist/reset.css'; @@ -25,7 +23,9 @@ export { default as Breadcrumb } from './breadcrumb'; export { default as Spin } from './spin'; export { default as Badge } from './badge'; -const { useToken } = theme; +import theme from './theme'; -// 直接导出 useToken,方便上层使用 +const { useToken } = theme; export { useToken }; + +export type { PresetStatusColorType } from 'antd/es/_util/colors'; diff --git a/packages/design/src/menu/index.ts b/packages/design/src/menu/index.ts index 44cf66981..7237d1176 100644 --- a/packages/design/src/menu/index.ts +++ b/packages/design/src/menu/index.ts @@ -1 +1,2 @@ +export * from 'antd/es/menu/hooks/useItems'; export * from 'antd/es/menu'; diff --git a/packages/design/src/spin/demo/custom-indicator.tsx b/packages/design/src/spin/demo/custom-indicator.tsx index ebbbca258..a533b5db3 100644 --- a/packages/design/src/spin/demo/custom-indicator.tsx +++ b/packages/design/src/spin/demo/custom-indicator.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Alert, Space, Spin } from '@oceanbase/design'; -import { LoadingOutlined } from '@ant-design/icons'; +import { LoadingOutlined } from '@oceanbase/icons'; const indicator = ; diff --git a/packages/design/src/table/demo/fixed-columns-header-tables.tsx b/packages/design/src/table/demo/fixed-columns-header-tables.tsx index ea2b1232c..6ba0d3e74 100644 --- a/packages/design/src/table/demo/fixed-columns-header-tables.tsx +++ b/packages/design/src/table/demo/fixed-columns-header-tables.tsx @@ -46,7 +46,7 @@ const App: React.FC = () => { const columns: ColumnProps[] = [ { title: 'Full Name', - width: 100, + width: 120, dataIndex: 'name', key: 'name', fixed: 'left', @@ -76,7 +76,7 @@ const App: React.FC = () => { }, ]; - return ; + return
; }; export default App; diff --git a/packages/design/src/table/style/index.ts b/packages/design/src/table/style/index.ts index 9a8d16013..f2394e2b4 100644 --- a/packages/design/src/table/style/index.ts +++ b/packages/design/src/table/style/index.ts @@ -107,6 +107,25 @@ export const genTableStyle: GenerateStyle = (token: TableToken): CSS }, }, + // 滚动表格样式 + // 由于滚动表格会在 tbody 下最前面多一个 tr 元素,因此需要设置相反的斑马条样式 + // .ant-table-scroll-horizontal: 水平滚动 + // .ant-table-fixed-header: 垂直滚动 + [`${componentCls}-wrapper ${componentCls}${componentCls}-scroll-horizontal, ${componentCls}-wrapper ${componentCls}${componentCls}-fixed-header`]: + { + [`${componentCls}-tbody`]: { + // 斑马纹样式 + [`tr:nth-child(2n + 1):not(${componentCls}-placeholder):not(${componentCls}-row-selected):not(${antCls}-descriptions-row) > td`]: + { + backgroundColor: colorFillQuaternary, + }, + [`tr:nth-child(2n):not(${componentCls}-placeholder):not(${componentCls}-row-selected):not(${componentCls}-expanded-row):not(${antCls}-descriptions-row) > td`]: + { + backgroundColor: colorBgBase, + }, + }, + }, + // large 表格样式 [`${componentCls}-wrapper`]: { [`${componentCls}:not(${componentCls}-middle):not(${componentCls}-small)`]: { diff --git a/packages/design/src/tabs/hooks/useInkBar.ts b/packages/design/src/tabs/hooks/useInkBar.ts deleted file mode 100644 index 313bef3dd..000000000 --- a/packages/design/src/tabs/hooks/useInkBar.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { SizeType } from 'antd/es/config-provider/SizeContext'; -import type { TabsPosition, TabsType } from 'antd/es/tabs'; -import type { MutableRefObject } from 'react'; -import { useEffect } from 'react'; - -export default ({ - prefixCls, - activeKey, - size, - type, - tabPosition, - containerRef, -}: { - prefixCls: string; - activeKey: string; - size: SizeType; - type: TabsType; - tabPosition: TabsPosition; - // 容器节点 - containerRef: MutableRefObject; -}) => { - const isHorizontal = !tabPosition || tabPosition === 'top' || tabPosition === 'bottom'; - const isVertical = tabPosition === 'left' || tabPosition === 'right'; - - useEffect(() => { - // Tabs 容器节点 - const containerNode = containerRef.current; - // inkbar 节点 - const inkBarNode = containerNode?.getElementsByClassName( - `${prefixCls}-ink-bar` - )?.[0] as HTMLElement; - // 当前选中的 Tab 节点 - const activeTabNode = containerNode?.querySelector(`[data-node-key="${activeKey}"]`) as - | HTMLElement - | undefined; - // 当前选中的 Tab 下的 btn 节点 - const activeTabBtnNode = containerNode?.querySelector( - `[data-node-key="${activeKey}"]>.${prefixCls}-tab-btn` - ) as HTMLElement | undefined; - - // 水平布局下 inkbar 的偏移量 - const horizontalOffset = 8; - - setTimeout(() => { - if ((!type || type === 'line') && inkBarNode) { - // 水平布局,如果 inkbar 宽度 >= 24,则两侧各减去 8px,并保持水平居中 - if (isHorizontal && activeTabNode?.offsetWidth >= 24) { - inkBarNode.style.width = `${activeTabNode?.offsetWidth - horizontalOffset * 2}px`; - inkBarNode.style.marginLeft = `${horizontalOffset}px`; - inkBarNode.style.marginTop = '0px'; - } - // 垂直布局,inkbar 高度始终与 Tab btn 相同,并保持垂直居中 - if (isVertical) { - inkBarNode.style.height = `${activeTabBtnNode?.offsetHeight}px`; - inkBarNode.style.marginTop = `${ - (activeTabNode?.offsetHeight - activeTabBtnNode?.offsetHeight) / 2 - }px`; - inkBarNode.style.marginLeft = '0px'; - } - } - }, 100); - }, [prefixCls, activeKey, size, type, isHorizontal, isVertical, containerRef]); -}; diff --git a/packages/design/src/tabs/index.tsx b/packages/design/src/tabs/index.tsx index d1272ff70..990757296 100644 --- a/packages/design/src/tabs/index.tsx +++ b/packages/design/src/tabs/index.tsx @@ -1,14 +1,12 @@ import { isNullValue } from '@oceanbase/util'; import { Space, Tabs as AntTabs, Tag } from 'antd'; -import React, { useState, useRef, useContext } from 'react'; +import React, { useContext } from 'react'; import type { TabsProps as AntTabsProps, TabsPosition as AntTabsPosition } from 'antd/es/tabs'; import type { Tab as AntTab } from 'rc-tabs/es/interface'; import classNames from 'classnames'; import ConfigProvider from '../config-provider'; -import useInkBar from './hooks/useInkBar'; import useLegacyItems from './hooks/useLegacyItems'; import useStyle from './style'; -import { useUpdateEffect } from 'ahooks'; import TabPane from './TabPane'; export * from 'antd/es/tabs'; @@ -28,10 +26,6 @@ export type TabsPosition = AntTabsPosition; const Tabs = ({ children, items, - defaultActiveKey, - activeKey: activeKeyProp, - onChange, - size, type, tabPosition, prefixCls: customizePrefixCls, @@ -43,7 +37,6 @@ const Tabs = ({ const { wrapSSR } = useStyle(prefixCls); const tabsCls = classNames(className); - const ref = useRef(); const isHorizontal = !tabPosition || tabPosition === 'top' || tabPosition === 'bottom'; let newItems = items?.map(item => { @@ -65,39 +58,9 @@ const Tabs = ({ newItems = useLegacyItems(newItems, children, prefixCls); - const [activeKey, setActiveKey] = useState( - activeKeyProp || defaultActiveKey || newItems?.[0]?.key - ); - - // 防止第一次顶掉默认值 - useUpdateEffect(() => { - // 外部触发的 activeKey 更改,需要同步内部状态变化 - if (activeKeyProp) { - setActiveKey(activeKeyProp); - } - }, [activeKeyProp]); - - useInkBar({ - prefixCls, - activeKey, - size, - type, - tabPosition, - containerRef: ref, - }); - return wrapSSR( { - setActiveKey(key); - onChange?.(key); - }} - size={size} type={type} tabPosition={tabPosition} tabBarGutter={!type || type === 'line' ? (isHorizontal ? 24 : 0) : undefined} diff --git a/packages/design/src/theme/default.ts b/packages/design/src/theme/default.ts index cfe8a3fae..b1a2eea27 100644 --- a/packages/design/src/theme/default.ts +++ b/packages/design/src/theme/default.ts @@ -4,7 +4,7 @@ export default { colorPrimaryBgHover: '#EAF1FF', colorPrimaryBorder: '#B3CCFF', colorPrimaryBorderHover: '#5189FB', - colorPrimaryHover: '#004CE6', + colorPrimaryHover: '#5189FB', colorPrimaryTextHover: '#5189FB', colorPrimaryText: '#006AFF', colorPrimaryTextActive: '#004CE6', @@ -60,11 +60,10 @@ export default { colorBgMask: 'rgba(19, 32, 57, 0.65)', colorBorder: '#CDD5E4', colorBorderSecondary: '#E2E8F3', - colorFillQuaternary: 'rgb(245, 248, 254, 0.45)', - colorFillTertiary: 'rgb(245, 248, 254, 0.65)', - colorFillSecondary: 'rgb(245, 248, 254, 0.85)', + colorFillQuaternary: '#F8FAFE', + colorFillTertiary: '#F5F8FE', + colorFillSecondary: '#F5F8FE', colorFill: '#F5F8FE', - colorIcon: '#8592AD', boxShadowSecondary: '0 6px 16px 0 rgba(54, 69, 99, 0.08), 0 3px 6px -4px rgba(54, 69, 99, 0.12), 0 9px 28px 8px rgba(54, 69, 99, 0.05)', boxShadow: diff --git a/packages/icons/package.json b/packages/icons/package.json index c76a2ff8e..13d7b18da 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -1,6 +1,6 @@ { "name": "@oceanbase/icons", - "version": "0.2.6", + "version": "0.2.8", "description": "The Icon Library for OceanBase", "keywords": [ "oceanbase", @@ -37,10 +37,10 @@ "dependencies": { "@ant-design/colors": "^7.0.0", "@ant-design/icons": "^5.2.6", - "@oceanbase/icons-svg": "^1.0.4" + "@oceanbase/icons-svg": "^1.0.5" }, "peerDependencies": { - "react": "^16.9.0", - "react-dom": "^16.9.0" + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } } diff --git a/packages/icons/src/components/AntdIcon.tsx b/packages/icons/src/components/AntdIcon.tsx index 2998316fb..50eab110a 100644 --- a/packages/icons/src/components/AntdIcon.tsx +++ b/packages/icons/src/components/AntdIcon.tsx @@ -5,19 +5,15 @@ import { IconDefinition } from '../types'; import { IconBaseProps } from './Icon'; import ReactIcon from './IconBase'; -import { - getTwoToneColor, - TwoToneColor, - setTwoToneColor, -} from './twoTonePrimaryColor'; +import { getTwoToneColor, TwoToneColor, setTwoToneColor } from './twoTonePrimaryColor'; import { normalizeTwoToneColors } from './utils'; export interface AntdIconProps extends IconBaseProps { - twoToneColor?: TwoToneColor; + twoToneColor?: TwoToneColor; } export interface IconComponentProps extends AntdIconProps { - icon: IconDefinition; + icon: IconDefinition; } // Initial setting @@ -26,76 +22,70 @@ setTwoToneColor('#1890ff'); // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34757#issuecomment-488848720 interface IconBaseComponent - extends React.ForwardRefExoticComponent< - Props & React.RefAttributes - > { - getTwoToneColor: typeof getTwoToneColor; - setTwoToneColor: typeof setTwoToneColor; + extends React.ForwardRefExoticComponent> { + getTwoToneColor: typeof getTwoToneColor; + setTwoToneColor: typeof setTwoToneColor; } -const Icon = React.forwardRef( - (props, ref) => { - const { - // affect outter ... - className, - - // affect inner ... - icon, - spin, - rotate, - - tabIndex, - onClick, - - // other - twoToneColor, - - ...restProps - } = props; - - const classString = classNames( - 'anticon', - { [`anticon-${icon.name}`]: Boolean(icon.name) }, - { 'anticon-spin': !!spin || icon.name === 'loading' }, - className, - ); - - let iconTabIndex = tabIndex; - if (iconTabIndex === undefined && onClick) { - iconTabIndex = -1; - } - - const svgStyle = rotate - ? { - msTransform: `rotate(${rotate}deg)`, - transform: `rotate(${rotate}deg)`, - } - : undefined; - - const [primaryColor, secondaryColor] = normalizeTwoToneColors( - twoToneColor, - ); - - return ( - - - - ); - }, -) as IconBaseComponent; +const Icon = React.forwardRef((props, ref) => { + const { + // affect outter ... + className, + + // affect inner ... + icon, + spin, + rotate, + + tabIndex, + onClick, + + // other + twoToneColor, + + ...restProps + } = props; + + const classString = classNames( + 'anticon', + { [`anticon-${icon.name}`]: Boolean(icon.name) }, + { 'anticon-spin': !!spin || icon.name === 'loading' }, + className + ); + + let iconTabIndex = tabIndex; + if (iconTabIndex === undefined && onClick) { + iconTabIndex = -1; + } + + const svgStyle = rotate + ? { + msTransform: `rotate(${rotate}deg)`, + transform: `rotate(${rotate}deg)`, + } + : undefined; + + const [primaryColor, secondaryColor] = normalizeTwoToneColors(twoToneColor); + + return ( + + + + ); +}) as IconBaseComponent; Icon.displayName = 'AntdIcon'; Icon.getTwoToneColor = getTwoToneColor; diff --git a/packages/icons/src/components/Icon.tsx b/packages/icons/src/components/Icon.tsx index d0db06693..393f3cc6d 100644 --- a/packages/icons/src/components/Icon.tsx +++ b/packages/icons/src/components/Icon.tsx @@ -1,7 +1,7 @@ -import * as React from "react"; -import classNames from "classnames"; +import * as React from 'react'; +import classNames from 'classnames'; -import { warning, svgBaseProps } from "./utils"; +import { warning, svgBaseProps } from './utils'; export interface IconBaseProps extends React.HTMLProps { spin?: boolean; @@ -19,113 +19,109 @@ export interface CustomIconComponentProps { export interface IconComponentProps extends IconBaseProps { viewBox?: string; - component?: React.ComponentType< - CustomIconComponentProps | React.SVGProps - >; - ariaLabel?: React.AriaAttributes["aria-label"]; + component?: React.ComponentType>; + ariaLabel?: React.AriaAttributes['aria-label']; } -const Icon = React.forwardRef( - (props, ref) => { - const { - // affect outter ... - className, +const Icon = React.forwardRef((props, ref) => { + const { + // affect outter ... + className, - // affect inner ... - component: Component, - viewBox, - spin, - rotate, + // affect inner ... + component: Component, + viewBox, + spin, + rotate, - tabIndex, - onClick, + tabIndex, + onClick, - // children - children, - ...restProps - } = props; + // children + children, + ...restProps + } = props; - if (!!Component || !!children) { - console.error("Should have `component` prop or `children`."); - } - // warning( - // Boolean(Component || children), - // 'Should have `component` prop or `children`.', - // ); - - // useInsertStyles(); - - const classString = classNames("anticon", className); - - const svgClassString = classNames({ - "anticon-spin": !!spin, - }); - - const svgStyle = rotate - ? { - msTransform: `rotate(${rotate}deg)`, - transform: `rotate(${rotate}deg)`, - } - : undefined; - - const innerSvgProps: CustomIconComponentProps = { - ...svgBaseProps, - className: svgClassString, - style: svgStyle, - viewBox, - }; - - if (!viewBox) { - delete innerSvgProps.viewBox; - } + if (!!Component || !!children) { + console.error('Should have `component` prop or `children`.'); + } + // warning( + // Boolean(Component || children), + // 'Should have `component` prop or `children`.', + // ); - // component > children - const renderInnerNode = () => { - if (Component) { - // @ts-ignore - return {children}; - } + // useInsertStyles(); + + const classString = classNames('anticon', className); + + const svgClassString = classNames({ + 'anticon-spin': !!spin, + }); - if (children) { - warning( - Boolean(viewBox) || - (React.Children.count(children) === 1 && - React.isValidElement(children) && - React.Children.only(children).type === "use"), - "Make sure that you provide correct `viewBox`" + - " prop (default `0 0 1024 1024`) to the icon." - ); - - return ( - - {children} - - ); + const svgStyle = rotate + ? { + msTransform: `rotate(${rotate}deg)`, + transform: `rotate(${rotate}deg)`, } + : undefined; - return null; - }; + const innerSvgProps: CustomIconComponentProps = { + ...svgBaseProps, + className: svgClassString, + style: svgStyle, + viewBox, + }; - let iconTabIndex = tabIndex; - if (iconTabIndex === undefined && onClick) { - iconTabIndex = -1; + if (!viewBox) { + delete innerSvgProps.viewBox; + } + + // component > children + const renderInnerNode = () => { + if (Component) { + // @ts-ignore + return {children}; } - return ( - - {renderInnerNode()} - - ); + if (children) { + warning( + Boolean(viewBox) || + (React.Children.count(children) === 1 && + React.isValidElement(children) && + React.Children.only(children).type === 'use'), + 'Make sure that you provide correct `viewBox`' + + ' prop (default `0 0 1024 1024`) to the icon.' + ); + + return ( + + {children} + + ); + } + + return null; + }; + + let iconTabIndex = tabIndex; + if (iconTabIndex === undefined && onClick) { + iconTabIndex = -1; } -); -Icon.displayName = "AntdIcon"; + return ( + + {renderInnerNode()} + + ); +}); + +Icon.displayName = 'AntdIcon'; export default Icon; diff --git a/packages/icons/src/components/IconBase.tsx b/packages/icons/src/components/IconBase.tsx index 35ff5d113..527087929 100644 --- a/packages/icons/src/components/IconBase.tsx +++ b/packages/icons/src/components/IconBase.tsx @@ -1,12 +1,12 @@ -import * as React from "react"; -import { AbstractNode, IconDefinition } from "../types"; +import * as React from 'react'; +import { AbstractNode, IconDefinition } from '../types'; import { generate, getSecondaryColor, isIconDefinition, warning, // useInsertStyles, -} from "./utils"; +} from './utils'; export interface IconProps { icon: IconDefinition; @@ -28,18 +28,14 @@ export interface TwoToneColorPalette extends TwoToneColorPaletteSetter { } const twoToneColorPalette: TwoToneColorPalette = { - primaryColor: "#333", - secondaryColor: "#E6E6E6", + primaryColor: '#333', + secondaryColor: '#E6E6E6', calculated: false, }; -function setTwoToneColors({ - primaryColor, - secondaryColor, -}: TwoToneColorPaletteSetter) { +function setTwoToneColors({ primaryColor, secondaryColor }: TwoToneColorPaletteSetter) { twoToneColorPalette.primaryColor = primaryColor; - twoToneColorPalette.secondaryColor = - secondaryColor || getSecondaryColor(primaryColor); + twoToneColorPalette.secondaryColor = secondaryColor || getSecondaryColor(primaryColor); twoToneColorPalette.calculated = !!secondaryColor; } @@ -54,16 +50,8 @@ interface IconBaseComponent

extends React.FC

{ setTwoToneColors: typeof setTwoToneColors; } -const IconBase: IconBaseComponent = (props) => { - const { - icon, - className, - onClick, - style, - primaryColor, - secondaryColor, - ...restProps - } = props; +const IconBase: IconBaseComponent = props => { + const { icon, className, onClick, style, primaryColor, secondaryColor, ...restProps } = props; let colors: TwoToneColorPalette = twoToneColorPalette; if (primaryColor) { @@ -75,17 +63,14 @@ const IconBase: IconBaseComponent = (props) => { // useInsertStyles(); - warning( - isIconDefinition(icon), - `icon should be icon definiton, but got ${icon}` - ); + warning(isIconDefinition(icon), `icon should be icon definiton, but got ${icon}`); if (!isIconDefinition(icon)) { return null; } let target = icon; - if (target && typeof target.icon === "function") { + if (target && typeof target.icon === 'function') { target = { ...target, icon: target.icon(colors.primaryColor, colors.secondaryColor), @@ -95,16 +80,16 @@ const IconBase: IconBaseComponent = (props) => { className, onClick, style, - "data-icon": target.name, - width: "1em", - height: "1em", - fill: "currentColor", - "aria-hidden": "true", + 'data-icon': target.name, + width: '1em', + height: '1em', + fill: 'currentColor', + 'aria-hidden': 'true', ...restProps, }); }; -IconBase.displayName = "IconReact"; +IconBase.displayName = 'IconReact'; IconBase.getTwoToneColors = getTwoToneColors; IconBase.setTwoToneColors = setTwoToneColors; diff --git a/packages/icons/src/components/IconFont.tsx b/packages/icons/src/components/IconFont.tsx index 4ca89cea5..df15deb6b 100644 --- a/packages/icons/src/components/IconFont.tsx +++ b/packages/icons/src/components/IconFont.tsx @@ -4,91 +4,80 @@ import Icon, { IconBaseProps } from './Icon'; const customCache = new Set(); export interface CustomIconOptions { - scriptUrl?: string | string[]; - extraCommonProps?: { [key: string]: any }; + scriptUrl?: string | string[]; + extraCommonProps?: { [key: string]: any }; } export interface IconFontProps extends IconBaseProps { - type: string; + type: string; } function isValidCustomScriptUrl(scriptUrl: string): boolean { - return Boolean( - typeof scriptUrl === 'string' && - scriptUrl.length && - !customCache.has(scriptUrl), - ); + return Boolean(typeof scriptUrl === 'string' && scriptUrl.length && !customCache.has(scriptUrl)); } -function createScriptUrlElements( - scriptUrls: string[], - index: number = 0, -): void { - const currentScriptUrl = scriptUrls[index]; - if (isValidCustomScriptUrl(currentScriptUrl)) { - const script = document.createElement('script'); - script.setAttribute('src', currentScriptUrl); - script.setAttribute('data-namespace', currentScriptUrl); - if (scriptUrls.length > index + 1) { - script.onload = () => { - createScriptUrlElements(scriptUrls, index + 1); - }; - script.onerror = () => { - createScriptUrlElements(scriptUrls, index + 1); - }; - } - customCache.add(currentScriptUrl); - document.body.appendChild(script); +function createScriptUrlElements(scriptUrls: string[], index: number = 0): void { + const currentScriptUrl = scriptUrls[index]; + if (isValidCustomScriptUrl(currentScriptUrl)) { + const script = document.createElement('script'); + script.setAttribute('src', currentScriptUrl); + script.setAttribute('data-namespace', currentScriptUrl); + if (scriptUrls.length > index + 1) { + script.onload = () => { + createScriptUrlElements(scriptUrls, index + 1); + }; + script.onerror = () => { + createScriptUrlElements(scriptUrls, index + 1); + }; } + customCache.add(currentScriptUrl); + document.body.appendChild(script); + } } -export default function create( - options: CustomIconOptions = {}, -): React.FC { - const { scriptUrl, extraCommonProps = {} } = options; +export default function create(options: CustomIconOptions = {}): React.FC { + const { scriptUrl, extraCommonProps = {} } = options; - /** - * DOM API required. - * Make sure in browser environment. - * The Custom Icon will create a