diff --git a/.changeset/feat-tag-variant.md b/.changeset/feat-tag-variant.md
new file mode 100644
index 00000000..1debc7bc
--- /dev/null
+++ b/.changeset/feat-tag-variant.md
@@ -0,0 +1,5 @@
+---
+"@tiny-design/react": minor
+---
+
+feat(tag): add `variant` prop with `filled`, `soft`, `solid`, and `outlined` styles
diff --git a/apps/docs/src/routers.tsx b/apps/docs/src/routers.tsx
index 2cf16675..315d22b7 100755
--- a/apps/docs/src/routers.tsx
+++ b/apps/docs/src/routers.tsx
@@ -171,8 +171,8 @@ export const getGuideMenu = (s: SiteLocale): RouterItem[] => {
{
title: s.guideMenu.groups.ai,
children: [
- { title: s.guideMenu.mcpServer, route: 'mcp-server', component: pick(guide.mcpServer, isZh), tag: New },
- { title: s.guideMenu.cli, route: 'cli', component: pick(guide.cli, isZh), tag: New },
+ { title: s.guideMenu.mcpServer, route: 'mcp-server', component: pick(guide.mcpServer, isZh), tag: New },
+ { title: s.guideMenu.cli, route: 'cli', component: pick(guide.cli, isZh), tag: New },
],
},
{
@@ -257,7 +257,7 @@ export const getComponentMenu = (s: SiteLocale): RouterItem[] => {
{ title: 'Timeline', route: 'timeline', component: pick(c.timeline, z) },
{ title: 'Tooltip', route: 'tooltip', component: pick(c.tooltip, z) },
{ title: 'Tree', route: 'tree', component: pick(c.tree, z) },
- { title: 'Chart', route: 'chart', component: pick(c.chart, z), tag: New },
+ { title: 'Chart', route: 'chart', component: pick(c.chart, z), tag: New },
],
},
{
diff --git a/packages/react/src/tag/__tests__/tag.test.tsx b/packages/react/src/tag/__tests__/tag.test.tsx
index c2574eb5..3be51599 100644
--- a/packages/react/src/tag/__tests__/tag.test.tsx
+++ b/packages/react/src/tag/__tests__/tag.test.tsx
@@ -37,4 +37,36 @@ describe('', () => {
container.firstChild && fireEvent.click(container.firstChild);
expect(fn).toHaveBeenCalledTimes(1);
});
+
+ // Variant tests
+ it('should apply filled variant class by default for preset color', () => {
+ const { container } = render(Blue);
+ expect(container.firstChild).toHaveClass('ty-tag_blue');
+ expect(container.firstChild).not.toHaveClass('ty-tag_blue-solid');
+ expect(container.firstChild).not.toHaveClass('ty-tag_blue-outlined');
+ });
+
+ it('should apply solid variant class', () => {
+ const { container } = render(Blue);
+ expect(container.firstChild).toHaveClass('ty-tag_blue-solid');
+ expect(container.firstChild).not.toHaveClass('ty-tag_blue');
+ });
+
+ it('should apply soft variant class', () => {
+ const { container } = render(Blue);
+ expect(container.firstChild).toHaveClass('ty-tag_blue-soft');
+ expect(container.firstChild).not.toHaveClass('ty-tag_blue');
+ });
+
+ it('should apply outlined variant class', () => {
+ const { container } = render(Blue);
+ expect(container.firstChild).toHaveClass('ty-tag_blue-outlined');
+ expect(container.firstChild).not.toHaveClass('ty-tag_blue');
+ });
+
+ it('should not apply variant class without color', () => {
+ const { container } = render(Tag);
+ expect(container.firstChild).toHaveClass('ty-tag');
+ expect(container.firstChild).not.toHaveClass('ty-tag_solid');
+ });
});
diff --git a/packages/react/src/tag/demo/Variant.tsx b/packages/react/src/tag/demo/Variant.tsx
new file mode 100644
index 00000000..da998544
--- /dev/null
+++ b/packages/react/src/tag/demo/Variant.tsx
@@ -0,0 +1,65 @@
+import { Tag, Typography } from '@tiny-design/react';
+
+const colors = ['blue', 'green', 'orange', 'red', 'purple'] as const;
+const statusColors = ['success', 'warning', 'info', 'danger'] as const;
+
+export default function VariantDemo() {
+ return (
+ <>
+
+ Filled (default):
+
+
+ {colors.map((color) => (
+ {color}
+ ))}
+
+
+
+ Soft:
+
+
+ {colors.map((color) => (
+ {color}
+ ))}
+
+
+
+ Solid:
+
+
+ {colors.map((color) => (
+ {color}
+ ))}
+
+
+
+ Outlined:
+
+
+ {colors.map((color) => (
+ {color}
+ ))}
+
+
+
+ Status (solid):
+
+
+ {statusColors.map((color) => (
+ {color}
+ ))}
+
+
+
+ Custom color variants:
+
+
+ filled
+ soft
+ solid
+ outlined
+
+ >
+ );
+}
diff --git a/packages/react/src/tag/index.md b/packages/react/src/tag/index.md
index cd4e60a9..967c0c46 100755
--- a/packages/react/src/tag/index.md
+++ b/packages/react/src/tag/index.md
@@ -10,6 +10,8 @@ import DynamicDemo from './demo/Dynamic';
import DynamicSource from './demo/Dynamic.tsx?raw';
import StatusDemo from './demo/Status';
import StatusSource from './demo/Status.tsx?raw';
+import VariantDemo from './demo/Variant';
+import VariantSource from './demo/Variant.tsx?raw';
# Tag
@@ -59,17 +61,6 @@ Adding or removing a set of tags dynamically.
-
-
-
-
-
-### Colorful Tag
-
-We preset a series of colorful tag styles for use in different situations. You can also set it to a hex color string for custom color.
-
-
-
@@ -88,6 +79,26 @@ By using the `visible` prop, you can control the close state of Tag.
+
+
+
+
+
+### Colorful Tag
+
+We preset a series of colorful tag styles for use in different situations. You can also set it to a hex color string for custom color.
+
+
+
+
+
+
+### Variant
+
+Tags support four variants: `filled` (default), `soft`, `solid`, and `outlined`.
+
+
+
@@ -96,16 +107,17 @@ By using the `visible` prop, you can control the close state of Tag.
### Tag
-| Property | Description | Type | Default |
-| -------------- | ---------------------------------------------- | ------------------------------ | ------- |
-| color | color of the tag (preset or custom hex) | string | - |
-| closable | whether the tag can be closed | boolean | false |
-| defaultVisible | initial visibility | boolean | true |
-| visible | controlled visibility | boolean | - |
-| onClose | callback when tag is closed | (e: MouseEvent) => void | - |
-| onClick | click callback | (e: MouseEvent) => void | - |
-| style | style object of container | CSSProperties | - |
-| className | className of container | string | - |
+| Property | Description | Type | Default |
+| -------------- | ---------------------------------------------- | --------------------------------------- | --------- |
+| color | color of the tag (preset or custom hex) | string | - |
+| variant | variant style of the tag | `'filled'` \| `'soft'` \| `'solid'` \| `'outlined'` | `'filled'` |
+| closable | whether the tag can be closed | boolean | false |
+| defaultVisible | initial visibility | boolean | true |
+| visible | controlled visibility | boolean | - |
+| onClose | callback when tag is closed | (e: MouseEvent) => void | - |
+| onClick | click callback | (e: MouseEvent) => void | - |
+| style | style object of container | CSSProperties | - |
+| className | className of container | string | - |
Preset colors: `magenta`, `red`, `volcano`, `orange`, `gold`, `lime`, `green`, `cyan`, `blue`, `geekblue`, `purple`.
diff --git a/packages/react/src/tag/index.zh_CN.md b/packages/react/src/tag/index.zh_CN.md
index 5b48f4f2..86398793 100644
--- a/packages/react/src/tag/index.zh_CN.md
+++ b/packages/react/src/tag/index.zh_CN.md
@@ -10,6 +10,8 @@ import DynamicDemo from './demo/Dynamic';
import DynamicSource from './demo/Dynamic.tsx?raw';
import StatusDemo from './demo/Status';
import StatusSource from './demo/Status.tsx?raw';
+import VariantDemo from './demo/Variant';
+import VariantSource from './demo/Variant.tsx?raw';
# Tag
@@ -59,17 +61,6 @@ const { CheckableTag } = Tag;
-
-
-
-
-
-### 多彩标签
-
-我们提供了一系列预设的彩色标签样式,适用于不同场景。你也可以自定义十六进制颜色值。
-
-
-
@@ -88,6 +79,26 @@ const { CheckableTag } = Tag;
+
+
+
+
+
+### 多彩标签
+
+我们提供了一系列预设的彩色标签样式,适用于不同场景。你也可以自定义十六进制颜色值。
+
+
+
+
+
+
+### 变体
+
+标签支持四种变体样式:`filled`(默认)、`soft`、`solid` 和 `outlined`。
+
+
+
@@ -96,16 +107,17 @@ const { CheckableTag } = Tag;
### Tag
-| 属性 | 说明 | 类型 | 默认值 |
-| -------------- | ---------------------------------------------- | ------------------------------ | ------- |
-| color | 标签颜色(预设颜色或自定义十六进制值) | string | - |
-| closable | 标签是否可关闭 | boolean | false |
-| defaultVisible | 初始显示状态 | boolean | true |
-| visible | 受控的显示状态 | boolean | - |
-| onClose | 关闭标签时的回调 | (e: MouseEvent) => void | - |
-| onClick | 点击回调 | (e: MouseEvent) => void | - |
-| style | 容器样式对象 | CSSProperties | - |
-| className | 容器的 className | string | - |
+| 属性 | 说明 | 类型 | 默认值 |
+| -------------- | ---------------------------------------------- | ----------------------------------------------------- | ---------- |
+| color | 标签颜色(预设颜色或自定义十六进制值) | string | - |
+| variant | 标签的变体样式 | `'filled'` \| `'soft'` \| `'solid'` \| `'outlined'` | `'filled'` |
+| closable | 标签是否可关闭 | boolean | false |
+| defaultVisible | 初始显示状态 | boolean | true |
+| visible | 受控的显示状态 | boolean | - |
+| onClose | 关闭标签时的回调 | (e: MouseEvent) => void | - |
+| onClick | 点击回调 | (e: MouseEvent) => void | - |
+| style | 容器样式对象 | CSSProperties | - |
+| className | 容器的 className | string | - |
预设颜色:`magenta`、`red`、`volcano`、`orange`、`gold`、`lime`、`green`、`cyan`、`blue`、`geekblue`、`purple`。
diff --git a/packages/react/src/tag/style/_index.scss b/packages/react/src/tag/style/_index.scss
index 36b0dd9c..e7e2a373 100755
--- a/packages/react/src/tag/style/_index.scss
+++ b/packages/react/src/tag/style/_index.scss
@@ -1,5 +1,8 @@
@use '../../style/variables' as *;
+$tag-preset-colors: magenta, red, volcano, orange, gold, lime, green, cyan, blue, geekblue, purple;
+$tag-status-colors: success, info, warning, danger;
+
.#{$prefix}-tag {
user-select: none;
display: none;
@@ -37,93 +40,139 @@
}
}
- &_magenta {
- color: var(--ty-tag-magenta-color);
- background: var(--ty-tag-magenta-bg);
- border-color: var(--ty-tag-magenta-border);
+ // ============= Preset colors: filled (default) =============
+ @each $color in $tag-preset-colors {
+ &_#{$color} {
+ color: var(--ty-tag-#{$color}-color);
+ background: var(--ty-tag-#{$color}-bg);
+ border-color: var(--ty-tag-#{$color}-border);
+ }
}
- &_red {
- color: var(--ty-tag-red-color);
- background: var(--ty-tag-red-bg);
- border-color: var(--ty-tag-red-border);
+ // ============= Status colors: filled (default) =============
+ &_success {
+ color: var(--ty-color-success);
+ background: var(--ty-color-success-bg);
+ border-color: var(--ty-color-success-border);
}
- &_volcano {
- color: var(--ty-tag-volcano-color);
- background: var(--ty-tag-volcano-bg);
- border-color: var(--ty-tag-volcano-border);
+ &_info {
+ color: var(--ty-color-info);
+ background: var(--ty-color-info-bg);
+ border-color: var(--ty-color-info-border);
}
- &_orange {
- color: var(--ty-tag-orange-color);
- background: var(--ty-tag-orange-bg);
- border-color: var(--ty-tag-orange-border);
+ &_warning {
+ color: var(--ty-color-warning);
+ background: var(--ty-color-warning-bg);
+ border-color: var(--ty-color-warning-border);
}
- &_gold {
- color: var(--ty-tag-gold-color);
- background: var(--ty-tag-gold-bg);
- border-color: var(--ty-tag-gold-border);
+ &_danger {
+ color: var(--ty-color-danger);
+ background: var(--ty-color-danger-bg);
+ border-color: var(--ty-color-danger-border);
}
- &_lime {
- color: var(--ty-tag-lime-color);
- background: var(--ty-tag-lime-bg);
- border-color: var(--ty-tag-lime-border);
+ // ============= Preset colors: soft =============
+ @each $color in $tag-preset-colors {
+ &_#{$color}-soft {
+ color: var(--ty-tag-#{$color}-color);
+ background: var(--ty-tag-#{$color}-bg);
+ border-color: transparent;
+ }
}
- &_green {
- color: var(--ty-tag-green-color);
- background: var(--ty-tag-green-bg);
- border-color: var(--ty-tag-green-border);
+ // ============= Status colors: soft =============
+ &_success-soft {
+ color: var(--ty-color-success);
+ background: var(--ty-color-success-bg);
+ border-color: transparent;
}
- &_cyan {
- color: var(--ty-tag-cyan-color);
- background: var(--ty-tag-cyan-bg);
- border-color: var(--ty-tag-cyan-border);
+ &_info-soft {
+ color: var(--ty-color-info);
+ background: var(--ty-color-info-bg);
+ border-color: transparent;
}
- &_blue {
- color: var(--ty-tag-blue-color);
- background: var(--ty-tag-blue-bg);
- border-color: var(--ty-tag-blue-border);
+ &_warning-soft {
+ color: var(--ty-color-warning);
+ background: var(--ty-color-warning-bg);
+ border-color: transparent;
}
- &_geekblue {
- color: var(--ty-tag-geekblue-color);
- background: var(--ty-tag-geekblue-bg);
- border-color: var(--ty-tag-geekblue-border);
+ &_danger-soft {
+ color: var(--ty-color-danger);
+ background: var(--ty-color-danger-bg);
+ border-color: transparent;
}
- &_purple {
- color: var(--ty-tag-purple-color);
- background: var(--ty-tag-purple-bg);
- border-color: var(--ty-tag-purple-border);
+ // ============= Preset colors: solid =============
+ @each $color in $tag-preset-colors {
+ &_#{$color}-solid {
+ color: #fff;
+ background: var(--ty-tag-#{$color}-color);
+ border-color: var(--ty-tag-#{$color}-color);
+ }
}
- &_success {
+ // ============= Status colors: solid =============
+ &_success-solid {
+ color: #fff;
+ background: var(--ty-color-success);
+ border-color: var(--ty-color-success);
+ }
+
+ &_info-solid {
+ color: #fff;
+ background: var(--ty-color-info);
+ border-color: var(--ty-color-info);
+ }
+
+ &_warning-solid {
+ color: #fff;
+ background: var(--ty-color-warning);
+ border-color: var(--ty-color-warning);
+ }
+
+ &_danger-solid {
+ color: #fff;
+ background: var(--ty-color-danger);
+ border-color: var(--ty-color-danger);
+ }
+
+ // ============= Preset colors: outlined =============
+ @each $color in $tag-preset-colors {
+ &_#{$color}-outlined {
+ color: var(--ty-tag-#{$color}-color);
+ background: transparent;
+ border-color: var(--ty-tag-#{$color}-border);
+ }
+ }
+
+ // ============= Status colors: outlined =============
+ &_success-outlined {
color: var(--ty-color-success);
- background: var(--ty-color-success-bg);
+ background: transparent;
border-color: var(--ty-color-success-border);
}
- &_info {
+ &_info-outlined {
color: var(--ty-color-info);
- background: var(--ty-color-info-bg);
+ background: transparent;
border-color: var(--ty-color-info-border);
}
- &_warning {
+ &_warning-outlined {
color: var(--ty-color-warning);
- background: var(--ty-color-warning-bg);
+ background: transparent;
border-color: var(--ty-color-warning-border);
}
- &_danger {
+ &_danger-outlined {
color: var(--ty-color-danger);
- background: var(--ty-color-danger-bg);
+ background: transparent;
border-color: var(--ty-color-danger-border);
}
}
diff --git a/packages/react/src/tag/tag.tsx b/packages/react/src/tag/tag.tsx
index 48b41b7c..6bbc7d88 100644
--- a/packages/react/src/tag/tag.tsx
+++ b/packages/react/src/tag/tag.tsx
@@ -11,6 +11,7 @@ const Tag = React.memo(forwardRef((props, ref) => {
defaultVisible = true,
prefixCls: customisedCls,
color,
+ variant = 'filled',
onClose,
onClick,
className,
@@ -23,8 +24,12 @@ const Tag = React.memo(forwardRef((props, ref) => {
);
const configContext = useContext(ConfigContext);
const prefixCls = getPrefixCls('tag', configContext.prefixCls, customisedCls);
+ const isPresetColor = color && PresetColors.includes(color);
const cls = classNames(prefixCls, className, {
- [`${prefixCls}_${color}`]: color && PresetColors.includes(color),
+ [`${prefixCls}_${color}`]: isPresetColor && variant === 'filled',
+ [`${prefixCls}_${color}-soft`]: isPresetColor && variant === 'soft',
+ [`${prefixCls}_${color}-solid`]: isPresetColor && variant === 'solid',
+ [`${prefixCls}_${color}-outlined`]: isPresetColor && variant === 'outlined',
[`${prefixCls}_visible`]: visible,
[`${prefixCls}_closeable`]: closable,
});
@@ -41,10 +46,24 @@ const Tag = React.memo(forwardRef((props, ref) => {
visibleProp === undefined && setVisible(false);
};
+ const getCustomColorStyle = (): React.CSSProperties => {
+ if (!color || isPresetColor) return {};
+
+ switch (variant) {
+ case 'soft':
+ return { backgroundColor: color, borderColor: 'transparent', color: '#fff' };
+ case 'solid':
+ return { backgroundColor: color, borderColor: color, color: '#fff' };
+ case 'outlined':
+ return { backgroundColor: 'transparent', borderColor: color, color: color };
+ case 'filled':
+ default:
+ return { backgroundColor: color, borderColor: color, color: '#fff' };
+ }
+ };
+
const tagStyle: React.CSSProperties = {
- backgroundColor: color ? (PresetColors.includes(color) ? undefined : color) : undefined,
- borderColor: color ? (PresetColors.includes(color) ? undefined : color) : undefined,
- color: color ? (PresetColors.includes(color) ? undefined : '#fff') : undefined,
+ ...getCustomColorStyle(),
...style,
};
diff --git a/packages/react/src/tag/types.ts b/packages/react/src/tag/types.ts
index 7bd3816f..0d23ab00 100644
--- a/packages/react/src/tag/types.ts
+++ b/packages/react/src/tag/types.ts
@@ -10,6 +10,8 @@ export interface CheckableTagProps extends BaseProps {
export type StatusColor = 'success' | 'warning' | 'info' | 'danger';
+export type TagVariant = 'filled' | 'soft' | 'solid' | 'outlined';
+
export const StatusColors: StatusColor[] = ['success', 'info', 'warning', 'danger'];
export const PresetColors = [
@@ -29,6 +31,7 @@ export const PresetColors = [
export interface TagProps extends BaseProps, React.PropsWithoutRef {
color?: string | StatusColor;
+ variant?: TagVariant;
closable?: boolean;
onClose?: React.MouseEventHandler;
onClick?: React.MouseEventHandler;