diff --git a/src/components/ui/icon-button/index.figma.tsx b/src/components/ui/icon-button/index.figma.tsx
index c7cca12c..f4878bea 100644
--- a/src/components/ui/icon-button/index.figma.tsx
+++ b/src/components/ui/icon-button/index.figma.tsx
@@ -38,6 +38,7 @@ figma.connect(
theme: figma.enum("theme", {
primary: "primary",
negative: "negative",
+ neutral: "neutral",
}),
// No matching props could be found for these Figma properties:
icon: figma.instance("icon").getProps<{
diff --git a/src/components/ui/icon-button/index.stories.tsx b/src/components/ui/icon-button/index.stories.tsx
index 7c2faf73..17fb4c18 100644
--- a/src/components/ui/icon-button/index.stories.tsx
+++ b/src/components/ui/icon-button/index.stories.tsx
@@ -13,7 +13,7 @@ const meta = {
argTypes: {
variant: {
control: "select",
- options: ["solid", "outline-solid", "ghost"],
+ options: ["solid", "outline", "ghost"],
},
size: {
control: "select",
@@ -21,7 +21,7 @@ const meta = {
},
theme: {
control: "select",
- options: ["primary", "secondary", "negative"],
+ options: ["primary", "neutral", "negative"],
},
isLoading: {
control: "boolean",
@@ -59,53 +59,59 @@ export const Disabled: Story = {
},
};
-export const Variant = () => {
- return (
-
-
- solid
-
-
- outline
-
-
- ghost
-
-
- );
+export const Variant: Story = {
+ render: args => {
+ return (
+
+
+ solid
+
+
+ outline
+
+
+ ghost
+
+
+ );
+ },
};
-export const Size = () => {
- return (
-
-
- extra small
-
-
- small
-
-
- medium
-
-
- large
-
-
- );
+export const Size: Story = {
+ render: args => {
+ return (
+
+
+ extra small
+
+
+ small
+
+
+ medium
+
+
+ large
+
+
+ );
+ },
};
-export const Theme = () => {
- return (
-
-
- primary
-
-
- secondary
-
-
- negative
-
-
- );
+export const Theme: Story = {
+ render: args => {
+ return (
+
+
+ primary
+
+
+ neutral
+
+
+ negative
+
+
+ );
+ },
};
diff --git a/src/components/ui/icon-button/index.test.tsx b/src/components/ui/icon-button/index.test.tsx
new file mode 100644
index 00000000..46fe62ab
--- /dev/null
+++ b/src/components/ui/icon-button/index.test.tsx
@@ -0,0 +1,479 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import React from "react";
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import {
+ TestContainer,
+ EventHelpers,
+ A11yHelpers,
+ StyleHelpers,
+} from "@/test/helpers";
+import { IconButton } from "./index";
+
+describe("IconButton", () => {
+ let testContainer: TestContainer;
+
+ beforeEach(() => {
+ testContainer = new TestContainer();
+ testContainer.setup();
+ });
+
+ afterEach(() => {
+ testContainer.cleanup();
+ });
+
+ describe("Basic Rendering", () => {
+ it("renders with default props", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(button).toBeDefined();
+ expect(button.tagName).toBe("BUTTON");
+ expect(button.type).toBe("button");
+ });
+
+ it("renders the correct icon", () => {
+ testContainer.render();
+ const container = testContainer.getContainer();
+ const iconSpan = container.querySelector('span[aria-hidden="true"]');
+
+ expect(iconSpan).toBeDefined();
+ expect(iconSpan?.textContent).toBe("edit");
+ });
+
+ it("applies default variant, size, and theme classes", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ // Default: variant="solid", size="md", theme="primary"
+ expect(StyleHelpers.hasClass(button, "w-10")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "h-10")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "bg-primary-500")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "text-white")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "border-primary-600")).toBe(true);
+ });
+
+ it("forwards custom className", () => {
+ const customClass = "my-custom-class";
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, customClass)).toBe(true);
+ });
+
+ it("forwards arbitrary props", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryByTestId("custom-button");
+
+ expect(button).toBeDefined();
+ });
+ });
+
+ describe("Variant Styling", () => {
+ describe("solid variant", () => {
+ it("applies solid primary classes", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "bg-primary-500")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "text-white")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "border-primary-600")).toBe(true);
+ });
+
+ it("applies solid neutral classes", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "bg-neutral-500")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "text-white")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "border-neutral-600")).toBe(true);
+ });
+
+ it("applies solid negative classes", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "bg-negative-500")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "text-white")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "border-negative-600")).toBe(true);
+ });
+ });
+
+ describe("outline variant", () => {
+ it("applies outline primary classes", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "bg-white")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "text-primary-500")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "border-primary-300")).toBe(true);
+ });
+
+ it("applies outline neutral classes", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "bg-white")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "text-neutral-700")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "border-neutral-300")).toBe(true);
+ });
+
+ it("applies outline negative classes", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "bg-white")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "text-negative-500")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "border-negative-300")).toBe(true);
+ });
+ });
+
+ describe("ghost variant", () => {
+ it("applies ghost primary classes", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "text-primary-500")).toBe(true);
+ // Ghost variant doesn't have background or border by default
+ expect(StyleHelpers.hasClass(button, "bg-primary-500")).toBe(false);
+ });
+
+ it("applies ghost neutral classes", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "text-neutral-700")).toBe(true);
+ });
+
+ it("applies ghost negative classes", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "text-negative-500")).toBe(true);
+ });
+ });
+ });
+
+ describe("Size Variants", () => {
+ it("applies extra small size classes", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "w-6")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "h-6")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "p-1")).toBe(true);
+ });
+
+ it("applies small size classes", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "w-8")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "h-8")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "p-1.5")).toBe(true);
+ });
+
+ it("applies medium size classes (default)", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "w-10")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "h-10")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "p-2")).toBe(true);
+ });
+
+ it("applies large size classes", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "w-12")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "h-12")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "p-2")).toBe(true);
+ });
+ });
+
+ describe("Loading State", () => {
+ it("shows spinner when loading", () => {
+ testContainer.render();
+ const container = testContainer.getContainer();
+
+ // Spinner should be present
+ const spinner = container.querySelector(
+ '[data-testid="spinner"], .animate-spin'
+ );
+ expect(spinner).toBeDefined();
+
+ // Icon should not be present
+ const iconSpan = container.querySelector('span[aria-hidden="true"]');
+ expect(iconSpan?.textContent).not.toBe("plus");
+ });
+
+ it("applies loading cursor style", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "cursor-not-allowed")).toBe(true);
+ });
+
+ it("is disabled when loading", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(button.disabled).toBe(true);
+ });
+
+ it("does not trigger click events when loading", () => {
+ const handleClick = vi.fn();
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ EventHelpers.click(button);
+
+ expect(handleClick).not.toHaveBeenCalled();
+ });
+ });
+
+ describe("Disabled State", () => {
+ it("is disabled with isDisabled prop", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(button.disabled).toBe(true);
+ });
+
+ it("is disabled with disabled prop", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(button.disabled).toBe(true);
+ });
+
+ it("applies disabled cursor style", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "cursor-not-allowed")).toBe(true);
+ });
+
+ it("applies disabled styling for solid variant", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "disabled:bg-primary-200")).toBe(
+ true
+ );
+ expect(StyleHelpers.hasClass(button, "disabled:text-white")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "disabled:border-none")).toBe(true);
+ });
+
+ it("applies disabled styling for outline variant", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "disabled:bg-white")).toBe(true);
+ expect(StyleHelpers.hasClass(button, "disabled:text-primary-200")).toBe(
+ true
+ );
+ expect(StyleHelpers.hasClass(button, "disabled:border-primary-100")).toBe(
+ true
+ );
+ });
+
+ it("applies disabled styling for ghost variant", () => {
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ expect(StyleHelpers.hasClass(button, "disabled:text-primary-200")).toBe(
+ true
+ );
+ });
+
+ it("does not trigger click events when disabled", () => {
+ const handleClick = vi.fn();
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ EventHelpers.click(button);
+
+ expect(handleClick).not.toHaveBeenCalled();
+ });
+ });
+
+ describe("User Interaction", () => {
+ it("handles click events properly", () => {
+ const handleClick = vi.fn();
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ EventHelpers.click(button);
+
+ expect(handleClick).toHaveBeenCalledTimes(1);
+ });
+
+ it("handles keyboard events", () => {
+ const handleKeyDown = vi.fn();
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ EventHelpers.keyDown(button, "Enter");
+
+ expect(handleKeyDown).toHaveBeenCalledTimes(1);
+ });
+
+ it("handles focus and blur events", () => {
+ const handleFocus = vi.fn();
+ const handleBlur = vi.fn();
+ testContainer.render(
+
+ );
+ const button = testContainer.queryButton();
+
+ EventHelpers.focus(button);
+ expect(handleFocus).toHaveBeenCalledTimes(1);
+
+ EventHelpers.blur(button);
+ expect(handleBlur).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe("AsChild Functionality", () => {
+ it("renders as a Slot when asChild is true", () => {
+ testContainer.render(
+
+ Custom element
+
+ );
+ const container = testContainer.getContainer();
+
+ // When asChild is true, Slot merges props with the child element
+ // The div should still exist but with the button's props merged
+ const customElement = container.querySelector(
+ '[data-testid="custom-element"]'
+ );
+ expect(customElement).toBeDefined();
+
+ // Check if the element exists and has some content
+ if (customElement) {
+ expect(customElement.textContent).toContain("Custom element");
+ }
+ });
+ });
+
+ describe("Accessibility", () => {
+ it("has proper button semantics", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(button.tagName).toBe("BUTTON");
+ expect(button.type).toBe("button");
+ });
+
+ it("supports aria-label", () => {
+ const ariaLabel = "Add item";
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(A11yHelpers.hasAriaLabel(button, ariaLabel)).toBe(true);
+ });
+
+ it("has proper focus styles", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(
+ StyleHelpers.hasClass(button, "focus-visible:outline-hidden")
+ ).toBe(true);
+ expect(StyleHelpers.hasClass(button, "focus-visible:ring-2")).toBe(true);
+ });
+
+ it("properly hides icon from screen readers", () => {
+ testContainer.render();
+ const container = testContainer.getContainer();
+ const iconSpan = container.querySelector('span[aria-hidden="true"]');
+
+ expect(iconSpan?.getAttribute("aria-hidden")).toBe("true");
+ });
+ });
+
+ describe("Edge Cases", () => {
+ it("handles empty icon gracefully", () => {
+ testContainer.render();
+ const container = testContainer.getContainer();
+ const iconSpan = container.querySelector('span[aria-hidden="true"]');
+
+ expect(iconSpan?.textContent).toBe("");
+ });
+
+ it("handles both isDisabled and isLoading being true", () => {
+ testContainer.render();
+ const button = testContainer.queryButton();
+
+ expect(button.disabled).toBe(true);
+ expect(StyleHelpers.hasClass(button, "cursor-not-allowed")).toBe(true);
+ });
+
+ it("prioritizes isLoading over normal icon display", () => {
+ testContainer.render();
+ const container = testContainer.getContainer();
+
+ // Should show spinner instead of icon
+ const spinner = container.querySelector(
+ '[data-testid="spinner"], .animate-spin'
+ );
+ expect(spinner).toBeDefined();
+
+ // Icon should not be the original icon
+ const iconSpan = container.querySelector('span[aria-hidden="true"]');
+ expect(iconSpan?.textContent).not.toBe("plus");
+ });
+
+ it("maintains display name for debugging", () => {
+ expect(IconButton.displayName).toBe("IconButton");
+ });
+ });
+
+ describe("Ref Forwarding", () => {
+ it("forwards ref to button element", () => {
+ const ref = { current: null as HTMLButtonElement | null };
+
+ testContainer.render();
+
+ expect(ref.current).toBeInstanceOf(HTMLButtonElement);
+ expect(ref.current?.tagName).toBe("BUTTON");
+ });
+ });
+});
diff --git a/src/components/ui/icon-button/index.tsx b/src/components/ui/icon-button/index.tsx
index f19d8334..333f34bc 100644
--- a/src/components/ui/icon-button/index.tsx
+++ b/src/components/ui/icon-button/index.tsx
@@ -7,28 +7,32 @@ import { Icon } from "@/components/ui/icon";
import { Spinner } from "@/components/ui/spinner";
const iconButtonVariants = cva(
- "inline-flex items-center justify-center whitespace-nowrap rounded-md ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 relative",
+ [
+ "inline-flex items-center justify-center whitespace-nowrap rounded-action",
+ "ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[var(--color-ring-normal)] focus-visible:ring-offset-2",
+ "relative cursor-pointer",
+ ].join(" "),
{
variants: {
variant: {
- solid: "border shadow-sm",
- outline: "border shadow-sm",
+ solid: "border",
+ outline: "border",
ghost: "",
},
size: {
- xs: "w-6 h-6 p-0.5 character-1-bold-pro",
- sm: "w-8 h-8 p-1 character-2-bold-pro",
- md: "w-10 h-10 p-2 character-3-bold-pro",
- lg: "w-12 h-12 p-2.5 character-4-bold-pro",
+ xs: "w-6 h-6 p-1",
+ sm: "w-8 h-8 p-1.5",
+ md: "w-10 h-10 p-2",
+ lg: "w-12 h-12 p-2",
},
theme: {
primary: "",
- secondary: "",
+ neutral: "",
negative: "",
},
isLoading: {
true: "cursor-not-allowed",
- false: "gap-2",
+ false: "",
},
isDisabled: {
true: "cursor-not-allowed",
@@ -42,8 +46,11 @@ const iconButtonVariants = cva(
theme: "primary",
isLoading: false,
isDisabled: false,
- className:
- "bg-primary-500 text-white border-primary-600 hover:bg-primary-600 hover:border-primary-700 active:bg-primary-700 active:border-primary-800 active:shadow-sm",
+ className: [
+ "bg-primary-500 text-white border-primary-600",
+ "hover:bg-primary-600 hover:border-primary-700",
+ "active:bg-primary-700 active:border-primary-800",
+ ].join(" "),
},
{
variant: "solid",
@@ -53,21 +60,24 @@ const iconButtonVariants = cva(
className: "bg-primary-500 text-white border-primary-600",
},
- // Solid Secondary バリアント
+ // Solid Neutral バリアント
{
variant: "solid",
- theme: "secondary",
+ theme: "neutral",
isLoading: false,
isDisabled: false,
- className:
- "bg-secondary-500 text-white border-secondary-600 hover:bg-secondary-600 hover:border-secondary-700 active:bg-secondary-700 active:border-secondary-800 active:shadow-sm",
+ className: [
+ "bg-neutral-500 text-white border-neutral-600",
+ "hover:bg-neutral-600 hover:border-neutral-700",
+ "active:bg-neutral-700 active:border-neutral-800",
+ ].join(" "),
},
{
variant: "solid",
- theme: "secondary",
+ theme: "neutral",
isLoading: true,
isDisabled: false,
- className: "bg-secondary-500 text-white border-secondary-600",
+ className: "bg-neutral-500 text-white border-neutral-600",
},
// Solid Negative バリアント
@@ -76,8 +86,11 @@ const iconButtonVariants = cva(
theme: "negative",
isLoading: false,
isDisabled: false,
- className:
- "bg-negative-500 text-white border-negative-600 hover:bg-negative-600 hover:border-negative-700 active:bg-negative-700 active:border-negative-800 active:shadow-sm",
+ className: [
+ "bg-negative-500 text-white border-negative-600",
+ "hover:bg-negative-600 hover:border-negative-700",
+ "active:bg-negative-700 active:border-negative-800",
+ ].join(" "),
},
{
variant: "solid",
@@ -92,31 +105,37 @@ const iconButtonVariants = cva(
variant: "outline",
theme: "primary",
isLoading: false,
- className:
- "bg-white text-primary-500 border-primary-500 shadow-sm hover:bg-primary-50 active:bg-primary-100",
+ className: [
+ "bg-white text-primary-500 border-primary-300",
+ "hover:bg-primary-50",
+ "active:bg-primary-100 active:border-primary-400 active:text-primary-600",
+ ].join(" "),
},
{
variant: "outline",
theme: "primary",
isLoading: true,
isDisabled: false,
- className: "bg-white text-primary-500 border-primary-500 shadow-sm",
+ className: "bg-white text-primary-500 border-primary-300",
},
- // Outline Secondary バリアント
+ // Outline Neutral バリアント
{
variant: "outline",
- theme: "secondary",
+ theme: "neutral",
isLoading: false,
- className:
- "bg-white text-secondary-700 border-secondary-500 shadow-sm hover:bg-secondary-50 active:bg-secondary-100",
+ className: [
+ "bg-white text-neutral-700 border-neutral-300",
+ "hover:bg-neutral-50",
+ "active:bg-neutral-100",
+ ].join(" "),
},
{
variant: "outline",
- theme: "secondary",
+ theme: "neutral",
isLoading: true,
isDisabled: false,
- className: "bg-white text-secondary-700 border-secondary-500 shadow-sm",
+ className: "bg-white text-neutral-700 border-neutral-300",
},
// Outline Negative バリアント
@@ -125,15 +144,18 @@ const iconButtonVariants = cva(
theme: "negative",
isLoading: false,
isDisabled: false,
- className:
- "bg-white text-negative-500 border-negative-500 shadow-sm hover:bg-negative-50 active:bg-negative-100",
+ className: [
+ "bg-white text-negative-500 border-negative-300",
+ "hover:bg-negative-50",
+ "active:bg-negative-100 active:border-negative-400 active:text-negative-600",
+ ].join(" "),
},
{
variant: "outline",
theme: "negative",
isLoading: true,
isDisabled: false,
- className: "bg-white text-negative-500 border-negative-500 shadow-sm",
+ className: "bg-white text-negative-500 border-negative-300",
},
// Ghost Primary バリアント
@@ -142,7 +164,8 @@ const iconButtonVariants = cva(
theme: "primary",
isLoading: false,
isDisabled: false,
- className: "text-primary-500 hover:bg-primary-50 active:bg-primary-100",
+ className:
+ "text-primary-500 hover:bg-primary-50 active:bg-primary-100 active:text-primary-600",
},
{
variant: "ghost",
@@ -152,21 +175,20 @@ const iconButtonVariants = cva(
className: "text-primary-500",
},
- // Ghost Secondary バリアント
+ // Ghost neutral バリアント
{
variant: "ghost",
- theme: "secondary",
+ theme: "neutral",
isLoading: false,
isDisabled: false,
- className:
- "text-secondary-700 hover:bg-secondary-50 active:bg-secondary-100",
+ className: "text-neutral-700 hover:bg-neutral-50 active:bg-neutral-100",
},
{
variant: "ghost",
- theme: "secondary",
+ theme: "neutral",
isLoading: true,
isDisabled: false,
- className: "text-secondary-700",
+ className: "text-neutral-700",
},
// Ghost Negative バリアント
@@ -176,7 +198,7 @@ const iconButtonVariants = cva(
isLoading: false,
isDisabled: false,
className:
- "text-negative-500 hover:bg-negative-100 active:bg-negative-100",
+ "text-negative-500 hover:bg-negative-50 active:bg-negative-100 active:text-negative-600",
},
{
variant: "ghost",
@@ -196,10 +218,10 @@ const iconButtonVariants = cva(
},
{
variant: "solid",
- theme: "secondary",
+ theme: "neutral",
isDisabled: true,
className:
- "disabled:bg-secondary-200 disabled:text-white disabled:border-none",
+ "disabled:bg-neutral-200 disabled:text-white disabled:border-none",
},
{
variant: "solid",
@@ -217,10 +239,10 @@ const iconButtonVariants = cva(
},
{
variant: "outline",
- theme: "secondary",
+ theme: "neutral",
isDisabled: true,
className:
- "disabled:bg-white disabled:text-secondary-200 disabled:border-secondary-100",
+ "disabled:bg-white disabled:text-neutral-200 disabled:border-neutral-100",
},
{
variant: "outline",
@@ -237,9 +259,9 @@ const iconButtonVariants = cva(
},
{
variant: "ghost",
- theme: "secondary",
+ theme: "neutral",
isDisabled: true,
- className: "disabled:text-secondary-200",
+ className: "disabled:text-neutral-200",
},
{
variant: "ghost",
@@ -258,9 +280,24 @@ const iconButtonVariants = cva(
}
);
+type IconButtonVariants = VariantProps;
export interface IconButtonProps
- extends React.ButtonHTMLAttributes,
- VariantProps {
+ extends React.ButtonHTMLAttributes {
+ /**
+ * アイコンボタンのバリエーション
+ * en: Variation of the icon button
+ */
+ variant?: IconButtonVariants["variant"];
+ /**
+ * アイコンボタンのサイズ
+ * en: Size of the icon button
+ */
+ size?: IconButtonVariants["size"];
+ /**
+ * アイコンボタンのテーマ
+ * en: Theme of the icon button
+ */
+ theme?: IconButtonVariants["theme"];
/**
* ボタンを別コンポーネントの子としてレンダリングするか
* en: Whether to render the button as a child component
@@ -315,7 +352,7 @@ const IconButton = React.forwardRef(
ref
) => {
// disabled状態の管理(isDisabled、disabled、またはisLoadingがtrueの場合)
- const isIconButtonDisabled = isLoading || isDisabled;
+ const isIconButtonDisabled = isLoading || isDisabled || disabled;
const Comp = asChild ? SlotPrimitive.Slot : "button";
@@ -335,6 +372,7 @@ const IconButton = React.forwardRef(
return (
(
>
{isLoading ? (
<>
-
+
>
) : (
diff --git a/src/components/ui/icon-button/item.json b/src/components/ui/icon-button/item.json
index 65a78de6..ada381a1 100644
--- a/src/components/ui/icon-button/item.json
+++ b/src/components/ui/icon-button/item.json
@@ -3,7 +3,7 @@
"name": "icon-button",
"type": "registry:component",
"title": "IconButton",
- "description": "アイコンボタンはアイコンのみを表示するコンパクトなボタンコンポーネントです。",
+ "description": "アイコンボタンはフォームの送信、ダイアログの展開、アクションのキャンセル、削除の実行など、アクションやイベントのトリガーとして使用するコンポーネントです。",
"files": [
{
"path": "src/components/ui/icon-button/index.tsx",
diff --git a/src/components/ui/inline-message/index.tsx b/src/components/ui/inline-message/index.tsx
index 6488270f..39f6a64e 100644
--- a/src/components/ui/inline-message/index.tsx
+++ b/src/components/ui/inline-message/index.tsx
@@ -132,7 +132,7 @@ const InlineMessage = React.forwardRef(
icon="close"
size="sm"
variant="ghost"
- theme="secondary"
+ theme="neutral"
onClick={onClose}
aria-label="閉じる"
className="shrink-0"
diff --git a/src/components/ui/input/index.tsx b/src/components/ui/input/index.tsx
index 53ba84b9..cc2ab254 100644
--- a/src/components/ui/input/index.tsx
+++ b/src/components/ui/input/index.tsx
@@ -312,7 +312,7 @@ const Input = React.forwardRef(