chore: auto merge branches (#50582)

chore: merge master into feature
This commit is contained in:
github-actions[bot] 2024-08-26 21:56:33 +00:00 committed by GitHub
commit 6176d3cefe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 307 additions and 142 deletions

View File

@ -23,6 +23,7 @@ export default defineConfig({
mfsu: false,
mako: {},
crossorigin: {},
runtimePublicPath: {},
outputPath: '_site',
favicons: ['https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png'],
resolve: {

View File

@ -16,6 +16,14 @@ tag: vVERSION
---
## 5.20.3
`2024-08-26`
- 🐞 Refactor Typography native css ellipsis measure logic to handle precision edge case. [#50514](https://github.com/ant-design/ant-design/pull/50514) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix ColorPicker `onChangeComplete` not correct when click directly without move on the picker panel. [#50501](https://github.com/ant-design/ant-design/pull/50501) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix FloatButton.Group with controlled mode warning for nest updating issue. [#50500](https://github.com/ant-design/ant-design/pull/50500) [@zombieJ](https://github.com/zombieJ)
## 5.20.2
`2024-08-19`

View File

@ -15,6 +15,14 @@ tag: vVERSION
---
## 5.20.3
`2024-08-26`
- 🐞 重构 Typography 在使用 css 原生省略时的检查逻辑,以解决屏幕缩放等情况下的精度问题。[#50514](https://github.com/ant-design/ant-design/pull/50514) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 ColorPicker 组件在面板上不拖拽直接点击的时候,`onChangeComplete` 返回值不正确的问题。[#50501](https://github.com/ant-design/ant-design/pull/50501) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 FloatButton.Group 在受控模式下 React 会警告递归更新的问题。[#50500](https://github.com/ant-design/ant-design/pull/50500) [@zombieJ](https://github.com/zombieJ)
## 5.20.2
`2024-08-19`

View File

@ -57,6 +57,8 @@
## 🖥 兼容环境
支持范围https://browsersl.ist/#q=defaults
- 现代浏览器。
- 支持服务端渲染。
- [Electron](https://www.electronjs.org/)

7
codecov.yml Normal file
View File

@ -0,0 +1,7 @@
coverage:
status:
project: #add everything under here, more options at https://docs.codecov.com/docs/commit-status
default:
target: 100%
threshold: 0%
base: auto

View File

@ -45,8 +45,8 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
Common props ref[Common props](/docs/react/common-props)
<!-- prettier-ignore -->
:::info{title=注意}
v5 use `rootClassName` & `rootStyle` to config wrapper style instead of `className` & `style` in v4 to align the API with Modal.
:::info{title=Note}
v5 uses `rootClassName` & `rootStyle` to configure the outermost element style, instead of `className` & `style` from v4. This is done to align the API with Modal.
:::
| Props | Description | Type | Default | Version |

View File

@ -8,8 +8,8 @@ import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils';
import Button from '../../button';
describe('Popconfirm', () => {
mountTest(Popconfirm as any);
rtlTest(Popconfirm as any);
mountTest(() => <Popconfirm title="test" />);
rtlTest(() => <Popconfirm title="test" />);
const eventObject = expect.objectContaining({
target: expect.anything(),

View File

@ -29,8 +29,8 @@ function expectMatchChecked(container: HTMLElement, checkedList: boolean[]) {
}
describe('Segmented', () => {
mountTest(Segmented as any);
rtlTest(Segmented as any);
mountTest(() => <Segmented options={[]} />);
rtlTest(() => <Segmented options={[]} />);
beforeAll(() => {
jest.useFakeTimers();

View File

@ -1,7 +1,11 @@
/* eslint-disable import/prefer-default-export */
import getDesignToken from './getDesignToken';
import type { GlobalToken, MappingAlgorithm } from './interface';
import { defaultConfig, useToken as useInternalToken } from './internal';
import {
defaultConfig,
DesignTokenContext as InternalDesignTokenContext,
useToken as useInternalToken,
} from './internal';
import compactAlgorithm from './themes/compact';
import darkAlgorithm from './themes/dark';
import defaultAlgorithm from './themes/default';
@ -19,15 +23,21 @@ function useToken() {
export type { GlobalToken, MappingAlgorithm };
export default {
/** @private Test Usage. Do not use in production. */
defaultConfig,
/** Default seedToken */
defaultSeed: defaultConfig.token,
useToken,
defaultAlgorithm,
darkAlgorithm,
compactAlgorithm,
getDesignToken,
/**
* @private Private variable
* @warring 🔥 Do not use in production. 🔥
*/
defaultConfig,
/**
* @private Private variable
* @warring 🔥 Do not use in production. 🔥
*/
_internalContext: InternalDesignTokenContext,
};

View File

@ -29,7 +29,10 @@ const App: React.FC = () => (
treeData={treeData}
height={233}
defaultExpandAll
titleRender={(item) => <MemoTooltip title={item.title as any}>{item.title as any}</MemoTooltip>}
titleRender={(item) => {
const title = item.title as React.ReactNode;
return <MemoTooltip title={title}>{title}</MemoTooltip>;
}}
/>
);

View File

@ -224,6 +224,11 @@ export default function EllipsisMeasure(props: EllipsisProps) {
// ========================= Text Content =========================
const finalContent = React.useMemo(() => {
// Skip everything if `enableMeasure` is disabled
if (!enableMeasure) {
return children(nodeList, false);
}
if (
needEllipsis !== STATUS_MEASURE_NEED_ELLIPSIS ||
!ellipsisCutIndex ||

View File

@ -25,7 +25,7 @@ import Typography from '../Typography';
import CopyBtn from './CopyBtn';
import Ellipsis from './Ellipsis';
import EllipsisTooltip from './EllipsisTooltip';
import { getEleSize } from './util';
import { isEleEllipsis } from './util';
export type BaseType = 'secondary' | 'success' | 'warning' | 'danger';
@ -281,11 +281,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
const textEle = typographyRef.current;
if (enableEllipsis && cssEllipsis && textEle) {
const [offsetWidth, offsetHeight] = getEleSize(textEle);
const currentEllipsis = cssLineClamp
? offsetHeight < textEle.scrollHeight
: offsetWidth < textEle.scrollWidth;
const currentEllipsis = isEleEllipsis(textEle);
if (isNativeEllipsis !== currentEllipsis) {
setIsNativeEllipsis(currentEllipsis);
@ -379,15 +375,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
// Expand
const renderExpand = () => {
const { expandable, symbol } = ellipsisConfig;
if (!expandable) {
return null;
}
if (expanded && expandable !== 'collapsible') {
return null;
}
return (
return expandable ? (
<TransButton
key="expand"
className={`${prefixCls}-${expanded ? 'collapse' : 'expand'}`}
@ -396,7 +384,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
>
{typeof symbol === 'function' ? symbol(expanded) : symbol}
</TransButton>
);
) : null;
};
// Edit
@ -446,7 +434,6 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
};
const renderOperations = (canEllipsis: boolean) => [
// (renderExpanded || ellipsisConfig.collapsible) && renderExpand(),
canEllipsis && renderExpand(),
renderEdit(),
renderCopy(),

View File

@ -13,20 +13,38 @@ export function getNode(dom: React.ReactNode, defaultNode: React.ReactNode, need
}
/**
* Get React of element with precision.
* ref: https://github.com/ant-design/ant-design/issues/50143
* Check for element is native ellipsis
* ref:
* - https://github.com/ant-design/ant-design/issues/50143
* - https://github.com/ant-design/ant-design/issues/50414
*/
export function getEleSize(ele: HTMLElement): [width: number, height: number] {
const rect = ele.getBoundingClientRect();
const { offsetWidth, offsetHeight } = ele;
export function isEleEllipsis(ele: HTMLElement): boolean {
// Create a new div to get the size
const childDiv = document.createElement('em');
ele.appendChild(childDiv);
let returnWidth = offsetWidth;
let returnHeight = offsetHeight;
if (Math.abs(offsetWidth - rect.width) < 1 && Math.abs(offsetHeight - rect.height) < 1) {
returnWidth = rect.width;
returnHeight = rect.height;
// For test case
if (process.env.NODE_ENV !== 'production') {
childDiv.className = 'ant-typography-css-ellipsis-content-measure';
}
return [returnWidth, returnHeight];
const rect = ele.getBoundingClientRect();
const childRect = childDiv.getBoundingClientRect();
// Reset
ele.removeChild(childDiv);
// Range checker
if (
// Horizontal in range
rect.left <= childRect.left &&
childRect.right <= rect.right &&
// Vertical in range
rect.top <= childRect.top &&
childRect.bottom <= rect.bottom
) {
return false;
}
return true;
}

View File

@ -364,29 +364,48 @@ describe('Typography.Ellipsis', () => {
describe('should tooltip support', () => {
let domSpy: ReturnType<typeof spyElementPrototypes>;
let containerWidth = 100;
let contentWidth = 200;
let rectContainerWidth = 100;
let containerRect = {
left: 0,
top: 0,
right: 100,
bottom: 22,
};
let measureRect = {
left: 200,
top: 0,
};
beforeAll(() => {
domSpy = spyElementPrototypes(HTMLElement, {
offsetWidth: {
get: () => containerWidth,
getBoundingClientRect() {
if (
(this as unknown as HTMLElement).classList.contains(
'ant-typography-css-ellipsis-content-measure',
)
) {
return {
...measureRect,
right: measureRect.left,
bottom: measureRect.top + 22,
};
}
return containerRect;
},
scrollWidth: {
get: () => contentWidth,
},
getBoundingClientRect: () => ({
width: rectContainerWidth,
height: 0,
}),
});
});
beforeEach(() => {
containerWidth = 100;
contentWidth = 200;
rectContainerWidth = 100;
containerRect = {
left: 0,
top: 0,
right: 100,
bottom: 22,
};
measureRect = {
left: 200,
top: 0,
};
});
afterAll(() => {
@ -453,22 +472,40 @@ describe('Typography.Ellipsis', () => {
});
});
// https://github.com/ant-design/ant-design/issues/50143
it('precision', async () => {
containerWidth = 100;
contentWidth = 100;
rectContainerWidth = 99.9;
describe('precision', () => {
// https://github.com/ant-design/ant-design/issues/50143
it('should show', async () => {
containerRect.right = 99.9;
measureRect.left = 100;
const { container, baseElement } = await getWrapper({
title: true,
className: 'tooltip-class-name',
});
fireEvent.mouseEnter(container.firstChild!);
const { container, baseElement } = await getWrapper({
title: true,
className: 'tooltip-class-name',
});
fireEvent.mouseEnter(container.firstChild!);
await waitFakeTimer();
await waitFor(() => {
expect(container.querySelector('.tooltip-class-name')).toBeTruthy();
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
// https://github.com/ant-design/ant-design/issues/50414
it('should not show', async () => {
containerRect.right = 48.52;
measureRect.left = 48.52;
const { container, baseElement } = await getWrapper({
title: true,
className: 'tooltip-class-name',
});
fireEvent.mouseEnter(container.firstChild!);
await waitFakeTimer();
expect(container.querySelector('.tooltip-class-name')).toBeTruthy();
expect(baseElement.querySelector('.ant-tooltip-open')).toBeFalsy();
});
});
});
@ -490,22 +527,26 @@ describe('Typography.Ellipsis', () => {
it('should display tooltip if line clamp', async () => {
mockRectSpy = spyElementPrototypes(HTMLElement, {
scrollHeight: {
get() {
let html = (this as any).innerHTML;
html = html.replace(/<[^>]*>/g, '');
const lines = Math.ceil(html.length / LINE_STR_COUNT);
return lines * 16;
},
},
offsetHeight: {
get: () => 32,
},
offsetWidth: {
get: () => 100,
},
scrollWidth: {
get: () => 100,
getBoundingClientRect() {
if (
(this as unknown as HTMLElement).classList.contains(
'ant-typography-css-ellipsis-content-measure',
)
) {
return {
left: 0,
right: 0,
top: 100,
bottom: 122,
};
}
return {
left: 0,
right: 100,
top: 0,
bottom: 22 * 3,
};
},
});
@ -527,6 +568,32 @@ describe('Typography.Ellipsis', () => {
// https://github.com/ant-design/ant-design/issues/46580
it('dynamic to be ellipsis should show tooltip', async () => {
let dynamicWidth = 100;
mockRectSpy = spyElementPrototypes(HTMLElement, {
getBoundingClientRect() {
if (
(this as unknown as HTMLElement).classList.contains(
'ant-typography-css-ellipsis-content-measure',
)
) {
return {
left: 0,
right: dynamicWidth,
top: 0,
bottom: 22,
};
}
return {
left: 100,
right: 100,
top: 0,
bottom: 22,
};
},
});
const ref = React.createRef<HTMLElement>();
render(
<Base ellipsis={{ tooltip: 'bamboo' }} component="p" ref={ref}>
@ -535,8 +602,7 @@ describe('Typography.Ellipsis', () => {
);
// Force to narrow
offsetWidth = 1;
scrollWidth = 100;
dynamicWidth = 50;
triggerResize(ref.current!);
await waitFakeTimer();
@ -544,6 +610,8 @@ describe('Typography.Ellipsis', () => {
fireEvent.mouseEnter(ref.current!);
await waitFakeTimer();
expect(document.querySelector('.ant-tooltip')).toBeTruthy();
mockRectSpy.mockRestore();
});
it('not force single line if expanded', async () => {

View File

@ -5,37 +5,24 @@ order: 1
title: CSS Compatible
---
Ant Design supports the last 2 versions of modern browsers. If you need to be compatible with legacy browsers, please perform downgrade processing according to actual needs:
### Default Style Compatibility
## StyleProvider
Ant Design supports the [last 2 versions of modern browsers](https://browsersl.ist/#q=defaults). If you need to be compatible with legacy browsers, please perform downgrade processing according to actual needs:
Please ref [`@ant-design/cssinjs`](https://github.com/ant-design/cssinjs#styleprovider).
| Feature | antd version | Compatibility | Minimum Chrome Version | Compatibility workaround |
| --- | --- | --- | --- | --- |
| [:where Selector](https://developer.mozilla.org/en-US/docs/Web/CSS/:where) | `>=5.0.0` | [caniuse](https://caniuse.com/?search=%3Awhere) | Chrome 88 | `<StyleProvider hashPriority="high">` |
| [CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties) | `>=5.0.0` | [caniuse](https://caniuse.com/css-logical-props) | Chrome 89 | `<StyleProvider transformers={[legacyLogicalPropertiesTransformer]}>` |
## `layer` Downgrade
If you need to support older browsers, please use [StyleProvider](https://github.com/ant-design/cssinjs#styleprovider) for degradation handling according to your actual requirements.
Ant Design supports configuring `layer` for unified downgrade since `5.17.0`. After the downgrade, the style of antd will always be lower than the default CSS selector priority, so that users can override the style (please be sure to check the browser compatibility of `@layer`):
## `:where` in selector
```tsx
import { StyleProvider } from '@ant-design/cssinjs';
export default () => (
<StyleProvider layer>
<MyApp />
</StyleProvider>
);
```
antd styles will be encapsulated in `@layer` to lower the priority:
```diff
++ @layer antd {
:where(.css-bAMboO).ant-btn {
color: #fff;
}
++ }
```
## Compatible adjustment
- antd version: `>=5.0.0`
- MDN: [:where](https://developer.mozilla.org/en-US/docs/Web/CSS/:where)
- Browser Compatibility: [caniuse](https://caniuse.com/?search=%3Awhere)
- Minimum Chrome Version Supported: 88
- Default Enabled: Yes
The CSS-in-JS feature of Ant Design uses the ":where" selector by default to lower the CSS selector specificity, reducing the additional cost of adjusting custom styles when upgrading for users. However, the compatibility of the ":where" syntax is relatively poor in older browsers ([compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/:where#browser_compatibility)). In certain scenarios, if you need to support older browsers, you can use `@ant-design/cssinjs` to disable the default lowering of specificity (please ensure version consistency with antd).
@ -77,6 +64,12 @@ Raise priority through plugin:
## CSS Logical Properties
- antd version: `>=5.0.0`
- MDN[CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties)
- Browser Compatibility: [caniuse](https://caniuse.com/css-logical-props)
- Minimum Chrome Version Supported: 89
- Default Enabled: Yes
To unify LTR and RTL styles, Ant Design uses CSS logical properties. For example, the original `margin-left` is replaced by `margin-inline-start`, so that it is the starting position spacing under both LTR and RTL. If you need to be compatible with older browsers, you can configure `transformers` through the `StyleProvider` of `@ant-design/cssinjs`:
```tsx
@ -102,6 +95,36 @@ When toggled, styles will downgrade CSS logical properties:
}
```
## `@layer`
- antd version: `>=5.17.0`
- MDN[CSS @layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer)
- Browser Compatibility: [caniuse](https://caniuse.com/css-at-rule-layer)
- Minimum Chrome Version Supported: 99
- Default Enabled: No
Ant Design supports configuring `@layer` for unified css priority downgrade since `5.17.0`. After the downgrade, the style of antd will always be lower than the default CSS selector priority, so that users can override the style (please be sure to check the browser compatibility of `@layer`):
```tsx
import { StyleProvider } from '@ant-design/cssinjs';
export default () => (
<StyleProvider layer>
<MyApp />
</StyleProvider>
);
```
antd styles will be encapsulated in `@layer` to lower the priority:
```diff
++ @layer antd {
:where(.css-bAMboO).ant-btn {
color: #fff;
}
++ }
```
## Rem Adaptation
In responsive web development, there is a need for a convenient and flexible way to achieve page adaptation and responsive design. The `px2remTransformer` transformer can quickly and accurately convert pixel units in style sheets to rem units relative to the root element (HTML tag), enabling the implementation of adaptive and responsive layouts.

View File

@ -5,38 +5,25 @@ order: 1
title: 样式兼容
---
Ant Design 支持最近 2 个版本的现代浏览器。如果你需要兼容旧版浏览器,请根据实际需求进行降级处理:
## 默认样式兼容性说明
## StyleProvider
Ant Design 5.x 支持[最近 2 个版本的现代浏览器](https://browsersl.ist/#q=defaults)。默认情况下,我们使用了一些现代 CSS 特性来提高样式的可维护性和可扩展性,这些特性在旧版浏览器中可能不被支持,好在我们可以通过一些降级兼容方案来解决。
查看 [`@ant-design/cssinjs`](https://github.com/ant-design/cssinjs#styleprovider).
| 特性 | antd 版本 | 兼容性 | 最低 Chrome 版本 | 降级兼容方案 |
| --- | --- | --- | --- | --- |
| [:where 选择器](https://developer.mozilla.org/en-US/docs/Web/CSS/:where) | `>=5.0.0` | [caniuse](https://caniuse.com/?search=%3Awhere) | Chrome 88 | `<StyleProvider hashPriority="high">` |
| [CSS 逻辑属性](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties) | `>=5.0.0` | [caniuse](https://caniuse.com/css-logical-props) | Chrome 89 | `<StyleProvider transformers={[legacyLogicalPropertiesTransformer]}>` |
## `layer` 降权
Ant Design 从 `5.17.0` 起支持配置 `layer` 进行统一降权。经过降权后antd 的样式将始终低于默认的 CSS 选择器优先级,以便于用户进行样式覆盖(请务必注意检查 `@layer` 浏览器兼容性):
```tsx
import { StyleProvider } from '@ant-design/cssinjs';
export default () => (
<StyleProvider layer>
<MyApp />
</StyleProvider>
);
```
antd 的样式会被封装在 `@layer` 中,以降低优先级:
```diff
++ @layer antd {
:where(.css-bAMboO).ant-btn {
color: #fff;
}
++ }
```
如果你需要兼容旧版浏览器,请根据实际需求使用 [StyleProvider](https://github.com/ant-design/cssinjs#styleprovider) 降级处理。
## `:where` 选择器
- 支持版本:`>=5.0.0`
- MDN 文档:[:where](https://developer.mozilla.org/en-US/docs/Web/CSS/:where)
- 浏览器兼容性:[caniuse](https://caniuse.com/?search=%3Awhere)
- Chrome 最低支持版本88
- 默认启用:是
Ant Design 的 CSS-in-JS 默认通过 `:where` 选择器降低 CSS Selector 优先级,以减少用户升级时额外调整自定义样式的成本,不过 `:where` 语法的[兼容性](https://developer.mozilla.org/en-US/docs/Web/CSS/:where#browser_compatibility)在低版本浏览器比较差。在某些场景下你如果需要支持旧版浏览器,你可以使用 `@ant-design/cssinjs` 取消默认的降权操作(请注意版本保持与 antd 一致):
```tsx
@ -77,9 +64,15 @@ export default () => (
## CSS 逻辑属性
- 支持版本:`>=5.0.0`
- MDN 文档:[:where](https://developer.mozilla.org/en-US/docs/Web/CSS/:where)
- 浏览器兼容性:[caniuse](https://caniuse.com/css-logical-props)
- Chrome 最低支持版本89
- 默认启用:是
为了统一 LTR 和 RTL 样式Ant Design 使用了 CSS 逻辑属性。例如原 `margin-left` 使用 `margin-inline-start` 代替,使其在 LTR 和 RTL 下都为起始位置间距。如果你需要兼容旧版浏览器(如 360 浏览器、QQ 浏览器 等等),可以通过 `@ant-design/cssinjs``StyleProvider` 配置 `transformers` 将其转换:
```tsx
```tsx | pure
import { legacyLogicalPropertiesTransformer, StyleProvider } from '@ant-design/cssinjs';
// `transformers` 提供预处理功能将样式进行转换
@ -102,11 +95,41 @@ export default () => (
}
```
## `@layer` 样式优先级降权
- 支持版本:`>=5.17.0`
- MDN 文档:[@layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer)
- 浏览器兼容性:[caniuse](https://caniuse.com/?search=%40layer)
- Chrome 最低支持版本99
- 默认启用:否
Ant Design 从 `5.17.0` 起支持配置 `layer` 进行统一降权。经过降权后antd 的样式将始终低于默认的 CSS 选择器优先级,以便于用户进行样式覆盖(请务必注意检查 `@layer` 浏览器兼容性):
```tsx | pure
import { StyleProvider } from '@ant-design/cssinjs';
export default () => (
<StyleProvider layer>
<MyApp />
</StyleProvider>
);
```
antd 的样式会被封装在 `@layer` 中,以降低优先级:
```diff
++ @layer antd {
:where(.css-bAMboO).ant-btn {
color: #fff;
}
++ }
```
## rem 适配
在响应式网页开发中,需要一种方便且灵活的方式来实现页面的适配和响应式设计。`px2remTransformer` 转换器可以快速而准确地将样式表中的像素单位转换为相对于根元素HTML 标签)的 rem 单位,实现页面的自适应和响应式布局。
```tsx
```tsx | pure
import { px2remTransformer, StyleProvider } from '@ant-design/cssinjs';
const px2rem = px2remTransformer({

View File

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "5.20.2",
"version": "5.20.3",
"description": "An enterprise-class UI design language and React components implementation",
"keywords": [
"ant",
@ -340,11 +340,13 @@
"size-limit": [
{
"path": "./dist/antd.min.js",
"limit": "350 KiB"
"limit": "500 KiB",
"gzip": true
},
{
"path": "./dist/antd-with-locales.min.js",
"limit": "400 KiB"
"limit": "500 KiB",
"gzip": true
}
],
"title": "Ant Design",