@_tc/template-core 0.1.15 → 0.1.16

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.
Files changed (28) hide show
  1. package/.skills/tc-component-usage-skills/reference/component-api.md +7 -1
  2. package/fe/packages/ui/react/components/Button/Button.js +3 -2
  3. package/fe/packages/ui/react/components/Card/Card.d.ts +5 -0
  4. package/fe/packages/ui/react/components/Card/Card.js +6 -2
  5. package/fe/packages/ui/react/components/Date/Date.js +3 -3
  6. package/fe/packages/ui/react/components/Drawer/Drawer.js +10 -1
  7. package/fe/packages/ui/react/components/Dropdown/Dropdown.js +44 -4
  8. package/fe/packages/ui/react/components/ImagePreview/ImagePreview.js +14 -12
  9. package/fe/packages/ui/react/components/ImagePreview/PreviewImage.js +1 -1
  10. package/fe/packages/ui/react/components/Input/Input.js +2 -2
  11. package/fe/packages/ui/react/components/InputNumber/InputNumber.js +2 -2
  12. package/fe/packages/ui/react/components/Loading/Loading.d.ts +16 -0
  13. package/fe/packages/ui/react/components/Loading/Loading.js +12 -4
  14. package/fe/packages/ui/react/components/Menu/Menu.js +9 -2
  15. package/fe/packages/ui/react/components/Menu/MenuItem.js +4 -4
  16. package/fe/packages/ui/react/components/Menu/SubMenu.js +4 -4
  17. package/fe/packages/ui/react/components/Message/Message.js +5 -5
  18. package/fe/packages/ui/react/components/Modal/Modal.js +1 -1
  19. package/fe/packages/ui/react/components/Notification/Notification.js +5 -5
  20. package/fe/packages/ui/react/components/Pagination/Pagination.js +18 -10
  21. package/fe/packages/ui/react/components/Popup/Popup.js +1 -1
  22. package/fe/packages/ui/react/components/Select/Select.js +101 -4
  23. package/fe/packages/ui/react/components/Textarea/Textarea.js +3 -3
  24. package/fe/packages/ui/react/components/Tooltip/Tooltip.js +1 -1
  25. package/fe/packages/ui/react/components/Upload/ImageUpload.js +2 -2
  26. package/fe/packages/ui/react/components/Upload/Upload.js +2 -1
  27. package/fe/packages/ui/react/index.css +66 -9
  28. package/package.json +1 -1
