sparkle-design 0.9.0

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 (125) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +5 -0
  3. package/README.en.md +275 -0
  4. package/README.md +328 -0
  5. package/dist/components/ui/badge/index.d.ts +67 -0
  6. package/dist/components/ui/badge/index.figma.d.ts +1 -0
  7. package/dist/components/ui/badge/index.figma.js +28 -0
  8. package/dist/components/ui/badge/index.js +129 -0
  9. package/dist/components/ui/breadcrumb/index.d.ts +70 -0
  10. package/dist/components/ui/breadcrumb/index.figma.d.ts +1 -0
  11. package/dist/components/ui/breadcrumb/index.figma.js +28 -0
  12. package/dist/components/ui/breadcrumb/index.js +96 -0
  13. package/dist/components/ui/button/index.d.ts +145 -0
  14. package/dist/components/ui/button/index.figma.d.ts +1 -0
  15. package/dist/components/ui/button/index.figma.js +59 -0
  16. package/dist/components/ui/button/index.js +375 -0
  17. package/dist/components/ui/card/index.d.ts +193 -0
  18. package/dist/components/ui/card/index.figma.d.ts +1 -0
  19. package/dist/components/ui/card/index.figma.js +28 -0
  20. package/dist/components/ui/card/index.js +219 -0
  21. package/dist/components/ui/checkbox/index.d.ts +62 -0
  22. package/dist/components/ui/checkbox/index.figma.d.ts +1 -0
  23. package/dist/components/ui/checkbox/index.figma.js +34 -0
  24. package/dist/components/ui/checkbox/index.js +192 -0
  25. package/dist/components/ui/dialog/index.d.ts +106 -0
  26. package/dist/components/ui/dialog/index.figma.d.ts +1 -0
  27. package/dist/components/ui/dialog/index.figma.js +21 -0
  28. package/dist/components/ui/dialog/index.js +157 -0
  29. package/dist/components/ui/divider/index.d.ts +44 -0
  30. package/dist/components/ui/divider/index.figma.d.ts +1 -0
  31. package/dist/components/ui/divider/index.figma.js +39 -0
  32. package/dist/components/ui/divider/index.js +81 -0
  33. package/dist/components/ui/form/index.d.ts +171 -0
  34. package/dist/components/ui/form/index.figma.d.ts +1 -0
  35. package/dist/components/ui/form/index.figma.js +129 -0
  36. package/dist/components/ui/form/index.js +214 -0
  37. package/dist/components/ui/form/label.d.ts +4 -0
  38. package/dist/components/ui/form/label.js +20 -0
  39. package/dist/components/ui/icon/index.d.ts +71 -0
  40. package/dist/components/ui/icon/index.figma.d.ts +1 -0
  41. package/dist/components/ui/icon/index.figma.js +1858 -0
  42. package/dist/components/ui/icon/index.js +57 -0
  43. package/dist/components/ui/icon-button/index.d.ts +77 -0
  44. package/dist/components/ui/icon-button/index.figma.d.ts +1 -0
  45. package/dist/components/ui/icon-button/index.figma.js +43 -0
  46. package/dist/components/ui/icon-button/index.js +341 -0
  47. package/dist/components/ui/inline-message/index.d.ts +83 -0
  48. package/dist/components/ui/inline-message/index.figma.d.ts +1 -0
  49. package/dist/components/ui/inline-message/index.figma.js +26 -0
  50. package/dist/components/ui/inline-message/index.js +154 -0
  51. package/dist/components/ui/input/index.d.ts +118 -0
  52. package/dist/components/ui/input/index.figma.d.ts +1 -0
  53. package/dist/components/ui/input/index.figma.js +36 -0
  54. package/dist/components/ui/input/index.js +256 -0
  55. package/dist/components/ui/input-password/index.d.ts +27 -0
  56. package/dist/components/ui/input-password/index.figma.d.ts +1 -0
  57. package/dist/components/ui/input-password/index.figma.js +36 -0
  58. package/dist/components/ui/input-password/index.js +35 -0
  59. package/dist/components/ui/link/index.d.ts +68 -0
  60. package/dist/components/ui/link/index.figma.d.ts +1 -0
  61. package/dist/components/ui/link/index.figma.js +22 -0
  62. package/dist/components/ui/link/index.js +92 -0
  63. package/dist/components/ui/modal/index.d.ts +83 -0
  64. package/dist/components/ui/modal/index.figma.d.ts +1 -0
  65. package/dist/components/ui/modal/index.figma.js +61 -0
  66. package/dist/components/ui/modal/index.js +143 -0
  67. package/dist/components/ui/overlay/index.d.ts +19 -0
  68. package/dist/components/ui/overlay/index.figma.d.ts +1 -0
  69. package/dist/components/ui/overlay/index.figma.js +14 -0
  70. package/dist/components/ui/overlay/index.js +39 -0
  71. package/dist/components/ui/radio/index.d.ts +94 -0
  72. package/dist/components/ui/radio/index.figma.d.ts +1 -0
  73. package/dist/components/ui/radio/index.figma.js +63 -0
  74. package/dist/components/ui/radio/index.js +204 -0
  75. package/dist/components/ui/select/index.d.ts +175 -0
  76. package/dist/components/ui/select/index.figma.d.ts +1 -0
  77. package/dist/components/ui/select/index.figma.js +30 -0
  78. package/dist/components/ui/select/index.js +281 -0
  79. package/dist/components/ui/skeleton/index.d.ts +21 -0
  80. package/dist/components/ui/skeleton/index.figma.d.ts +1 -0
  81. package/dist/components/ui/skeleton/index.figma.js +14 -0
  82. package/dist/components/ui/skeleton/index.js +32 -0
  83. package/dist/components/ui/slider/index.d.ts +75 -0
  84. package/dist/components/ui/slider/index.figma.d.ts +1 -0
  85. package/dist/components/ui/slider/index.figma.js +28 -0
  86. package/dist/components/ui/slider/index.js +113 -0
  87. package/dist/components/ui/slider/utils.d.ts +16 -0
  88. package/dist/components/ui/slider/utils.js +56 -0
  89. package/dist/components/ui/spinner/index.d.ts +42 -0
  90. package/dist/components/ui/spinner/index.figma.d.ts +1 -0
  91. package/dist/components/ui/spinner/index.figma.js +16 -0
  92. package/dist/components/ui/spinner/index.js +51 -0
  93. package/dist/components/ui/switch/index.d.ts +46 -0
  94. package/dist/components/ui/switch/index.figma.d.ts +1 -0
  95. package/dist/components/ui/switch/index.figma.js +27 -0
  96. package/dist/components/ui/switch/index.js +75 -0
  97. package/dist/components/ui/tabs/index.d.ts +53 -0
  98. package/dist/components/ui/tabs/index.figma.d.ts +1 -0
  99. package/dist/components/ui/tabs/index.figma.js +55 -0
  100. package/dist/components/ui/tabs/index.js +164 -0
  101. package/dist/components/ui/tag/index.d.ts +50 -0
  102. package/dist/components/ui/tag/index.figma.d.ts +1 -0
  103. package/dist/components/ui/tag/index.figma.js +32 -0
  104. package/dist/components/ui/tag/index.js +177 -0
  105. package/dist/components/ui/textarea/index.d.ts +49 -0
  106. package/dist/components/ui/textarea/index.figma.d.ts +1 -0
  107. package/dist/components/ui/textarea/index.figma.js +27 -0
  108. package/dist/components/ui/textarea/index.js +94 -0
  109. package/dist/components/ui/toast/index.d.ts +60 -0
  110. package/dist/components/ui/toast/index.figma.d.ts +1 -0
  111. package/dist/components/ui/toast/index.figma.js +26 -0
  112. package/dist/components/ui/toast/index.js +57 -0
  113. package/dist/components/ui/tooltip/index.d.ts +52 -0
  114. package/dist/components/ui/tooltip/index.figma.d.ts +1 -0
  115. package/dist/components/ui/tooltip/index.figma.js +23 -0
  116. package/dist/components/ui/tooltip/index.js +65 -0
  117. package/dist/index.d.ts +27 -0
  118. package/dist/index.js +27 -0
  119. package/dist/lib/utils.d.ts +2 -0
  120. package/dist/lib/utils.js +5 -0
  121. package/dist/test/helpers.d.ts +56 -0
  122. package/dist/test/helpers.js +182 -0
  123. package/dist/test/setup.d.ts +1 -0
  124. package/dist/test/setup.js +38 -0
  125. package/package.json +253 -0
