mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-09 02:49:18 +08:00
Merge branch feature into next-merge-feature
This commit is contained in:
101
.dumi/hooks/useLocalStorage.ts
Normal file
101
.dumi/hooks/useLocalStorage.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import React, { useCallback, useEffect, useEffectEvent } from 'react';
|
||||
|
||||
const ANT_SYNC_STORAGE_EVENT_KEY = 'ANT_SYNC_STORAGE_EVENT_KEY';
|
||||
|
||||
const isFunction = (val: any): val is (...args: any[]) => any => {
|
||||
return typeof val === 'function';
|
||||
};
|
||||
|
||||
interface Options<T> {
|
||||
defaultValue?: T;
|
||||
serializer?: (value: T) => string;
|
||||
deserializer?: (value: string) => T;
|
||||
onError?: (error: unknown) => void;
|
||||
}
|
||||
|
||||
const useLocalStorage = <T>(key: string, options: Options<T> = {}) => {
|
||||
const storage = typeof window !== 'undefined' ? localStorage : null;
|
||||
|
||||
const {
|
||||
defaultValue,
|
||||
serializer = JSON.stringify,
|
||||
deserializer = JSON.parse,
|
||||
onError = console.error,
|
||||
} = options;
|
||||
|
||||
const getStoredValue = () => {
|
||||
try {
|
||||
const rawData = storage?.getItem(key);
|
||||
if (rawData) {
|
||||
return deserializer(rawData);
|
||||
}
|
||||
} catch (e) {
|
||||
onError(e);
|
||||
}
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
const [state, setState] = React.useState<T>(getStoredValue);
|
||||
|
||||
useEffect(() => {
|
||||
setState(getStoredValue());
|
||||
}, [key]);
|
||||
|
||||
const updateState: React.Dispatch<React.SetStateAction<T>> = (value) => {
|
||||
const currentState = isFunction(value) ? value(state) : value;
|
||||
setState(currentState);
|
||||
try {
|
||||
let newValue: string | null;
|
||||
const oldValue = storage?.getItem(key);
|
||||
if (typeof currentState === 'undefined') {
|
||||
newValue = null;
|
||||
storage?.removeItem(key);
|
||||
} else {
|
||||
newValue = serializer(currentState);
|
||||
storage?.setItem(key, newValue);
|
||||
}
|
||||
dispatchEvent(
|
||||
new CustomEvent(ANT_SYNC_STORAGE_EVENT_KEY, {
|
||||
detail: { key, newValue, oldValue, storageArea: storage },
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
onError(e);
|
||||
}
|
||||
};
|
||||
|
||||
const shouldSync = (ev: StorageEvent) => {
|
||||
return ev && ev.key === key && ev.storageArea === storage;
|
||||
};
|
||||
|
||||
const onNativeStorage = useCallback(
|
||||
(event: StorageEvent) => {
|
||||
if (shouldSync(event)) {
|
||||
setState(getStoredValue());
|
||||
}
|
||||
},
|
||||
[key],
|
||||
);
|
||||
|
||||
const onCustomStorage = useCallback(
|
||||
(event: Event) => {
|
||||
if (shouldSync(event as StorageEvent)) {
|
||||
setState(getStoredValue());
|
||||
}
|
||||
},
|
||||
[key],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window?.addEventListener('storage', onNativeStorage);
|
||||
window?.addEventListener(ANT_SYNC_STORAGE_EVENT_KEY, onCustomStorage);
|
||||
return () => {
|
||||
window?.removeEventListener('storage', onNativeStorage);
|
||||
window?.removeEventListener(ANT_SYNC_STORAGE_EVENT_KEY, onCustomStorage);
|
||||
};
|
||||
}, [key, onNativeStorage, onCustomStorage]);
|
||||
|
||||
return [state, useEffectEvent(updateState)] as const;
|
||||
};
|
||||
|
||||
export default useLocalStorage;
|
||||
@@ -31,15 +31,17 @@ const locales = {
|
||||
|
||||
const useStyle = createStyles(({ cssVar, css, cx }, siteConfig: SiteContextProps) => {
|
||||
const textShadow = `0 0 4px ${cssVar.colorBgContainer}`;
|
||||
const isDark = siteConfig.theme.includes('dark');
|
||||
const mask = cx(css`
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
backdrop-filter: blur(2px);
|
||||
opacity: 1;
|
||||
background-color: ${isDark ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.2)'};
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
transition: all 1s ease;
|
||||
pointer-events: none;
|
||||
[data-prefers-color='dark'] & {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
`);
|
||||
|
||||
const block = cx(css`
|
||||
|
||||
@@ -60,7 +60,15 @@ export type Extras = {
|
||||
|
||||
export type Icons = Icon[];
|
||||
|
||||
export type HeadingBanner = {
|
||||
[key in 'cn' | 'en']: {
|
||||
title?: string;
|
||||
href?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type SiteData = {
|
||||
headingBanner: HeadingBanner;
|
||||
articles: Articles;
|
||||
authors: Authors;
|
||||
recommendations: Recommendations;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { Suspense, useEffect } from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import { App, Button, ConfigProvider, Skeleton } from 'antd';
|
||||
import { enUS, zhCN } from 'antd-token-previewer';
|
||||
import type { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
import { Helmet } from 'dumi';
|
||||
|
||||
import useLocale from '../../hooks/useLocale';
|
||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||
|
||||
const ThemeEditor = React.lazy(() => import('antd-token-previewer/lib/ThemeEditor'));
|
||||
|
||||
@@ -33,24 +34,18 @@ const locales = {
|
||||
},
|
||||
};
|
||||
|
||||
const ANT_DESIGN_V5_THEME_EDITOR_THEME = 'ant-design-v5-theme-editor-theme';
|
||||
const ANT_THEME_EDITOR_THEME = 'ant-theme-editor-theme';
|
||||
|
||||
const CustomTheme: React.FC = () => {
|
||||
const { message } = App.useApp();
|
||||
const [locale, lang] = useLocale(locales);
|
||||
|
||||
const [theme, setTheme] = React.useState<ThemeConfig>({});
|
||||
|
||||
useEffect(() => {
|
||||
const storedConfig = localStorage.getItem(ANT_DESIGN_V5_THEME_EDITOR_THEME);
|
||||
if (storedConfig) {
|
||||
const themeConfig = JSON.parse(storedConfig);
|
||||
setTheme(themeConfig);
|
||||
}
|
||||
}, []);
|
||||
const [themeConfig, setThemeConfig] = useLocalStorage<ThemeConfig>(ANT_THEME_EDITOR_THEME, {
|
||||
defaultValue: {},
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
localStorage.setItem(ANT_DESIGN_V5_THEME_EDITOR_THEME, JSON.stringify(theme));
|
||||
setThemeConfig(themeConfig);
|
||||
message.success(locale.saveSuccessfully);
|
||||
};
|
||||
|
||||
@@ -65,11 +60,9 @@ const CustomTheme: React.FC = () => {
|
||||
<ThemeEditor
|
||||
advanced
|
||||
hideAdvancedSwitcher
|
||||
theme={{ name: 'Custom Theme', key: 'test', config: theme }}
|
||||
theme={{ name: 'Custom Theme', key: 'test', config: themeConfig }}
|
||||
style={{ height: 'calc(100vh - 64px)' }}
|
||||
onThemeChange={(newTheme) => {
|
||||
setTheme(newTheme.config);
|
||||
}}
|
||||
onThemeChange={(newTheme) => setThemeConfig(newTheme.config)}
|
||||
locale={lang === 'cn' ? zhCN : enUS}
|
||||
actions={
|
||||
<Button type="primary" onClick={handleSave}>
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
|
||||
const isEnabled = always || enabledCondition.every(Boolean);
|
||||
|
||||
if (!isEnabled) return;
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prefixCls = 'antd-mirror-notify';
|
||||
const primaryColor = '#1677ff';
|
||||
|
||||
@@ -12,18 +12,18 @@ import type { MenuProps } from 'antd';
|
||||
import { CompactTheme, DarkTheme } from 'antd-token-previewer/es/icons';
|
||||
import { FormattedMessage, useLocation } from 'dumi';
|
||||
|
||||
import useLocalStorage from '../../../hooks/useLocalStorage';
|
||||
import useThemeAnimation from '../../../hooks/useThemeAnimation';
|
||||
import type { SiteContextProps } from '../../slots/SiteContext';
|
||||
import SiteContext from '../../slots/SiteContext';
|
||||
import { getLocalizedPathname, isLocalStorageNameSupported, isZhCN } from '../../utils';
|
||||
import { getLocalizedPathname, isZhCN } from '../../utils';
|
||||
import Link from '../Link';
|
||||
import PromptDrawer from './PromptDrawer';
|
||||
import ThemeIcon from './ThemeIcon';
|
||||
|
||||
export type ThemeName = 'light' | 'dark' | 'auto' | 'compact' | 'motion-off' | 'happy-work';
|
||||
|
||||
// 主题持久化存储键名
|
||||
const ANT_DESIGN_SITE_THEME = 'ant-design-site-theme';
|
||||
export const ANT_DESIGN_SITE_THEME = 'ant-design-site-theme';
|
||||
|
||||
export interface ThemeSwitchProps {
|
||||
value?: ThemeName[];
|
||||
@@ -36,6 +36,10 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
|
||||
const lastThemeKey = useRef<string>(theme.includes('dark') ? 'dark' : 'light');
|
||||
const [isMarketDrawerOpen, setIsMarketDrawerOpen] = useState(false);
|
||||
|
||||
const [, setTheme] = useLocalStorage<ThemeName>(ANT_DESIGN_SITE_THEME, {
|
||||
defaultValue: undefined,
|
||||
});
|
||||
|
||||
const badge = <Badge color="blue" style={{ marginTop: -1 }} />;
|
||||
|
||||
// 主题选项配置
|
||||
@@ -159,14 +163,9 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
|
||||
const filteredTheme = theme.filter((t) => !['light', 'dark', 'auto'].includes(t));
|
||||
const newTheme = [...filteredTheme, themeKey];
|
||||
|
||||
updateSiteConfig({
|
||||
theme: newTheme,
|
||||
});
|
||||
updateSiteConfig({ theme: newTheme });
|
||||
|
||||
// 持久化到 localStorage
|
||||
if (isLocalStorageNameSupported()) {
|
||||
localStorage.setItem(ANT_DESIGN_SITE_THEME, themeKey);
|
||||
}
|
||||
setTheme(themeKey);
|
||||
} else {
|
||||
// 其他主题选项是开关式的
|
||||
const hasTheme = theme.includes(themeKey);
|
||||
|
||||
@@ -20,11 +20,11 @@ import type { ThemeName } from '../common/ThemeSwitch';
|
||||
import SiteThemeProvider from '../SiteThemeProvider';
|
||||
import type { SimpleComponentClassNames, SiteContextProps } from '../slots/SiteContext';
|
||||
import SiteContext from '../slots/SiteContext';
|
||||
import { isLocalStorageNameSupported } from '../utils';
|
||||
|
||||
type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
|
||||
|
||||
const RESPONSIVE_MOBILE = 768;
|
||||
|
||||
export const ANT_DESIGN_NOT_SHOW_BANNER = 'ANT_DESIGN_NOT_SHOW_BANNER';
|
||||
|
||||
// 主题持久化存储键名
|
||||
@@ -58,13 +58,12 @@ const getFinalTheme = (urlTheme: ThemeName[]): ThemeName[] => {
|
||||
// 只认 light/dark
|
||||
const baseTheme = urlTheme.filter((t) => !['light', 'dark', 'auto'].includes(t));
|
||||
const urlColor = urlTheme.find((t) => t === 'light' || t === 'dark');
|
||||
if (urlColor) return [...baseTheme, urlColor];
|
||||
|
||||
if (isLocalStorageNameSupported()) {
|
||||
const stored = localStorage.getItem(ANT_DESIGN_SITE_THEME) as ThemeName;
|
||||
if (stored && ['light', 'dark', 'auto'].includes(stored)) {
|
||||
return [...baseTheme, stored];
|
||||
}
|
||||
if (urlColor) {
|
||||
return [...baseTheme, urlColor];
|
||||
}
|
||||
const stored = localStorage.getItem(ANT_DESIGN_SITE_THEME) as ThemeName;
|
||||
if (stored && ['light', 'dark', 'auto'].includes(stored)) {
|
||||
return [...baseTheme, stored];
|
||||
}
|
||||
// 默认 auto
|
||||
return [...baseTheme, 'auto'];
|
||||
|
||||
@@ -8,6 +8,8 @@ import { useLocation, useSiteData } from 'dumi';
|
||||
import DumiSearchBar from 'dumi/theme-default/slots/SearchBar';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import useLocalStorage from '../../../hooks/useLocalStorage';
|
||||
import { useAntdSiteConfig } from '../../../pages/index/components/util';
|
||||
import ThemeSwitch from '../../common/ThemeSwitch';
|
||||
import DirectionIcon from '../../icons/DirectionIcon';
|
||||
import { ANT_DESIGN_NOT_SHOW_BANNER } from '../../layouts/GlobalLayout';
|
||||
@@ -22,20 +24,7 @@ import SwitchBtn from './SwitchBtn';
|
||||
const RESPONSIVE_XS = 1120;
|
||||
const RESPONSIVE_SM = 1200;
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
message: '语雀征文 · 说说你和开源的故事,赢取 Ant Design 精美周边 🎁',
|
||||
shortMessage: '语雀征文 · 说说你和开源的故事,赢取 Ant Design 精美周边 🎁',
|
||||
more: '前往了解',
|
||||
link: 'https://www.yuque.com/opensource2023',
|
||||
},
|
||||
en: {
|
||||
message: '',
|
||||
shortMessage: '',
|
||||
more: '',
|
||||
link: '',
|
||||
},
|
||||
};
|
||||
export const ANT_LOCAL_TYPE_KEY = 'ANT_LOCAL_TYPE_KEY';
|
||||
|
||||
const useStyle = createStyles(({ cssVar, token, css }) => {
|
||||
const searchIconColor = '#ced4d9';
|
||||
@@ -162,11 +151,13 @@ interface HeaderState {
|
||||
|
||||
// ================================= Header =================================
|
||||
const Header: React.FC = () => {
|
||||
const [locale, lang] = useLocale(locales);
|
||||
const [, lang] = useLocale();
|
||||
const { data: siteData } = useAntdSiteConfig();
|
||||
|
||||
const { pkg } = useSiteData();
|
||||
|
||||
const themeConfig = getThemeConfig();
|
||||
|
||||
const [headerState, setHeaderState] = useState<HeaderState>({
|
||||
menuVisible: false,
|
||||
windowWidth: 1400,
|
||||
@@ -179,24 +170,33 @@ const Header: React.FC = () => {
|
||||
|
||||
const { styles } = useStyle();
|
||||
|
||||
const [, setTopBannerDay] = useLocalStorage<string>(ANT_DESIGN_NOT_SHOW_BANNER, {
|
||||
defaultValue: undefined,
|
||||
});
|
||||
|
||||
const [, setLocalType] = useLocalStorage<string>(ANT_LOCAL_TYPE_KEY, {
|
||||
defaultValue: undefined,
|
||||
});
|
||||
|
||||
const handleHideMenu = useCallback(() => {
|
||||
setHeaderState((prev) => ({ ...prev, menuVisible: false }));
|
||||
}, []);
|
||||
|
||||
const onWindowResize = useCallback(() => {
|
||||
setHeaderState((prev) => ({ ...prev, windowWidth: window.innerWidth }));
|
||||
}, []);
|
||||
|
||||
const onMenuVisibleChange = useCallback((visible: boolean) => {
|
||||
setHeaderState((prev) => ({ ...prev, menuVisible: visible }));
|
||||
}, []);
|
||||
|
||||
const onDirectionChange = () => {
|
||||
updateSiteConfig({ direction: direction !== 'rtl' ? 'rtl' : 'ltr' });
|
||||
};
|
||||
|
||||
const onBannerClose = () => {
|
||||
updateSiteConfig({ bannerVisible: false });
|
||||
|
||||
if (utils.isLocalStorageNameSupported()) {
|
||||
localStorage.setItem(ANT_DESIGN_NOT_SHOW_BANNER, dayjs().toISOString());
|
||||
}
|
||||
setTopBannerDay(dayjs().toISOString());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -238,9 +238,8 @@ const Header: React.FC = () => {
|
||||
const currentProtocol = `${window.location.protocol}//`;
|
||||
const currentHref = window.location.href.slice(currentProtocol.length);
|
||||
|
||||
if (utils.isLocalStorageNameSupported()) {
|
||||
localStorage.setItem('locale', utils.isZhCN(pathname) ? 'en-US' : 'zh-CN');
|
||||
}
|
||||
setLocalType(utils.isZhCN(pathname) ? 'en-US' : 'zh-CN');
|
||||
|
||||
window.location.href =
|
||||
currentProtocol +
|
||||
currentHref.replace(
|
||||
@@ -272,6 +271,12 @@ const Header: React.FC = () => {
|
||||
const isHome = ['', 'index', 'index-cn'].includes(pathname);
|
||||
const isZhCN = lang === 'cn';
|
||||
const isRTL = direction === 'rtl';
|
||||
|
||||
// Get banner data from site config
|
||||
const bannerData = siteData?.headingBanner?.[lang as 'cn' | 'en'];
|
||||
const bannerTitle = bannerData?.title || '';
|
||||
const bannerHref = bannerData?.href || '';
|
||||
|
||||
let responsive: null | 'narrow' | 'crowded' = null;
|
||||
if (windowWidth < RESPONSIVE_XS) {
|
||||
responsive = 'crowded';
|
||||
@@ -373,7 +378,7 @@ const Header: React.FC = () => {
|
||||
<MenuOutlined className="nav-phone-icon" />
|
||||
</Popover>
|
||||
)}
|
||||
{isZhCN && bannerVisible && (
|
||||
{isZhCN && bannerVisible && bannerTitle && bannerHref && (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
@@ -385,23 +390,25 @@ const Header: React.FC = () => {
|
||||
<Alert
|
||||
className={styles.banner}
|
||||
title={
|
||||
<>
|
||||
<span>{isMobile ? locale.shortMessage : locale.message}</span>
|
||||
<a
|
||||
className={styles.link}
|
||||
href={locale.link}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={() => {
|
||||
window.gtag?.('event', '点击', {
|
||||
event_category: 'top_banner',
|
||||
event_label: locale.link,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{locale.more}
|
||||
</a>
|
||||
</>
|
||||
bannerTitle && bannerHref ? (
|
||||
<>
|
||||
<span>{bannerTitle}</span>
|
||||
<a
|
||||
className={styles.link}
|
||||
href={bannerHref}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={() => {
|
||||
window.gtag?.('event', '点击', {
|
||||
event_category: 'top_banner',
|
||||
event_label: bannerHref,
|
||||
});
|
||||
}}
|
||||
>
|
||||
前往了解
|
||||
</a>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
type="info"
|
||||
banner
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import semver from 'semver';
|
||||
import flatten from 'lodash/flatten';
|
||||
import flattenDeep from 'lodash/flattenDeep';
|
||||
import semver from 'semver';
|
||||
|
||||
import deprecatedVersions from '../../../BUG_VERSIONS.json';
|
||||
import themeConfig from '../themeConfig';
|
||||
|
||||
@@ -157,19 +158,6 @@ export function getLocalizedPathname(
|
||||
return { pathname: fullPath, search };
|
||||
}
|
||||
|
||||
export function isLocalStorageNameSupported() {
|
||||
const testKey = 'test';
|
||||
const storage = window.localStorage;
|
||||
try {
|
||||
storage.setItem(testKey, '1');
|
||||
storage.removeItem(testKey);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Your web browser does not support storing settings locally.', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function loadScript(src: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
@@ -177,7 +165,7 @@ export function loadScript(src: string) {
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head!.appendChild(script);
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
13
.dumirc.ts
13
.dumirc.ts
@@ -131,17 +131,6 @@ export default defineConfig({
|
||||
headScripts: [
|
||||
`
|
||||
(function () {
|
||||
function isLocalStorageNameSupported() {
|
||||
const testKey = 'test';
|
||||
const storage = window.localStorage;
|
||||
try {
|
||||
storage.setItem(testKey, '1');
|
||||
storage.removeItem(testKey);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 优先级提高到所有静态资源的前面,语言不对,加载其他静态资源没意义
|
||||
const pathname = location.pathname;
|
||||
|
||||
@@ -171,7 +160,7 @@ export default defineConfig({
|
||||
}
|
||||
|
||||
// 首页无视链接里面的语言设置 https://github.com/ant-design/ant-design/issues/4552
|
||||
if (isLocalStorageNameSupported() && (pathname === '/' || pathname === '/index-cn')) {
|
||||
if (pathname === '/' || pathname === '/index-cn') {
|
||||
const lang =
|
||||
(window.localStorage && localStorage.getItem('locale')) ||
|
||||
((navigator.language || navigator.browserLanguage).toLowerCase() === 'zh-cn'
|
||||
|
||||
@@ -39,7 +39,9 @@ jobs:
|
||||
const now = new Date();
|
||||
|
||||
for (const issue of issues) {
|
||||
if (issue.pull_request) continue;
|
||||
if (issue.pull_request) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const updatedAt = new Date(issue.updated_at);
|
||||
const daysInactive = (now - updatedAt) / (1000 * 60 * 60 * 24);
|
||||
|
||||
@@ -217,7 +217,9 @@ const Alert = React.forwardRef<AlertRef, AlertProps>((props, ref) => {
|
||||
|
||||
// closeable when closeText or closeIcon is assigned
|
||||
const isClosable = React.useMemo<boolean>(() => {
|
||||
if (typeof closable === 'object' && closable.closeIcon) return true;
|
||||
if (typeof closable === 'object' && closable.closeIcon) {
|
||||
return true;
|
||||
}
|
||||
if (closeText) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
友好的 [React 错误处理](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html) 包裹组件。
|
||||
友好的 [React 错误处理](https://zh-hans.react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) 包裹组件。
|
||||
|
||||
## en-US
|
||||
|
||||
ErrorBoundary Component for making error handling easier in [React](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html).
|
||||
ErrorBoundary Component for making error handling easier in [React](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary).
|
||||
|
||||
@@ -65,8 +65,12 @@ const App: React.FC = () => {
|
||||
};
|
||||
|
||||
const cellRender: CalendarProps<Dayjs>['cellRender'] = (current, info) => {
|
||||
if (info.type === 'date') return dateCellRender(current);
|
||||
if (info.type === 'month') return monthCellRender(current);
|
||||
if (info.type === 'date') {
|
||||
return dateCellRender(current);
|
||||
}
|
||||
if (info.type === 'month') {
|
||||
return monthCellRender(current);
|
||||
}
|
||||
return info.originNode;
|
||||
};
|
||||
|
||||
|
||||
@@ -13,8 +13,12 @@ const PickerWithType = ({
|
||||
type: PickerType;
|
||||
onChange: TimePickerProps['onChange'] | DatePickerProps['onChange'];
|
||||
}) => {
|
||||
if (type === 'time') return <TimePicker onChange={onChange} />;
|
||||
if (type === 'date') return <DatePicker onChange={onChange} />;
|
||||
if (type === 'time') {
|
||||
return <TimePicker onChange={onChange} />;
|
||||
}
|
||||
if (type === 'date') {
|
||||
return <DatePicker onChange={onChange} />;
|
||||
}
|
||||
return <DatePicker picker={type} onChange={onChange} />;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
通过 `Form.useForm` 对表单数据域进行交互。
|
||||
|
||||
> 注意 `useForm` 是 [React Hooks](https://reactjs.org/docs/hooks-intro.html) 的实现,只能用于函数组件。如果是在 Class Component 下,你也可以通过 `ref` 获取数据域:https://codesandbox.io/p/sandbox/ngtjtm
|
||||
> 注意 `useForm` 是 [React Hooks](https://zh-hans.react.dev/reference/react/hooks) 的实现,只能用于函数组件。如果是在 Class Component 下,你也可以通过 `ref` 获取数据域:https://codesandbox.io/p/sandbox/ngtjtm
|
||||
|
||||
## en-US
|
||||
|
||||
Call form method with `Form.useForm`.
|
||||
|
||||
> Note that `useForm` is a [React Hooks](https://reactjs.org/docs/hooks-intro.html) that only works in functional component. You can also use `ref` to get the form instance in class component: https://codesandbox.io/p/sandbox-ngtjtm
|
||||
> Note that `useForm` is a [React Hooks](https://react.dev/reference/react/hooks) that only works in functional component. You can also use `ref` to get the form instance in class component: https://codesandbox.io/p/sandbox-ngtjtm
|
||||
|
||||
@@ -606,7 +606,7 @@ Components inside Form.Item with name property will turn into controlled mode, w
|
||||
|
||||
### Why can not call `ref` of Form at first time?
|
||||
|
||||
`ref` only receives the mounted instance. please ref React official doc: <https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs>
|
||||
`ref` only receives the mounted instance. please ref React official doc: <https://react.dev/learn/manipulating-the-dom-with-refs#when-react-attaches-the-refs>
|
||||
|
||||
### Why will `resetFields` re-mount component?
|
||||
|
||||
|
||||
@@ -605,7 +605,7 @@ Form.Item 默认绑定值属性到 `value` 上,而 Switch、Checkbox 等组件
|
||||
|
||||
### 为什么第一次调用 `ref` 的 Form 为空?
|
||||
|
||||
`ref` 仅在节点被加载时才会被赋值,请参考 React 官方文档:<https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs>
|
||||
`ref` 仅在节点被加载时才会被赋值,请参考 React 官方文档:<https://zh-hans.react.dev/learn/manipulating-the-dom-with-refs#when-react-attaches-the-refs>
|
||||
|
||||
### 为什么 `resetFields` 会重新 mount 组件?
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@ const formItemNameBlackList = ['parentNode'];
|
||||
const defaultItemNamePrefixCls: string = 'form_item';
|
||||
|
||||
export function toArray<T>(candidate?: T | T[] | false): T[] {
|
||||
if (candidate === undefined || candidate === false) return [];
|
||||
|
||||
if (candidate === undefined || candidate === false) {
|
||||
return [];
|
||||
}
|
||||
return Array.isArray(candidate) ? candidate : [candidate];
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
|
||||
> When `Input` is used in a `Form.Item` context, if the `Form.Item` has the `id` props defined then `value`, `defaultValue`, and `id` props of `Input` are automatically set.
|
||||
|
||||
The rest of the props of Input are exactly the same as the original [input](https://reactjs.org/docs/dom-elements.html#all-supported-html-attributes).
|
||||
The rest of the props of Input are exactly the same as the original [input](https://react.dev/reference/react-dom/components/input).
|
||||
|
||||
#### CountConfig
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ demo:
|
||||
|
||||
> 如果 `Input` 在 `Form.Item` 内,并且 `Form.Item` 设置了 `id` 属性,则 `value` `defaultValue` 和 `id` 属性会被自动设置。
|
||||
|
||||
Input 的其他属性和 React 自带的 [input](https://reactjs.org/docs/dom-elements.html#all-supported-html-attributes) 一致。
|
||||
Input 的其他属性和 React 自带的 [input](https://zh-hans.react.dev/reference/react-dom/components/input) 一致。
|
||||
|
||||
#### CountConfig
|
||||
|
||||
|
||||
@@ -16,8 +16,9 @@ const App: React.FC = () => {
|
||||
fetch(`https://api.github.com/search/users?q=${key}`)
|
||||
.then((res) => res.json())
|
||||
.then(({ items = [] }) => {
|
||||
if (ref.current !== key) return;
|
||||
|
||||
if (ref.current !== key) {
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
setUsers(items.slice(0, 10));
|
||||
});
|
||||
|
||||
@@ -171,103 +171,203 @@ exports[`renders components/segmented/demo/block.tsx extend context correctly 1`
|
||||
exports[`renders components/segmented/demo/block.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/segmented/demo/componentToken.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
aria-label="segmented control"
|
||||
class="ant-segmented css-var-test-id"
|
||||
role="radiogroup"
|
||||
tabindex="0"
|
||||
>
|
||||
Array [
|
||||
<div
|
||||
class="ant-segmented-group"
|
||||
aria-label="segmented control"
|
||||
class="ant-segmented css-var-test-id"
|
||||
role="radiogroup"
|
||||
tabindex="0"
|
||||
>
|
||||
<label
|
||||
class="ant-segmented-item ant-segmented-item-selected"
|
||||
<div
|
||||
class="ant-segmented-group"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="true"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Daily"
|
||||
<label
|
||||
class="ant-segmented-item ant-segmented-item-selected"
|
||||
>
|
||||
Daily
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
<input
|
||||
checked=""
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="true"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Daily"
|
||||
>
|
||||
Daily
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Weekly"
|
||||
>
|
||||
Weekly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Monthly"
|
||||
>
|
||||
Monthly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Quarterly"
|
||||
>
|
||||
Quarterly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Yearly"
|
||||
>
|
||||
Yearly
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>,
|
||||
,
|
||||
<div
|
||||
aria-label="segmented control"
|
||||
class="ant-segmented css-var-test-id"
|
||||
role="radiogroup"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-segmented-group"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Weekly"
|
||||
<label
|
||||
class="ant-segmented-item ant-segmented-item-selected"
|
||||
>
|
||||
Weekly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Monthly"
|
||||
<input
|
||||
checked=""
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="true"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Daily"
|
||||
>
|
||||
Daily
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
Monthly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Quarterly"
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Weekly"
|
||||
>
|
||||
Weekly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
Quarterly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Yearly"
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Monthly"
|
||||
>
|
||||
Monthly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
Yearly
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Quarterly"
|
||||
>
|
||||
Quarterly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Yearly"
|
||||
>
|
||||
Yearly
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/segmented/demo/componentToken.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
@@ -167,103 +167,203 @@ exports[`renders components/segmented/demo/block.tsx correctly 1`] = `
|
||||
`;
|
||||
|
||||
exports[`renders components/segmented/demo/componentToken.tsx correctly 1`] = `
|
||||
<div
|
||||
aria-label="segmented control"
|
||||
class="ant-segmented css-var-test-id"
|
||||
role="radiogroup"
|
||||
tabindex="0"
|
||||
>
|
||||
Array [
|
||||
<div
|
||||
class="ant-segmented-group"
|
||||
aria-label="segmented control"
|
||||
class="ant-segmented css-var-test-id"
|
||||
role="radiogroup"
|
||||
tabindex="0"
|
||||
>
|
||||
<label
|
||||
class="ant-segmented-item ant-segmented-item-selected"
|
||||
<div
|
||||
class="ant-segmented-group"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="true"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Daily"
|
||||
<label
|
||||
class="ant-segmented-item ant-segmented-item-selected"
|
||||
>
|
||||
Daily
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
<input
|
||||
checked=""
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="true"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Daily"
|
||||
>
|
||||
Daily
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Weekly"
|
||||
>
|
||||
Weekly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Monthly"
|
||||
>
|
||||
Monthly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Quarterly"
|
||||
>
|
||||
Quarterly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Yearly"
|
||||
>
|
||||
Yearly
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>,
|
||||
,
|
||||
<div
|
||||
aria-label="segmented control"
|
||||
class="ant-segmented css-var-test-id"
|
||||
role="radiogroup"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-segmented-group"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Weekly"
|
||||
<label
|
||||
class="ant-segmented-item ant-segmented-item-selected"
|
||||
>
|
||||
Weekly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Monthly"
|
||||
<input
|
||||
checked=""
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="true"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Daily"
|
||||
>
|
||||
Daily
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
Monthly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Quarterly"
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Weekly"
|
||||
>
|
||||
Weekly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
Quarterly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Yearly"
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Monthly"
|
||||
>
|
||||
Monthly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
Yearly
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Quarterly"
|
||||
>
|
||||
Quarterly
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
/>
|
||||
<div
|
||||
aria-checked="false"
|
||||
class="ant-segmented-item-label"
|
||||
role="radio"
|
||||
title="Yearly"
|
||||
>
|
||||
Yearly
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/segmented/demo/controlled.tsx correctly 1`] = `
|
||||
|
||||
@@ -2,22 +2,41 @@ import React from 'react';
|
||||
import { ConfigProvider, Segmented } from 'antd';
|
||||
|
||||
const Demo: React.FC = () => (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Segmented: {
|
||||
itemColor: '#222',
|
||||
itemHoverColor: '#333',
|
||||
itemHoverBg: 'rgba(0, 0, 0, 0.06)',
|
||||
itemSelectedBg: '#aaa',
|
||||
itemActiveBg: '#ccc',
|
||||
itemSelectedColor: '#fff',
|
||||
<>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Segmented: {
|
||||
itemColor: '#222',
|
||||
itemHoverColor: '#333',
|
||||
itemHoverBg: 'rgba(0, 0, 0, 0.06)',
|
||||
itemSelectedBg: '#aaa',
|
||||
itemActiveBg: '#ccc',
|
||||
itemSelectedColor: '#fff',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Segmented options={['Daily', 'Weekly', 'Monthly', 'Quarterly', 'Yearly']} />
|
||||
</ConfigProvider>
|
||||
}}
|
||||
>
|
||||
<Segmented options={['Daily', 'Weekly', 'Monthly', 'Quarterly', 'Yearly']} />
|
||||
</ConfigProvider>
|
||||
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Segmented: {
|
||||
itemColor: '#222',
|
||||
itemHoverColor: '#333',
|
||||
itemHoverBg: 'rgba(0, 0, 0, 0.06)',
|
||||
itemSelectedBg: 'linear-gradient(225deg, #c200ff 0%, #00ffff 100%)',
|
||||
itemActiveBg: '#ccc',
|
||||
itemSelectedColor: '#fff',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Segmented options={['Daily', 'Weekly', 'Monthly', 'Quarterly', 'Yearly']} />
|
||||
</ConfigProvider>
|
||||
</>
|
||||
);
|
||||
|
||||
export default Demo;
|
||||
|
||||
@@ -65,7 +65,7 @@ function getItemDisabledStyle(cls: string, token: SegmentedToken): CSSObject {
|
||||
|
||||
function getItemSelectedStyle(token: SegmentedToken): CSSObject {
|
||||
return {
|
||||
backgroundColor: token.itemSelectedBg,
|
||||
background: token.itemSelectedBg,
|
||||
boxShadow: token.boxShadowTertiary,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ const Compact: React.FC<SpaceCompactProps> = (props) => {
|
||||
</CompactItem>
|
||||
);
|
||||
}),
|
||||
[size, childNodes, compactItemContext],
|
||||
[childNodes, compactItemContext, direction, mergedSize, prefixCls],
|
||||
);
|
||||
|
||||
// =========================== Render ===========================
|
||||
|
||||
@@ -161,7 +161,7 @@ const Statistic = React.forwardRef<StatisticRef, StatisticProps>((props, ref) =>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Skeleton paragraph={false} loading={loading} className={`${prefixCls}-skeleton`}>
|
||||
<Skeleton paragraph={false} loading={loading} className={`${prefixCls}-skeleton`} active>
|
||||
<div className={contentClassNames} style={{ ...valueStyle, ...mergedStyles.content }}>
|
||||
{prefix && (
|
||||
<span className={prefixClassNames} style={mergedStyles.prefix}>
|
||||
|
||||
@@ -166,7 +166,7 @@ exports[`renders components/statistic/demo/basic.tsx extend context correctly 1`
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-skeleton ant-statistic-skeleton css-var-test-id"
|
||||
class="ant-skeleton ant-skeleton-active ant-statistic-skeleton css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-skeleton-section"
|
||||
@@ -437,7 +437,7 @@ exports[`renders components/statistic/demo/component-token.tsx extend context co
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-skeleton ant-statistic-skeleton css-var-test-id"
|
||||
class="ant-skeleton ant-skeleton-active ant-statistic-skeleton css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-skeleton-section"
|
||||
|
||||
@@ -160,7 +160,7 @@ exports[`renders components/statistic/demo/basic.tsx correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-skeleton ant-statistic-skeleton css-var-test-id"
|
||||
class="ant-skeleton ant-skeleton-active ant-statistic-skeleton css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-skeleton-section"
|
||||
@@ -427,7 +427,7 @@ exports[`renders components/statistic/demo/component-token.tsx correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-skeleton ant-statistic-skeleton css-var-test-id"
|
||||
class="ant-skeleton ant-skeleton-active ant-statistic-skeleton css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-skeleton-section"
|
||||
|
||||
@@ -39,7 +39,9 @@ const App: React.FC = () => {
|
||||
};
|
||||
|
||||
const remove = (targetKey: TargetKey) => {
|
||||
if (!items) return;
|
||||
if (!items) {
|
||||
return;
|
||||
}
|
||||
const targetIndex = items.findIndex((item) => item.key === targetKey);
|
||||
const newItems = items.filter((item) => item.key !== targetKey);
|
||||
|
||||
|
||||
104
components/timeline/TimelineItemList.tsx
Normal file
104
components/timeline/TimelineItemList.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import * as React from 'react';
|
||||
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import type { TimelineProps } from './Timeline';
|
||||
import TimelineItem from './TimelineItem';
|
||||
import type { TimelineItemProps } from './TimelineItem';
|
||||
|
||||
const TimelineItemList: React.FC<TimelineProps & { hashId: string; direction?: string }> = ({
|
||||
prefixCls,
|
||||
className,
|
||||
pending = false,
|
||||
children,
|
||||
items,
|
||||
rootClassName,
|
||||
reverse = false,
|
||||
direction,
|
||||
hashId,
|
||||
pendingDot,
|
||||
mode = '' as TimelineProps['mode'],
|
||||
...restProps
|
||||
}) => {
|
||||
const getPositionCls = (position: string, idx: number) => {
|
||||
if (mode === 'alternate') {
|
||||
if (position === 'right') {
|
||||
return `${prefixCls}-item-right`;
|
||||
}
|
||||
if (position === 'left') {
|
||||
return `${prefixCls}-item-left`;
|
||||
}
|
||||
return idx % 2 === 0 ? `${prefixCls}-item-left` : `${prefixCls}-item-right`;
|
||||
}
|
||||
if (mode === 'left') {
|
||||
return `${prefixCls}-item-left`;
|
||||
}
|
||||
if (mode === 'right') {
|
||||
return `${prefixCls}-item-right`;
|
||||
}
|
||||
if (position === 'right') {
|
||||
return `${prefixCls}-item-right`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
const mergedItems = [...(items || [])];
|
||||
const pendingNode = typeof pending === 'boolean' ? null : pending;
|
||||
|
||||
if (pending) {
|
||||
mergedItems.push({
|
||||
pending: !!pending,
|
||||
dot: pendingDot || <LoadingOutlined />,
|
||||
children: pendingNode,
|
||||
});
|
||||
}
|
||||
|
||||
if (reverse) {
|
||||
mergedItems.reverse();
|
||||
}
|
||||
const itemsCount = mergedItems.length;
|
||||
const lastCls = `${prefixCls}-item-last`;
|
||||
|
||||
const itemsList = mergedItems
|
||||
.filter((item: TimelineItemProps) => !!item)
|
||||
.map((item: TimelineItemProps, idx: number) => {
|
||||
const pendingClass = idx === itemsCount - 2 ? lastCls : '';
|
||||
const readyClass = idx === itemsCount - 1 ? lastCls : '';
|
||||
const { className: itemClassName, ...itemProps } = item;
|
||||
|
||||
return (
|
||||
<TimelineItem
|
||||
{...itemProps}
|
||||
className={classNames([
|
||||
itemClassName,
|
||||
!reverse && !!pending ? pendingClass : readyClass,
|
||||
getPositionCls(item?.position ?? '', idx),
|
||||
])}
|
||||
key={item?.key || idx}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const hasLabelItem = mergedItems.some((item: TimelineItemProps) => !!item?.label);
|
||||
|
||||
const classString = classNames(
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}-pending`]: !!pending,
|
||||
[`${prefixCls}-reverse`]: !!reverse,
|
||||
[`${prefixCls}-${mode}`]: !!mode && !hasLabelItem,
|
||||
[`${prefixCls}-label`]: hasLabelItem,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
},
|
||||
className,
|
||||
rootClassName,
|
||||
hashId,
|
||||
);
|
||||
|
||||
return (
|
||||
<ol {...restProps} className={classString}>
|
||||
{itemsList}
|
||||
</ol>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimelineItemList;
|
||||
@@ -76,8 +76,9 @@ const Editable: React.FC<EditableProps> = (props) => {
|
||||
|
||||
const onKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = ({ keyCode }) => {
|
||||
// We don't record keyCode when IME is using
|
||||
if (inComposition.current) return;
|
||||
|
||||
if (inComposition.current) {
|
||||
return;
|
||||
}
|
||||
lastKeyCode.current = keyCode;
|
||||
};
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ The `defaultXxxx` (e.g. `defaultValue`) of `Input`/`Select`(etc...) only works o
|
||||
|
||||
## Why does modifying props in mutable way not trigger a component update?
|
||||
|
||||
antd use shallow compare of props to optimize performance. You should always pass the new object when updating the state. Please ref [React's document](https://reactjs.org/docs/thinking-in-react.html)
|
||||
antd use shallow compare of props to optimize performance. You should always pass the new object when updating the state. Please ref [React's document](https://react.dev/learn/thinking-in-react)
|
||||
|
||||
## After I set the `value` of an `Input`/`Select`(etc.) component, the value cannot be changed by user's action.
|
||||
|
||||
@@ -181,7 +181,7 @@ Static methods like message/notification/Modal.confirm are not using the same re
|
||||
|
||||
## Why shouldn't I use component internal props or state with ref?
|
||||
|
||||
You should only access the API by official doc with ref. Directly access internal `props` or `state` is not recommended which will make your code strong coupling with current version. Any refactor will break your code like refactor with [Hooks](https://reactjs.org/docs/hooks-intro.html) version, delete or rename internal `props` or `state`, adjust internal node constructor, etc.
|
||||
You should only access the API by official doc with ref. Directly access internal `props` or `state` is not recommended which will make your code strong coupling with current version. Any refactor will break your code like refactor with [Hooks](https://react.dev/reference/react/hooks) version, delete or rename internal `props` or `state`, adjust internal node constructor, etc.
|
||||
|
||||
<div id="why-open"></div>
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ antd 在 minor 和 patch 版本迭代中会避免引入破坏性变更,遵从
|
||||
|
||||
## 为什么修改组件传入的对象或数组属性组件不会更新?
|
||||
|
||||
antd 内部会对 props 进行浅比较实现性能优化。当状态变更,你总是应该传递一个新的对象。具体请参考 [React 的文档](https://zh-hans.reactjs.org/docs/thinking-in-react.html)
|
||||
antd 内部会对 props 进行浅比较实现性能优化。当状态变更,你总是应该传递一个新的对象。具体请参考 [React 的文档](https://zh-hans.react.dev/learn/thinking-in-react)
|
||||
|
||||
## 当我设置了 `Input`/`Select` 等的 `value` 时它就无法修改了。
|
||||
|
||||
@@ -204,7 +204,7 @@ message/notification/Modal.confirm 等静态方法不同于 `<Button />` 的渲
|
||||
|
||||
## 为什么我不应该通过 ref 访问组件内部的 props 和 state?
|
||||
|
||||
你通过 ref 获得引用时只应该使用文档提供的方法。直接读取组件内部的 `props` 和 `state` 不是一个好的设计,这会使你的代码与组件版本强耦合。任何重构都可能会使你的代码无法工作,其中重构包括且不仅限于改造成 [Hooks](https://reactjs.org/docs/hooks-intro.html) 版本、移除 / 更名内部 `props` 与 `state`、调整内部 React 节点结构等等。
|
||||
你通过 ref 获得引用时只应该使用文档提供的方法。直接读取组件内部的 `props` 和 `state` 不是一个好的设计,这会使你的代码与组件版本强耦合。任何重构都可能会使你的代码无法工作,其中重构包括且不仅限于改造成 [Hooks](https://zh-hans.react.dev/reference/react/hooks) 版本、移除 / 更名内部 `props` 与 `state`、调整内部 React 节点结构等等。
|
||||
|
||||
<div id="why-open"></div>
|
||||
|
||||
|
||||
@@ -249,7 +249,9 @@ export default function Page() {
|
||||
queryClient.invalidateQueries({ queryKey: ['products'] });
|
||||
},
|
||||
});
|
||||
if (productsQuery.isLoading) return null;
|
||||
if (productsQuery.isLoading) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h1 className={styles.title}>Page products</h1>
|
||||
|
||||
@@ -249,7 +249,9 @@ export default function Page() {
|
||||
queryClient.invalidateQueries({ queryKey: ['products'] });
|
||||
},
|
||||
});
|
||||
if (productsQuery.isLoading) return null;
|
||||
if (productsQuery.isLoading) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h1 className={styles.title}>Page products</h1>
|
||||
|
||||
@@ -30,7 +30,7 @@ We provide comprehensive design guidelines, best practices, resources, and tools
|
||||
|
||||
## Front-end Implementation
|
||||
|
||||
[React](http://facebook.github.io/react/) is used to encapsulate a library of components which embody our design language. We welcome the community to implement [our design system](/docs/spec/introduce) in other front-end frameworks of their choice.
|
||||
[React](https://react.dev/) is used to encapsulate a library of components which embody our design language. We welcome the community to implement [our design system](/docs/spec/introduce) in other front-end frameworks of their choice.
|
||||
|
||||
- [Ant Design of React](/docs/react/introduce)(official implementation)
|
||||
- [NG-ZORRO - Ant Design of Angular](http://ng.ant.design)
|
||||
|
||||
@@ -30,7 +30,7 @@ title: 介绍
|
||||
|
||||
## 前端实现
|
||||
|
||||
我们采用 [React](https://reactjs.org) 封装了一套 Ant Design 的组件库,也欢迎社区其他框架的实现版本。
|
||||
我们采用 [React](https://zh-hans.react.dev/) 封装了一套 Ant Design 的组件库,也欢迎社区其他框架的实现版本。
|
||||
|
||||
- [Ant Design of React](/docs/react/introduce)(官方实现)
|
||||
- [NG-ZORRO - Ant Design of Angular](http://ng.ant.design)(社区实现)
|
||||
|
||||
Reference in New Issue
Block a user