@zmzai/theme 0.2.1 → 0.2.3
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.
- package/package.json +1 -1
- package/src/components/button/Button.tsx +3 -2
- package/src/components/icon-button/IconButton.tsx +46 -0
- package/src/components/icon-button/index.ts +2 -0
- package/src/components/index.ts +2 -0
- package/src/components/tabs/Tabs.tsx +46 -0
- package/src/components/tabs/index.ts +2 -0
package/package.json
CHANGED
|
@@ -29,8 +29,9 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
29
29
|
return (
|
|
30
30
|
<motion.button
|
|
31
31
|
ref={ref}
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// ghost 是列表项/行选择场景,hover 缩放会跳;仅 primary/secondary 保留微交互
|
|
33
|
+
whileHover={disabled || loading || variant === "ghost" ? undefined : { scale: 1.03 }}
|
|
34
|
+
whileTap={disabled || loading || variant === "ghost" ? undefined : { scale: 0.98 }}
|
|
34
35
|
transition={{ duration: 0.15, ease: [0.16, 1, 0.3, 1] }}
|
|
35
36
|
className={cn(buttonVariants({ variant, size }), className)}
|
|
36
37
|
disabled={disabled || loading}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef, type ButtonHTMLAttributes } from "react";
|
|
4
|
+
import { cn } from "../../utils/cn";
|
|
5
|
+
|
|
6
|
+
export interface IconButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "ref"> {
|
|
7
|
+
/** 尺寸:sm=h-6 md=h-7 lg=h-8(密集工具条场景,不做 hover 缩放) */
|
|
8
|
+
size?: "sm" | "md" | "lg";
|
|
9
|
+
/** 视觉:ghost=透明 hover 背景;quiet=无 hover 背景只有颜色变化 */
|
|
10
|
+
tone?: "ghost" | "quiet";
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* IconButton — 密集场景的纯图标按钮。
|
|
16
|
+
*
|
|
17
|
+
* 与 Button 的区别:不做 framer-motion hover 缩放(工具条里会跳),
|
|
18
|
+
* 尺寸更小(6/7/8),tone=quiet 时连背景都没有(纯颜色 hover)。
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <IconButton label="重命名"><Icon name="edit" size={12} /></IconButton>
|
|
22
|
+
*/
|
|
23
|
+
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
24
|
+
({ className, size = "md", tone = "ghost", label, type = "button", ...props }, ref) => {
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
ref={ref}
|
|
28
|
+
type={type}
|
|
29
|
+
aria-label={label}
|
|
30
|
+
title={label}
|
|
31
|
+
className={cn(
|
|
32
|
+
"inline-flex shrink-0 cursor-pointer items-center justify-center rounded-sm text-ink-3 transition-colors disabled:pointer-events-none disabled:opacity-40",
|
|
33
|
+
size === "sm" && "h-6 w-6",
|
|
34
|
+
size === "md" && "h-7 w-7",
|
|
35
|
+
size === "lg" && "h-8 w-8",
|
|
36
|
+
tone === "ghost" && "hover:bg-surface-2 hover:text-ink",
|
|
37
|
+
tone === "quiet" && "hover:text-ink",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
IconButton.displayName = "IconButton";
|
package/src/components/index.ts
CHANGED
|
@@ -13,6 +13,8 @@ export * from "./tooltip";
|
|
|
13
13
|
export * from "./dialog";
|
|
14
14
|
export * from "./dropdown-menu";
|
|
15
15
|
export * from "./select";
|
|
16
|
+
export * from "./icon-button";
|
|
17
|
+
export * from "./tabs";
|
|
16
18
|
|
|
17
19
|
// === Interactive Enhancement ===
|
|
18
20
|
export * from "./magnetic-button";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn";
|
|
4
|
+
|
|
5
|
+
export type TabItem = { value: string; label: React.ReactNode; count?: number };
|
|
6
|
+
|
|
7
|
+
export interface TabsProps {
|
|
8
|
+
items: TabItem[];
|
|
9
|
+
value: string;
|
|
10
|
+
onValueChange: (value: string) => void;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Tabs — 下划线激活态的水平标签栏(锐角风)。
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* <Tabs items={[{value:"a",label:"产物",count:2},{value:"b",label:"改动"}]} value={tab} onValueChange={setTab} />
|
|
19
|
+
*/
|
|
20
|
+
export function Tabs({ items, value, onValueChange, className }: TabsProps) {
|
|
21
|
+
return (
|
|
22
|
+
<div className={cn("flex gap-0 border-b border-line", className)} role="tablist">
|
|
23
|
+
{items.map((item) => {
|
|
24
|
+
const active = item.value === value;
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
key={item.value}
|
|
28
|
+
type="button"
|
|
29
|
+
role="tab"
|
|
30
|
+
aria-selected={active}
|
|
31
|
+
className={cn(
|
|
32
|
+
"cursor-pointer border-b-2 px-3 py-2.5 text-xs font-medium transition-colors",
|
|
33
|
+
active ? "border-ink text-ink" : "border-transparent text-ink-3 hover:text-ink-2",
|
|
34
|
+
)}
|
|
35
|
+
onClick={() => onValueChange(item.value)}
|
|
36
|
+
>
|
|
37
|
+
{item.label}
|
|
38
|
+
{typeof item.count === "number" && (
|
|
39
|
+
<span className="ml-1 rounded-full bg-surface-2 px-1.5 text-[10px]">{item.count}</span>
|
|
40
|
+
)}
|
|
41
|
+
</button>
|
|
42
|
+
);
|
|
43
|
+
})}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|