@@ -0,0 +1,96 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ import { cn } from "../../../lib/utils.js";
14
+ import { Link } from "../../../components/ui/link/index.js";
15
+ /**
16
+ * **概要 / Overview**
17
+ *
18
+ * - パンくずはユーザーが現在のページ階層を理解し、親の階層へ戻るためのナビゲーションとして機能するコンポーネントです。
19
+ * - en: The Breadcrumb component helps users understand the current page hierarchy and navigate back to parent levels.
20
+ *
21
+ * **使用例 / Usage Example**
22
+ *
23
+ * ```tsx
24
+ * <Breadcrumb>
25
+ * <BreadcrumbList>
26
+ * <BreadcrumbItem>
27
+ * <BreadcrumbLink href="/">Home</BreadcrumbLink>
28
+ * </BreadcrumbItem>
29
+ * <BreadcrumbSeparator />
30
+ * <BreadcrumbItem>
31
+ * <BreadcrumbLink href="/">Link</BreadcrumbLink>
32
+ * </BreadcrumbItem>
33
+ * <BreadcrumbSeparator />
34
+ * <BreadcrumbItem>
35
+ * <BreadcrumbPage>Current Page</BreadcrumbPage>
36
+ * </BreadcrumbItem>
37
+ * </BreadcrumbList>
38
+ * </Breadcrumb>
39
+ * ```
40
+ *
41
+ * @param {BreadcrumbProps} props
42
+ */
43
+ export function Breadcrumb(_a) {
44
+ var { className } = _a, props = __rest(_a, ["className"]);
45
+ return (_jsx("nav", Object.assign({ "aria-label": "breadcrumb", "data-slot": "breadcrumb", className: cn("w-full", className) }, props)));
46
+ }
47
+ /**
48
+ * パンくずリスト(ol)
49
+ * en: Breadcrumb list (ol)
50
+ *
51
+ * @param {React.ComponentProps<"ol">} props
52
+ */
53
+ export function BreadcrumbList(_a) {
54
+ var { className } = _a, props = __rest(_a, ["className"]);
55
+ return (_jsx("ol", Object.assign({ "data-slot": "breadcrumb-list", className: cn("flex flex-wrap items-center break-words gap-1", className) }, props)));
56
+ }
57
+ /**
58
+ * パンくずアイテム(li)
59
+ * en: Breadcrumb item (li)
60
+ *
61
+ * @param {React.ComponentProps<"li">} props
62
+ */
63
+ export function BreadcrumbItem(_a) {
64
+ var { className } = _a, props = __rest(_a, ["className"]);
65
+ return (_jsx("li", Object.assign({ "data-slot": "breadcrumb-item", className: cn("inline-flex items-center p-0.5", className) }, props)));
66
+ }
67
+ /**
68
+ * パンくずリンク
69
+ * en: Breadcrumb link
70
+ *
71
+ * @param {React.ComponentProps<typeof Link>} props
72
+ */
73
+ export function BreadcrumbLink(_a) {
74
+ var { className } = _a, props = __rest(_a, ["className"]);
75
+ return (_jsx(Link, Object.assign({ "data-slot": "breadcrumb-link", className: cn("character-3-regular-pro", className) }, props)));
76
+ }
77
+ /**
78
+ * パンくずの区切り(/など)
79
+ * en: Breadcrumb separator (e.g. /)
80
+ *
81
+ * @param {React.ComponentProps<"span">} props
82
+ */
83
+ export function BreadcrumbSeparator(_a) {
84
+ var { className, children } = _a, props = __rest(_a, ["className", "children"]);
85
+ return (_jsx("span", Object.assign({ "aria-hidden": "true", "data-slot": "breadcrumb-separator", className: cn("mx-1 text-neutral-500 select-none", className) }, props, { children: children !== null && children !== void 0 ? children : "/" })));
86
+ }
87
+ /**
88
+ * 現在ページ表示用
89
+ * en: Current page label (not a link)
90
+ *
91
+ * @param {React.ComponentProps<"span">} props
92
+ */
93
+ export function BreadcrumbPage(_a) {
94
+ var { className } = _a, props = __rest(_a, ["className"]);
95
+ return (_jsx("span", Object.assign({ "data-slot": "breadcrumb-page", "aria-disabled": "true", "aria-current": "page", className: cn("character-3-regular-pro text-text-high cursor-default", className) }, props)));
96
+ }
@@ -0,0 +1,145 @@
1
+ import * as React from "react";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ declare const buttonVariants: (props?: ({
4
+ variant?: "solid" | "outline" | "ghost" | null | undefined;
5
+ size?: "sm" | "md" | "lg" | null | undefined;
6
+ theme?: "primary" | "neutral" | "negative" | null | undefined;
7
+ isLoading?: boolean | null | undefined;
8
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
9
+ type ButtonVariantProps = VariantProps<typeof buttonVariants>;
10
+ type NativeButtonProps = React.ComponentPropsWithoutRef<"button">;
11
+ export interface ButtonProps extends Omit<NativeButtonProps, "onClick" | "onKeyDown"> {
12
+ /**
13
+ * ボタンのサイズバリエーション
14
+ * en: Size variation of the button
15
+ */
16
+ size?: ButtonVariantProps["size"];
17
+ /**
18
+ * ボタンのスタイルバリエーション
19
+ * en: Style variation of the button
20
+ */
21
+ variant?: ButtonVariantProps["variant"];
22
+ /**
23
+ * ボタンのテーマバリエーション
24
+ * en: Theme variation of the button
25
+ */
26
+ theme?: ButtonVariantProps["theme"];
27
+ /**
28
+ * ボタンを別コンポーネントの子としてレンダリングするか
29
+ * en: Whether to render the button as a child component
30
+ */
31
+ asChild?: boolean;
32
+ /**
33
+ * 左側に表示するアイコン名
34
+ * en: Icon name displayed on the left side
35
+ */
36
+ prefixIcon?: string;
37
+ /**
38
+ * 右側に表示するアイコン名
39
+ * en: Icon name displayed on the right side
40
+ */
41
+ suffixIcon?: string;
42
+ /**
43
+ * ローディング状態かどうか
44
+ * en: Indicates if the button is in a loading state
45
+ */
46
+ isLoading?: boolean;
47
+ /**
48
+ * ボタンを無効化するかどうか
49
+ * en: Disables the button when set to true
50
+ */
51
+ isDisabled?: boolean;
52
+ /**
53
+ * onClick handler.
54
+ *
55
+ * We intentionally type this as HTMLElement because `asChild` can render non-button elements.
56
+ */
57
+ onClick?: React.MouseEventHandler<HTMLElement>;
58
+ /**
59
+ * onKeyDown handler.
60
+ *
61
+ * We intentionally type this as HTMLElement because `asChild` can render non-button elements.
62
+ */
63
+ onKeyDown?: React.KeyboardEventHandler<HTMLElement>;
64
+ /**
65
+ * @deprecated アクセシビリティ観点(WCAG 2.5.2 Pointer Cancellation)により、基本的に使用を避けてください。
66
+ * en: Deprecated for accessibility reasons (WCAG 2.5.2 Pointer Cancellation). Avoid using this in most cases.
67
+ *
68
+ * Prefer using `onClick` (activation on release) instead of triggering actions on pointer down.
69
+ * ref: https://www.w3.org/TR/WCAG21/#pointer-cancellation
70
+ */
71
+ onMouseDown?: React.MouseEventHandler<HTMLElement>;
72
+ /**
73
+ * @deprecated アクセシビリティ観点(WCAG 2.5.2 Pointer Cancellation)により、基本的に使用を避けてください。
74
+ * en: Deprecated for accessibility reasons (WCAG 2.5.2 Pointer Cancellation). Avoid using this in most cases.
75
+ *
76
+ * Prefer using `onClick` (activation on release) instead of triggering actions on pointer down.
77
+ * ref: https://www.w3.org/TR/WCAG21/#pointer-cancellation
78
+ */
79
+ onPointerDown?: React.PointerEventHandler<HTMLElement>;
80
+ /**
81
+ * @deprecated アクセシビリティ観点(WCAG 2.5.2 Pointer Cancellation)により、基本的に使用を避けてください。
82
+ * en: Deprecated for accessibility reasons (WCAG 2.5.2 Pointer Cancellation). Avoid using this in most cases.
83
+ *
84
+ * Prefer using `onClick` (activation on release) instead of triggering actions on pointer down.
85
+ * ref: https://www.w3.org/TR/WCAG21/#pointer-cancellation
86
+ */
87
+ onTouchStart?: React.TouchEventHandler<HTMLElement>;
88
+ }
89
+ /**
90
+ * **概要 / Overview**
91
+ *
92
+ * - ボタンはフォームの送信、ダイアログの展開、アクションのキャンセル、削除の実行など、アクションやイベントのトリガーとして使用するコンポーネントです。
93
+ * - en: The Button component is used as a trigger for actions and events such as form submission, dialog expansion, action cancellation, and deletion execution.
94
+ *
95
+ * **使用例 / Usage Example**
96
+ *
97
+ * ```tsx
98
+ * <Button variant="solid" size="md" theme="primary" prefixIcon="check">確定</Button>
99
+ * ```
100
+ *
101
+ * **アンチパターン / Anti-patterns**
102
+ *
103
+ * - `<Icon>` を children として渡さないでください。`prefixIcon` / `suffixIcon` props を使用してください。
104
+ * en: Do not pass `<Icon>` as children. Use `prefixIcon` / `suffixIcon` props instead.
105
+ * - アイコンのみのボタンには `IconButton` を使用してください。
106
+ * en: Use `IconButton` for icon-only buttons.
107
+ * - `asChild` 使用時は `prefixIcon` / `suffixIcon` / `isLoading` が反映されません。必要ならスロット先でアイコンやローディング表現を構成してください。
108
+ * en: In `asChild` mode, `prefixIcon`, `suffixIcon`, and `isLoading` are ignored. Render icons and loading states inside the slotted child when needed.
109
+ *
110
+ * ```tsx
111
+ * // ✅ Correct
112
+ * <Button prefixIcon="check">確定</Button>
113
+ *
114
+ * // ❌ Wrong - Icon を children に入れない
115
+ * <Button><Icon icon="check" /> 確定</Button>
116
+ *
117
+ * // ❌ Wrong - children なしの Button + prefixIcon は IconButton に置き換える
118
+ * <Button prefixIcon="close" aria-label="閉じる" />
119
+ *
120
+ * // ❌ Wrong - asChild では prefixIcon は反映されない
121
+ * <Button asChild prefixIcon="add">
122
+ * <Link href="/items/new">新規作成</Link>
123
+ * </Button>
124
+ *
125
+ * // ✅ Correct
126
+ * <IconButton icon="edit" aria-label="編集" />
127
+ * <Button asChild>
128
+ * <Link href="/items/new">新規作成</Link>
129
+ * </Button>
130
+ * ```
131
+
132
+ * **アクセシビリティ / Accessibility**
133
+ *
134
+ * - ボタンにはアクセシブルネームが必要です(通常は `children` のテキスト)。
135
+ * アイコンのみの場合は `aria-label` / `aria-labelledby` を付与するか、可能なら `IconButton` を使用してください。
136
+ * - `isLoading` の場合でもアクセシブルネームは維持されます。
137
+ * - `asChild` を使う場合、子要素がボタン相当のセマンティクス(role/disabled/キーボード操作)を満たすようにしてください。
138
+ *
139
+ * @param {ButtonProps} props
140
+ */
141
+ declare function Button({ className, variant, size, theme, isLoading, isDisabled, asChild, disabled, prefixIcon, suffixIcon, children, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
142
+ declare namespace Button {
143
+ var displayName: string;
144
+ }
145
+ export { Button, buttonVariants };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import figma from "@figma/code-connect";
3
+ import { Button } from "./index.js";
4
+ /**
5
+ * -- This file was auto-generated by Code Connect --
6
+ * `props` includes a mapping from your code props to Figma properties.
7
+ * You should check this is correct, and update the `example` function
8
+ * to return the code example you'd like to see in Figma
9
+ */
10
+ figma.connect(Button, "https://www.figma.com/design/7alBZXZf65YgcII41TWT0r/Sparkle-Design?node-id=11672%3A4288", {
11
+ props: {
12
+ // These props were automatically mapped based on your linked code:
13
+ size: figma.enum("size", {
14
+ sm: "sm",
15
+ md: "md",
16
+ lg: "lg",
17
+ }),
18
+ variant: figma.enum("variant", {
19
+ solid: "solid",
20
+ outline: "outline",
21
+ ghost: "ghost",
22
+ }),
23
+ theme: figma.enum("theme", {
24
+ primary: "primary",
25
+ neutral: "neutral",
26
+ negative: "negative",
27
+ }),
28
+ isLoading: figma.enum("state", {
29
+ loading: true,
30
+ }),
31
+ isDisabled: figma.enum("state", {
32
+ disabled: true,
33
+ }),
34
+ disabled: figma.enum("state", {
35
+ disabled: true,
36
+ }),
37
+ label: figma.string("label"),
38
+ // No matching props could be found for these Figma properties:
39
+ // "isFocused": figma.boolean('isFocused'),
40
+ suffixIcon: figma.boolean("isSuffixIcon", {
41
+ // FIXME: 複数箇所でアイコンのインスタンスを参照しようとするとCode Connectがエラーになるためコメントアウト中
42
+ // true: figma.instance("suffixIcon").getProps<{
43
+ // icon: string;
44
+ // size: number;
45
+ // fill: boolean;
46
+ // }>(),
47
+ true: "expand_more",
48
+ false: undefined,
49
+ }),
50
+ prefixIcon: figma.boolean("isPrefixIcon", {
51
+ true: figma.instance("prefixIcon").getProps(),
52
+ false: undefined,
53
+ }),
54
+ },
55
+ example: props => {
56
+ var _a;
57
+ return (_jsx(Button, { size: props.size, variant: props.variant, theme: props.theme, isLoading: props.isLoading, isDisabled: props.isDisabled, disabled: props.disabled, prefixIcon: (_a = props.prefixIcon) === null || _a === void 0 ? void 0 : _a.icon, suffixIcon: props.suffixIcon, children: props.label }));
58
+ },
59
+ });
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Copyright 2026 Goodpatch Inc.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ "use client";
6
+ var __rest = (this && this.__rest) || function (s, e) {
7
+ var t = {};
8
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
9
+ t[p] = s[p];
10
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
11
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
12
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
13
+ t[p[i]] = s[p[i]];
14
+ }
15
+ return t;
16
+ };
17
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import * as React from "react";
19
+ import { Slot as SlotPrimitive } from "radix-ui";
20
+ import { cva } from "class-variance-authority";
21
+ import { cn } from "../../../lib/utils.js";
22
+ import { Icon } from "../../../components/ui/icon/index.js";
23
+ import { Spinner } from "../../../components/ui/spinner/index.js";
24
+ const buttonVariants = cva([
25
+ // `relative` is required because we absolutely-position the loading spinner at the center.
26
+ "relative inline-flex items-center justify-center gap-0.5 whitespace-nowrap rounded-action transition-colors",
27
+ "cursor-pointer disabled:cursor-not-allowed",
28
+ "shrink-0 outline-none",
29
+ "focus-visible:ring-2 focus-visible:ring-[var(--color-ring-normal)] focus-visible:ring-offset-2",
30
+ ].join(" "), {
31
+ variants: {
32
+ variant: {
33
+ solid: "border shadow-raise",
34
+ outline: "border shadow-raise",
35
+ ghost: "",
36
+ },
37
+ size: {
38
+ sm: "h-8 min-w-16 px-3 py-1 character-2-bold-pro",
39
+ md: "h-10 min-w-20 px-4 py-1.5 character-3-bold-pro",
40
+ lg: "h-12 min-w-24 px-5 py-2 character-4-bold-pro",
41
+ },
42
+ theme: {
43
+ primary: "",
44
+ neutral: "",
45
+ negative: "",
46
+ },
47
+ isLoading: {
48
+ true: "",
49
+ false: "",
50
+ },
51
+ },
52
+ compoundVariants: [
53
+ // solid primary
54
+ {
55
+ variant: "solid",
56
+ theme: "primary",
57
+ isLoading: false,
58
+ className: [
59
+ "bg-primary-500",
60
+ "text-white",
61
+ "border-primary-600",
62
+ "hover:bg-primary-600",
63
+ "hover:border-primary-700",
64
+ "active:bg-primary-700",
65
+ "active:border-primary-800",
66
+ "disabled:bg-primary-200",
67
+ "disabled:border-none",
68
+ ].join(" "),
69
+ },
70
+ {
71
+ variant: "solid",
72
+ theme: "primary",
73
+ isLoading: true,
74
+ className: "disabled:bg-primary-500 disabled:text-white disabled:border-primary-600",
75
+ },
76
+ // solid neutral
77
+ {
78
+ variant: "solid",
79
+ theme: "neutral",
80
+ isLoading: false,
81
+ className: [
82
+ "bg-[var(--color-black-alpha-600)]",
83
+ "text-white",
84
+ "border-neutral-600",
85
+ "hover:bg-[var(--color-black-alpha-700)]",
86
+ "hover:border-neutral-700",
87
+ "active:bg-[var(--color-black-alpha-800)]",
88
+ "active:border-neutral-800",
89
+ "disabled:bg-[var(--color-black-alpha-200)]",
90
+ "disabled:border-none",
91
+ ].join(" "),
92
+ },
93
+ {
94
+ variant: "solid",
95
+ theme: "neutral",
96
+ isLoading: true,
97
+ className: "disabled:bg-[var(--color-black-alpha-600)] disabled:text-white disabled:border-neutral-600",
98
+ },
99
+ // solid negative
100
+ {
101
+ variant: "solid",
102
+ theme: "negative",
103
+ isLoading: false,
104
+ className: [
105
+ "bg-negative-500",
106
+ "text-white",
107
+ "border-negative-600",
108
+ "hover:bg-negative-600",
109
+ "hover:border-negative-700",
110
+ "active:bg-negative-700",
111
+ "active:border-negative-800",
112
+ "disabled:bg-negative-200",
113
+ "disabled:border-none",
114
+ ].join(" "),
115
+ },
116
+ {
117
+ variant: "solid",
118
+ theme: "negative",
119
+ isLoading: true,
120
+ className: "disabled:bg-negative-500 disabled:text-white disabled:border-negative-600",
121
+ },
122
+ // outline primary
123
+ {
124
+ variant: "outline",
125
+ theme: "primary",
126
+ isLoading: false,
127
+ className: [
128
+ "bg-white",
129
+ "text-primary-500",
130
+ "border-primary-300",
131
+ "hover:bg-primary-50",
132
+ "active:bg-primary-100",
133
+ "active:border-primary-400",
134
+ "active:text-primary-600",
135
+ "disabled:bg-[var(--color-white-alpha-700)]",
136
+ "disabled:border-primary-100",
137
+ "disabled:text-primary-200",
138
+ ].join(" "),
139
+ },
140
+ {
141
+ variant: "outline",
142
+ theme: "primary",
143
+ isLoading: true,
144
+ className: "disabled:white disabled:text-primary-500 disabled:border-primary-300",
145
+ },
146
+ // outline neutral
147
+ {
148
+ variant: "outline",
149
+ theme: "neutral",
150
+ isLoading: false,
151
+ className: [
152
+ "bg-white",
153
+ "text-neutral-700",
154
+ "border-neutral-300",
155
+ "hover:bg-[var(--color-black-alpha-50)]",
156
+ "active:bg-[var(--color-black-alpha-100)]",
157
+ "active:border-neutral-300",
158
+ "disabled:bg-[var(--color-white-alpha-700)]",
159
+ "disabled:border-neutral-100",
160
+ "disabled:text-neutral-200",
161
+ ].join(" "),
162
+ },
163
+ {
164
+ variant: "outline",
165
+ theme: "neutral",
166
+ isLoading: true,
167
+ className: "disabled:bg-white disabled:text-neutral-700 disabled:border-neutral-300",
168
+ },
169
+ // outline negative
170
+ {
171
+ variant: "outline",
172
+ theme: "negative",
173
+ isLoading: false,
174
+ className: [
175
+ "bg-white",
176
+ "text-negative-500",
177
+ "border-negative-300",
178
+ "hover:bg-negative-50",
179
+ "active:bg-negative-100",
180
+ "active:border-negative-400",
181
+ "active:text-negative-600",
182
+ "disabled:bg-[var(--color-white-alpha-700)]",
183
+ "disabled:border-negative-100",
184
+ "disabled:text-negative-200",
185
+ ].join(" "),
186
+ },
187
+ {
188
+ variant: "outline",
189
+ theme: "negative",
190
+ isLoading: true,
191
+ className: "disabled:bg-white disabled:text-negative-500 disabled:border-negative-300",
192
+ },
193
+ // ghost primary
194
+ {
195
+ variant: "ghost",
196
+ theme: "primary",
197
+ isLoading: false,
198
+ className: [
199
+ "text-primary-500",
200
+ "hover:bg-primary-50",
201
+ "active:bg-primary-100",
202
+ "active:text-primary-600",
203
+ "disabled:bg-[var(--color-white-alpha-700)]",
204
+ "disabled:text-primary-200",
205
+ ].join(" "),
206
+ },
207
+ {
208
+ variant: "ghost",
209
+ theme: "primary",
210
+ isLoading: true,
211
+ className: "disabled:text-primary-500",
212
+ },
213
+ // ghost neutral
214
+ {
215
+ variant: "ghost",
216
+ theme: "neutral",
217
+ isLoading: false,
218
+ className: [
219
+ "text-neutral-700",
220
+ "hover:bg-[var(--color-black-alpha-50)]",
221
+ "active:bg-[var(--color-black-alpha-100)]",
222
+ "disabled:bg-[var(--color-white-alpha-700)]",
223
+ "disabled:text-neutral-200",
224
+ ].join(" "),
225
+ },
226
+ {
227
+ variant: "ghost",
228
+ theme: "neutral",
229
+ isLoading: true,
230
+ className: "disabled:text-neutral-700",
231
+ },
232
+ // ghost negative
233
+ {
234
+ variant: "ghost",
235
+ theme: "negative",
236
+ isLoading: false,
237
+ className: [
238
+ "text-negative-500",
239
+ "hover:bg-negative-50",
240
+ "active:bg-negative-100",
241
+ "active:text-negative-600",
242
+ "disabled:bg-[var(--color-white-alpha-700)]",
243
+ "disabled:text-negative-200",
244
+ ].join(" "),
245
+ },
246
+ {
247
+ variant: "ghost",
248
+ theme: "negative",
249
+ isLoading: true,
250
+ className: "disabled:text-negative-500",
251
+ },
252
+ ],
253
+ defaultVariants: {
254
+ variant: "solid",
255
+ size: "md",
256
+ theme: "primary",
257
+ },
258
+ });
259
+ /**
260
+ * **概要 / Overview**
261
+ *
262
+ * - ボタンはフォームの送信、ダイアログの展開、アクションのキャンセル、削除の実行など、アクションやイベントのトリガーとして使用するコンポーネントです。
263
+ * - en: The Button component is used as a trigger for actions and events such as form submission, dialog expansion, action cancellation, and deletion execution.
264
+ *
265
+ * **使用例 / Usage Example**
266
+ *
267
+ * ```tsx
268
+ * <Button variant="solid" size="md" theme="primary" prefixIcon="check">確定</Button>
269
+ * ```
270
+ *
271
+ * **アンチパターン / Anti-patterns**
272
+ *
273
+ * - `<Icon>` を children として渡さないでください。`prefixIcon` / `suffixIcon` props を使用してください。
274
+ * en: Do not pass `<Icon>` as children. Use `prefixIcon` / `suffixIcon` props instead.
275
+ * - アイコンのみのボタンには `IconButton` を使用してください。
276
+ * en: Use `IconButton` for icon-only buttons.
277
+ * - `asChild` 使用時は `prefixIcon` / `suffixIcon` / `isLoading` が反映されません。必要ならスロット先でアイコンやローディング表現を構成してください。
278
+ * en: In `asChild` mode, `prefixIcon`, `suffixIcon`, and `isLoading` are ignored. Render icons and loading states inside the slotted child when needed.
279
+ *
280
+ * ```tsx
281
+ * // ✅ Correct
282
+ * <Button prefixIcon="check">確定</Button>
283
+ *
284
+ * // ❌ Wrong - Icon を children に入れない
285
+ * <Button><Icon icon="check" /> 確定</Button>
286
+ *
287
+ * // ❌ Wrong - children なしの Button + prefixIcon は IconButton に置き換える
288
+ * <Button prefixIcon="close" aria-label="閉じる" />
289
+ *
290
+ * // ❌ Wrong - asChild では prefixIcon は反映されない
291
+ * <Button asChild prefixIcon="add">
292
+ * <Link href="/items/new">新規作成</Link>
293
+ * </Button>
294
+ *
295
+ * // ✅ Correct
296
+ * <IconButton icon="edit" aria-label="編集" />
297
+ * <Button asChild>
298
+ * <Link href="/items/new">新規作成</Link>
299
+ * </Button>
300
+ * ```
301
+
302
+ * **アクセシビリティ / Accessibility**
303
+ *
304
+ * - ボタンにはアクセシブルネームが必要です(通常は `children` のテキスト)。
305
+ * アイコンのみの場合は `aria-label` / `aria-labelledby` を付与するか、可能なら `IconButton` を使用してください。
306
+ * - `isLoading` の場合でもアクセシブルネームは維持されます。
307
+ * - `asChild` を使う場合、子要素がボタン相当のセマンティクス(role/disabled/キーボード操作)を満たすようにしてください。
308
+ *
309
+ * @param {ButtonProps} props
310
+ */
311
+ function Button(_a) {
312
+ var { className, variant, size, theme, isLoading = false, isDisabled = false, asChild = false, disabled, prefixIcon, suffixIcon, children } = _a, props = __rest(_a, ["className", "variant", "size", "theme", "isLoading", "isDisabled", "asChild", "disabled", "prefixIcon", "suffixIcon", "children"]);
313
+ const Comp = asChild ? SlotPrimitive.Slot : "button";
314
+ // disabled状態の管理(isDisabled、disabled、またはisLoadingがtrueの場合)
315
+ const isButtonDisabled = isLoading || isDisabled || disabled;
316
+ // アイコンのサイズをボタンサイズに合わせて設定
317
+ const getIconSize = () => {
318
+ switch (size) {
319
+ case "sm":
320
+ return 4;
321
+ case "lg":
322
+ return 6;
323
+ default:
324
+ return 5;
325
+ }
326
+ };
327
+ const hasAccessibleNameProp = ("aria-label" in props && Boolean(props["aria-label"])) ||
328
+ ("aria-labelledby" in props && Boolean(props["aria-labelledby"]));
329
+ const hasChildren = React.Children.count(children) > 0;
330
+ if (process.env.NODE_ENV !== "production") {
331
+ if (!hasChildren && !hasAccessibleNameProp) {
332
+ // Icon-only button should use IconButton, or provide aria-label/labelledby.
333
+ // Keep it as a warning (not an exception) to avoid breaking existing usage.
334
+ console.warn("[Button] Accessible name is missing. Provide children text, or set aria-label/aria-labelledby. For icon-only actions, consider using IconButton.");
335
+ }
336
+ if (asChild && isButtonDisabled) {
337
+ console.warn("[Button] asChild + disabled/loading: the child element must handle disabled semantics (e.g., aria-disabled + preventing activation). Ensure the slotted element is button-like.");
338
+ }
339
+ if (asChild && (prefixIcon || suffixIcon || isLoading)) {
340
+ console.warn("[Button] asChild mode does not support prefixIcon, suffixIcon, or isLoading. These props will be ignored.");
341
+ }
342
+ if (props.onMouseDown || props.onPointerDown || props.onTouchStart) {
343
+ console.warn("[Button] onMouseDown/onPointerDown/onTouchStart are deprecated for accessibility reasons (WCAG 2.5.2 Pointer Cancellation). Prefer onClick (activation on release).");
344
+ }
345
+ }
346
+ const { onClick, onKeyDown } = props, restProps = __rest(props, ["onClick", "onKeyDown"]);
347
+ const handleClick = event => {
348
+ if (isButtonDisabled) {
349
+ event.preventDefault();
350
+ event.stopPropagation();
351
+ return;
352
+ }
353
+ onClick === null || onClick === void 0 ? void 0 : onClick(event);
354
+ };
355
+ const handleKeyDown = event => {
356
+ if (isButtonDisabled) {
357
+ // Prevent activation keys when used with asChild (e.g., <a>).
358
+ if (event.key === "Enter" || event.key === " ") {
359
+ event.preventDefault();
360
+ event.stopPropagation();
361
+ }
362
+ return;
363
+ }
364
+ onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
365
+ };
366
+ return (_jsx(Comp, Object.assign({ "data-slot": "button", "aria-busy": isLoading || undefined, "aria-disabled": asChild && isButtonDisabled ? true : undefined, "data-disabled": asChild && isButtonDisabled ? "true" : undefined, disabled: asChild ? undefined : isButtonDisabled, className: cn(buttonVariants({
367
+ variant,
368
+ size,
369
+ theme,
370
+ isLoading,
371
+ className,
372
+ })), type: asChild ? undefined : props.type || "button", onClick: handleClick, onKeyDown: handleKeyDown }, restProps, { children: asChild ? (children) : (_jsxs(_Fragment, { children: [prefixIcon && (_jsx(Icon, { icon: prefixIcon, size: getIconSize(), className: cn({ "opacity-0": isLoading }) })), isLoading ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 inline-flex", children: _jsx(Spinner, { size: getIconSize(), className: "text-current" }) }), _jsx("span", { className: "opacity-0", children: children })] })) : (_jsx("span", { className: "px-1", children: children })), suffixIcon && (_jsx(Icon, { icon: suffixIcon, size: getIconSize(), className: cn({ "opacity-0": isLoading }) }))] })) })));
373
+ }
374
+ Button.displayName = "Button";
375
+ export { Button, buttonVariants };