@@ -725,10 +725,16 @@ interface NotificationProps {
725
725
  interface LoadingProps {
726
726
  size?: 'sm' | 'md' | 'lg'
727
727
  text?: ReactNode
728
+ className?: string
729
+ }
730
+
731
+ interface LoadingIndicatorProps {
732
+ size?: 'sm' | 'md' | 'lg'
733
+ className?: string
728
734
  }
729
735
  ```
730
736
 
731
- Spinning circle with optional text.
737
+ `Loading` renders the shared spinner with optional text. `LoadingIndicator` is the icon-only spinner used by compact surfaces such as `Button.loading`.
732
738
 
733
739
  ---
734
740
 
@@ -1,6 +1,7 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cn } from '../../lib/utils';
3
3
  import { forwardRef } from 'react';
4
+ import { LoadingIndicator } from '../Loading';
4
5
  const Button = forwardRef((props, ref) => {
5
6
  const { className, size = 'md', state, disabled: disabledProp, loading: loadingProp, leftIcon, rightIcon, noUnderline, children, } = props;
6
7
  const variant = props.variant ?? 'default';
@@ -46,7 +47,7 @@ const Button = forwardRef((props, ref) => {
46
47
  md: 'h-4 w-4',
47
48
  lg: 'h-5 w-5',
48
49
  };
49
- const loadingIcon = loading ? (_jsx("span", { className: cn('flex items-center', children && 'mr-2'), children: _jsxs("svg", { className: cn('animate-spin', spinnerSize[size]), xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }) }, "loading")) : null;
50
+ const loadingIcon = loading ? (_jsx("span", { className: cn('flex items-center', children && 'mr-2'), children: _jsx(LoadingIndicator, { size: "sm", className: cn('text-current', spinnerSize[size]) }) }, "loading")) : null;
50
51
  // 展示内容
51
52
  const contents = [
52
53
  loadingIcon,
@@ -4,6 +4,11 @@ export type CardProps = {
4
4
  footer?: ReactNode;
5
5
  children?: ReactNode;
6
6
  className?: string;
7
+ /**
8
+ * 是否启用 hover 悬浮阴影
9
+ * @default true
10
+ */
11
+ hoverable?: boolean;
7
12
  };
8
13
  declare const Card: import("react").ForwardRefExoticComponent<CardProps & import("react").RefAttributes<HTMLDivElement>>;
9
14
  export { Card };
@@ -1,8 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../../lib/utils';
3
3
  import { forwardRef } from 'react';
4
- const Card = forwardRef(({ header, footer, children, className }, ref) => {
5
- return (_jsxs("div", { ref: ref, className: cn('tc-ui-card rounded-lg border bg-card text-card-foreground shadow-sm', className), children: [header && (_jsx("div", { className: "tc-ui-card-header border-b px-4 py-3 font-medium", children: header })), _jsx("div", { className: "tc-ui-card-body px-4 py-4", children: children }), footer && (_jsx("div", { className: "tc-ui-card-footer border-t px-4 py-3", children: footer }))] }));
4
+ const Card = forwardRef(({ header, footer, children, className, hoverable = true }, ref) => {
5
+ return (_jsxs("div", { ref: ref, className: cn('tc-ui-card rounded-xl border border-border/60 bg-card text-card-foreground', 'shadow-[inset_0_1px_0_hsl(var(--foreground)/0.04)]', hoverable && [
6
+ 'transition-[transform,box-shadow,border-color] duration-200 ease-out',
7
+ 'hover:-translate-y-0.5 hover:border-border/80',
8
+ 'hover:shadow-[var(--shadow-panel),inset_0_1px_0_hsl(var(--foreground)/0.06)]',
9
+ ], className), children: [header && (_jsx("div", { className: "tc-ui-card-header border-b border-border/50 px-4 py-3 text-sm font-medium", children: header })), _jsx("div", { className: "tc-ui-card-body px-4 py-4", children: children }), footer && (_jsx("div", { className: "tc-ui-card-footer border-t border-border/50 px-4 py-3", children: footer }))] }));
6
10
  });
7
11
  Card.displayName = 'Card';
8
12
  export { Card };
@@ -187,10 +187,10 @@ const DatePicker = forwardRef((props, ref) => {
187
187
  }
188
188
  };
189
189
  const showClearIcon = allowClear && selectedDate && !disabled && isHover;
190
- return (_jsx("div", { ref: ref, className: cn("tc-ui-date-picker relative w-full", mode === ModeType.Range ? "min-w-[255px]" : "", className), children: _jsxs("div", { ref: containerRef, className: "relative", children: [_jsxs("div", { className: cn("flex items-center w-full rounded-lg px-4 py-3 text-sm", "border transition-colors duration-200", "bg-background cursor-pointer", disabled &&
191
- "cursor-not-allowed bg-muted border-border text-muted-foreground", !disabled && !selectedDate && !isOpen && "border-border", !disabled && (selectedDate || isOpen) && "border-theme"), onClick: () => !disabled && setIsOpen(!isOpen), onMouseEnter: () => setIsHover(true), onMouseLeave: () => setIsHover(false), children: [_jsx(Input, { ref: inputRef, type: "text", value: inputValue, onChange: (_, e) => handleInputChange(e), placeholder: finalPlaceholder, disabled: disabled, readOnly: mode === ModeType.Range, className: "flex-1", inputClassName: cn("flex-1 rounded-none border-0 bg-transparent px-0 py-0 outline-none", "text-sm text-foreground placeholder:text-muted-foreground", disabled && "cursor-not-allowed", mode === ModeType.Range &&
190
+ return (_jsx("div", { ref: ref, className: cn("tc-ui-date-picker relative w-full", mode === ModeType.Range ? "min-w-[255px]" : "", className), children: _jsxs("div", { ref: containerRef, className: "relative", children: [_jsxs("div", { className: cn("flex h-10 items-center w-full rounded-lg px-3 py-0 text-sm", "border transition-colors duration-200", "bg-background cursor-pointer", disabled &&
191
+ "cursor-not-allowed bg-muted border-border text-muted-foreground", !disabled && !isOpen && "border-border", !disabled && isOpen && "border-theme ring-2 ring-theme/25", !disabled && "focus-within:border-theme focus-within:ring-2 focus-within:ring-theme/25"), onClick: () => !disabled && setIsOpen(!isOpen), onMouseEnter: () => setIsHover(true), onMouseLeave: () => setIsHover(false), children: [_jsx(Input, { ref: inputRef, type: "text", value: inputValue, onChange: (_, e) => handleInputChange(e), placeholder: finalPlaceholder, disabled: disabled, readOnly: mode === ModeType.Range, className: "min-w-0 flex-1", inputClassName: cn("h-8 min-w-0 flex-1 rounded-none border-0 bg-transparent px-0 py-0 outline-none", "ring-0 shadow-none focus:ring-0 focus-visible:ring-0", "text-sm text-foreground placeholder:text-muted-foreground", disabled && "cursor-not-allowed", mode === ModeType.Range &&
192
192
  !disabled &&
193
- "cursor-pointer caret-transparent") }), showClearIcon ? (_jsx(Button, { variant: "text", type: "button", onClick: handleClear, title: locale.clear, "aria-label": locale.clear, className: cn("ml-2 flex items-center justify-center", "w-5 h-5 rounded-full", "bg-muted hover:bg-muted/80", "text-foreground transition-colors", "cursor-pointer"), children: _jsx(X, { size: 12 }) })) : (_jsx(CalendarIcon, { size: 16, className: "ml-2 text-muted-foreground" }))] }), _jsx(Popup, { open: isOpen && !disabled, anchorRef: containerRef, contentRef: dropdownContentRef, positioning: positioning, keepMounted: true, onOpenChange: (nextOpen) => {
193
+ "cursor-pointer caret-transparent") }), showClearIcon ? (_jsx(Button, { variant: "text", type: "button", onClick: handleClear, title: locale.clear, "aria-label": locale.clear, className: cn("ml-2 flex h-7 w-7 items-center justify-center rounded-md", "text-muted-foreground transition-colors hover:bg-foreground/8 hover:text-foreground", "cursor-pointer"), children: _jsx(X, { size: 12 }) })) : (_jsx(CalendarIcon, { size: 16, className: "ml-2 shrink-0 text-muted-foreground" }))] }), _jsx(Popup, { open: isOpen && !disabled, anchorRef: containerRef, contentRef: dropdownContentRef, positioning: positioning, keepMounted: true, onOpenChange: (nextOpen) => {
194
194
  setIsOpen(nextOpen);
195
195
  if (!nextOpen) {
196
196
  rangeDraftRef.current = false;
@@ -16,6 +16,15 @@ const getDrawerPositionClassName = (placement) => {
16
16
  };
17
17
  return positionClassNames[placement];
18
18
  };
19
+ const getDrawerRadiusClassName = (placement) => {
20
+ const radiusClassNames = {
21
+ top: 'rounded-b-2xl',
22
+ right: 'rounded-l-2xl',
23
+ bottom: 'rounded-t-2xl',
24
+ left: 'rounded-r-2xl',
25
+ };
26
+ return radiusClassNames[placement];
27
+ };
19
28
  const getDrawerTransform = (placement, open) => {
20
29
  if (open)
21
30
  return 'translate3d(0, 0, 0)';
@@ -96,5 +105,5 @@ export function Drawer({ open = false, onClose, title, children, footer, classNa
96
105
  };
97
106
  return (_jsx(Overlay, { open: mounted, visible: visible, onClose: onClose, mask: mask, maskClosable: maskClosable, lockScroll: lockScroll, keyboard: keyboard, closable: closable, renderNode: renderNode, zIndex: zIndex, className: "tc-ui-drawer", style: {
98
107
  transition: `opacity ${DRAWER_ANIMATION_DURATION}ms ${DRAWER_ANIMATION_EASING}`,
99
- }, children: _jsxs("div", { className: cn('fixed bg-background shadow-lg', 'flex flex-col', getDrawerPositionClassName(placement), placement === 'top' || placement === 'bottom' ? 'max-h-[90vh]' : 'max-w-[90vw]', className), style: drawerStyle, onClick: handleContentClick, children: [(title || closable) && (_jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-border", children: [title && _jsx("div", { className: "text-lg font-semibold text-foreground", children: title }), closable && (_jsx(Button, { variant: "text", type: "button", onClick: onClose, "aria-label": t('components.modal.close'), className: cn('ml-auto p-1 rounded-md', 'text-muted-foreground hover:text-foreground', 'hover:bg-muted', 'transition-colors', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2'), children: _jsx(X, { size: 20 }) }))] })), _jsx("div", { className: cn('flex-1 overflow-auto px-6 py-4', contentClassName), children: children }), footer && _jsx("div", { className: "px-6 py-4 border-border", children: footer })] }) }));
108
+ }, children: _jsxs("div", { className: cn('fixed border border-border/70 bg-popover text-popover-foreground', 'shadow-[0_22px_70px_hsl(var(--background)/0.58),inset_0_1px_0_hsl(var(--foreground)/0.06)]', 'flex flex-col', getDrawerPositionClassName(placement), getDrawerRadiusClassName(placement), placement === 'top' || placement === 'bottom' ? 'max-h-[90vh]' : 'max-w-[90vw]', className), style: drawerStyle, onClick: handleContentClick, children: [(title || closable) && (_jsxs("div", { className: "flex items-center justify-between border-b border-border/60 px-4 py-3", children: [title && _jsx("div", { className: "text-sm font-semibold leading-5 text-foreground", children: title }), closable && (_jsx(Button, { variant: "text", type: "button", onClick: onClose, "aria-label": t('components.modal.close'), className: cn('ml-auto h-7 w-7 rounded-md p-0', 'text-muted-foreground hover:text-foreground', 'hover:bg-foreground/8', 'transition-colors', 'focus:outline-none focus:ring-2 focus:ring-ring/40 focus:ring-offset-0'), children: _jsx(X, { size: 16 }) }))] })), _jsx("div", { className: cn('flex-1 overflow-auto px-4 py-4', contentClassName), children: children }), footer && _jsx("div", { className: "border-t border-border/60 px-4 py-3", children: footer })] }) }));
100
109
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../../lib/utils';
3
- import { Fragment, useCallback, useRef, useState } from 'react';
3
+ import { cloneElement, Fragment, isValidElement, useCallback, useRef, useState } from 'react';
4
4
  import { Button } from '../Button';
5
5
  import { Popup } from '../Popup';
6
6
  import { getDefaultDropdownPosition, } from '../hooks/useDropdownPositioning';
@@ -29,21 +29,61 @@ export function Dropdown({ items, children, className, placement = 'bottom-end',
29
29
  const handleToggle = useCallback(() => {
30
30
  setIsOpen((prev) => !prev);
31
31
  }, []);
32
+ const handleTriggerKeyDown = useCallback((event) => {
33
+ if (event.key === 'Enter' || event.key === ' ') {
34
+ event.preventDefault();
35
+ handleToggle();
36
+ }
37
+ if (event.key === 'ArrowDown') {
38
+ event.preventDefault();
39
+ setIsOpen(true);
40
+ }
41
+ if (event.key === 'Escape') {
42
+ setIsOpen(false);
43
+ }
44
+ }, [handleToggle]);
32
45
  const handleItemClick = useCallback((item) => {
33
46
  if (!isDropdownRenderItem(item)) {
34
47
  item.onClick?.();
35
48
  }
36
49
  setIsOpen(false);
37
50
  }, []);
38
- return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, className: cn('tc-ui-dropdown relative inline-block', className), children: _jsx("div", { onClick: handleToggle, children: children }) }), _jsx(Popup, { open: isOpen, anchorRef: triggerRef, placement: placement, onOpenChange: setIsOpen, container: getPopupContainer?.(), strategy: getDropdownPosition, className: cn('min-w-24 overflow-hidden p-1'), children: items.map((item, index) => {
51
+ const triggerProps = {
52
+ role: 'button',
53
+ tabIndex: 0,
54
+ 'aria-haspopup': 'menu',
55
+ 'aria-expanded': isOpen,
56
+ className: 'outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-0',
57
+ };
58
+ const trigger = isValidElement(children)
59
+ ? cloneElement(children, {
60
+ ...triggerProps,
61
+ onClick: (event) => {
62
+ ;
63
+ children.props.onClick?.(event);
64
+ if (!event.defaultPrevented) {
65
+ handleToggle();
66
+ }
67
+ },
68
+ onKeyDown: (event) => {
69
+ ;
70
+ children.props.onKeyDown?.(event);
71
+ if (!event.defaultPrevented) {
72
+ handleTriggerKeyDown(event);
73
+ }
74
+ },
75
+ className: cn(children.props.className, triggerProps.className),
76
+ })
77
+ : (_jsx("span", { ...triggerProps, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, children: children }));
78
+ return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, className: cn('tc-ui-dropdown relative inline-block', className), children: trigger }), _jsx(Popup, { open: isOpen, anchorRef: triggerRef, placement: placement, onOpenChange: setIsOpen, container: getPopupContainer?.(), strategy: getDropdownPosition, className: cn('min-w-24 overflow-hidden border-border/70 bg-popover p-1 shadow-[0_22px_70px_hsl(var(--background)/0.58),inset_0_1px_0_hsl(var(--foreground)/0.06)]'), children: items.map((item, index) => {
39
79
  const onClick = () => handleItemClick(item);
40
80
  const close = () => setIsOpen(false);
41
81
  const key = isDropdownRenderItem(item) ? index : item.key;
42
82
  if (isDropdownRenderItem(item)) {
43
83
  return _jsx(Fragment, { children: item.render(item, { onClick, close }) }, key);
44
84
  }
45
- return (_jsx(Button, { type: "button", variant: "text", state: item.danger ? 'danger' : undefined, onClick: onClick, className: cn('flex h-10 w-full items-center justify-start gap-2 rounded-md px-3 text-left text-sm transition-colors', item.danger
85
+ return (_jsx(Button, { type: "button", variant: "text", state: item.danger ? 'danger' : undefined, onClick: onClick, className: cn('flex h-9 w-full items-center justify-start gap-2 rounded-md px-3 text-left text-sm transition-colors', item.danger
46
86
  ? 'text-destructive hover:bg-destructive/10'
47
- : 'text-popover-foreground hover:bg-muted'), children: _jsxs(_Fragment, { children: [item.icon && _jsx("span", { className: "flex-shrink-0", children: item.icon }), _jsx("span", { children: item.label })] }) }, key));
87
+ : 'text-popover-foreground hover:bg-foreground/8'), children: _jsxs(_Fragment, { children: [item.icon && _jsx("span", { className: "flex-shrink-0 text-muted-foreground [&>svg]:h-4 [&>svg]:w-4", children: item.icon }), _jsx("span", { children: item.label })] }) }, key));
48
88
  }) })] }));
49
89
  }
@@ -101,13 +101,15 @@ function PreviewCore({ images, initialIndex, onClose }) {
101
101
  }, [rotation, scale, flipX, flipY, position]);
102
102
  if (!hasImages)
103
103
  return null;
104
- const content = (_jsxs("div", { className: "tc-ui-image-preview fixed inset-0 z-[1100] bg-black/40 text-white", onClick: onClose, children: [_jsx(Button, { variant: "text", type: "button", onClick: onClose, className: "absolute right-6 top-6 p-2 rounded-full bg-black/70 hover:bg-black/80 transition-colors z-10", "aria-label": t('components.imagePreview.closePreview'), children: _jsx(X, { size: 20 }) }), images.length > 1 && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "text", type: "button", onClick: (e) => {
104
+ const floatingIconButtonClassName = 'z-10 rounded-full border border-border/60 bg-popover text-popover-foreground shadow-[var(--shadow-floating)] transition-colors duration-200 hover:bg-muted/80 active:bg-muted focus-visible:ring-theme/30';
105
+ const toolbarButtonClassName = 'flex items-center gap-2 rounded-md px-3 py-2 text-sm text-popover-foreground transition-colors duration-200 hover:bg-foreground/8 active:bg-foreground/12 focus-visible:ring-theme/30';
106
+ const content = (_jsxs("div", { className: "tc-ui-image-preview fixed inset-0 z-[1100] bg-background/80 text-foreground", onClick: onClose, children: [_jsx(Button, { variant: "text", type: "button", onClick: onClose, className: `absolute right-6 top-6 p-2 ${floatingIconButtonClassName}`, "aria-label": t('components.imagePreview.closePreview'), children: _jsx(X, { size: 20 }) }), images.length > 1 && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "text", type: "button", onClick: (e) => {
105
107
  e.stopPropagation();
106
108
  toPrev();
107
- }, className: "absolute left-4 top-1/2 -translate-y-1/2 p-3 rounded-full bg-black/70 hover:bg-black/80 transition-colors z-10", "aria-label": t('components.imagePreview.previousImage'), children: _jsx(ArrowLeft, { size: 20 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
109
+ }, className: `absolute left-4 top-1/2 -translate-y-1/2 p-3 ${floatingIconButtonClassName}`, "aria-label": t('components.imagePreview.previousImage'), children: _jsx(ArrowLeft, { size: 20 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
108
110
  e.stopPropagation();
109
111
  toNext();
110
- }, className: "absolute right-4 top-1/2 -translate-y-1/2 p-3 rounded-full bg-black/70 hover:bg-black/80 transition-colors z-10", "aria-label": t('components.imagePreview.nextImage'), children: _jsx(ArrowRight, { size: 20 }) })] })), _jsx("div", { className: "absolute inset-0 flex items-center justify-center overflow-hidden", onClick: (e) => e.stopPropagation(), onWheel: (e) => {
112
+ }, className: `absolute right-4 top-1/2 -translate-y-1/2 p-3 ${floatingIconButtonClassName}`, "aria-label": t('components.imagePreview.nextImage'), children: _jsx(ArrowRight, { size: 20 }) })] })), _jsx("div", { className: "absolute inset-0 flex items-center justify-center overflow-hidden", onClick: (e) => e.stopPropagation(), onWheel: (e) => {
111
113
  e.preventDefault();
112
114
  if (e.deltaY < 0) {
113
115
  zoomIn();
@@ -115,13 +117,13 @@ function PreviewCore({ images, initialIndex, onClose }) {
115
117
  else {
116
118
  zoomOut();
117
119
  }
118
- }, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, style: { cursor: isDragging ? 'grabbing' : scale > 1 ? 'grab' : 'default' }, children: _jsx("div", { className: "relative max-h-[85vh] max-w-[90vw]", children: _jsx("img", { ref: imgRef, src: currentImage, alt: "", draggable: false, className: " select-none max-h-[85vh] max-w-[90vw] absolute left-1/2 top-1/2 origin-center" }) }) }), _jsxs("div", { className: "absolute bottom-6 left-1/2 -translate-x-1/2 flex items-center gap-2 bg-black/70 backdrop-blur px-3 py-2 rounded-lg", children: [_jsx(Button, { variant: "text", type: "button", onClick: (e) => {
120
+ }, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, style: { cursor: isDragging ? 'grabbing' : scale > 1 ? 'grab' : 'default' }, children: _jsx("div", { className: "relative max-h-[85vh] max-w-[90vw]", children: _jsx("img", { ref: imgRef, src: currentImage, alt: "", draggable: false, className: " select-none max-h-[85vh] max-w-[90vw] absolute left-1/2 top-1/2 origin-center" }) }) }), _jsxs("div", { className: "absolute bottom-6 left-1/2 flex -translate-x-1/2 items-center gap-2 rounded-xl border border-border/60 bg-popover px-3 py-2 text-popover-foreground shadow-[var(--shadow-floating)]", children: [_jsx(Button, { variant: "text", type: "button", onClick: (e) => {
119
121
  e.stopPropagation();
120
122
  zoomOut();
121
- }, className: "flex items-center gap-1 px-3 py-2 rounded-md hover:bg-white/15 text-sm", "aria-label": t('components.imagePreview.zoomOut'), children: _jsx(ZoomOut, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
123
+ }, className: toolbarButtonClassName, "aria-label": t('components.imagePreview.zoomOut'), children: _jsx(ZoomOut, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
122
124
  e.stopPropagation();
123
125
  zoomIn();
124
- }, className: "flex items-center gap-1 px-3 py-2 rounded-md hover:bg-white/15 text-sm", "aria-label": t('components.imagePreview.zoomIn'), children: _jsx(ZoomIn, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
126
+ }, className: toolbarButtonClassName, "aria-label": t('components.imagePreview.zoomIn'), children: _jsx(ZoomIn, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
125
127
  e.stopPropagation();
126
128
  if (!imgRef.current)
127
129
  return;
@@ -143,22 +145,22 @@ function PreviewCore({ images, initialIndex, onClose }) {
143
145
  setScale(lastScaleBeforeNatural.current);
144
146
  lastScaleBeforeNatural.current = null;
145
147
  }
146
- }, className: "flex items-center gap-2 px-3 py-2 rounded-md hover:bg-white/15 text-sm", "aria-label": "1:1", children: "1:1" }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
148
+ }, className: toolbarButtonClassName, "aria-label": "1:1", children: "1:1" }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
147
149
  e.stopPropagation();
148
150
  reset();
149
- }, className: "flex items-center gap-2 px-3 py-2 rounded-md hover:bg-white/15 text-sm", "aria-label": t('components.imagePreview.reset'), children: _jsx(RefreshCw, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
151
+ }, className: toolbarButtonClassName, "aria-label": t('components.imagePreview.reset'), children: _jsx(RefreshCw, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
150
152
  e.stopPropagation();
151
153
  rotateLeft();
152
- }, className: "flex items-center gap-2 px-3 py-2 rounded-md hover:bg-white/15 text-sm", "aria-label": t('components.imagePreview.rotateLeft'), children: _jsx(RotateCcw, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
154
+ }, className: toolbarButtonClassName, "aria-label": t('components.imagePreview.rotateLeft'), children: _jsx(RotateCcw, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
153
155
  e.stopPropagation();
154
156
  rotateRight();
155
- }, className: "flex items-center gap-2 px-3 py-2 rounded-md hover:bg-white/15 text-sm", "aria-label": t('components.imagePreview.rotateRight'), children: _jsx(RotateCw, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
157
+ }, className: toolbarButtonClassName, "aria-label": t('components.imagePreview.rotateRight'), children: _jsx(RotateCw, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
156
158
  e.stopPropagation();
157
159
  setFlipX((prev) => !prev);
158
- }, className: "flex items-center gap-2 px-3 py-2 rounded-md hover:bg-white/15 text-sm", "aria-label": t('components.imagePreview.flipHorizontal'), children: _jsx(FlipHorizontal, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
160
+ }, className: toolbarButtonClassName, "aria-label": t('components.imagePreview.flipHorizontal'), children: _jsx(FlipHorizontal, { size: 16 }) }), _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
159
161
  e.stopPropagation();
160
162
  setFlipY((prev) => !prev);
161
- }, className: "flex items-center gap-2 px-3 py-2 rounded-md hover:bg-white/15 text-sm", "aria-label": t('components.imagePreview.flipVertical'), children: _jsx(FlipVertical, { size: 16 }) })] })] }));
163
+ }, className: toolbarButtonClassName, "aria-label": t('components.imagePreview.flipVertical'), children: _jsx(FlipVertical, { size: 16 }) })] })] }));
162
164
  return createPortal(content, document.body);
163
165
  }
164
166
  export function ImagePreviewController({ open, images, initialIndex = 0, onClose }) {
@@ -18,7 +18,7 @@ export function PreviewImage(props) {
18
18
  }, [thumbSize]);
19
19
  if (!images?.length)
20
20
  return null;
21
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: `flex gap-2 ${direction === 'vertical' ? 'flex-col' : ''}`, children: images.map((src, idx) => (_jsx(Button, { variant: "text", type: "button", className: `relative aspect-square overflow-hidden rounded border border-border ${sizeClass}`, style: sizeStyle, onClick: () => {
21
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: `flex gap-2 ${direction === 'vertical' ? 'flex-col' : ''}`, children: images.map((src, idx) => (_jsx(Button, { variant: "text", type: "button", className: `relative aspect-square overflow-hidden rounded-lg border border-border/60 bg-card shadow-[inset_0_1px_0_hsl(var(--foreground)/0.04)] transition-colors duration-200 hover:border-border/80 active:border-theme/60 focus-visible:ring-theme/30 ${sizeClass}`, style: sizeStyle, onClick: () => {
22
22
  setCurrentIndex(idx);
23
23
  setOpen(true);
24
24
  }, "aria-label": t('components.imagePreview.previewImage', { index: idx + 1 }), children: _jsx("img", { src: src, alt: "", className: "h-full w-full object-cover" }) }, src + idx))) }), _jsx(ImagePreviewController, { ...ext, images: images, initialIndex: currentIndex, open: open, onClose: () => {
@@ -45,7 +45,7 @@ const Input = forwardRef(({ className, inputClassName, type, value: controlledVa
45
45
  return undefined;
46
46
  }, [allowClear]);
47
47
  const showClearIcon = hasValue && clearIcon && !disabled;
48
- return (_jsxs("div", { className: cn('tc-ui-input relative w-full', className), children: [_jsx("input", { type: type, ref: setRefs, ...(!isFileInput && { value }), onChange: handleChange, className: cn('flex w-full rounded-lg px-4 py-3 text-sm', 'border transition-colors duration-200', 'placeholder:text-muted-foreground', 'focus:outline-none', showClearIcon && 'pr-10', addonAfter && 'pr-12', disabled ? 'cursor-not-allowed bg-muted border-border text-muted-foreground' : 'bg-background', !disabled && !hasValue && !isFocused && 'border-border', !disabled && (hasValue || isFocused) && 'border-theme', inputClassName), onFocus: (e) => {
48
+ return (_jsxs("div", { className: cn('tc-ui-input relative w-full', className), children: [_jsx("input", { type: type, ref: setRefs, ...(!isFileInput && { value }), onChange: handleChange, className: cn('flex h-10 w-full rounded-lg px-3 py-0 text-sm', 'border transition-colors duration-200', 'placeholder:text-muted-foreground', 'focus:outline-none', showClearIcon && 'pr-10', addonAfter && 'pr-12', disabled ? 'cursor-not-allowed bg-muted border-border text-muted-foreground' : 'bg-background', !disabled && !isFocused && 'border-border', !disabled && isFocused && 'border-theme ring-2 ring-theme/25', inputClassName), onFocus: (e) => {
49
49
  if (!disabled) {
50
50
  setIsFocused(true);
51
51
  props.onFocus?.(e);
@@ -55,7 +55,7 @@ const Input = forwardRef(({ className, inputClassName, type, value: controlledVa
55
55
  setIsFocused(false);
56
56
  props.onBlur?.(e);
57
57
  }
58
- }, disabled: disabled, ...props }), showClearIcon && (_jsx("button", { type: "button", onClick: handleClear, className: cn('absolute right-3 top-1/2 -translate-y-1/2'), children: clearIcon })), addonAfter && _jsx("div", { className: "absolute right-2 top-1/2 -translate-y-1/2", children: addonAfter })] }));
58
+ }, disabled: disabled, ...props }), showClearIcon && (_jsx("button", { type: "button", onClick: handleClear, "aria-label": "Clear input", title: "Clear input", className: cn('absolute right-3 top-1/2 -translate-y-1/2'), children: clearIcon })), addonAfter && _jsx("div", { className: "absolute right-2 top-1/2 -translate-y-1/2", children: addonAfter })] }));
59
59
  });
60
60
  Input.displayName = 'Input';
61
61
  export { Input };
@@ -105,7 +105,7 @@ const InputNumber = forwardRef(({ className, inputClassName, value: controlledVa
105
105
  return undefined;
106
106
  }
107
107
  if (typeof allowClear === 'boolean') {
108
- return (_jsx("span", { className: cn('h-5 w-5 rounded-full', 'bg-muted text-foreground', 'flex items-center justify-center', 'transition-colors hover:bg-muted/80'), children: _jsx(X, { size: 12 }) }));
108
+ return (_jsx("span", { className: cn('h-6 w-6 rounded-md', 'text-muted-foreground', 'flex items-center justify-center', 'transition-colors hover:bg-foreground/8 hover:text-foreground'), children: _jsx(X, { size: 12 }) }));
109
109
  }
110
110
  return allowClear.clearIcon;
111
111
  }, [allowClear]);
@@ -138,7 +138,7 @@ const InputNumber = forwardRef(({ className, inputClassName, value: controlledVa
138
138
  emitChange(undefined, syntheticEvent, true);
139
139
  inputRef.current?.focus();
140
140
  }, [createSyntheticChangeEvent, emitChange]);
141
- return (_jsxs("div", { className: cn('tc-ui-input-number relative w-full', className), children: [_jsx("input", { ref: setRefs, type: "text", value: displayValue, onChange: handleChange, inputMode: inputMode, className: cn('flex w-full rounded-lg px-4 py-3 text-sm', 'border transition-colors duration-200', 'placeholder:text-muted-foreground', 'focus:outline-none', showClearIcon && !allowStep && !addonAfter && 'pr-10', hasRightAccessories && 'pr-24', allowStep && addonAfter && 'pr-28', disabled ? 'cursor-not-allowed bg-muted border-border text-muted-foreground' : 'bg-background', !disabled && !hasValue && !isFocused && 'border-border', !disabled && (hasValue || isFocused) && 'border-theme', inputClassName), onFocus: handleFocus, onBlur: handleBlur, disabled: disabled, ...props }), hasRightAccessories && (_jsxs("div", { className: "absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-1", children: [showClearIcon && (_jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: handleClear, children: clearIcon })), addonAfter && _jsx("div", { children: addonAfter }), allowStep && (_jsxs("div", { className: "flex flex-col overflow-hidden rounded-md border border-border", children: [_jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => handleStep('up'), disabled: !canStep('up'), className: cn('flex h-4 w-4 items-center justify-center bg-background text-foreground transition-colors', 'border-b border-border', 'disabled:cursor-not-allowed disabled:text-muted-foreground', !disabled && canStep('up') && 'hover:bg-muted'), children: _jsx(ChevronUp, { size: 12 }) }), _jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => handleStep('down'), disabled: !canStep('down'), className: cn('flex h-4 w-4 items-center justify-center bg-background text-foreground transition-colors', 'disabled:cursor-not-allowed disabled:text-muted-foreground', !disabled && canStep('down') && 'hover:bg-muted'), children: _jsx(ChevronDown, { size: 12 }) })] }))] }))] }));
141
+ return (_jsxs("div", { className: cn('tc-ui-input-number relative w-full', className), children: [_jsx("input", { ref: setRefs, type: "text", value: displayValue, onChange: handleChange, inputMode: inputMode, className: cn('flex h-10 w-full rounded-lg px-3 py-0 text-sm', 'border transition-colors duration-200', 'placeholder:text-muted-foreground', 'focus:outline-none', showClearIcon && !allowStep && !addonAfter && 'pr-10', allowStep && !showClearIcon && !addonAfter && 'pr-12', allowStep && showClearIcon && !addonAfter && 'pr-20', addonAfter && !allowStep && 'pr-16', allowStep && addonAfter && 'pr-28', disabled ? 'cursor-not-allowed bg-muted border-border text-muted-foreground' : 'bg-background', !disabled && !isFocused && 'border-border', !disabled && isFocused && 'border-theme ring-2 ring-theme/25', inputClassName), onFocus: handleFocus, onBlur: handleBlur, disabled: disabled, ...props }), hasRightAccessories && (_jsxs("div", { className: "absolute right-1.5 top-1/2 flex -translate-y-1/2 items-center gap-1", children: [showClearIcon && (_jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: handleClear, "aria-label": "Clear number", title: "Clear number", className: "flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-foreground/8", children: clearIcon })), addonAfter && _jsx("div", { className: "flex h-7 items-center text-muted-foreground", children: addonAfter }), allowStep && (_jsxs("div", { className: "flex h-8 w-6 flex-col overflow-hidden rounded-md border border-border/60 bg-muted/30 text-muted-foreground", children: [_jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => handleStep('up'), disabled: !canStep('up'), "aria-label": "Increase value", title: "Increase value", className: cn('flex h-4 w-full items-center justify-center transition-colors', 'border-b border-border/50', 'disabled:cursor-not-allowed disabled:text-muted-foreground/45', !disabled && canStep('up') && 'hover:bg-foreground/8 hover:text-foreground'), children: _jsx(ChevronUp, { size: 12 }) }), _jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => handleStep('down'), disabled: !canStep('down'), "aria-label": "Decrease value", title: "Decrease value", className: cn('flex h-4 w-full items-center justify-center transition-colors', 'disabled:cursor-not-allowed disabled:text-muted-foreground/45', !disabled && canStep('down') && 'hover:bg-foreground/8 hover:text-foreground'), children: _jsx(ChevronDown, { size: 12 }) })] }))] }))] }));
142
142
  });
143
143
  InputNumber.displayName = 'InputNumber';
144
144
  export { InputNumber };
@@ -1,9 +1,25 @@
1
1
  import type { ReactNode } from 'react';
2
2
  export type LoadingProps = {
3
+ /**
4
+ * 加载图标尺寸
5
+ * @default 'md'
6
+ */
3
7
  size?: 'sm' | 'md' | 'lg';
8
+ /**
9
+ * 加载文案
10
+ */
4
11
  text?: ReactNode;
5
12
  className?: string;
6
13
  };
14
+ export type LoadingIndicatorProps = {
15
+ /**
16
+ * 仅图标尺寸,适合被 Button 等组件复用
17
+ * @default 'md'
18
+ */
19
+ size?: 'sm' | 'md' | 'lg';
20
+ className?: string;
21
+ };
22
+ export declare function LoadingIndicator({ size, className }: LoadingIndicatorProps): import("react/jsx-runtime").JSX.Element;
7
23
  declare const Loading: import("react").ForwardRefExoticComponent<LoadingProps & import("react").RefAttributes<HTMLDivElement>>;
8
24
  export { Loading };
9
25
  //# sourceMappingURL=Loading.d.ts.map
@@ -2,12 +2,20 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../../lib/utils';
3
3
  import { forwardRef } from 'react';
4
4
  const sizeStyles = {
5
- sm: 'h-4 w-4 border-2',
6
- md: 'h-8 w-8 border-2',
7
- lg: 'h-12 w-12 border-[3px]',
5
+ sm: 'h-4 w-4',
6
+ md: 'h-6 w-6',
7
+ lg: 'h-8 w-8',
8
8
  };
9
+ const textStyles = {
10
+ sm: 'text-xs',
11
+ md: 'text-xs',
12
+ lg: 'text-sm',
13
+ };
14
+ export function LoadingIndicator({ size = 'md', className }) {
15
+ return (_jsxs("svg", { className: cn('animate-spin text-theme', sizeStyles[size], className), xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }));
16
+ }
9
17
  const Loading = forwardRef(({ size = 'md', text, className }, ref) => {
10
- return (_jsxs("div", { ref: ref, className: cn('tc-ui-loading flex flex-col items-center justify-center gap-2', className), children: [_jsx("div", { className: cn('animate-spin rounded-full border-muted border-t-theme', sizeStyles[size]) }), text && _jsx("span", { className: "text-sm text-muted-foreground", children: text })] }));
18
+ return (_jsxs("div", { ref: ref, className: cn('tc-ui-loading inline-flex flex-col items-center justify-center gap-2 text-muted-foreground', className), children: [_jsx(LoadingIndicator, { size: size }), text && (_jsx("span", { className: cn('font-medium leading-none tracking-normal text-muted-foreground', textStyles[size]), children: text }))] }));
11
19
  });
12
20
  Loading.displayName = 'Loading';
13
21
  export { Loading };
@@ -29,6 +29,13 @@ export const CollapseToggle = forwardRef(({ className }, ref) => {
29
29
  }
30
30
  return true;
31
31
  };
32
+ const toggleCollapsed = () => setCollapsed(!collapsed);
33
+ const handleKeyDown = (event) => {
34
+ if (event.key === "Enter" || event.key === " ") {
35
+ event.preventDefault();
36
+ toggleCollapsed();
37
+ }
38
+ };
32
39
  useImperativeHandle(ref, () => ({
33
40
  collapse: () => setCollapsed(true),
34
41
  expand: () => setCollapsed(false),
@@ -36,14 +43,14 @@ export const CollapseToggle = forwardRef(({ className }, ref) => {
36
43
  if (!shouldShowCollapseToggle()) {
37
44
  return null;
38
45
  }
39
- return (_jsx("div", { role: "button", onClick: () => setCollapsed(!collapsed), className: cn("mx-1 mb-1 flex h-9 cursor-pointer items-center justify-center rounded-lg", "text-muted-foreground transition-all", "hover:bg-muted/60 hover:text-foreground", className), children: collapsed ? (_jsx(ChevronRight, { className: "h-4 w-4" })) : (_jsx(ChevronLeft, { className: "h-4 w-4" })) }));
46
+ return (_jsx("button", { type: "button", "aria-label": collapsed ? "Expand menu" : "Collapse menu", "aria-expanded": !collapsed, onClick: toggleCollapsed, onKeyDown: handleKeyDown, className: cn("mx-1 mb-1 flex h-9 cursor-pointer items-center justify-center rounded-lg", "text-muted-foreground transition-all", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-0", "hover:bg-foreground/8 hover:text-foreground", className), children: collapsed ? (_jsx(ChevronRight, { className: "h-4 w-4" })) : (_jsx(ChevronLeft, { className: "h-4 w-4" })) }));
40
47
  });
41
48
  CollapseToggle.displayName = "CollapseToggle";
42
49
  function MenuInner({ children, items, className, mode, }) {
43
50
  const collapsed = useMenuStore((s) => s.collapsed);
44
51
  const collapsedWidth = useMenuStore((s) => s.collapsedWidth);
45
52
  const content = items ? renderItems(items) : children;
46
- return (_jsxs("nav", { role: "menu", className: cn("tc-ui-menu relative overflow-hidden rounded-xl bg-background shadow-sm", mode === "left" && "flex flex-col p-1", mode === "top" && "flex flex-row items-center p-[3px]", className), style: mode === "left" && collapsed ? { width: collapsedWidth } : undefined, children: [mode === "left" && _jsx(CollapseToggle, {}), _jsx(CollapsedContext.Provider, { value: mode === "left" && collapsed, children: _jsx("div", { className: cn(mode === "left" && "flex flex-col gap-1", mode === "top" && "flex flex-row items-center gap-1"), children: content }) })] }));
53
+ return (_jsxs("nav", { role: "menu", className: cn("tc-ui-menu relative overflow-hidden rounded-xl border border-border/60 bg-card shadow-none", "shadow-[inset_0_1px_0_hsl(var(--foreground)/0.04)]", mode === "left" && "flex flex-col p-1", mode === "top" && "flex flex-row items-center p-[3px]", className), style: mode === "left" && collapsed ? { width: collapsedWidth } : undefined, children: [mode === "left" && _jsx(CollapseToggle, {}), _jsx(CollapsedContext.Provider, { value: mode === "left" && collapsed, children: _jsx("div", { className: cn(mode === "left" && "flex flex-col gap-1", mode === "top" && "flex flex-row items-center gap-1"), children: content }) })] }));
47
54
  }
48
55
  export const Menu = ({ mode = "left", expandTrigger = "click", expandMode = "accordion", selectedKey: controlledSelectedKey, defaultSelectedKey, onSelect, items, children, className, collapsed: controlledCollapsed, defaultCollapsed, onCollapse, collapsedWidth = 64, collapsedLabelTooltip = true, }) => {
49
56
  const storeRef = useRef(null);
@@ -25,9 +25,9 @@ export function MenuItem({ eventKey, icon, disabled, danger, onClick, children,
25
25
  collapsed &&
26
26
  "h-10 w-full justify-center gap-0 px-0 py-2", mode === "left" &&
27
27
  !collapsed &&
28
- "min-h-10 w-full gap-3 px-3 py-2.5 text-left", isSelected && "bg-theme text-theme-foreground shadow-sm", !isSelected &&
28
+ "min-h-10 w-full gap-3 px-3 py-2.5 text-left", isSelected && "bg-theme/12 text-foreground", !isSelected &&
29
29
  !danger &&
30
- "text-foreground hover:bg-gray-200 dark:hover:bg-gray-700", danger && !isSelected && "text-destructive hover:bg-destructive/10", disabled && "cursor-not-allowed opacity-50", className), style: collapsed ? style : { width, minWidth, maxWidth, ...style }, children: [icon && (_jsx("span", { className: cn("flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md transition-colors", isSelected
31
- ? "bg-theme-foreground/15"
32
- : "text-muted-foreground group-hover:text-foreground"), children: icon })), !collapsed && (_jsx("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap", children: children })), collapsed && !icon && collapsedLabelTooltip && (_jsx("span", { className: "absolute left-0 right-0 top-0 flex h-full items-center justify-center text-[10px]", children: getFirstChar(children) }))] }), _jsx(Popup, { open: showTooltip, anchorRef: triggerRef, placement: "right-start", offset: 8, onOpenChange: setShowTooltip, className: "border-0 bg-transparent p-0 shadow-none", keepMounted: true, children: _jsx("div", { className: "whitespace-nowrap rounded-lg bg-gray-900 px-3 py-1.5 text-xs text-white shadow-sm", children: children }) })] }));
30
+ "text-foreground hover:bg-foreground/8", danger && !isSelected && "text-destructive hover:bg-destructive/10", disabled && "cursor-not-allowed opacity-50", className), style: collapsed ? style : { width, minWidth, maxWidth, ...style }, children: [icon && (_jsx("span", { className: cn("flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-md transition-colors", "[&>svg]:h-4 [&>svg]:w-4", isSelected
31
+ ? "bg-foreground/8 text-foreground"
32
+ : "text-muted-foreground group-hover:text-foreground"), children: icon })), !collapsed && (_jsx("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap", children: children })), collapsed && !icon && collapsedLabelTooltip && (_jsx("span", { className: "absolute left-0 right-0 top-0 flex h-full items-center justify-center text-[10px]", children: getFirstChar(children) }))] }), _jsx(Popup, { open: showTooltip, anchorRef: triggerRef, placement: "right-start", offset: 8, onOpenChange: setShowTooltip, className: "border-0 bg-transparent p-0 shadow-none", keepMounted: true, children: _jsx("div", { className: "whitespace-nowrap rounded-lg border border-border/60 bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-[0_12px_30px_hsl(var(--background)/0.45)]", children: children }) })] }));
33
33
  }
@@ -69,9 +69,9 @@ function SubMenuItem({ triggerRef, onClick, icon, label, disabled, collapsed, de
69
69
  collapsed &&
70
70
  "h-10 w-full justify-center gap-0 px-0 py-2", mode === "left" &&
71
71
  !collapsed &&
72
- "min-h-10 w-full gap-3 px-3 py-2.5 text-left", descendantSelected && "bg-theme/10 text-theme shadow-sm", !descendantSelected &&
73
- "text-foreground hover:bg-gray-200 dark:hover:bg-gray-700", disabled && "cursor-not-allowed opacity-50"), children: [icon && (_jsx("span", { className: cn("flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md transition-colors", descendantSelected
74
- ? "bg-theme/10 text-theme"
72
+ "min-h-10 w-full gap-3 px-3 py-2.5 text-left", descendantSelected && "bg-theme/12 text-foreground", !descendantSelected &&
73
+ "text-foreground hover:bg-foreground/8", disabled && "cursor-not-allowed opacity-50"), children: [icon && (_jsx("span", { className: cn("flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-md transition-colors", "[&>svg]:h-4 [&>svg]:w-4", descendantSelected
74
+ ? "bg-foreground/8 text-foreground"
75
75
  : "text-muted-foreground group-hover:text-foreground"), children: icon })), !collapsed && _jsx("span", { className: "min-w-0 flex-1 truncate", children: label }), collapsed && !icon && collapsedLabelTooltip && (_jsx("span", { className: "absolute left-0 right-0 top-0 flex h-full items-center justify-center text-[10px]", children: getFirstChar(label) })), !collapsed && showArrow && (_jsx(ArrowIcon, { className: "h-4 w-4 flex-shrink-0 text-muted-foreground transition-transform group-hover:text-foreground" })), _jsx("div", { ref: setPopupContentRef })] }));
76
76
  }
77
77
  /**
@@ -154,7 +154,7 @@ export function SubMenu({ eventKey, label, icon, disabled, popup: forcePopup, pa
154
154
  const hoverBridgePlacement = mode === "top" && level > 0 ? "horizontal" : "auto";
155
155
  const subMenuContent = (_jsx("div", { className: cn(
156
156
  // "min-w-[180px]",
157
- "rounded-xl border border-border/80 bg-popover p-2 text-popover-foreground", "shadow-[0_20px_56px_hsl(var(--background)/0.7),0_8px_18px_hsl(var(--foreground)/0.16),inset_0_1px_0_hsl(var(--foreground)/0.06)]"), children: _jsx(SubMenuLevelContext.Provider, { value: level + 1, children: _jsx("div", { className: "flex flex-col gap-1", children: children }) }) }));
157
+ "rounded-xl border border-border/70 bg-popover p-2 text-popover-foreground", "shadow-[0_22px_70px_hsl(var(--background)/0.58),inset_0_1px_0_hsl(var(--foreground)/0.06)]"), children: _jsx(SubMenuLevelContext.Provider, { value: level + 1, children: _jsx("div", { className: "flex flex-col gap-1", children: children }) }) }));
158
158
  /**
159
159
  *
160
160
  * submenu 生成的节点是 1 menuItem
@@ -8,19 +8,19 @@ import { DEFAULT_DURATION } from './data';
8
8
  const typeConfig = {
9
9
  success: {
10
10
  icon: CheckCircle2,
11
- color: 'text-green-600',
11
+ color: 'text-chart-4',
12
12
  },
13
13
  error: {
14
14
  icon: XCircle,
15
- color: 'text-red-600',
15
+ color: 'text-destructive',
16
16
  },
17
17
  warning: {
18
18
  icon: AlertCircle,
19
- color: 'text-yellow-600',
19
+ color: 'text-chart-5',
20
20
  },
21
21
  info: {
22
22
  icon: Info,
23
- color: 'text-blue-600',
23
+ color: 'text-chart-3',
24
24
  },
25
25
  };
26
26
  export function Message({ type = 'info', content, children, duration = DEFAULT_DURATION, onClose, closable = true, className, }) {
@@ -54,5 +54,5 @@ export function Message({ type = 'info', content, children, duration = DEFAULT_D
54
54
  }, [duration]);
55
55
  if (!visible)
56
56
  return null;
57
- return (_jsxs("div", { ref: messageRef, className: cn('tc-ui-message flex items-start gap-3 px-4 py-3 rounded-lg shadow-lg', 'bg-background border border-border', 'min-w-[320px] max-w-[480px]', 'transition-all duration-200', 'pointer-events-auto', visible ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2', className), children: [_jsx(Icon, { size: 20, className: cn('flex-shrink-0 mt-0.5', iconColor) }), _jsx("div", { className: "flex-1 text-sm text-foreground", children: displayContent }), closable && (_jsx(Button, { variant: "text", type: "button", onClick: handleClose, "aria-label": t('components.message.close'), className: cn('flex-shrink-0 p-0.5 rounded-md', 'text-muted-foreground hover:text-foreground', 'hover:bg-muted', 'transition-colors', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2'), children: _jsx(X, { size: 16 }) }))] }));
57
+ return (_jsxs("div", { ref: messageRef, className: cn('tc-ui-message flex items-start gap-3 rounded-xl px-4 py-3', 'border border-border/60 bg-popover text-popover-foreground shadow-[var(--shadow-floating)]', 'min-w-[320px] max-w-[480px]', 'transition-[opacity,transform] duration-200', 'pointer-events-auto', visible ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2', className), children: [_jsx(Icon, { size: 20, className: cn('flex-shrink-0 mt-0.5', iconColor) }), _jsx("div", { className: "flex-1 text-sm text-popover-foreground", children: displayContent }), closable && (_jsx(Button, { variant: "text", type: "button", onClick: handleClose, "aria-label": t('components.message.close'), className: cn('flex-shrink-0 p-0.5 rounded-md', 'text-muted-foreground hover:text-popover-foreground', 'hover:bg-foreground/8 active:bg-foreground/12', 'transition-colors', 'focus-visible:ring-theme/30'), children: _jsx(X, { size: 16 }) }))] }));
58
58
  }
@@ -10,5 +10,5 @@ export function Modal({ open = false, onClose, title, children, footer, classNam
10
10
  const handleContentClick = (e) => {
11
11
  e.stopPropagation();
12
12
  };
13
- return (_jsx(Overlay, { open: open, onClose: onClose, mask: mask, maskClosable: maskClosable, lockScroll: lockScroll, keyboard: keyboard, closable: closable, renderNode: renderNode, zIndex: zIndex, className: cn('tc-ui-modal flex items-center justify-center'), children: _jsxs("div", { className: cn('relative bg-background rounded-lg shadow-lg', 'flex flex-col max-h-[90vh]', 'transition-all duration-200', open ? 'scale-100 opacity-100' : 'scale-95 opacity-0', centered ? 'my-auto' : 'mt-[10vh]', className), style: { width: typeof width === 'number' ? `${width}px` : width }, onClick: handleContentClick, children: [(title || closable) && (_jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-border", children: [title && _jsx("div", { className: "text-lg font-semibold text-foreground", children: title }), closable && (_jsx(Button, { variant: "text", type: "button", onClick: onClose, "aria-label": t('components.modal.close'), className: cn('ml-auto p-1 rounded-md', 'text-muted-foreground hover:text-foreground', 'hover:bg-muted', 'transition-colors', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2'), children: _jsx(X, { size: 20 }) }))] })), _jsx("div", { className: cn('flex-1 overflow-auto px-6 py-4', contentClassName), children: children }), footer && _jsx("div", { className: "px-6 py-4 border-border", children: footer })] }) }));
13
+ return (_jsx(Overlay, { open: open, onClose: onClose, mask: mask, maskClosable: maskClosable, lockScroll: lockScroll, keyboard: keyboard, closable: closable, renderNode: renderNode, zIndex: zIndex, className: cn('tc-ui-modal flex items-center justify-center'), children: _jsxs("div", { className: cn('relative rounded-2xl border border-border/70 bg-popover text-popover-foreground', 'shadow-[0_22px_70px_hsl(var(--background)/0.58),inset_0_1px_0_hsl(var(--foreground)/0.06)]', 'flex flex-col max-h-[90vh]', 'max-w-[calc(100vw-2rem)]', 'transition-all duration-200', open ? 'scale-100 opacity-100' : 'scale-95 opacity-0', centered ? 'my-auto' : 'mt-[10vh]', className), style: { width: typeof width === 'number' ? `${width}px` : width }, onClick: handleContentClick, children: [(title || closable) && (_jsxs("div", { className: "flex items-center justify-between border-b border-border/60 px-4 py-3", children: [title && _jsx("div", { className: "text-sm font-semibold leading-5 text-foreground", children: title }), closable && (_jsx(Button, { variant: "text", type: "button", onClick: onClose, "aria-label": t('components.modal.close'), className: cn('ml-auto h-7 w-7 rounded-md p-0', 'text-muted-foreground hover:text-foreground', 'hover:bg-foreground/8', 'transition-colors', 'focus:outline-none focus:ring-2 focus:ring-ring/40 focus:ring-offset-0'), children: _jsx(X, { size: 16 }) }))] })), _jsx("div", { className: cn('flex-1 overflow-auto px-4 py-4', contentClassName), children: children }), footer && _jsx("div", { className: "border-t border-border/60 px-4 py-3", children: footer })] }) }));
14
14
  }
@@ -3,10 +3,10 @@ import { cn } from '../../lib/utils';
3
3
  import { X } from 'lucide-react';
4
4
  import { forwardRef, useCallback, useEffect, useState } from 'react';
5
5
  const typeStyles = {
6
- info: 'border-blue-500 bg-blue-50 dark:bg-blue-950',
7
- success: 'border-green-500 bg-green-50 dark:bg-green-950',
8
- warning: 'border-yellow-500 bg-yellow-50 dark:bg-yellow-950',
9
- error: 'border-red-500 bg-red-50 dark:bg-red-950',
6
+ info: 'text-chart-3',
7
+ success: 'text-chart-4',
8
+ warning: 'text-chart-5',
9
+ error: 'text-destructive',
10
10
  };
11
11
  const Notification = forwardRef(({ type = 'info', title, message, duration, closable = true, onClose, className }, ref) => {
12
12
  const [visible, setVisible] = useState(true);
@@ -23,7 +23,7 @@ const Notification = forwardRef(({ type = 'info', title, message, duration, clos
23
23
  }, [duration, handleClose]);
24
24
  if (!visible)
25
25
  return null;
26
- return (_jsxs("div", { ref: ref, role: "alert", className: cn('tc-ui-notification flex items-start gap-3 rounded-lg border p-4 shadow-lg', typeStyles[type], className), children: [_jsxs("div", { className: "flex-1 min-w-0", children: [title && _jsx("div", { className: "font-medium text-sm", children: title }), message && _jsx("div", { className: "text-sm text-muted-foreground mt-1", children: message })] }), closable && (_jsx("button", { type: "button", onClick: handleClose, className: "shrink-0 rounded p-0.5 hover:bg-black/5 dark:hover:bg-white/10 transition-colors", children: _jsx(X, { className: "h-4 w-4" }) }))] }));
26
+ return (_jsxs("div", { ref: ref, role: "alert", className: cn('tc-ui-notification flex items-start gap-3 rounded-xl border border-border/60 bg-popover p-4 text-popover-foreground shadow-[var(--shadow-floating)]', className), children: [_jsx("span", { className: cn('mt-1 h-2 w-2 shrink-0 rounded-full bg-current', typeStyles[type]), "aria-hidden": "true" }), _jsxs("div", { className: "flex-1 min-w-0", children: [title && _jsx("div", { className: "font-medium text-sm text-popover-foreground", children: title }), message && _jsx("div", { className: "text-sm text-muted-foreground mt-1", children: message })] }), closable && (_jsx("button", { type: "button", onClick: handleClose, className: "shrink-0 rounded-md p-0.5 text-muted-foreground transition-colors hover:bg-foreground/8 hover:text-popover-foreground active:bg-foreground/12 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-theme/30", "aria-label": "Close notification", children: _jsx(X, { className: "h-4 w-4" }) }))] }));
27
27
  });
28
28
  Notification.displayName = 'Notification';
29
29
  export { Notification };
@@ -5,6 +5,9 @@ import { Button } from '../Button';
5
5
  import { Select } from '../Select';
6
6
  export function Pagination({ current, pageSize, total, onChange, onPageSizeChange, pageSizeOptions = [10, 20, 50, 100], className, }) {
7
7
  const totalPages = Math.ceil(total / pageSize);
8
+ const releaseFocus = (event) => {
9
+ event.currentTarget.blur();
10
+ };
8
11
  // 生成页码数组
9
12
  const getPageNumbers = () => {
10
13
  const pages = [];
@@ -46,17 +49,20 @@ export function Pagination({ current, pageSize, total, onChange, onPageSizeChang
46
49
  }
47
50
  return pages;
48
51
  };
49
- const handlePrevious = () => {
52
+ const handlePrevious = (event) => {
53
+ releaseFocus(event);
50
54
  if (current > 1) {
51
55
  onChange(current - 1);
52
56
  }
53
57
  };
54
- const handleNext = () => {
58
+ const handleNext = (event) => {
59
+ releaseFocus(event);
55
60
  if (current < totalPages) {
56
61
  onChange(current + 1);
57
62
  }
58
63
  };
59
- const handlePageClick = (page) => {
64
+ const handlePageClick = (page, event) => {
65
+ releaseFocus(event);
60
66
  if (typeof page === 'number' && page !== current) {
61
67
  onChange(page);
62
68
  }
@@ -72,16 +78,18 @@ export function Pagination({ current, pageSize, total, onChange, onPageSizeChang
72
78
  };
73
79
  if (totalPages === 0)
74
80
  return null;
75
- return (_jsxs("div", { className: cn('tc-ui-pagination flex items-center gap-4', className), children: [_jsxs("div", { className: "text-sm text-muted-foreground", children: ["Total ", total] }), _jsxs("div", { className: "flex-1 flex items-center justify-center gap-2", children: [_jsx(Button, { onClick: handlePrevious, disabled: current === 1, size: "sm", className: cn('inline-flex items-center justify-center', 'h-8 px-3 rounded-md', 'text-sm font-medium', 'border border-border', 'transition-colors', current === 1 ? 'cursor-not-allowed opacity-50 bg-muted' : 'hover:bg-muted bg-background cursor-pointer'), children: _jsx(ChevronLeft, { className: "h-4 w-4" }) }), getPageNumbers().map((page, index) => {
81
+ return (_jsxs("div", { className: cn('tc-ui-pagination flex items-center gap-4', className), children: [_jsxs("div", { className: "text-sm text-muted-foreground", children: ["Total ", total] }), _jsxs("div", { className: "flex flex-1 items-center justify-center gap-1.5", children: [_jsx(Button, { onClick: handlePrevious, disabled: current === 1, size: "sm", className: cn('inline-flex items-center justify-center', 'h-8 w-8 rounded-md px-0', 'text-sm font-medium', 'border border-border/70 bg-background', 'transition-colors duration-150', 'focus-visible:ring-2 focus-visible:ring-ring/35 focus-visible:ring-offset-0', current === 1
82
+ ? 'cursor-not-allowed text-muted-foreground/45'
83
+ : 'cursor-pointer text-muted-foreground hover:bg-foreground/8 hover:text-foreground'), children: _jsx(ChevronLeft, { className: "h-4 w-4" }) }), getPageNumbers().map((page, index) => {
76
84
  if (page === '...') {
77
85
  return (_jsx("span", { className: "px-2 text-muted-foreground", children: "..." }, `ellipsis-${index}`));
78
86
  }
79
- return (_jsx(Button, { onClick: () => handlePageClick(page), size: "sm", className: cn('inline-flex items-center justify-center', 'h-8 min-w-[32px] px-3 rounded-md', 'text-sm font-medium', 'border transition-colors', page === current
80
- ? 'bg-theme text-theme-foreground border-theme cursor-default'
81
- : 'bg-background border-border hover:bg-muted cursor-pointer'), children: page }, page));
82
- }), _jsx(Button, { onClick: handleNext, size: "sm", disabled: current === totalPages, className: cn('inline-flex items-center justify-center', 'h-8 px-3 rounded-md', 'text-sm font-medium', 'border border-border', 'transition-colors', current === totalPages
83
- ? 'cursor-not-allowed opacity-50 bg-muted'
84
- : 'hover:bg-muted bg-background cursor-pointer'), children: _jsx(ChevronRight, { className: "h-4 w-4" }) })] }), onPageSizeChange && (_jsx("div", { className: "flex items-center gap-2", children: _jsx(Select, { value: pageSize, onChange: handlePageSizeChange, options: pageSizeOptions.map((size) => ({
87
+ return (_jsx(Button, { onClick: (event) => handlePageClick(page, event), size: "sm", "aria-current": page === current ? 'page' : undefined, className: cn('inline-flex items-center justify-center', 'h-8 min-w-8 rounded-md px-2.5', 'text-sm font-medium', 'border transition-colors duration-150', 'focus-visible:ring-2 focus-visible:ring-ring/35 focus-visible:ring-offset-0', page === current
88
+ ? 'border-theme/45 bg-theme/12 text-foreground cursor-default'
89
+ : 'bg-background border-border/70 text-muted-foreground hover:bg-foreground/8 hover:text-foreground cursor-pointer'), children: page }, page));
90
+ }), _jsx(Button, { onClick: handleNext, size: "sm", disabled: current === totalPages, className: cn('inline-flex items-center justify-center', 'h-8 w-8 rounded-md px-0', 'text-sm font-medium', 'border border-border/70 bg-background', 'transition-colors duration-150', 'focus-visible:ring-2 focus-visible:ring-ring/35 focus-visible:ring-offset-0', current === totalPages
91
+ ? 'cursor-not-allowed text-muted-foreground/45'
92
+ : 'cursor-pointer text-muted-foreground hover:bg-foreground/8 hover:text-foreground'), children: _jsx(ChevronRight, { className: "h-4 w-4" }) })] }), onPageSizeChange && (_jsx("div", { className: "flex items-center gap-2", children: _jsx(Select, { value: pageSize, onChange: handlePageSizeChange, options: pageSizeOptions.map((size) => ({
85
93
  label: `${size}`,
86
94
  value: size,
87
95
  })), className: "w-20 [&>div]:bg-muted [&>div]:border-border [&>div]:shadow-sm [&>div]:py-2 [&>div]:px-3 [&>div]:h-8", clearable: false, searchable: false }) }))] }));
@@ -3,7 +3,7 @@ import { cn } from "../../lib/utils";
3
3
  import { useEffect, useMemo, useRef } from "react";
4
4
  import { createPortal } from "react-dom";
5
5
  import { getDefaultDropdownPosition, useDropdownPositioning, } from "../hooks/useDropdownPositioning";
6
- const DEFAULT_POPUP_CLASSNAME = cn("fixed z-[9999]", "bg-background rounded-lg shadow-lg border border-gray-200", "animate-in fade-in-0 zoom-in-95");
6
+ const DEFAULT_POPUP_CLASSNAME = cn("fixed z-[1000]", "rounded-xl border border-border/60 bg-popover text-popover-foreground", "shadow-[var(--shadow-floating)]", "animate-in fade-in-0 zoom-in-95");
7
7
  const canUseDocument = () => typeof document !== "undefined";
8
8
  export function Popup({ open, anchorRef, children, className, style, placement = "bottom-end", offset = 8, keepMounted = false, closeOnClickOutside = true, closeOnEscape = true, matchAnchorWidth = false, position, positioning, strategy = getDefaultDropdownPosition, contentRef, container, positionMode = "fixed", hoverBridge = 0, hoverBridgePlacement = "auto", onOpenChange, }) {
9
9
  // 默认自己保存弹窗 DOM;如果外部传了 contentRef,就用外部的。
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useI18n } from '../../i18n';
3
3
  import { cn } from '../../lib/utils';
4
4
  import { ChevronUp, X } from 'lucide-react';
5
- import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
+ import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
6
6
  import { Button } from '../Button';
7
7
  import { Input } from '../Input';
8
8
  import { Popup } from '../Popup';
@@ -14,11 +14,14 @@ const Select = forwardRef(({ options, value: controlledValue, defaultValue, plac
14
14
  const [internalValue, setInternalValue] = useState(defaultValue);
15
15
  const [isHover, setIsHover] = useState(false);
16
16
  const [searchTerm, setSearchTerm] = useState('');
17
+ const [activeOptionIndex, setActiveOptionIndex] = useState(0);
17
18
  const rootRef = useRef(null);
18
19
  const selectRef = useRef(null);
19
20
  const dropdownRef = useRef(null);
20
21
  const inputRef = useRef(null);
21
22
  const popupContainerRef = useRef(null);
23
+ const reactId = useId();
24
+ const listboxId = `${reactId}-listbox`;
22
25
  const isParentPopupContainer = getPopupContainer === 'parent';
23
26
  popupContainerRef.current = isParentPopupContainer ? (rootRef.current?.parentElement ?? null) : null;
24
27
  useEffect(() => {
@@ -59,6 +62,15 @@ const Select = forwardRef(({ options, value: controlledValue, defaultValue, plac
59
62
  return searchText.toLowerCase().includes(lower);
60
63
  });
61
64
  }, [options, searchTerm, searchable]);
65
+ const activeOption = filteredOptions[activeOptionIndex];
66
+ const activeOptionId = activeOption ? `${reactId}-option-${activeOptionIndex}` : undefined;
67
+ useEffect(() => {
68
+ setActiveOptionIndex((current) => {
69
+ if (filteredOptions.length === 0)
70
+ return 0;
71
+ return Math.min(current, filteredOptions.length - 1);
72
+ });
73
+ }, [filteredOptions.length]);
62
74
  const positioning = useDropdownPositioning({
63
75
  open: isOpen,
64
76
  anchorRef: selectRef,
@@ -92,9 +104,21 @@ const Select = forwardRef(({ options, value: controlledValue, defaultValue, plac
92
104
  if (next && searchable) {
93
105
  setSearchTerm('');
94
106
  }
107
+ if (next) {
108
+ setActiveOptionIndex(Math.max(options.findIndex((opt) => opt.value === value), 0));
109
+ }
95
110
  return next;
96
111
  });
97
- }, [disabled, searchable]);
112
+ }, [disabled, options, searchable, value]);
113
+ const openSelect = useCallback(() => {
114
+ if (disabled)
115
+ return;
116
+ if (searchable) {
117
+ setSearchTerm('');
118
+ }
119
+ setActiveOptionIndex(Math.max(options.findIndex((opt) => opt.value === value), 0));
120
+ setIsOpen(true);
121
+ }, [disabled, options, searchable, value]);
98
122
  const handleSelect = useCallback((optionValue) => {
99
123
  if (!isControlled) {
100
124
  setInternalValue(optionValue);
@@ -112,11 +136,84 @@ const Select = forwardRef(({ options, value: controlledValue, defaultValue, plac
112
136
  }, [disabled, onChange]);
113
137
  const handleSearchChange = useCallback((event) => {
114
138
  setSearchTerm(event.target.value);
139
+ setActiveOptionIndex(0);
115
140
  }, []);
141
+ const moveActiveOption = useCallback((direction) => {
142
+ if (filteredOptions.length === 0)
143
+ return;
144
+ setActiveOptionIndex((current) => {
145
+ const nextIndex = current + direction;
146
+ if (nextIndex < 0)
147
+ return filteredOptions.length - 1;
148
+ if (nextIndex >= filteredOptions.length)
149
+ return 0;
150
+ return nextIndex;
151
+ });
152
+ }, [filteredOptions.length]);
153
+ const selectActiveOption = useCallback(() => {
154
+ const option = filteredOptions[activeOptionIndex];
155
+ if (option) {
156
+ handleSelect(option.value);
157
+ }
158
+ }, [activeOptionIndex, filteredOptions, handleSelect]);
159
+ const handleKeyDown = useCallback((event) => {
160
+ if (disabled)
161
+ return;
162
+ switch (event.key) {
163
+ case 'ArrowDown':
164
+ event.preventDefault();
165
+ if (!isOpen) {
166
+ openSelect();
167
+ }
168
+ else {
169
+ moveActiveOption(1);
170
+ }
171
+ break;
172
+ case 'ArrowUp':
173
+ event.preventDefault();
174
+ if (!isOpen) {
175
+ openSelect();
176
+ }
177
+ else {
178
+ moveActiveOption(-1);
179
+ }
180
+ break;
181
+ case 'Enter':
182
+ event.preventDefault();
183
+ if (!isOpen) {
184
+ openSelect();
185
+ }
186
+ else {
187
+ selectActiveOption();
188
+ }
189
+ break;
190
+ case ' ':
191
+ if (event.currentTarget !== inputRef.current) {
192
+ event.preventDefault();
193
+ if (!isOpen) {
194
+ openSelect();
195
+ }
196
+ }
197
+ break;
198
+ case 'Escape':
199
+ if (isOpen) {
200
+ event.preventDefault();
201
+ setIsOpen(false);
202
+ selectRef.current?.focus();
203
+ }
204
+ break;
205
+ default:
206
+ break;
207
+ }
208
+ }, [disabled, isOpen, moveActiveOption, openSelect, selectActiveOption]);
116
209
  const popupContainer = getPopupContainer === 'parent' ? (rootRef.current?.parentElement ?? undefined) : getPopupContainer?.();
117
- return (_jsxs("div", { ref: rootRef, className: cn('tc-ui-select relative w-full', className), children: [_jsxs("div", { ref: setRefs, onClick: handleToggle, onMouseEnter: () => setIsHover(true), onMouseLeave: () => setIsHover(false), className: cn('flex w-full items-center justify-between rounded-lg px-4 py-3 text-sm', 'border transition-colors duration-200', 'focus:outline-none', disabled ? 'cursor-not-allowed bg-muted border-border' : 'cursor-pointer bg-background', !disabled && isOpen && 'border-theme', !disabled && !isOpen && 'border-border'), children: [searchable && isOpen ? (_jsx(Input, { ref: inputRef, value: searchTerm, onChange: (_, e) => handleSearchChange(e), onClick: (e) => e.stopPropagation(), placeholder: typeof displayText === 'string' ? displayText : placeholder, className: "min-w-0 flex-1", inputClassName: cn('h-5 min-w-0 flex-1 rounded-none border-0 bg-transparent px-0 py-0 text-left leading-5 outline-none', 'placeholder:text-muted-foreground', disabled && 'text-muted-foreground cursor-not-allowed'), disabled: disabled })) : (_jsx("span", { className: cn('min-h-5 min-w-0 flex-1 text-left truncate leading-5', disabled ? 'text-muted-foreground' : !selectedOption && 'text-muted-foreground'), children: displayText })), clearable && !disabled && value !== undefined && isHover ? (_jsx(Button, { variant: "text", type: "button", onClick: handleClear, className: "ml-2 flex items-center justify-center text-muted-foreground hover:text-foreground cursor-pointer", "aria-label": "Clear selection", title: "Clear", children: _jsx(X, { size: 14 }) })) : (_jsx(ChevronUp, { size: 16, className: cn('ml-2 transition-transform duration-200', isOpen ? 'rotate-0' : 'rotate-180') }))] }), _jsx(Popup, { open: isOpen, anchorRef: selectRef, contentRef: dropdownRef, positioning: positioning, keepMounted: true, onOpenChange: setIsOpen, container: popupContainer, positionMode: isParentPopupContainer ? 'absolute' : 'fixed', className: cn('z-[1000] w-full rounded-lg bg-background shadow-lg', 'border border-border', 'max-h-60 overflow-auto'), children: _jsx("div", { className: cn('w-full', !isOpen && 'hidden'), children: _jsx("div", { className: "p-1", children: filteredOptions.length > 0 ? (filteredOptions.map((option) => {
210
+ return (_jsxs("div", { ref: rootRef, className: cn('tc-ui-select relative w-full', className), children: [_jsxs("div", { ref: setRefs, onClick: handleToggle, onKeyDown: handleKeyDown, onMouseEnter: () => setIsHover(true), onMouseLeave: () => setIsHover(false), role: "combobox", "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-controls": listboxId, "aria-activedescendant": isOpen ? activeOptionId : undefined, tabIndex: disabled ? -1 : 0, className: cn('flex h-10 w-full items-center justify-between rounded-lg px-3 py-0 text-sm', 'border transition-colors duration-200', 'focus:outline-none focus-visible:border-theme focus-visible:ring-2 focus-visible:ring-theme/25', disabled ? 'cursor-not-allowed bg-muted border-border' : 'cursor-pointer bg-background', !disabled && isOpen && 'border-theme ring-2 ring-theme/25', !disabled && !isOpen && 'border-border'), children: [searchable && isOpen ? (_jsx(Input, { ref: inputRef, value: searchTerm, onChange: (_, e) => handleSearchChange(e), onClick: (e) => e.stopPropagation(), onKeyDown: (e) => {
211
+ e.stopPropagation();
212
+ handleKeyDown(e);
213
+ }, placeholder: typeof displayText === 'string' ? displayText : placeholder, className: "min-w-0 flex-1", inputClassName: cn('h-5 min-w-0 flex-1 rounded-none border-0 bg-transparent px-0 py-0 text-left leading-5 outline-none ring-0', 'placeholder:text-muted-foreground', disabled && 'text-muted-foreground cursor-not-allowed'), role: "searchbox", "aria-controls": listboxId, "aria-activedescendant": isOpen ? activeOptionId : undefined, disabled: disabled })) : (_jsx("span", { className: cn('min-h-5 min-w-0 flex-1 text-left truncate leading-5', disabled ? 'text-muted-foreground' : !selectedOption && 'text-muted-foreground'), children: displayText })), clearable && !disabled && value !== undefined && isHover ? (_jsx(Button, { variant: "text", type: "button", onClick: handleClear, className: "ml-2 flex items-center justify-center text-muted-foreground hover:text-foreground cursor-pointer", "aria-label": "Clear selection", title: "Clear", children: _jsx(X, { size: 14 }) })) : (_jsx(ChevronUp, { size: 16, className: cn('ml-2 transition-transform duration-200', isOpen ? 'rotate-0' : 'rotate-180') }))] }), _jsx(Popup, { open: isOpen, anchorRef: selectRef, contentRef: dropdownRef, positioning: positioning, keepMounted: true, onOpenChange: setIsOpen, container: popupContainer, positionMode: isParentPopupContainer ? 'absolute' : 'fixed', className: cn('w-full rounded-xl border border-border/60 bg-popover text-popover-foreground', 'shadow-[var(--shadow-floating)]', 'max-h-60 overflow-auto'), children: _jsx("div", { className: cn('w-full', !isOpen && 'hidden'), children: _jsx("div", { id: listboxId, role: "listbox", className: "p-1", children: filteredOptions.length > 0 ? (filteredOptions.map((option, index) => {
118
214
  const isSelected = option.value === value;
119
- return (_jsx("div", { onClick: () => handleSelect(option.value), className: cn('px-4 py-2 text-sm cursor-pointer transition-colors rounded-md', 'hover:bg-muted', isSelected && 'bg-muted'), children: option.label }, option.value));
215
+ const isActive = index === activeOptionIndex;
216
+ return (_jsx("div", { id: `${reactId}-option-${index}`, role: "option", "aria-selected": isSelected, onClick: () => handleSelect(option.value), onMouseEnter: () => setActiveOptionIndex(index), className: cn('px-4 py-2 text-sm cursor-pointer transition-colors rounded-md', 'hover:bg-foreground/8', (isSelected || isActive) && 'bg-theme/12 text-foreground'), children: option.label }, option.value));
120
217
  })) : (_jsx("div", { className: "px-4 py-2 text-sm text-muted-foreground text-center", children: t('components.select.empty') })) }) }) })] }));
121
218
  });
122
219
  Select.displayName = 'Select';
@@ -3,7 +3,7 @@ import { cn } from "../../lib/utils";
3
3
  import { forwardRef, useCallback } from "react";
4
4
  import { useInputController } from "../hooks/useInputController";
5
5
  const Textarea = forwardRef(({ className, textareaClassName, value: controlledValue, onChange, defaultValue, maxLength = 500, showCount = true, placeholder, disabled, ...props }, ref) => {
6
- const { isFocused, setIsFocused, setInternalValue, setRefs, value, hasValue, isControlledRef, currentLength, } = useInputController({
6
+ const { isFocused, setIsFocused, setInternalValue, setRefs, value, isControlledRef, currentLength, } = useInputController({
7
7
  controlledValue,
8
8
  defaultValue,
9
9
  ref,
@@ -19,9 +19,9 @@ const Textarea = forwardRef(({ className, textareaClassName, value: controlledVa
19
19
  }
20
20
  onChange?.(newValue, e);
21
21
  }, [maxLength, isControlledRef, onChange, setInternalValue]);
22
- return (_jsxs("div", { className: cn("tc-ui-textarea relative w-fit", className), children: [_jsx("textarea", { ref: setRefs, value: value, onChange: handleChange, maxLength: maxLength, placeholder: placeholder, className: cn("flex w-full rounded-lg px-4 py-3 text-sm", "border transition-colors duration-200", "placeholder:text-muted-foreground", "focus:outline-none", "resize-none", showCount && "pb-10", disabled
22
+ return (_jsxs("div", { className: cn("tc-ui-textarea relative w-fit", className), children: [_jsx("textarea", { ref: setRefs, value: value, onChange: handleChange, maxLength: maxLength, placeholder: placeholder, className: cn("flex min-h-24 w-full rounded-lg px-3 py-2 text-sm", "border transition-colors duration-200", "placeholder:text-muted-foreground", "focus:outline-none", "resize-none", showCount && "pb-10", disabled
23
23
  ? "cursor-not-allowed bg-muted border-border text-muted-foreground"
24
- : "bg-background", !disabled && !hasValue && !isFocused && "border-border", !disabled && (hasValue || isFocused) && "border-foreground", textareaClassName), onFocus: (e) => {
24
+ : "bg-background", !disabled && !isFocused && "border-border", !disabled && isFocused && "border-theme ring-2 ring-theme/25", textareaClassName), onFocus: (e) => {
25
25
  if (!disabled) {
26
26
  setIsFocused(true);
27
27
  props.onFocus?.(e);
@@ -72,7 +72,7 @@ export function Tooltip({ content, children, placement = "right", visible, class
72
72
  }
73
73
  }, [isVisible, placement]);
74
74
  return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, onMouseEnter: showTooltip, onMouseLeave: hideTooltip, className: "tc-ui-tooltip inline-block", children: children }), isVisible &&
75
- createPortal(_jsx("div", { ref: tooltipRef, className: cn("fixed z-[9999] px-3 py-2", "bg-gray-800 text-white text-xs rounded", "shadow-lg", "pointer-events-none", "max-w-xs", className), style: {
75
+ createPortal(_jsx("div", { ref: tooltipRef, className: cn("fixed z-[1000] px-3 py-2", "rounded-xl border border-border/50 bg-popover text-xs text-popover-foreground", "shadow-[var(--shadow-floating)]", "pointer-events-none", "max-w-xs", className), style: {
76
76
  top: `${position.top}px`,
77
77
  left: `${position.left}px`,
78
78
  }, children: content }), document.body)] }));
@@ -68,8 +68,8 @@ export function ImageUpload({ accept = '.jpg,.png', maxCount = 1, value, onChang
68
68
  internalUrlsRef.current = [];
69
69
  };
70
70
  }, []);
71
- return (_jsxs("div", { className: "tc-ui-image-upload flex flex-wrap gap-3", children: [urls.map((url, index) => (_jsxs("div", { className: "relative w-24 h-24 rounded-lg overflow-hidden border border-gray-200 group cursor-pointer", children: [_jsx("img", { src: url, alt: "", className: "w-full h-full object-cover" }), _jsx("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-center justify-center", children: _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
71
+ return (_jsxs("div", { className: "tc-ui-image-upload flex flex-wrap gap-3", children: [urls.map((url, index) => (_jsxs("div", { className: "relative h-24 w-24 overflow-hidden rounded-lg border border-border/60 bg-card group cursor-pointer shadow-[inset_0_1px_0_hsl(var(--foreground)/0.04)] transition-colors duration-200 hover:border-border/80 active:border-theme/60", children: [_jsx("img", { src: url, alt: "", className: "w-full h-full object-cover" }), _jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/0 transition-colors duration-200 group-hover:bg-background/55", children: _jsx(Button, { variant: "text", type: "button", onClick: (e) => {
72
72
  e.stopPropagation();
73
73
  handleRemove(index);
74
- }, className: "opacity-0 group-hover:opacity-100 transition-opacity p-1.5 rounded-full bg-red-500 hover:bg-red-600 text-white", "aria-label": t('components.upload.deleteImage'), title: t('components.upload.deleteImage'), children: _jsx(X, { size: 16 }) }) })] }, index))), urls.length < maxCount && (_jsx(FileUpload, { inputRef: inputRef, accept: mAccept, multiple: maxCount > 1, onChange: handleFileChange, labelClassName: "w-24 h-24 p-0 border-0 cursor-pointer", selectLabel: _jsxs("div", { className: "w-full h-full rounded-[8px] bg-[#F5F5F5] border border-gray-200 flex flex-col items-center justify-center gap-1 hover:bg-gray-50 transition-colors", children: [_jsx(Camera, { className: "text-gray-500", size: 24 }), _jsx("span", { className: "text-xs text-gray-500", children: t('components.upload.upload') })] }) }))] }));
74
+ }, className: "rounded-full bg-destructive p-1.5 text-destructive-foreground opacity-0 shadow-[var(--shadow-accent)] transition-[background-color,opacity] duration-200 hover:bg-destructive/90 active:bg-destructive/80 focus-visible:ring-destructive group-hover:opacity-100", "aria-label": t('components.upload.deleteImage'), title: t('components.upload.deleteImage'), children: _jsx(X, { size: 16 }) }) })] }, index))), urls.length < maxCount && (_jsx(FileUpload, { inputRef: inputRef, accept: mAccept, multiple: maxCount > 1, onChange: handleFileChange, labelClassName: "h-24 w-24 cursor-pointer rounded-lg border-0 p-0 focus-within:ring-2 focus-within:ring-theme/25", selectLabel: _jsxs("div", { className: "flex h-full w-full flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-border/60 bg-card text-muted-foreground transition-colors duration-200 hover:border-border/80 hover:bg-muted/50 hover:text-foreground active:bg-muted/70", children: [_jsx(Camera, { size: 24 }), _jsx("span", { className: "text-xs", children: t('components.upload.upload') })] }) }))] }));
75
75
  }
@@ -2,5 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button } from '../Button';
3
3
  import { Input } from '../Input';
4
4
  export function FileUpload({ inputRef, accept = '*', multiple, onChange, selectLabel, labelClassName, showClear, onClear, clearLabel, }) {
5
- return (_jsxs("div", { className: "tc-ui-file-upload flex items-center gap-3", children: [_jsxs("label", { className: labelClassName ?? 'flex items-center gap-2 px-4 py-2 border rounded-lg cursor-pointer bg-background', children: [_jsx(Input, { ref: inputRef, type: "file", accept: accept, multiple: multiple, className: "hidden", onChange: (_, e) => onChange?.(e) }), selectLabel] }), showClear && onClear && (_jsx(Button, { size: "sm", variant: "link", onClick: onClear, children: clearLabel }))] }));
5
+ return (_jsxs("div", { className: "tc-ui-file-upload flex items-center gap-3", children: [_jsxs("label", { className: labelClassName ??
6
+ 'flex h-10 items-center gap-2 rounded-lg border border-border/60 bg-card px-4 py-2 text-sm text-card-foreground cursor-pointer transition-colors duration-200 hover:border-border/80 hover:bg-muted/50 active:bg-muted/70 focus-within:border-theme focus-within:ring-2 focus-within:ring-theme/25', children: [_jsx(Input, { ref: inputRef, type: "file", accept: accept, multiple: multiple, className: "hidden", onChange: (_, e) => onChange?.(e) }), selectLabel] }), showClear && onClear && (_jsx(Button, { size: "sm", variant: "link", onClick: onClear, children: clearLabel }))] }));
6
7
  }
@@ -76,14 +76,14 @@
76
76
 
77
77
  /* CSS 变量定义 */
78
78
  :root {
79
- --background: 0 0% 100%;
79
+ --background: 0 0% 99%;
80
80
  --foreground: 240 10% 3.9%;
81
- --card: 0 0% 100%;
81
+ --card: 0 0% 99%;
82
82
  --card-foreground: 240 10% 3.9%;
83
- --popover: 0 0% 100%;
83
+ --popover: 0 0% 99%;
84
84
  --popover-foreground: 240 10% 3.9%;
85
85
  --primary: 240 5.9% 10%;
86
- --primary-foreground: 0 0% 98%;
86
+ --primary-foreground: 0 0% 97%;
87
87
  --secondary: 240 4.8% 95.9%;
88
88
  --secondary-foreground: 240 5.9% 10%;
89
89
  --muted: 240 4.8% 95.9%;
@@ -101,10 +101,13 @@
101
101
  --chart-3: 197 37% 24%;
102
102
  --chart-4: 43 74% 66%;
103
103
  --chart-5: 27 87% 67%;
104
+ --shadow-panel: 0 18px 60px hsl(240 10% 12% / 0.14);
105
+ --shadow-floating: 0 18px 48px hsl(240 10% 12% / 0.18), 0 1px 0 hsl(0 0% 100% / 0.82) inset;
106
+ --shadow-accent: 0 10px 28px hsl(240 5.9% 10% / 0.16);
104
107
 
105
108
  /* 主题色 - 默认亮色中性主题 */
106
- --theme-primary: 0 0% 0%;
107
- --theme-primary-foreground: 0 0% 100%;
109
+ --theme-primary: 240 5.9% 10%;
110
+ --theme-primary-foreground: 0 0% 97%;
108
111
  --theme-bg: 210 20% 98%;
109
112
  --theme-text: 222 47% 11%;
110
113
 
@@ -114,13 +117,13 @@
114
117
  --table-head-text: 217 19% 27%;
115
118
  --table-cell-text: 180 4% 16%;
116
119
  --table-action-bg: 220 13% 91%;
117
- --table-action-text: 0 0% 0%;
120
+ --table-action-text: 240 5.9% 10%;
118
121
 
119
122
  /* 面包屑颜色 - 默认亮色中性主题 */
120
123
  --breadcrumb-text: 220 10% 46%;
121
124
  --breadcrumb-border: 0 0% 88%;
122
- --breadcrumb-active-text: 0 0% 100%;
123
- --breadcrumb-active-bg: 0 0% 0%;
125
+ --breadcrumb-active-text: 0 0% 97%;
126
+ --breadcrumb-active-bg: 240 5.9% 10%;
124
127
  }
125
128
 
126
129
  /* 绿色主题 */
@@ -169,6 +172,9 @@
169
172
  --chart-3: 30 80% 55%;
170
173
  --chart-4: 280 65% 60%;
171
174
  --chart-5: 340 75% 55%;
175
+ --shadow-panel: 0 18px 60px hsl(240 10% 2% / 0.38);
176
+ --shadow-floating: 0 20px 64px hsl(240 10% 2% / 0.5), 0 1px 0 hsl(0 0% 100% / 0.05) inset;
177
+ --shadow-accent: 0 10px 28px hsl(0 0% 95% / 0.16);
172
178
 
173
179
  /* 主题色调整 */
174
180
  --theme-primary: 0 0% 92%;
@@ -191,6 +197,57 @@
191
197
  --breadcrumb-active-bg: 0 0% 92%;
192
198
  }
193
199
 
200
+ .dark[data-ui-style='shuttle'],
201
+ .dark [data-ui-style='shuttle'] {
202
+ --background: 240 8% 3%;
203
+ --foreground: 0 0% 93%;
204
+
205
+ --card: 150 5% 7%;
206
+ --card-foreground: 0 0% 92%;
207
+
208
+ --popover: 240 4% 14%;
209
+ --popover-foreground: 0 0% 94%;
210
+
211
+ --primary: 35 100% 55%;
212
+ --primary-foreground: 240 8% 6%;
213
+ --secondary: 240 4% 18%;
214
+ --secondary-foreground: 0 0% 94%;
215
+ --muted: 240 4% 18%;
216
+ --muted-foreground: 240 3% 58%;
217
+ --accent: 247 88% 70%;
218
+ --accent-foreground: 240 8% 6%;
219
+ --destructive: 0 72% 58%;
220
+ --destructive-foreground: 0 0% 94%;
221
+ --border: 240 4% 18%;
222
+ --input: 240 4% 16%;
223
+ --ring: 35 100% 58%;
224
+ --chart-1: 35 100% 55%;
225
+ --chart-2: 247 88% 70%;
226
+ --chart-3: 207 88% 72%;
227
+ --chart-4: 145 58% 52%;
228
+ --chart-5: 42 96% 58%;
229
+ --shadow-panel: 0 18px 60px hsl(240 10% 2% / 0.45);
230
+ --shadow-floating: 0 22px 70px hsl(240 10% 2% / 0.58), 0 1px 0 hsl(0 0% 100% / 0.06) inset;
231
+ --shadow-accent: 0 10px 28px hsl(35 100% 55% / 0.25);
232
+
233
+ --theme-primary: 35 100% 55%;
234
+ --theme-primary-foreground: 240 8% 6%;
235
+ --theme-bg: 240 5% 12%;
236
+ --theme-text: 0 0% 94%;
237
+
238
+ --table-header-bg: 240 5% 12%;
239
+ --table-header-text: 240 3% 58%;
240
+ --table-head-text: 0 0% 82%;
241
+ --table-cell-text: 0 0% 90%;
242
+ --table-action-bg: 240 4% 18%;
243
+ --table-action-text: 0 0% 94%;
244
+
245
+ --breadcrumb-text: 240 3% 62%;
246
+ --breadcrumb-border: 240 4% 18%;
247
+ --breadcrumb-active-text: 240 8% 6%;
248
+ --breadcrumb-active-bg: 35 100% 55%;
249
+ }
250
+
194
251
  .dark[data-theme='green'],
195
252
  .dark [data-theme='green'] {
196
253
  --theme-primary: 142 68% 46%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_tc/template-core",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "A full-stack TypeScript admin framework powered by Koa, React, and Vite - monorepo root",
5
5
  "types": "./types/index.d.ts",
6
6
  "exports": {