diff --git a/.dumirc.ts b/.dumirc.ts index 030f258d0..7615538a9 100644 --- a/.dumirc.ts +++ b/.dumirc.ts @@ -138,6 +138,7 @@ export default defineConfig({ { title: 'Form 表单', link: '/components/form' }, { title: 'Input 输入框', link: '/components/input' }, { title: 'InputNumber 数字输入框', link: '/components/input-number' }, + { title: 'Radio 单选框', link: '/components/radio' }, { title: 'Select 选择器', link: '/components/select' }, { title: 'TreeSelect 树选择', link: '/components/tree-select' }, ], diff --git a/docs/design/design-CHANGELOG.md b/docs/design/design-CHANGELOG.md index d5108634f..a7a0a795e 100644 --- a/docs/design/design-CHANGELOG.md +++ b/docs/design/design-CHANGELOG.md @@ -8,6 +8,17 @@ group: 基础组件 --- +## 0.3.3 + +`2024-04-25` + +- ConfigProvider + - 🐞 修复 ConfigProvider 开启 `theme.customFont` 并且多次嵌套后 `fontFamily` 不正确的问题。[#572](https://github.com/oceanbase/oceanbase-design/pull/572) + - 🐞 修复 ConfigProvider 自定义 `theme.token.fontFamily` 不生效的问题。[#573](https://github.com/oceanbase/oceanbase-design/pull/573) + - 🐞 修复 ConfigProvider 多次使用会默认多次注入 StaticFunction,导致 Modal、message 和 notification 静态方法不会正常展示的问题。[#574](https://github.com/oceanbase/oceanbase-design/pull/574) +- 🐞 修复主题 Token `boxShadowSecondary` 通过静态 token 对象和 less 变量访问时值不正确的问题。[#569](https://github.com/oceanbase/oceanbase-design/pull/569) +- 💄 优化 Radio.Button 选中置灰态的背景颜色,避免和字体颜色区分不清。[#570](https://github.com/oceanbase/oceanbase-design/pull/570) + ## 0.3.2 `2024-04-12` diff --git a/docs/ui/ui-CHANGELOG.md b/docs/ui/ui-CHANGELOG.md index d1c495779..863d58c71 100644 --- a/docs/ui/ui-CHANGELOG.md +++ b/docs/ui/ui-CHANGELOG.md @@ -8,6 +8,12 @@ group: 业务组件 --- +## 0.3.3 + +`2024-04-25` + +- ⭐️ Boundary 组件支持 `className` 属性,并且根据不同组件内置 ob-boundary-error、ob-boundary-403 和 ob-boundary-404 类名,便于上层判断异常类型。[#571](https://github.com/oceanbase/oceanbase-design/pull/571) + ## 0.3.2 `2024-04-12` diff --git a/packages/codemod/package.json b/packages/codemod/package.json index 606928268..8cb29ed59 100644 --- a/packages/codemod/package.json +++ b/packages/codemod/package.json @@ -1,6 +1,6 @@ { "name": "@oceanbase/codemod", - "version": "0.3.1", + "version": "0.3.2", "description": "Codemod for OceanBase Design upgrade", "keywords": [ "oceanbase", diff --git a/packages/design/package.json b/packages/design/package.json index 07ae57aef..90172f002 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@oceanbase/design", - "version": "0.3.2", + "version": "0.3.3", "description": "The Design System of OceanBase", "keywords": [ "oceanbase", diff --git a/packages/design/src/config-provider/__tests__/injectStaticFunction.test.tsx b/packages/design/src/config-provider/__tests__/injectStaticFunction.test.tsx new file mode 100644 index 000000000..4fd54cde6 --- /dev/null +++ b/packages/design/src/config-provider/__tests__/injectStaticFunction.test.tsx @@ -0,0 +1,18 @@ +import React, { useContext } from 'react'; +import { render } from '@testing-library/react'; +import { ConfigProvider, useToken } from '@oceanbase/design'; +import { injectedStaticFunction } from '../../static-function'; + +describe('ConfigProvider injectStaticFunction', () => { + it('injectStaticFunction', () => { + const Child = () => { + expect(injectedStaticFunction).toBe(true); + return
; + }; + render( + + + + ); + }); +}); diff --git a/packages/design/src/config-provider/__tests__/static-function.test.tsx b/packages/design/src/config-provider/__tests__/static-function.test.tsx index 27a9f1365..a3ca9c85f 100644 --- a/packages/design/src/config-provider/__tests__/static-function.test.tsx +++ b/packages/design/src/config-provider/__tests__/static-function.test.tsx @@ -4,7 +4,12 @@ import { ConfigProvider, token } from '@oceanbase/design'; import defaultTheme from '../../theme/default'; describe('ConfigProvider static function', () => { - it('token', () => { + it('static token', () => { + expect(token.boxShadow).toBe(defaultTheme.token.boxShadow); + expect(token.boxShadowSecondary).toBe(defaultTheme.token.boxShadowSecondary); + }); + + it('static token in ConfigProvider', () => { render(
diff --git a/packages/design/src/config-provider/__tests__/theme.test.tsx b/packages/design/src/config-provider/__tests__/theme.test.tsx index 021cb6b1f..2e9068bfb 100644 --- a/packages/design/src/config-provider/__tests__/theme.test.tsx +++ b/packages/design/src/config-provider/__tests__/theme.test.tsx @@ -1,10 +1,12 @@ import React, { useContext } from 'react'; import { render } from '@testing-library/react'; -import { ConfigProvider, useToken } from '@oceanbase/design'; +import { ConfigProvider, useToken, theme } from '@oceanbase/design'; import defaultTheme from '../../theme/default'; +const antToken = theme.getDesignToken(); + describe('ConfigProvider theme', () => { - it('ConfigProvider theme token', () => { + it('token', () => { const Child1 = () => { const { token } = useToken(); expect(token.colorBgLayout).toBe(defaultTheme.token.colorBgLayout); @@ -38,4 +40,73 @@ describe('ConfigProvider theme', () => { ); }); + + // test order should before customFont to avoid be affected + it('token.fontFamily', () => { + const Child1 = () => { + const { token } = useToken(); + expect(token.fontFamily).toBe(antToken.fontFamily); + return
; + }; + const Child2 = () => { + const { token } = useToken(); + expect(token.fontFamily).toBe(`'Custom Font'`); + return
; + }; + const Child3 = () => { + const { token } = useToken(); + expect(token.fontFamily).toBe(`'Custom Font'`); + return
; + }; + render( + + + + + + + + + + ); + }); + + it('customFont', () => { + const Child1 = () => { + const { token } = useToken(); + expect(token.fontFamily).toBe(antToken.fontFamily); + return
; + }; + const Child2 = () => { + const { token } = useToken(); + expect(token.fontFamily).toBe(`'Source Sans Pro', ${antToken.fontFamily}`); + return
; + }; + const Child3 = () => { + const { token } = useToken(); + expect(token.fontFamily).toBe(`'Source Sans Pro', ${antToken.fontFamily}`); + return
; + }; + render( + + + + + + + + + + ); + }); }); diff --git a/packages/design/src/config-provider/index.tsx b/packages/design/src/config-provider/index.tsx index ab9bcc567..a6efa8925 100644 --- a/packages/design/src/config-provider/index.tsx +++ b/packages/design/src/config-provider/index.tsx @@ -13,7 +13,7 @@ import type { StyleProviderProps } from '@ant-design/cssinjs'; import StyleContext from '@ant-design/cssinjs/es/StyleContext'; import type { StyleContextProps } from '@ant-design/cssinjs/es/StyleContext'; import { merge } from 'lodash'; -import StaticFunction from '../static-function'; +import StaticFunction, { injectedStaticFunction } from '../static-function'; import themeConfig from '../theme'; import defaultTheme from '../theme/default'; import darkTheme from '../theme/dark'; @@ -97,7 +97,7 @@ const ConfigProvider: ConfigProviderType = ({ spin, table, tabs, - injectStaticFunction = true, + injectStaticFunction = !injectedStaticFunction, styleProviderProps, ...restProps }) => { @@ -108,6 +108,8 @@ const ConfigProvider: ConfigProviderType = ({ const mergedTheme = merge(parentContext.theme, theme); const currentTheme = mergedTheme?.isDark ? darkTheme : defaultTheme; const { token } = themeConfig.useToken(); + const fontFamily = mergedTheme.token?.fontFamily || token.fontFamily; + const customFont = mergedTheme.customFont; // inherit from parent StyleProvider const parentStyleContext = React.useContext(StyleContext); @@ -134,9 +136,10 @@ const ConfigProvider: ConfigProviderType = ({ )} theme={merge(currentTheme, mergedTheme, { token: { - fontFamily: mergedTheme.customFont - ? `'Source Sans Pro', ${token.fontFamily}` - : token.fontFamily, + fontFamily: + customFont && !fontFamily.startsWith(`'Source Sans Pro'`) + ? `'Source Sans Pro', ${fontFamily}` + : fontFamily, }, })} renderEmpty={ diff --git a/packages/design/src/radio/demo/radio-button.tsx b/packages/design/src/radio/demo/radio-button.tsx new file mode 100644 index 000000000..e126e579e --- /dev/null +++ b/packages/design/src/radio/demo/radio-button.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Radio, Space } from '@oceanbase/design'; +import type { RadioChangeEvent } from '@oceanbase/design'; + +const App: React.FC = () => { + const onChange = (e: RadioChangeEvent) => { + console.log(`radio checked:${e.target.value}`); + }; + + return ( + + + Hangzhou + Shanghai + Beijing + Chengdu + + + Hangzhou + + Shanghai + + Beijing + Chengdu + + + Hangzhou + Shanghai + Beijing + Chengdu + + + ); +}; + +export default App; diff --git a/packages/design/src/radio/demo/radio.tsx b/packages/design/src/radio/demo/radio.tsx new file mode 100644 index 000000000..4b9ede0e4 --- /dev/null +++ b/packages/design/src/radio/demo/radio.tsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; +import { Radio, Space } from '@oceanbase/design'; +import type { RadioChangeEvent } from '@oceanbase/design'; + +const App: React.FC = () => { + const [value, setValue] = useState('A'); + + const onChange = (e: RadioChangeEvent) => { + console.log('radio checked', e.target.value); + setValue(e.target.value); + }; + + return ( + + + A + B + C + D + + + A + B + C + D + + + ); +}; + +export default App; diff --git a/packages/design/src/radio/index.md b/packages/design/src/radio/index.md new file mode 100644 index 000000000..25e435405 --- /dev/null +++ b/packages/design/src/radio/index.md @@ -0,0 +1,21 @@ +--- +title: Radio 单选框 +nav: + title: 基础组件 + path: /components +demo: + cols: 2 +--- + +- 🔥 完全继承 antd [Radio](https://ant.design/components/radio-cn) 的能力和 API,可无缝切换。 +- 💄 定制主题和样式,符合 OceanBase Design 设计规范。 + +## 代码演示 + + + + + +## API + +- 详见 antd Radio 文档: https://ant.design/components/radio-cn diff --git a/packages/design/src/static-function/index.tsx b/packages/design/src/static-function/index.tsx index 056745c84..4ed880366 100644 --- a/packages/design/src/static-function/index.tsx +++ b/packages/design/src/static-function/index.tsx @@ -21,7 +21,7 @@ const mapToken = { // 需要覆盖部分 Alias Token 的值 override: { boxShadow: defaultTheme.token.boxShadow, - boxShadowSecondary: defaultTheme.token.boxShadow, + boxShadowSecondary: defaultTheme.token.boxShadowSecondary, }, }; let token = formatToken(mapToken); @@ -36,6 +36,9 @@ let modal: Omit & { useModal: typeof AntModal.useModal; } = AntModal; +// injected static function or not +let injectedStaticFunction = false; + export default () => { // 自动注入 useToken,避免每次使用都要声明一遍,比较繁琐 token = useToken().token; @@ -54,7 +57,8 @@ export default () => { ...staticFunction.modal, useModal: AntModal.useModal, }; + injectedStaticFunction = true; return null; }; -export { token, message, notification, modal }; +export { token, message, notification, modal, injectedStaticFunction }; diff --git a/packages/design/src/theme/default.ts b/packages/design/src/theme/default.ts index 14a299798..297d4dfe1 100644 --- a/packages/design/src/theme/default.ts +++ b/packages/design/src/theme/default.ts @@ -84,6 +84,10 @@ const defaultTheme: ThemeConfig = { InputNumber: { handleVisible: true, }, + Radio: { + // temporarily fix style for checked disabled Radio.Button + controlItemBgActiveDisabled: '#e2e8f3', + }, Select: { // work for all multiple select component, including Select, TreeSelect and Cascader and so on multipleItemBg: '#F8FAFE', diff --git a/packages/design/src/theme/style/compact.less b/packages/design/src/theme/style/compact.less index b7e45a357..9c258129f 100644 --- a/packages/design/src/theme/style/compact.less +++ b/packages/design/src/theme/style/compact.less @@ -409,7 +409,7 @@ @borderRadiusSM: 4; @borderRadiusLG: 8; @borderRadiusOuter: 4; -@boxShadowSecondary: 0 1px 2px 0 rgba(54, 69, 99, 0.03), 0 1px 6px -1px rgba(54, 69, 99, 0.02), 0 2px 4px 0 rgba(54, 69, 99, 0.02); +@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: 0 1px 2px 0 rgba(54, 69, 99, 0.03), 0 1px 6px -1px rgba(54, 69, 99, 0.02), 0 2px 4px 0 rgba(54, 69, 99, 0.02); @colorFillContent: #e2e8f3; @colorFillContentHover: #cdd5e4; diff --git a/packages/design/src/theme/style/default.less b/packages/design/src/theme/style/default.less index dcf5a5d0e..25f2e7c06 100644 --- a/packages/design/src/theme/style/default.less +++ b/packages/design/src/theme/style/default.less @@ -409,7 +409,7 @@ @borderRadiusSM: 4; @borderRadiusLG: 8; @borderRadiusOuter: 4; -@boxShadowSecondary: 0 1px 2px 0 rgba(54, 69, 99, 0.03), 0 1px 6px -1px rgba(54, 69, 99, 0.02), 0 2px 4px 0 rgba(54, 69, 99, 0.02); +@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: 0 1px 2px 0 rgba(54, 69, 99, 0.03), 0 1px 6px -1px rgba(54, 69, 99, 0.02), 0 2px 4px 0 rgba(54, 69, 99, 0.02); @colorFillContent: #e2e8f3; @colorFillContentHover: #cdd5e4; diff --git a/packages/ui/package.json b/packages/ui/package.json index 612ca85cb..40be205be 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@oceanbase/ui", - "version": "0.3.2", + "version": "0.3.3", "description": "The UI library based on OceanBase Design", "keywords": [ "oceanbase", diff --git a/packages/ui/src/Boundary/Components/Code.tsx b/packages/ui/src/Boundary/Components/Code.tsx index 836bbf82b..77370fcc6 100644 --- a/packages/ui/src/Boundary/Components/Code.tsx +++ b/packages/ui/src/Boundary/Components/Code.tsx @@ -6,8 +6,9 @@ import type { CodeType } from '../constant'; import { CODE_PRESET } from '../constant'; import type { BoundaryLocale } from '../IBoundary'; import zhCN from '../locale/zh-CN'; +import classNames from 'classnames'; -export interface IBoundaryCode extends LocaleWrapperProps { +export interface IBoundaryCode extends LocaleWrapperProps, React.HTMLProps { code: CodeType; onClick?: () => void; children?: React.ReactNode; @@ -18,7 +19,8 @@ export interface IBoundaryCode extends LocaleWrapperProps { } const BoundaryCode: React.FC = props => { - const { children, onClick, code, imageUrl, title, buttonText, locale } = props; + const { children, onClick, code, imageUrl, title, buttonText, locale, className, ...restProps } = + props; const info = useMemo(() => { const data = CODE_PRESET(locale); @@ -27,7 +29,15 @@ const BoundaryCode: React.FC = props => { }, [code, locale]); return ( -
+

{title || info.title}

diff --git a/packages/ui/src/Boundary/Components/Exception.tsx b/packages/ui/src/Boundary/Components/Exception.tsx index cd42a7422..4ca210562 100644 --- a/packages/ui/src/Boundary/Components/Exception.tsx +++ b/packages/ui/src/Boundary/Components/Exception.tsx @@ -5,8 +5,9 @@ import LocaleWrapper from '../../locale/LocaleWrapper'; import { EXCEPTION_PRESET } from '../constant'; import type { BoundaryLocale } from '../IBoundary'; import zhCN from '../locale/zh-CN'; +import classNames from 'classnames'; -export interface ExceptionProps extends LocaleWrapperProps { +export interface ExceptionProps extends LocaleWrapperProps, React.HTMLProps { children?: React.ReactNode; style?: React.CSSProperties; imageUrl?: string; @@ -62,6 +63,8 @@ class BoundaryException extends React.PureComponent +

{title || errorInfo.title}

@@ -105,7 +111,14 @@ class BoundaryException extends React.PureComponent +

{title || notCompatibleInfo.title}

diff --git a/packages/ui/src/Boundary/Components/Function.tsx b/packages/ui/src/Boundary/Components/Function.tsx index 4025a8930..881fbbdc1 100644 --- a/packages/ui/src/Boundary/Components/Function.tsx +++ b/packages/ui/src/Boundary/Components/Function.tsx @@ -1,8 +1,9 @@ import { Button } from '@oceanbase/design'; import React, { useMemo } from 'react'; import type { FunctionConfigType, FunctionStateType } from '../constant'; +import classNames from 'classnames'; -export interface IBoundaryFunction { +export interface IBoundaryFunction extends React.HTMLProps { children?: React.ReactNode; state: FunctionStateType; config: FunctionConfigType; @@ -10,13 +11,16 @@ export interface IBoundaryFunction { } export const BoundaryFunction: React.FC = props => { - const { children, state, config, onClick } = props; + const { children, state, config, onClick, className, ...restProps } = props; const info = useMemo(() => { return state ? config[state] : config[Object.keys(config)[0]]; }, [config, state]); return ( -
+

{info.title}

diff --git a/plugin-theme-less.ts b/plugin-theme-less.ts index 9a173bc72..b3ae14431 100644 --- a/plugin-theme-less.ts +++ b/plugin-theme-less.ts @@ -59,7 +59,7 @@ export default (api: IApi) => { : // 对于非暗色主题,需要覆盖部分 Alias Token 的值 { boxShadow: item.token.boxShadow, - boxShadowSecondary: item.token.boxShadow, + boxShadowSecondary: item.token.boxShadowSecondary, }, }; const aliasToken = formatToken(mapToken); diff --git a/tests/setupTests.ts b/tests/setupTests.ts index f6eab975f..50fab256b 100644 --- a/tests/setupTests.ts +++ b/tests/setupTests.ts @@ -16,14 +16,6 @@ global.React = React; ReactDOM.createPortal = vi.fn(modal => modal); -vi.mock('react', async () => { - const mockReact = await vi.importActual('react'); - return { - ...mockReact, - useLayoutEffect: mockReact.useEffect, - }; -}); - excludeAllWarning(); const fetchMocker = createFetchMock(vi);