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.
- package/LICENSE +201 -0
- package/NOTICE +5 -0
- package/README.en.md +275 -0
- package/README.md +328 -0
- package/dist/components/ui/badge/index.d.ts +67 -0
- package/dist/components/ui/badge/index.figma.d.ts +1 -0
- package/dist/components/ui/badge/index.figma.js +28 -0
- package/dist/components/ui/badge/index.js +129 -0
- package/dist/components/ui/breadcrumb/index.d.ts +70 -0
- package/dist/components/ui/breadcrumb/index.figma.d.ts +1 -0
- package/dist/components/ui/breadcrumb/index.figma.js +28 -0
- package/dist/components/ui/breadcrumb/index.js +96 -0
- package/dist/components/ui/button/index.d.ts +145 -0
- package/dist/components/ui/button/index.figma.d.ts +1 -0
- package/dist/components/ui/button/index.figma.js +59 -0
- package/dist/components/ui/button/index.js +375 -0
- package/dist/components/ui/card/index.d.ts +193 -0
- package/dist/components/ui/card/index.figma.d.ts +1 -0
- package/dist/components/ui/card/index.figma.js +28 -0
- package/dist/components/ui/card/index.js +219 -0
- package/dist/components/ui/checkbox/index.d.ts +62 -0
- package/dist/components/ui/checkbox/index.figma.d.ts +1 -0
- package/dist/components/ui/checkbox/index.figma.js +34 -0
- package/dist/components/ui/checkbox/index.js +192 -0
- package/dist/components/ui/dialog/index.d.ts +106 -0
- package/dist/components/ui/dialog/index.figma.d.ts +1 -0
- package/dist/components/ui/dialog/index.figma.js +21 -0
- package/dist/components/ui/dialog/index.js +157 -0
- package/dist/components/ui/divider/index.d.ts +44 -0
- package/dist/components/ui/divider/index.figma.d.ts +1 -0
- package/dist/components/ui/divider/index.figma.js +39 -0
- package/dist/components/ui/divider/index.js +81 -0
- package/dist/components/ui/form/index.d.ts +171 -0
- package/dist/components/ui/form/index.figma.d.ts +1 -0
- package/dist/components/ui/form/index.figma.js +129 -0
- package/dist/components/ui/form/index.js +214 -0
- package/dist/components/ui/form/label.d.ts +4 -0
- package/dist/components/ui/form/label.js +20 -0
- package/dist/components/ui/icon/index.d.ts +71 -0
- package/dist/components/ui/icon/index.figma.d.ts +1 -0
- package/dist/components/ui/icon/index.figma.js +1858 -0
- package/dist/components/ui/icon/index.js +57 -0
- package/dist/components/ui/icon-button/index.d.ts +77 -0
- package/dist/components/ui/icon-button/index.figma.d.ts +1 -0
- package/dist/components/ui/icon-button/index.figma.js +43 -0
- package/dist/components/ui/icon-button/index.js +341 -0
- package/dist/components/ui/inline-message/index.d.ts +83 -0
- package/dist/components/ui/inline-message/index.figma.d.ts +1 -0
- package/dist/components/ui/inline-message/index.figma.js +26 -0
- package/dist/components/ui/inline-message/index.js +154 -0
- package/dist/components/ui/input/index.d.ts +118 -0
- package/dist/components/ui/input/index.figma.d.ts +1 -0
- package/dist/components/ui/input/index.figma.js +36 -0
- package/dist/components/ui/input/index.js +256 -0
- package/dist/components/ui/input-password/index.d.ts +27 -0
- package/dist/components/ui/input-password/index.figma.d.ts +1 -0
- package/dist/components/ui/input-password/index.figma.js +36 -0
- package/dist/components/ui/input-password/index.js +35 -0
- package/dist/components/ui/link/index.d.ts +68 -0
- package/dist/components/ui/link/index.figma.d.ts +1 -0
- package/dist/components/ui/link/index.figma.js +22 -0
- package/dist/components/ui/link/index.js +92 -0
- package/dist/components/ui/modal/index.d.ts +83 -0
- package/dist/components/ui/modal/index.figma.d.ts +1 -0
- package/dist/components/ui/modal/index.figma.js +61 -0
- package/dist/components/ui/modal/index.js +143 -0
- package/dist/components/ui/overlay/index.d.ts +19 -0
- package/dist/components/ui/overlay/index.figma.d.ts +1 -0
- package/dist/components/ui/overlay/index.figma.js +14 -0
- package/dist/components/ui/overlay/index.js +39 -0
- package/dist/components/ui/radio/index.d.ts +94 -0
- package/dist/components/ui/radio/index.figma.d.ts +1 -0
- package/dist/components/ui/radio/index.figma.js +63 -0
- package/dist/components/ui/radio/index.js +204 -0
- package/dist/components/ui/select/index.d.ts +175 -0
- package/dist/components/ui/select/index.figma.d.ts +1 -0
- package/dist/components/ui/select/index.figma.js +30 -0
- package/dist/components/ui/select/index.js +281 -0
- package/dist/components/ui/skeleton/index.d.ts +21 -0
- package/dist/components/ui/skeleton/index.figma.d.ts +1 -0
- package/dist/components/ui/skeleton/index.figma.js +14 -0
- package/dist/components/ui/skeleton/index.js +32 -0
- package/dist/components/ui/slider/index.d.ts +75 -0
- package/dist/components/ui/slider/index.figma.d.ts +1 -0
- package/dist/components/ui/slider/index.figma.js +28 -0
- package/dist/components/ui/slider/index.js +113 -0
- package/dist/components/ui/slider/utils.d.ts +16 -0
- package/dist/components/ui/slider/utils.js +56 -0
- package/dist/components/ui/spinner/index.d.ts +42 -0
- package/dist/components/ui/spinner/index.figma.d.ts +1 -0
- package/dist/components/ui/spinner/index.figma.js +16 -0
- package/dist/components/ui/spinner/index.js +51 -0
- package/dist/components/ui/switch/index.d.ts +46 -0
- package/dist/components/ui/switch/index.figma.d.ts +1 -0
- package/dist/components/ui/switch/index.figma.js +27 -0
- package/dist/components/ui/switch/index.js +75 -0
- package/dist/components/ui/tabs/index.d.ts +53 -0
- package/dist/components/ui/tabs/index.figma.d.ts +1 -0
- package/dist/components/ui/tabs/index.figma.js +55 -0
- package/dist/components/ui/tabs/index.js +164 -0
- package/dist/components/ui/tag/index.d.ts +50 -0
- package/dist/components/ui/tag/index.figma.d.ts +1 -0
- package/dist/components/ui/tag/index.figma.js +32 -0
- package/dist/components/ui/tag/index.js +177 -0
- package/dist/components/ui/textarea/index.d.ts +49 -0
- package/dist/components/ui/textarea/index.figma.d.ts +1 -0
- package/dist/components/ui/textarea/index.figma.js +27 -0
- package/dist/components/ui/textarea/index.js +94 -0
- package/dist/components/ui/toast/index.d.ts +60 -0
- package/dist/components/ui/toast/index.figma.d.ts +1 -0
- package/dist/components/ui/toast/index.figma.js +26 -0
- package/dist/components/ui/toast/index.js +57 -0
- package/dist/components/ui/tooltip/index.d.ts +52 -0
- package/dist/components/ui/tooltip/index.figma.d.ts +1 -0
- package/dist/components/ui/tooltip/index.figma.js +23 -0
- package/dist/components/ui/tooltip/index.js +65 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/test/helpers.d.ts +56 -0
- package/dist/test/helpers.js +182 -0
- package/dist/test/setup.d.ts +1 -0
- package/dist/test/setup.js +38 -0
- 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 };
|