@particle-network/ui-react 0.3.0-beta.15 → 0.3.0-beta.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ interface CommonProps {
3
+ className?: string;
4
+ copyText?: string | number;
5
+ toastText?: string;
6
+ withTooltip?: boolean;
7
+ tooltipContent?: string;
8
+ }
9
+ interface UXCopyProps extends CommonProps {
10
+ children: React.ReactElement;
11
+ }
12
+ interface AddressProps extends CommonProps {
13
+ startContent?: React.ReactNode;
14
+ withCopyIcon?: boolean;
15
+ children: string;
16
+ }
17
+ export declare const UXCopy: React.FC<UXCopyProps> & {
18
+ Address: React.FC<AddressProps>;
19
+ };
20
+ export declare const Address: React.FC<AddressProps>;
21
+ export {};
@@ -0,0 +1,54 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import react from "react";
3
+ import { cn } from "@heroui/theme";
4
+ import CopyIcon from "@particle-network/icons/web/CopyIcon";
5
+ import copy_to_clipboard from "copy-to-clipboard";
6
+ import { useI18n } from "../../hooks/useI18n.js";
7
+ import { toast } from "../UXToast/index.js";
8
+ import { UXTooltip } from "../UXTooltip/index.js";
9
+ import { shortAddress } from "./utils.js";
10
+ const UXCopy = ({ className, copyText, toastText, withTooltip, tooltipContent, children })=>{
11
+ const i18n = useI18n();
12
+ const handleCopy = ()=>{
13
+ try {
14
+ copy_to_clipboard(String(copyText));
15
+ toast.success(toastText || i18n.copy.success);
16
+ } catch (error) {
17
+ toast.error(`${i18n.copy.error}${error instanceof Error ? error.message : String(error)}`);
18
+ }
19
+ };
20
+ const childrenWithProps = /*#__PURE__*/ react.cloneElement(children, {
21
+ onClick: handleCopy
22
+ });
23
+ return /*#__PURE__*/ jsx(UXTooltip, {
24
+ isDisabled: !withTooltip,
25
+ content: tooltipContent || i18n.copy.copy,
26
+ placement: "bottom",
27
+ children: /*#__PURE__*/ jsx("span", {
28
+ className: cn('cursor-pointer', className),
29
+ children: childrenWithProps
30
+ })
31
+ });
32
+ };
33
+ const Address = ({ children, copyText = children, tooltipContent = children, startContent, withCopyIcon, ...props })=>{
34
+ const i18n = useI18n();
35
+ return /*#__PURE__*/ jsx(UXCopy, {
36
+ tooltipContent: tooltipContent,
37
+ copyText: copyText,
38
+ toastText: `${i18n.copy.address}${children}`,
39
+ ...props,
40
+ children: /*#__PURE__*/ jsxs("div", {
41
+ className: "flex items-center gap-1 hover:opacity-70 text-body2 font-medium",
42
+ children: [
43
+ startContent,
44
+ shortAddress(children),
45
+ withCopyIcon && /*#__PURE__*/ jsx(CopyIcon, {
46
+ size: 14
47
+ })
48
+ ]
49
+ })
50
+ });
51
+ };
52
+ UXCopy.displayName = 'UXCopy';
53
+ UXCopy.Address = Address;
54
+ export { Address, UXCopy };
@@ -0,0 +1 @@
1
+ export declare const shortAddress: (address: string | undefined | null, chars?: number, endChars?: number) => string;
@@ -0,0 +1,5 @@
1
+ const shortAddress = (address, chars = 4, endChars = -4)=>{
2
+ if (!address) return '';
3
+ return `${address.slice(0, chars)}...${address.slice(endChars)}`;
4
+ };
5
+ export { shortAddress };
@@ -8,7 +8,7 @@ export interface UXSpinnerProps extends SquareProps {
8
8
  color?: UXForegroundColor | 'currentColor';
9
9
  /**
10
10
  * 动画持续时间(毫秒)
11
- * @default 500
11
+ * @default 400
12
12
  */
13
13
  duration?: number;
14
14
  }
@@ -4,7 +4,7 @@ import { cn } from "@heroui/theme";
4
4
  import { colorToClassName } from "@particle-network/ui-shared";
5
5
  import { Square } from "../layout/index.js";
6
6
  import { SpinnerIcon } from "./SpinnerIcon.js";
