diff --git a/.dumirc.ts b/.dumirc.ts index 31a999814..0dd578982 100644 --- a/.dumirc.ts +++ b/.dumirc.ts @@ -111,6 +111,7 @@ export default defineConfig({ children: [ { title: 'Form 表单', link: '/components/form' }, { title: 'InputNumber 数字输入框', link: '/components/input-number' }, + { title: 'Select 选择器', link: '/components/select' }, ], }, { @@ -120,6 +121,7 @@ export default defineConfig({ { title: 'Descriptions 描述列表', link: '/components/descriptions' }, { title: 'Table 表格', link: '/components/table' }, { title: 'Tabs 标签页', link: '/components/tabs' }, + { title: 'Tag 标签', link: '/components/tag' }, { title: 'Tooltip 文字提示', link: '/components/tooltip' }, ], }, diff --git a/packages/design/src/config-provider/demo/size.tsx b/packages/design/src/config-provider/demo/size.tsx index 6cc84f02d..b3d2471e9 100644 --- a/packages/design/src/config-provider/demo/size.tsx +++ b/packages/design/src/config-provider/demo/size.tsx @@ -48,7 +48,7 @@ const App: React.FC = () => { - diff --git a/packages/design/src/index.ts b/packages/design/src/index.ts index 7bc546559..b93568656 100644 --- a/packages/design/src/index.ts +++ b/packages/design/src/index.ts @@ -17,6 +17,8 @@ export { message, notification, token } from './static-function'; export { default as Table } from './table'; export { default as Tabs } from './tabs'; +export { default as Tag } from './tag'; +export { default as Select } from './select'; export type { TabsProps } from './tabs'; export { default as theme } from './theme'; diff --git a/packages/design/src/select/demo/custom-tag-render.tsx b/packages/design/src/select/demo/custom-tag-render.tsx new file mode 100644 index 000000000..694b04de0 --- /dev/null +++ b/packages/design/src/select/demo/custom-tag-render.tsx @@ -0,0 +1,31 @@ +import { Select, Tag } from '@oceanbase/design'; +import React from 'react'; + +const options = ['gold', 'green', 'red', 'cyan']; + +const tagRender = props => { + const { label, value, closable, onClose } = props; + const onPreventMouseDown = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; + return ( + + {label} + + ); +}; + +const App: React.FC = () => ( + <> + +); + +export default App; \ No newline at end of file diff --git a/packages/design/src/select/index.md b/packages/design/src/select/index.md new file mode 100644 index 000000000..5729f6bed --- /dev/null +++ b/packages/design/src/select/index.md @@ -0,0 +1,16 @@ +--- +title: Select 选择器 +nav: + title: 基础组件 + path: /components +--- + +- 🔥 完全兼容 antd [Select](https://ant.design/components/select-cn/) 的能力和 API,可无缝切换。 +- 💄 定制主题和样式,符合 OceanBase Design 设计规范。 + +## 代码演示 + + + + +- 详见 antd Select 文档: https://ant.design/components/select-cn/ diff --git a/packages/design/src/select/index.ts b/packages/design/src/select/index.ts deleted file mode 100644 index f891e54a5..000000000 --- a/packages/design/src/select/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from 'antd/es/select'; diff --git a/packages/design/src/select/index.tsx b/packages/design/src/select/index.tsx new file mode 100644 index 000000000..2027bfd0e --- /dev/null +++ b/packages/design/src/select/index.tsx @@ -0,0 +1,30 @@ +import { Select as AntSelect } from 'antd'; +import type { SelectProps as AntSelectProps } from 'antd/es/select'; +import classNames from 'classnames'; +import React, { useContext } from 'react'; +import ConfigProvider from '../config-provider'; +import useStyle from './style'; + +const { Option, OptGroup } = AntSelect; + +export * from 'antd/es/select'; + +export type SelectProps = AntSelectProps; + +const Select: any = ({ prefixCls: customizePrefixCls, className, ...restProps }: SelectProps) => { + const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); + const prefixCls = getPrefixCls('select', customizePrefixCls); + const { wrapSSR } = useStyle(prefixCls); + + const selectCls = classNames(className); + return wrapSSR(); +}; + +Select.Option = Option; +Select.OptGroup = OptGroup; + +if (process.env.NODE_ENV !== 'production') { + Select.displayName = AntSelect.displayName; +} + +export default Select; diff --git a/packages/design/src/select/style/index.ts b/packages/design/src/select/style/index.ts new file mode 100644 index 000000000..4ab0ecb93 --- /dev/null +++ b/packages/design/src/select/style/index.ts @@ -0,0 +1,24 @@ +import type { CSSObject } from '@ant-design/cssinjs'; +import type { FullToken, GenerateStyle } from 'antd/es/theme/internal'; +import { genComponentStyleHook } from '../../_util/genComponentStyleHook'; + +export type SelectToken = FullToken<'Select'>; + +export const genSelectStyle: GenerateStyle = (token: SelectToken): CSSObject => { + const { componentCls } = token; + + return { + [`${componentCls}-multiple`]: { + [`${componentCls}-selection-item`]: { + border: `1px solid ${token.colorBorder}66`, + }, + }, + }; +}; + +export default (prefixCls: string) => { + const useStyle = genComponentStyleHook('Select', (token: SelectToken) => { + return [genSelectStyle(token)]; + }); + return useStyle(prefixCls); +}; diff --git a/packages/design/src/tag/demo/basic.tsx b/packages/design/src/tag/demo/basic.tsx new file mode 100644 index 000000000..ff4cb23b9 --- /dev/null +++ b/packages/design/src/tag/demo/basic.tsx @@ -0,0 +1,28 @@ +import { Tag } from '@oceanbase/design'; +import React from 'react'; + +const log = (e: React.MouseEvent) => { + console.log(e); +}; + +const preventDefault = (e: React.MouseEvent) => { + e.preventDefault(); + console.log('Clicked! But prevent default.'); +}; + +const App: React.FC = () => ( + <> + Tag 1 + + Link + + + Tag 2 + + + Prevent Default + + +); + +export default App; diff --git a/packages/design/src/tag/demo/borderless.tsx b/packages/design/src/tag/demo/borderless.tsx new file mode 100644 index 000000000..be2ff5b48 --- /dev/null +++ b/packages/design/src/tag/demo/borderless.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Divider, Space, Tag } from '@oceanbase/design'; + +const App: React.FC = () => { + return ( + <> + + Tag 1 + Tag 2 + + Tag 3 + + + Tag 4 + + + + + + processing + + + success + + + error + + + warning + + + magenta + + + red + + + volcano + + + orange + + + gold + + + lime + + + green + + + cyan + + + blue + + + geekblue + + + purple + + + + ); +}; + +export default App; diff --git a/packages/design/src/tag/demo/checkable.tsx b/packages/design/src/tag/demo/checkable.tsx new file mode 100644 index 000000000..b13fd760f --- /dev/null +++ b/packages/design/src/tag/demo/checkable.tsx @@ -0,0 +1,33 @@ +import { Tag } from '@oceanbase/design'; +import React, { useState } from 'react'; + +const { CheckableTag } = Tag; + +const tagsData = ['Movies', 'Books', 'Music', 'Sports']; + +const App: React.FC = () => { + const [selectedTags, setSelectedTags] = useState(['Books']); + + const handleChange = (tag: string, checked: boolean) => { + const nextSelectedTags = checked ? [...selectedTags, tag] : selectedTags.filter(t => t !== tag); + console.log('You are interested in: ', nextSelectedTags); + setSelectedTags(nextSelectedTags); + }; + + return ( + <> + Categories: + {tagsData.map(tag => ( + -1} + onChange={checked => handleChange(tag, checked)} + > + {tag} + + ))} + + ); +}; + +export default App; diff --git a/packages/design/src/tag/demo/color.tsx b/packages/design/src/tag/demo/color.tsx new file mode 100644 index 000000000..f5f50f5d2 --- /dev/null +++ b/packages/design/src/tag/demo/color.tsx @@ -0,0 +1,30 @@ +import { Tag, Divider } from '@oceanbase/design'; +import React from 'react'; + +const App: React.FC = () => ( + <> + Presets +
+ magenta + red + volcano + orange + gold + lime + green + cyan + blue + geekblue + purple +
+ Custom +
+ #f50 + #2db7f5 + #87d068 + #108ee9 +
+ +); + +export default App; diff --git a/packages/design/src/tag/demo/ellipsis.tsx b/packages/design/src/tag/demo/ellipsis.tsx new file mode 100644 index 000000000..b3267850b --- /dev/null +++ b/packages/design/src/tag/demo/ellipsis.tsx @@ -0,0 +1,15 @@ +import { Tag } from '@oceanbase/design'; +import React from 'react'; + +const App: React.FC = () => ( + <> + + Show ellipsis for excess.Show ellipsis for excess.Show ellipsis for excess.Show ellipsis for + excess.Show ellipsis for excess.Show ellipsis for excess.Show ellipsis for excess.Show + ellipsis for excess.Show ellipsis for excess.Show ellipsis for excess.Show ellipsis for + excess.Show ellipsis for excess.Show ellipsis for excess.Show ellipsis for excess. + + +); + +export default App; diff --git a/packages/design/src/tag/demo/icon.tsx b/packages/design/src/tag/demo/icon.tsx new file mode 100644 index 000000000..5f3ad3f96 --- /dev/null +++ b/packages/design/src/tag/demo/icon.tsx @@ -0,0 +1,27 @@ +import { + FacebookOutlined, + LinkedinOutlined, + TwitterOutlined, + YoutubeOutlined, +} from '@oceanbase/icons'; +import { Tag } from '@oceanbase/design'; +import React from 'react'; + +const App: React.FC = () => ( + <> + } color="#55acee"> + Twitter + + } color="#cd201f"> + Youtube + + } color="#3b5999"> + Facebook + + } color="#55acee"> + LinkedIn + + +); + +export default App; diff --git a/packages/design/src/tag/demo/select.tsx b/packages/design/src/tag/demo/select.tsx new file mode 100644 index 000000000..2ebae071e --- /dev/null +++ b/packages/design/src/tag/demo/select.tsx @@ -0,0 +1,40 @@ +import { Select, Tag, Divider } from '@oceanbase/design'; +import React from 'react'; + +const options = ['gold', 'green', 'red', 'cyan']; + +const tagRender = props => { + const { label, value, closable, onClose } = props; + const onPreventMouseDown = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; + return ( + + {label} + + ); +}; + +const App: React.FC = () => ( + <> + Custom Select + + +); + +export default App; diff --git a/packages/design/src/tag/demo/status.tsx b/packages/design/src/tag/demo/status.tsx new file mode 100644 index 000000000..486584dd3 --- /dev/null +++ b/packages/design/src/tag/demo/status.tsx @@ -0,0 +1,46 @@ +import { + CheckCircleOutlined, + ClockCircleOutlined, + CloseCircleOutlined, + ExclamationCircleOutlined, + MinusCircleOutlined, + SyncOutlined, +} from '@oceanbase/icons'; +import { Tag, Divider } from '@oceanbase/design'; +import React from 'react'; + +const App: React.FC = () => ( + <> + Without icon +
+ success + processing + error + warning + default +
+ With icon +
+ } color="success"> + success + + } color="processing"> + processing + + } color="error"> + error + + } color="warning"> + warning + + } color="default"> + waiting + + } color="default"> + stop + +
+ +); + +export default App; diff --git a/packages/design/src/tag/index.md b/packages/design/src/tag/index.md new file mode 100644 index 000000000..5ca14f9a4 --- /dev/null +++ b/packages/design/src/tag/index.md @@ -0,0 +1,21 @@ +--- +title: Tag 标签 +nav: + title: 基础组件 + path: /components +--- + +- 🔥 完全兼容 antd [Tag](https://ant.design/components/tag-cn/) 的能力和 API,可无缝切换。 +- 💄 定制主题和样式,符合 OceanBase Design 设计规范。 + +## 代码演示 + + + +## API + +| 参数 | 说明 | 类型 | 默认值 | +| :------- | :--------------------- | :------ | :----- | +| ellipsis | 内容超长时是否自动省略 | boolean | true | + +- 详见 antd Tag 文档: https://ant.design/components/tag-cn/ diff --git a/packages/design/src/tag/index.ts b/packages/design/src/tag/index.ts deleted file mode 100644 index 02028dbff..000000000 --- a/packages/design/src/tag/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from 'antd/es/tag'; diff --git a/packages/design/src/tag/index.tsx b/packages/design/src/tag/index.tsx new file mode 100644 index 000000000..5c36f79e0 --- /dev/null +++ b/packages/design/src/tag/index.tsx @@ -0,0 +1,60 @@ +import { Tag as AntTag } from 'antd'; +import type { TagProps as AntTagProps } from 'antd/es/tag'; +import { Typography } from '@oceanbase/design'; +import classNames from 'classnames'; +import React, { ReactElement, useContext } from 'react'; +import ConfigProvider from '../config-provider'; +import useStyle from './style'; + +export * from 'antd/es/tag'; + +export interface TagProps extends AntTagProps { + ellipsis?: boolean; +} + +const { CheckableTag } = AntTag; + +const Tag = ({ + prefixCls: customizePrefixCls, + className, + ellipsis = true, + ...restProps +}: TagProps) => { + const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); + const prefixCls = getPrefixCls('tag', customizePrefixCls); + const { wrapSSR } = useStyle(prefixCls); + + const tagCls = classNames( + { + [`${prefixCls}-ellipsis`]: ellipsis, + }, + className + ); + const childrenType = (restProps.children as ReactElement)?.type as any; + const { ellipsis: defalutEllipsis, children: defaultChildren } = (restProps.children as ReactElement)?.props || {}; + + const ellipsisConfig = + typeof defalutEllipsis === 'object' + ? defalutEllipsis + : { + tooltip: childrenType?.__ANT_TOOLTIP ? false : defaultChildren || restProps.children, + }; + + return ellipsis ? ( + wrapSSR( + + + + ) + ) : ( + + ); +}; + +if (process.env.NODE_ENV !== 'production') { + Tag.displayName = AntTag.displayName; +} + +Tag.CheckableTag = CheckableTag; + +export default Tag; diff --git a/packages/design/src/tag/style/index.ts b/packages/design/src/tag/style/index.ts new file mode 100644 index 000000000..a033169f1 --- /dev/null +++ b/packages/design/src/tag/style/index.ts @@ -0,0 +1,76 @@ +import { type FullToken, type GenerateStyle } from 'antd/es/theme/internal'; +import { genComponentStyleHook } from '../../_util/genComponentStyleHook'; +import { genPresetColor } from 'antd/lib/theme/internal'; +import { TinyColor } from '@ctrl/tinycolor'; +import type { CSSObject } from '@ant-design/cssinjs'; + +export type TagToken = FullToken<'Tag'>; + +const getTagBorderColor = (color: string) => { + return new TinyColor(color).setAlpha(0.4).toHex8String(); +}; + +const genTagPresetStatusStyle = ( + token: TagToken, + status: 'success' | 'processing' | 'error' | 'warning' +) => { + const borderColorMap = { + success: token.colorSuccessBorder, + processing: token.colorInfoBorder, + error: token.colorErrorBorder, + warning: token.colorWarningBorder, + }; + return { + [`${token.componentCls}${token.componentCls}-${status}`]: { + borderColor: getTagBorderColor(borderColorMap[status]), + }, + }; +}; + +const genPresetStyle = (token: TagToken) => + genPresetColor(token, (colorKey, { textColor, lightBorderColor }) => { + return { + [`${token.componentCls}${token.componentCls}-${colorKey}`]: { + color: textColor, + borderColor: getTagBorderColor(lightBorderColor), + }, + }; + }); + +export const genTagStyle: GenerateStyle = (token: TagToken): CSSObject => { + const { componentCls } = token; + return { + [`${componentCls}`]: { + borderColor: getTagBorderColor(token.colorBorder), + [`&-ellipsis`]: { + maxWidth: '100%', + textOverflow: 'ellipsis', + overflow: 'hidden', + verticalAlign: 'bottom', + }, + [`&-checkable`]: { + borderColor: 'transparent', + }, + [`&-hidden`]: { + display: 'none', + }, + [`&-borderless`]: { + border: 'transparent', + }, + }, + }; +}; + +export default (prefixCls: string) => { + const useStyle = genComponentStyleHook('Tag', (token: TagToken) => { + return [ + genTagStyle(token), + genPresetStyle(token), + genTagPresetStatusStyle(token, 'success'), + genTagPresetStatusStyle(token, 'error'), + genTagPresetStatusStyle(token, 'processing'), + genTagPresetStatusStyle(token, 'warning'), + ]; + }); + return useStyle(prefixCls); +}; diff --git a/packages/ui/src/Highlight/demo/theme.tsx b/packages/ui/src/Highlight/demo/theme.tsx index 5a693a097..3960d1115 100644 --- a/packages/ui/src/Highlight/demo/theme.tsx +++ b/packages/ui/src/Highlight/demo/theme.tsx @@ -35,7 +35,7 @@ export default () => { })} 主题选择: - +