7
- const UXSpinner = ({ size = 18, color = 'primary', duration = 500, className, style, ...restProps })=>{
7
+ const UXSpinner = ({ size = 18, color = 'primary', duration = 400, className, style, ...restProps })=>{
8
8
  const [currentIndex, setCurrentIndex] = useState(0);
9
9
  const animationRef = useRef(null);
10
10
  const iconClassName = 'currentColor' === color ? void 0 : colorToClassName[color];
@@ -1,9 +1,9 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import "react";
3
2
  import { ToastProvider, addToast, closeAll, closeToast } from "@heroui/toast";
4
3
  import CircleCheckIcon from "@particle-network/icons/web/CircleCheckIcon";
5
4
  import CircleCloseIcon from "@particle-network/icons/web/CircleCloseIcon";
6
5
  import CloseIcon from "@particle-network/icons/web/CloseIcon";
6
+ import { hasLongWord } from "../../utils/index.js";
7
7
  import { UXSpinner } from "../UXSpinner/index.js";
8
8
  const UXToastProvider = ()=>/*#__PURE__*/ jsx(ToastProvider, {
9
9
  disableAnimation: true,
@@ -11,10 +11,10 @@ const UXToastProvider = ()=>/*#__PURE__*/ jsx(ToastProvider, {
11
11
  maxVisibleToasts: 4,
12
12
  toastOffset: 40,
13
13
  toastProps: {
14
- timeout: 3000
14
+ timeout: 4000
15
15
  },
16
16
  regionProps: {
17
- className: 'items-center'
17
+ className: 'items-center py-10'
18
18
  }
19
19
  });
20
20
  const getIcon = (type)=>{
@@ -29,29 +29,22 @@ const getIcon = (type)=>{
29
29
  });
30
30
  return null;
31
31
  };
32
- const hasLongWord = (text)=>{
33
- if ('string' != typeof text) return false;
34
- const englishWords = text.match(/[a-zA-Z0-9_-]+/g);
35
- if (!englishWords) return false;
36
- return englishWords.some((word)=>word.length > 30);
37
- };
38
32
  const show = (props)=>{
39
33
  const { type, hideCloseButton, variant, classNames, description, ...toastProps } = props ?? {};
40
34
  const { base, description: descriptionClassName, icon, loadingComponent, content, closeButton, ...restClassNames } = classNames ?? {};
41
35
  return addToast({
42
36
  classNames: {
43
37
  base: [
44
- 'bg-tertiary rounded-xl px-3.5 py-3 shadow-none border-none !w-fit',
38
+ 'bg-tertiary rounded-xl px-3.5 py-3 shadow-none border-none !w-fit max-w-[90vw] md:max-w-[400px]',
45
39
  !hideCloseButton && 'pr-12',
46
40
  'flat' === variant && 'success' === type && 'bg-[#0E3728]',
47
41
  'flat' === variant && 'error' === type && 'bg-[#501D1D]',
48
- hasLongWord(description) ? 'md:max-w-[480px] max-w-[360px]' : 'max-w-[360px]',
49
42
  base
50
43
  ],
51
44
  description: [
52
45
  'text-foreground text-xs font-medium me-0 leading-4 line-clamp-4',
53
46
  'flat' === variant && 'text-white',
54
- hasLongWord(description) && 'max-w-[320px]',
47
+ hasLongWord(description) && 'break-all',
55
48
  descriptionClassName
56
49
  ],
57
50
  icon: [
@@ -1,5 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import "react";
3
+ import { hasLongWord } from "../../utils/index.js";
3
4
  import tooltip_extend from "./tooltip.extend.js";
4
5
  const renderContent = (content)=>{
5
6
  if (!content) return null;
@@ -13,9 +14,17 @@ const renderContent = (content)=>{
13
14
  ]);
14
15
  };
15
16
  const UXTooltip = (props)=>{
16
- const { content, ...restProps } = props;
17
+ const { content, classNames, ...restProps } = props;
18
+ const { content: contentClassName, ...restClassNames } = classNames ?? {};
17
19
  return /*#__PURE__*/ jsx(tooltip_extend, {
18
20
  content: renderContent(content),
21
+ classNames: {
22
+ content: [
23
+ hasLongWord(content) && 'break-all',
24
+ contentClassName
25
+ ],
26
+ ...restClassNames
27
+ },
19
28
  ...restProps
20
29
  });
21
30
  };
@@ -290,25 +290,25 @@ declare const ExtendedTooltip: import("react").ForwardRefExoticComponent<Omit<{
290
290
  as?: import("@heroui/system-rsc").As<any> | undefined;
291
291
  onClose?: ((() => void) & (() => void)) | undefined;
292
292
  classNames?: import("@heroui/theme").SlotsToClasses<"content" | "base" | "arrow"> | undefined;
293
+ placement?: import("@heroui/aria-utils").OverlayPlacement | undefined;
294
+ motionProps?: Omit<import("framer-motion").HTMLMotionProps<"div">, "ref"> | undefined;
295
+ delay?: number | undefined;
296
+ triggerScaleOnOpen?: boolean | undefined;
297
+ isTriggerDisabled?: boolean | undefined;
298
+ closeDelay?: number | undefined;
299
+ trigger?: "focus" | undefined;
300
+ portalContainer?: Element | undefined;
301
+ updatePositionDeps?: any[] | undefined;
293
302
  isOpen?: boolean | undefined;
303
+ isDismissable?: boolean | undefined;
304
+ shouldCloseOnBlur?: boolean | undefined;
305
+ isKeyboardDismissDisabled?: boolean | undefined;
306
+ shouldCloseOnInteractOutside?: ((element: Element) => boolean) | undefined;
294
307
  defaultOpen?: boolean | undefined;
295
308
  onOpenChange?: ((isOpen: boolean) => void) | undefined;
296
- placement?: import("@heroui/aria-utils").OverlayPlacement | undefined;
297
- containerPadding?: number | undefined;
309
+ showArrow?: boolean | undefined;
298
310
  crossOffset?: number | undefined;
299
311
  shouldFlip?: boolean | undefined;
300
- isKeyboardDismissDisabled?: boolean | undefined;
301
- shouldCloseOnInteractOutside?: ((element: Element) => boolean) | undefined;
302
- isDismissable?: boolean | undefined;
303
- shouldCloseOnBlur?: boolean | undefined;
304
- showArrow?: boolean | undefined;
305
- updatePositionDeps?: any[] | undefined;
306
- triggerScaleOnOpen?: boolean | undefined;
307
- isTriggerDisabled?: boolean | undefined;
308
- motionProps?: Omit<import("framer-motion").HTMLMotionProps<"div">, "ref"> | undefined;
309
- portalContainer?: Element | undefined;
310
- trigger?: "focus" | undefined;
311
- delay?: number | undefined;
312
- closeDelay?: number | undefined;
312
+ containerPadding?: number | undefined;
313
313
  }, "ref"> & import("react").RefAttributes<import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>>>;
314
314
  export default ExtendedTooltip;
@@ -5,6 +5,7 @@ export * from './UXButton';
5
5
  export * from './UXCalendar';
6
6
  export * from './UXCheckbox';
7
7
  export * from './UXChip';
8
+ export * from './UXCopy';
8
9
  export * from './UXDatePicker';
9
10
  export * from './UXDateRangePicker';
10
11
  export * from './UXDivider';
@@ -5,6 +5,7 @@ export * from "./UXButton/index.js";
5
5
  export * from "./UXCalendar/index.js";
6
6
  export * from "./UXCheckbox/index.js";
7
7
  export * from "./UXChip/index.js";
8
+ export * from "./UXCopy/index.js";
8
9
  export * from "./UXDatePicker/index.js";
9
10
  export * from "./UXDateRangePicker/index.js";
10
11
  export * from "./UXDivider/index.js";
@@ -0,0 +1,2 @@
1
+ export * from './useI18n';
2
+ export * from './useLang';
@@ -0,0 +1,2 @@
1
+ export * from "./useI18n.js";
2
+ export * from "./useLang.js";
@@ -2,4 +2,10 @@ export declare const useI18n: () => {
2
2
  table: {
3
3
  emptyContent: string;
4
4
  };
5
+ copy: {
6
+ copy: string;
7
+ success: string;
8
+ error: string;
9
+ address: string;
10
+ };
5
11
  };
@@ -2,11 +2,23 @@ import { useLang } from "./useLang.js";
2
2
  const en = {
3
3
  table: {
4
4
  emptyContent: 'No data available.'
5
+ },
6
+ copy: {
7
+ copy: 'Copy',
8
+ success: 'Copied successfully!',
9
+ error: 'Copy failed: ',
10
+ address: 'Address copied: '
5
11
  }
6
12
  };
7
13
  const zh = {
8
14
  table: {
9
15
  emptyContent: '暂无数据'
16
+ },
17
+ copy: {
18
+ copy: '复制',
19
+ success: '复制成功!',
20
+ error: '复制失败:',
21
+ address: '地址已复制:'
10
22
  }
11
23
  };
12
24
  const useI18n = ()=>{
@@ -1,25 +1,26 @@
1
1
  import { useEffect, useState } from "react";
2
- const STORAGE_KEY = 'ux-preference-language';
2
+ function getLangFromHtml() {
3
+ if ('undefined' == typeof window) return 'en';
4
+ const htmlLang = document.documentElement.lang || document.documentElement.getAttribute('lang');
5
+ if ('zh' === htmlLang || htmlLang?.startsWith('zh')) return 'zh';
6
+ return 'en';
7
+ }
3
8
  function useLang() {
4
- const [lang, setLang] = useState(()=>{
5
- if ('undefined' != typeof window) {
6
- const value = localStorage.getItem(STORAGE_KEY) ?? 'en';
7
- return value;
8
- }
9
- return 'en';
10
- });
9
+ const [lang, setLang] = useState(getLangFromHtml);
11
10
  useEffect(()=>{
12
- const handleStorageChange = (e)=>{
13
- if (e.key === STORAGE_KEY) setLang(e.newValue);
14
- };
15
- const handleCustomStorageChange = ()=>{
16
- setLang(localStorage.getItem(STORAGE_KEY));
17
- };
18
- window.addEventListener('storage', handleStorageChange);
19
- window.addEventListener('ux-lang-change', handleCustomStorageChange);
11
+ const observer = new MutationObserver((mutations)=>{
12
+ mutations.forEach((mutation)=>{
13
+ if ('attributes' === mutation.type && ('lang' === mutation.attributeName || 'data-lang' === mutation.attributeName)) setLang(getLangFromHtml());
14
+ });
15
+ });
16
+ observer.observe(document.documentElement, {
17
+ attributes: true,
18
+ attributeFilter: [
19
+ 'lang'
20
+ ]
21
+ });
20
22
  return ()=>{
21
- window.removeEventListener('storage', handleStorageChange);
22
- window.removeEventListener('ux-lang-change', handleCustomStorageChange);
23
+ observer.disconnect();
23
24
  };
24
25
  }, []);
25
26
  return lang;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './components';
2
+ export * from './hooks';
2
3
  export * from './utils';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./components/index.js";
2
+ export * from "./hooks/index.js";
2
3
  export * from "./utils/index.js";
@@ -0,0 +1,2 @@
1
+ import type React from 'react';
2
+ export declare const hasLongWord: (text: React.ReactNode) => boolean;
@@ -0,0 +1,7 @@
1
+ const hasLongWord = (text)=>{
2
+ if ('string' != typeof text) return false;
3
+ const englishWords = text.match(/[a-zA-Z0-9_-]+/g);
4
+ if (!englishWords) return false;
5
+ return englishWords.some((word)=>word.length > 30);
6
+ };
7
+ export { hasLongWord };
@@ -1,3 +1,4 @@
1
1
  export { absoluteFullClasses, baseStyles, collapseAdjacentVariantBorders, dataFocusVisibleClasses, focusVisibleClasses, groupDataFocusVisibleClasses, hiddenInputClasses, ringClasses, translateCenterClasses, } from './classes';
2
+ export * from './detect';
2
3
  export * from './input-classes';
3
4
  export { colorVariants } from './variants';
@@ -1,4 +1,5 @@
1
1
  import { absoluteFullClasses, baseStyles, collapseAdjacentVariantBorders, dataFocusVisibleClasses, focusVisibleClasses, groupDataFocusVisibleClasses, hiddenInputClasses, ringClasses, translateCenterClasses } from "./classes.js";
2
2
  import { colorVariants } from "./variants.js";
3
+ export * from "./detect.js";
3
4
  export * from "./input-classes.js";
4
5
  export { absoluteFullClasses, baseStyles, collapseAdjacentVariantBorders, colorVariants, dataFocusVisibleClasses, focusVisibleClasses, groupDataFocusVisibleClasses, hiddenInputClasses, ringClasses, translateCenterClasses };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-network/ui-react",
3
- "version": "0.3.0-beta.15",
3
+ "version": "0.3.0-beta.17",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -39,8 +39,8 @@
39
39
  "@types/react": "^19.1.10",
40
40
  "react": "^19.1.0",
41
41
  "typescript": "^5.8.3",
42
- "@particle-network/eslint-config": "0.3.0",
43
- "@particle-network/lintstaged-config": "0.1.0"
42
+ "@particle-network/lintstaged-config": "0.1.0",
43
+ "@particle-network/eslint-config": "0.3.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "react": ">=16.9.0",
@@ -48,6 +48,7 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "ahooks": "^3.9.4",
51
+ "copy-to-clipboard": "^3.3.3",
51
52
  "@particle-network/icons": "0.3.0-beta.6",
52
53
  "@particle-network/ui-shared": "0.2.0-beta.3"
53
54
  },