@ubie/vitals-ui-consumer 0.0.1

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 (170) hide show
  1. package/.storybook/main.ts +14 -0
  2. package/.storybook/preview.tsx +25 -0
  3. package/.storybook/vitest.setup.ts +7 -0
  4. package/dist/chunk-DKo7XVKm.mjs +33 -0
  5. package/dist/index.d.mts +1720 -0
  6. package/dist/index.d.mts.map +1 -0
  7. package/dist/index.mjs +10594 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/style.css +2299 -0
  10. package/package.json +47 -0
  11. package/src/components/Accordion/Accordion.module.css +75 -0
  12. package/src/components/Accordion/Accordion.spec.tsx +18 -0
  13. package/src/components/Accordion/Accordion.stories.tsx +61 -0
  14. package/src/components/Accordion/Accordion.tsx +89 -0
  15. package/src/components/ActionHalfModal/ActionHalfModal.module.css +180 -0
  16. package/src/components/ActionHalfModal/ActionHalfModal.spec.tsx +57 -0
  17. package/src/components/ActionHalfModal/ActionHalfModal.stories.tsx +469 -0
  18. package/src/components/ActionHalfModal/ActionHalfModal.tsx +269 -0
  19. package/src/components/ActionModal/ActionModal.module.css +145 -0
  20. package/src/components/ActionModal/ActionModal.spec.tsx +57 -0
  21. package/src/components/ActionModal/ActionModal.stories.tsx +302 -0
  22. package/src/components/ActionModal/ActionModal.tsx +232 -0
  23. package/src/components/Bold/Bold.module.css +4 -0
  24. package/src/components/Bold/Bold.spec.tsx +24 -0
  25. package/src/components/Bold/Bold.stories.tsx +54 -0
  26. package/src/components/Bold/Bold.tsx +31 -0
  27. package/src/components/Box/Box.module.css +46 -0
  28. package/src/components/Box/Box.spec.tsx +188 -0
  29. package/src/components/Box/Box.tsx +242 -0
  30. package/src/components/Button/Button.module.css +261 -0
  31. package/src/components/Button/Button.spec.tsx +82 -0
  32. package/src/components/Button/Button.tsx +99 -0
  33. package/src/components/Button/ButtonTypes.ts +107 -0
  34. package/src/components/Button/LinkButton.spec.tsx +86 -0
  35. package/src/components/Button/LinkButton.tsx +80 -0
  36. package/src/components/Button/VariantIcon.tsx +20 -0
  37. package/src/components/Button/useIcon.tsx +16 -0
  38. package/src/components/ButtonCard/ButtonCard.module.css +35 -0
  39. package/src/components/ButtonCard/ButtonCard.spec.tsx +18 -0
  40. package/src/components/ButtonCard/ButtonCard.stories.tsx +54 -0
  41. package/src/components/ButtonCard/ButtonCard.tsx +18 -0
  42. package/src/components/Center/Center.module.css +19 -0
  43. package/src/components/Center/Center.spec.tsx +143 -0
  44. package/src/components/Center/Center.tsx +108 -0
  45. package/src/components/Checkbox/Checkbox.module.css +124 -0
  46. package/src/components/Checkbox/Checkbox.spec.tsx +17 -0
  47. package/src/components/Checkbox/Checkbox.stories.tsx +213 -0
  48. package/src/components/Checkbox/Checkbox.tsx +50 -0
  49. package/src/components/CheckboxCard/CheckboxCard.module.css +102 -0
  50. package/src/components/CheckboxCard/CheckboxCard.spec.tsx +16 -0
  51. package/src/components/CheckboxCard/CheckboxCard.stories.tsx +205 -0
  52. package/src/components/CheckboxCard/CheckboxCard.tsx +53 -0
  53. package/src/components/CheckboxGroup/CheckboxGroup.module.css +16 -0
  54. package/src/components/CheckboxGroup/CheckboxGroup.spec.tsx +17 -0
  55. package/src/components/CheckboxGroup/CheckboxGroup.tsx +64 -0
  56. package/src/components/Color/Color.module.css +3 -0
  57. package/src/components/Color/Color.spec.tsx +24 -0
  58. package/src/components/Color/Color.stories.tsx +71 -0
  59. package/src/components/Color/Color.tsx +28 -0
  60. package/src/components/Divider/Divider.module.css +9 -0
  61. package/src/components/Divider/Divider.spec.tsx +42 -0
  62. package/src/components/Divider/Divider.stories.tsx +77 -0
  63. package/src/components/Divider/Divider.tsx +49 -0
  64. package/src/components/ErrorMessage/ErrorMessage.module.css +8 -0
  65. package/src/components/ErrorMessage/ErrorMessage.spec.tsx +12 -0
  66. package/src/components/ErrorMessage/ErrorMessage.tsx +20 -0
  67. package/src/components/Flex/Flex.module.css +24 -0
  68. package/src/components/Flex/Flex.spec.tsx +188 -0
  69. package/src/components/Flex/Flex.tsx +173 -0
  70. package/src/components/FlexItem/FlexItem.module.css +14 -0
  71. package/src/components/FlexItem/FlexItem.spec.tsx +84 -0
  72. package/src/components/FlexItem/FlexItem.tsx +106 -0
  73. package/src/components/Heading/Heading.module.css +131 -0
  74. package/src/components/Heading/Heading.tsx +86 -0
  75. package/src/components/HelperMessage/HelperMessage.module.css +8 -0
  76. package/src/components/HelperMessage/HelperMessage.tsx +15 -0
  77. package/src/components/Icon/Icon.module.css +6 -0
  78. package/src/components/Icon/Icon.spec.tsx +24 -0
  79. package/src/components/Icon/Icon.stories.tsx +100 -0
  80. package/src/components/Icon/Icon.tsx +101 -0
  81. package/src/components/Input/Input.module.css +51 -0
  82. package/src/components/Input/Input.spec.tsx +14 -0
  83. package/src/components/Input/Input.tsx +27 -0
  84. package/src/components/Label/Label.module.css +14 -0
  85. package/src/components/Label/Label.tsx +39 -0
  86. package/src/components/LinkCard/LinkCard.module.css +72 -0
  87. package/src/components/LinkCard/LinkCard.tsx +96 -0
  88. package/src/components/MessageHalfModal/MessageHalfModal.module.css +181 -0
  89. package/src/components/MessageHalfModal/MessageHalfModal.spec.tsx +73 -0
  90. package/src/components/MessageHalfModal/MessageHalfModal.stories.tsx +242 -0
  91. package/src/components/MessageHalfModal/MessageHalfModal.tsx +194 -0
  92. package/src/components/MessageModal/MessageModal.module.css +149 -0
  93. package/src/components/MessageModal/MessageModal.spec.tsx +57 -0
  94. package/src/components/MessageModal/MessageModal.stories.tsx +223 -0
  95. package/src/components/MessageModal/MessageModal.tsx +178 -0
  96. package/src/components/Pre/Pre.module.css +8 -0
  97. package/src/components/Pre/Pre.spec.tsx +11 -0
  98. package/src/components/Pre/Pre.stories.tsx +76 -0
  99. package/src/components/Pre/Pre.tsx +40 -0
  100. package/src/components/RadioButton/RadioButton.module.css +92 -0
  101. package/src/components/RadioButton/RadioButton.spec.tsx +25 -0
  102. package/src/components/RadioButton/RadioButton.tsx +55 -0
  103. package/src/components/RadioCard/RadioCard.module.css +109 -0
  104. package/src/components/RadioCard/RadioCard.tsx +61 -0
  105. package/src/components/RadioGroup/RadioGroup.module.css +16 -0
  106. package/src/components/RadioGroup/RadioGroup.spec.tsx +17 -0
  107. package/src/components/RadioGroup/RadioGroup.tsx +60 -0
  108. package/src/components/Select/Select.module.css +70 -0
  109. package/src/components/Select/Select.spec.tsx +12 -0
  110. package/src/components/Select/Select.tsx +56 -0
  111. package/src/components/Stack/Stack.module.css +10 -0
  112. package/src/components/Stack/Stack.spec.tsx +177 -0
  113. package/src/components/Stack/Stack.tsx +151 -0
  114. package/src/components/Stepper/Stepper.module.css +137 -0
  115. package/src/components/Stepper/Stepper.spec.tsx +198 -0
  116. package/src/components/Stepper/Stepper.stories.tsx +192 -0
  117. package/src/components/Stepper/Stepper.tsx +70 -0
  118. package/src/components/Stepper/StepperItem.tsx +113 -0
  119. package/src/components/Text/Text.module.css +168 -0
  120. package/src/components/Text/Text.tsx +192 -0
  121. package/src/components/TextArea/TextArea.module.css +46 -0
  122. package/src/components/TextArea/TextArea.spec.tsx +13 -0
  123. package/src/components/TextArea/TextArea.tsx +29 -0
  124. package/src/components/Toggle/Toggle.module.css +71 -0
  125. package/src/components/Toggle/Toggle.spec.tsx +21 -0
  126. package/src/components/Toggle/Toggle.tsx +56 -0
  127. package/src/font.ts +2 -0
  128. package/src/hooks/useScrollable.ts +58 -0
  129. package/src/icons/AppleIcon.tsx +14 -0
  130. package/src/icons/GoogleIcon.tsx +27 -0
  131. package/src/icons/LINEIcon.tsx +16 -0
  132. package/src/index.ts +35 -0
  133. package/src/sharedComponents/RequiredLabel/RequiredLabel.module.css +10 -0
  134. package/src/sharedComponents/RequiredLabel/RequiredLabel.tsx +8 -0
  135. package/src/sharedComponents/VisuallyHidden/VisuallyHidden.module.css +15 -0
  136. package/src/sharedComponents/VisuallyHidden/VisuallyHidden.tsx +22 -0
  137. package/src/stories/Accordion.stories.portable.ts +4 -0
  138. package/src/stories/Box.stories.tsx +474 -0
  139. package/src/stories/Button.stories.tsx +262 -0
  140. package/src/stories/Center.stories.tsx +126 -0
  141. package/src/stories/ErrorMessage.stories.tsx +19 -0
  142. package/src/stories/Flex.stories.tsx +345 -0
  143. package/src/stories/Form.stories.tsx +83 -0
  144. package/src/stories/Heading.stories.tsx +263 -0
  145. package/src/stories/HelperMessage.stories.tsx +22 -0
  146. package/src/stories/Input.stories.tsx +145 -0
  147. package/src/stories/Label.stories.tsx +32 -0
  148. package/src/stories/LinkButton.stories.tsx +207 -0
  149. package/src/stories/LinkCard.stories.tsx +90 -0
  150. package/src/stories/RadioButton.stories.tsx +168 -0
  151. package/src/stories/RadioCard.stories.tsx +236 -0
  152. package/src/stories/Select.stories.tsx +97 -0
  153. package/src/stories/Stack.stories.tsx +167 -0
  154. package/src/stories/Text.stories.tsx +396 -0
  155. package/src/stories/TextArea.stories.tsx +49 -0
  156. package/src/stories/Toggle.stories.tsx +30 -0
  157. package/src/test/vitest-jest-dom.d.ts +12 -0
  158. package/src/types/attributes.ts +6 -0
  159. package/src/types/global.d.ts +11 -0
  160. package/src/types/icon.ts +3 -0
  161. package/src/types/style.ts +254 -0
  162. package/src/utils/component.ts +8 -0
  163. package/src/utils/style.spec.ts +57 -0
  164. package/src/utils/style.ts +387 -0
  165. package/src/utils/types.ts +8 -0
  166. package/tsconfig.json +18 -0
  167. package/tsconfig.spec-lint.tsbuildinfo +1 -0
  168. package/tsconfig.tsbuildinfo +1 -0
  169. package/vite.config.ts +50 -0
  170. package/vitest.shims.d.ts +1 -0
@@ -0,0 +1,82 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { createRef } from "react";
3
+ import { Button } from "./Button";
4
+
5
+ describe("<Button>", () => {
6
+ it("access to DOM through ref prop", () => {
7
+ const ref = createRef<HTMLButtonElement>();
8
+ render(<Button ref={ref}>Test</Button>);
9
+ expect(ref.current).not.toBeNull();
10
+ expect(ref.current?.tagName).toBe("BUTTON");
11
+ });
12
+
13
+ it("has all the margins through m prop", () => {
14
+ render(
15
+ <Button m="xxs" data-testid="box">
16
+ Test
17
+ </Button>,
18
+ );
19
+ const div = screen.getByTestId("box");
20
+
21
+ expect(div).toHaveStyle("--margin-top: var(--size-spacing-xxs)");
22
+ expect(div).toHaveStyle("--margin-right: var(--size-spacing-xxs)");
23
+ expect(div).toHaveStyle("--margin-bottom: var(--size-spacing-xxs)");
24
+ expect(div).toHaveStyle("--margin-left: var(--size-spacing-xxs)");
25
+ });
26
+
27
+ it("has a horizontal margins through mx prop", () => {
28
+ render(
29
+ <Button mx="xxs" data-testid="box">
30
+ Test
31
+ </Button>,
32
+ );
33
+ const div = screen.getByTestId("box");
34
+
35
+ expect(div).toHaveStyle("--margin-top: 0");
36
+ expect(div).toHaveStyle("--margin-right: var(--size-spacing-xxs)");
37
+ expect(div).toHaveStyle("--margin-bottom: 0");
38
+ expect(div).toHaveStyle("--margin-left: var(--size-spacing-xxs)");
39
+ });
40
+
41
+ it("has a vertical margins of through my prop", () => {
42
+ render(
43
+ <Button my="xxs" data-testid="box">
44
+ Test
45
+ </Button>,
46
+ );
47
+ const div = screen.getByTestId("box");
48
+
49
+ expect(div).toHaveStyle("--margin-top: var(--size-spacing-xxs)");
50
+ expect(div).toHaveStyle("--margin-right: 0");
51
+ expect(div).toHaveStyle("--margin-bottom: var(--size-spacing-xxs)");
52
+ expect(div).toHaveStyle("--margin-left: 0");
53
+ });
54
+
55
+ it("has all margins through individual margin props", () => {
56
+ render(
57
+ <Button mt="xxs" mr="xs" mb="sm" ml="md" data-testid="box">
58
+ Test
59
+ </Button>,
60
+ );
61
+ const div = screen.getByTestId("box");
62
+
63
+ expect(div).toHaveStyle("--margin-top: var(--size-spacing-xxs)");
64
+ expect(div).toHaveStyle("--margin-right: var(--size-spacing-xs)");
65
+ expect(div).toHaveStyle("--margin-bottom: var(--size-spacing-sm)");
66
+ expect(div).toHaveStyle("--margin-left: var(--size-spacing-md)");
67
+ });
68
+
69
+ it("gives priority to individual margin props", () => {
70
+ render(
71
+ <Button m="xxs" mx="xs" my="xs" mt="sm" mr="md" mb="lg" ml="xl" data-testid="box">
72
+ Test
73
+ </Button>,
74
+ );
75
+ const div = screen.getByTestId("box");
76
+
77
+ expect(div).toHaveStyle("--margin-top: var(--size-spacing-sm)");
78
+ expect(div).toHaveStyle("--margin-right: var(--size-spacing-md)");
79
+ expect(div).toHaveStyle("--margin-bottom: var(--size-spacing-lg)");
80
+ expect(div).toHaveStyle("--margin-left: var(--size-spacing-xl)");
81
+ });
82
+ });
@@ -0,0 +1,99 @@
1
+ "use client";
2
+
3
+ import { clsx } from "clsx";
4
+ import { type CSSProperties, forwardRef } from "react";
5
+ import styles from "./Button.module.css";
6
+ import { useIcon } from "./useIcon";
7
+ import { marginVariables } from "../../utils/style";
8
+ import type { ButtonProps } from "./ButtonTypes";
9
+
10
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
11
+ (
12
+ {
13
+ children,
14
+ variant = "primary",
15
+ size = "large",
16
+ block = false,
17
+ icon,
18
+ prefixIcon: _prefixIcon,
19
+ fixedIcon: _fixedIcon,
20
+ suffixIcon: _suffixIcon,
21
+ type = "button",
22
+ disabled = false,
23
+ loading = false,
24
+ loadingLabel = "通信中",
25
+ onClick,
26
+ whiteSpace = "normal",
27
+ m,
28
+ mx,
29
+ my,
30
+ mt,
31
+ mr,
32
+ mb,
33
+ ml,
34
+ ...props
35
+ },
36
+ ref,
37
+ ) => {
38
+ const prefixIcon = useIcon(icon || _prefixIcon, variant);
39
+ const fixedIcon = useIcon(_fixedIcon, variant);
40
+ const suffixIcon = useIcon(_suffixIcon, variant);
41
+ const cls = clsx({
42
+ [styles.button]: true,
43
+ [styles[variant]]: true,
44
+ [styles[size]]: true,
45
+ [styles.block]: block,
46
+ [styles.disabled]: disabled,
47
+ [styles.loading]: loading,
48
+ });
49
+
50
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
51
+ if (loading) {
52
+ e.preventDefault();
53
+ return;
54
+ }
55
+
56
+ onClick?.(e);
57
+ };
58
+
59
+ return (
60
+ <button
61
+ type={type}
62
+ className={cls}
63
+ style={
64
+ {
65
+ ...marginVariables({
66
+ m,
67
+ mx,
68
+ my,
69
+ mt,
70
+ mr,
71
+ mb,
72
+ ml,
73
+ }),
74
+ "--white-space": whiteSpace,
75
+ } as CSSProperties
76
+ }
77
+ ref={ref}
78
+ disabled={disabled}
79
+ aria-disabled={loading}
80
+ onClick={handleClick}
81
+ {...props}
82
+ >
83
+ {loading && <span className={styles.loadingLabel}>{loadingLabel}</span>}
84
+ {fixedIcon && <span className={styles.fixedIcon}>{fixedIcon}</span>}
85
+ <span className={styles.label}>
86
+ {prefixIcon && (
87
+ <span className={clsx(styles.icon, loading && styles.loading)}>{prefixIcon}</span>
88
+ )}
89
+ <span className={clsx(styles.children, loading && styles.loading)}>{children}</span>
90
+ {suffixIcon && (
91
+ <span className={clsx(styles.suffixIcon, loading && styles.loading)}>{suffixIcon}</span>
92
+ )}
93
+ </span>
94
+ </button>
95
+ );
96
+ },
97
+ );
98
+
99
+ Button.displayName = "Button";
@@ -0,0 +1,107 @@
1
+ import { CustomDataAttributeProps } from "../../types/attributes";
2
+ import { IconName } from "../../types/icon";
3
+ import type { MarginProps } from "../../types/style";
4
+ import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactElement, ReactNode } from "react";
5
+
6
+ export type BaseProps = {
7
+ /**
8
+ * ボタンのラベルとして表示する内容
9
+ */
10
+ children: ReactNode;
11
+ /**
12
+ * ボタンの種類
13
+ * @default primary
14
+ */
15
+ variant?:
16
+ | "primary"
17
+ | "secondary"
18
+ | "alert"
19
+ | "text"
20
+ | "textAlert"
21
+ | "authGoogle"
22
+ | "authLINE"
23
+ | "authApple";
24
+ /**
25
+ * 種類
26
+ * @default large
27
+ */
28
+ size?: "large" | "medium" | "small";
29
+ /**
30
+ * 横幅を100%占有する
31
+ */
32
+ block?: boolean;
33
+ /**
34
+ * Fixedアイコン
35
+ */
36
+ fixedIcon?: "default" | ReactElement | IconName;
37
+ /**
38
+ * 後方配置のアイコン
39
+ */
40
+ suffixIcon?: "default" | ReactElement | IconName;
41
+ /**
42
+ * ラベルの折り返しを指定
43
+ */
44
+ whiteSpace?: "normal" | "nowrap" | "pre" | "pre-wrap" | "pre-line" | "break-spaces";
45
+ } & (
46
+ | {
47
+ /**
48
+ * アイコン
49
+ */
50
+ icon?: "default" | ReactElement | IconName;
51
+ prefixIcon?: never;
52
+ }
53
+ | {
54
+ /**
55
+ * アイコン
56
+ */
57
+ prefixIcon?: "default" | ReactElement | IconName;
58
+ icon?: never;
59
+ }
60
+ | {
61
+ icon?: never;
62
+ prefixIcon?: never;
63
+ }
64
+ ) &
65
+ MarginProps &
66
+ CustomDataAttributeProps;
67
+
68
+ export type OnlyButtonProps = {
69
+ /**
70
+ * ネイティブのbutton要素のtype属性
71
+ * @default button
72
+ */
73
+ type?: HTMLButtonElement["type"];
74
+ /**
75
+ * ボタンを無効化するかどうか
76
+ * @default false
77
+ */
78
+ disabled?: boolean;
79
+ /**
80
+ * ローディング状態を示す
81
+ */
82
+ loading?: boolean;
83
+ /**
84
+ * ローディング中に表示する文言
85
+ */
86
+ loadingLabel?: string;
87
+ };
88
+
89
+ export type OnlyLinkButtonProps = {
90
+ /**
91
+ * レンダリングされる要素を変更。フレームワークのリンクコンポーネントなどを指定
92
+ */
93
+ render?: ReactElement;
94
+ };
95
+
96
+ export type ButtonProps = Omit<
97
+ ButtonHTMLAttributes<HTMLButtonElement>,
98
+ "children" | "className" | keyof BaseProps | keyof OnlyButtonProps
99
+ > &
100
+ BaseProps &
101
+ OnlyButtonProps;
102
+ export type LinkButtonProps = Omit<
103
+ AnchorHTMLAttributes<HTMLAnchorElement>,
104
+ "children" | "className" | keyof BaseProps
105
+ > &
106
+ BaseProps &
107
+ OnlyLinkButtonProps;
@@ -0,0 +1,86 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { createRef } from "react";
3
+ import { LinkButton } from "./LinkButton";
4
+
5
+ describe("<LinkButton>", () => {
6
+ it("access to DOM through ref prop", () => {
7
+ const ref = createRef<HTMLAnchorElement>();
8
+ render(
9
+ <LinkButton ref={ref} href=".">
10
+ Test
11
+ </LinkButton>,
12
+ );
13
+ expect(ref.current).not.toBeNull();
14
+ expect(ref.current?.tagName).toBe("A");
15
+ });
16
+
17
+ it("has all the margins through m prop", () => {
18
+ render(
19
+ <LinkButton m="xxs" data-testid="box">
20
+ Test
21
+ </LinkButton>,
22
+ );
23
+ const div = screen.getByTestId("box");
24
+
25
+ expect(div).toHaveStyle("--margin-top: var(--size-spacing-xxs)");
26
+ expect(div).toHaveStyle("--margin-right: var(--size-spacing-xxs)");
27
+ expect(div).toHaveStyle("--margin-bottom: var(--size-spacing-xxs)");
28
+ expect(div).toHaveStyle("--margin-left: var(--size-spacing-xxs)");
29
+ });
30
+
31
+ it("has a horizontal margins through mx prop", () => {
32
+ render(
33
+ <LinkButton mx="xxs" data-testid="box">
34
+ Test
35
+ </LinkButton>,
36
+ );
37
+ const div = screen.getByTestId("box");
38
+
39
+ expect(div).toHaveStyle("--margin-top: 0");
40
+ expect(div).toHaveStyle("--margin-right: var(--size-spacing-xxs)");
41
+ expect(div).toHaveStyle("--margin-bottom: 0");
42
+ expect(div).toHaveStyle("--margin-left: var(--size-spacing-xxs)");
43
+ });
44
+
45
+ it("has a vertical margins of through my prop", () => {
46
+ render(
47
+ <LinkButton my="xxs" data-testid="box">
48
+ Test
49
+ </LinkButton>,
50
+ );
51
+ const div = screen.getByTestId("box");
52
+
53
+ expect(div).toHaveStyle("--margin-top: var(--size-spacing-xxs)");
54
+ expect(div).toHaveStyle("--margin-right: 0");
55
+ expect(div).toHaveStyle("--margin-bottom: var(--size-spacing-xxs)");
56
+ expect(div).toHaveStyle("--margin-left: 0");
57
+ });
58
+
59
+ it("has all margins through individual margin props", () => {
60
+ render(
61
+ <LinkButton mt="xxs" mr="xs" mb="sm" ml="md" data-testid="box">
62
+ Test
63
+ </LinkButton>,
64
+ );
65
+ const div = screen.getByTestId("box");
66
+
67
+ expect(div).toHaveStyle("--margin-top: var(--size-spacing-xxs)");
68
+ expect(div).toHaveStyle("--margin-right: var(--size-spacing-xs)");
69
+ expect(div).toHaveStyle("--margin-bottom: var(--size-spacing-sm)");
70
+ expect(div).toHaveStyle("--margin-left: var(--size-spacing-md)");
71
+ });
72
+
73
+ it("gives priority to individual margin props", () => {
74
+ render(
75
+ <LinkButton m="xxs" mx="xs" my="xs" mt="sm" mr="md" mb="lg" ml="xl" data-testid="box">
76
+ Test
77
+ </LinkButton>,
78
+ );
79
+ const div = screen.getByTestId("box");
80
+
81
+ expect(div).toHaveStyle("--margin-top: var(--size-spacing-sm)");
82
+ expect(div).toHaveStyle("--margin-right: var(--size-spacing-md)");
83
+ expect(div).toHaveStyle("--margin-bottom: var(--size-spacing-lg)");
84
+ expect(div).toHaveStyle("--margin-left: var(--size-spacing-xl)");
85
+ });
86
+ });
@@ -0,0 +1,80 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import { cloneElement, CSSProperties, forwardRef } from "react";
5
+ import styles from "./Button.module.css";
6
+ import { useIcon } from "./useIcon";
7
+ import { marginVariables } from "../../utils/style";
8
+ import type { LinkButtonProps } from "./ButtonTypes";
9
+ import type { ReactNode } from "react";
10
+
11
+ export const LinkButton = forwardRef<HTMLAnchorElement, LinkButtonProps>(
12
+ (
13
+ {
14
+ render,
15
+ children,
16
+ variant = "primary",
17
+ size = "large",
18
+ block = false,
19
+ icon,
20
+ prefixIcon: _prefixIcon,
21
+ fixedIcon: _fixedIcon,
22
+ suffixIcon: _suffixIcon,
23
+ whiteSpace = "normal",
24
+ m,
25
+ mx,
26
+ my,
27
+ mt,
28
+ mr,
29
+ mb,
30
+ ml,
31
+ ...props
32
+ },
33
+ forwardedRef,
34
+ ) => {
35
+ const prefixIcon = useIcon(icon || _prefixIcon, variant);
36
+ const fixedIcon = useIcon(_fixedIcon, variant);
37
+ const suffixIcon = useIcon(_suffixIcon, variant);
38
+ const cls = clsx({
39
+ [styles.button]: true,
40
+ [styles[variant]]: true,
41
+ [styles[size]]: true,
42
+ [styles.block]: block,
43
+ });
44
+
45
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
46
+ const createElement = (props: any, children: ReactNode) => {
47
+ return render ? cloneElement(render, props, children) : <a {...props}>{children}</a>;
48
+ };
49
+
50
+ return createElement(
51
+ {
52
+ className: cls,
53
+ style: {
54
+ ...marginVariables({
55
+ m,
56
+ mx,
57
+ my,
58
+ mt,
59
+ mr,
60
+ mb,
61
+ ml,
62
+ }),
63
+ "--white-space": whiteSpace,
64
+ } as CSSProperties,
65
+ ...props,
66
+ ref: forwardedRef,
67
+ },
68
+ <>
69
+ {fixedIcon && <span className={styles.fixedIcon}>{fixedIcon}</span>}
70
+ <span className={styles.label}>
71
+ {prefixIcon && <span className={styles.icon}>{prefixIcon}</span>}
72
+ {children}
73
+ {suffixIcon && <span className={styles.suffixIcon}>{suffixIcon}</span>}
74
+ </span>
75
+ </>,
76
+ );
77
+ },
78
+ );
79
+
80
+ LinkButton.displayName = "Link";
@@ -0,0 +1,20 @@
1
+ import { ReactNode, FC } from "react";
2
+ import { AppleIcon } from "../../icons/AppleIcon";
3
+ import { GoogleIcon } from "../../icons/GoogleIcon";
4
+ import { LINEIcon } from "../../icons/LINEIcon";
5
+ import type { BaseProps } from "./ButtonTypes";
6
+
7
+ export const VariantIcon: FC<{ variant: BaseProps["variant"]; fallback?: ReactNode }> = ({
8
+ variant,
9
+ fallback,
10
+ }) => {
11
+ return variant === "authGoogle" ? (
12
+ <GoogleIcon />
13
+ ) : variant === "authLINE" ? (
14
+ <LINEIcon />
15
+ ) : variant === "authApple" ? (
16
+ <AppleIcon />
17
+ ) : (
18
+ <>{fallback}</>
19
+ );
20
+ };
@@ -0,0 +1,16 @@
1
+ import { useMemo } from "react";
2
+ import { VariantIcon } from "./VariantIcon";
3
+ import { Icon } from "../Icon/Icon";
4
+ import type { ButtonProps } from "./ButtonTypes";
5
+
6
+ export function useIcon(icon: ButtonProps["icon"], variant: ButtonProps["variant"]) {
7
+ return useMemo(() => {
8
+ if (icon === "default") {
9
+ return <VariantIcon variant={variant} />;
10
+ } else if (typeof icon === "string") {
11
+ return <Icon icon={icon}></Icon>;
12
+ } else {
13
+ return icon;
14
+ }
15
+ }, [icon, variant]);
16
+ }
@@ -0,0 +1,35 @@
1
+ .card {
2
+ --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);
3
+
4
+ display: flex;
5
+ align-items: center;
6
+ width: 100%;
7
+ padding: var(--size-spacing-md) var(--size-spacing-sm);
8
+ font-size: var(--text-button-lg-size);
9
+ font-weight: bold;
10
+ hyphens: auto;
11
+
12
+ /* TODO: Replaced by tokens when @ubie/design-tokens is up-to-date. */
13
+ line-height: 1.5;
14
+ color: var(--color-on-surface);
15
+ text-align: left;
16
+ text-decoration: none;
17
+ overflow-wrap: anywhere;
18
+ cursor: pointer;
19
+ background-color: var(--color-ubie-white);
20
+ border: 1px solid var(--color-outline);
21
+ border-radius: var(--radius-md);
22
+ transition: background-color 0.3s var(--ease-out-quint);
23
+ }
24
+
25
+ @media (hover: hover) {
26
+ .card:hover:not(:disabled) {
27
+ background-color: var(--color-ubie-blue-100);
28
+ }
29
+ }
30
+
31
+ .card:disabled {
32
+ color: var(--color-placeholder);
33
+ cursor: initial;
34
+ background-color: var(--color-outline-variant);
35
+ }
@@ -0,0 +1,18 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { ButtonCard } from "./ButtonCard";
3
+
4
+ describe("<ButtonCard>", () => {
5
+ it("receives the custom data attribute", () => {
6
+ render(<ButtonCard data-testid="button-custom-attribute">Test</ButtonCard>);
7
+ const button = screen.getByTestId("button-custom-attribute");
8
+
9
+ expect(button).toBeInTheDocument();
10
+ });
11
+
12
+ it("A receives the id attribute", () => {
13
+ render(<ButtonCard id="button-id">Test</ButtonCard>);
14
+ const button = screen.getByRole("button");
15
+
16
+ expect(button.getAttribute("id")).toBe("button-id");
17
+ });
18
+ });
@@ -0,0 +1,54 @@
1
+ import { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { ButtonCard } from "./ButtonCard";
3
+ import { Box } from "../Box/Box";
4
+ import { Flex } from "../Flex/Flex";
5
+
6
+ export default {
7
+ title: "Form/ButtonCard",
8
+ component: ButtonCard,
9
+ args: {
10
+ onClick: () => {},
11
+ children: "自分",
12
+ },
13
+ } satisfies Meta<typeof ButtonCard>;
14
+
15
+ const defaultArgs = {};
16
+
17
+ type Story = StoryObj<typeof ButtonCard>;
18
+
19
+ export const Default: Story = {
20
+ render: (args) => <ButtonCard {...args} />,
21
+ args: defaultArgs,
22
+ };
23
+
24
+ export const WrapText: Story = {
25
+ render: (args) => (
26
+ <Box width="200px">
27
+ <ButtonCard {...args} />
28
+ </Box>
29
+ ),
30
+ args: {
31
+ ...defaultArgs,
32
+ children: "自分自分自分自分自分自分自分自分自分自分自分自分自分自分自分自分",
33
+ },
34
+ };
35
+
36
+ export const Disabled: Story = {
37
+ render: (args) => <ButtonCard {...args} />,
38
+ args: {
39
+ ...defaultArgs,
40
+ disabled: true,
41
+ },
42
+ };
43
+
44
+ export const Horizontal: Story = {
45
+ render: (args) => (
46
+ <Flex spacing="xs">
47
+ <ButtonCard {...args} />
48
+ <ButtonCard {...args} />
49
+ </Flex>
50
+ ),
51
+ args: {
52
+ ...defaultArgs,
53
+ },
54
+ };
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ import { forwardRef, type PropsWithChildren, type ButtonHTMLAttributes } from "react";
4
+ import styles from "./ButtonCard.module.css";
5
+
6
+ type Props = PropsWithChildren<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className">>;
7
+
8
+ export const ButtonCard = forwardRef<HTMLButtonElement, Props>(
9
+ ({ children, type = "button", ...rest }, ref) => {
10
+ return (
11
+ <button type={type} ref={ref} className={styles.card} {...rest}>
12
+ {children}
13
+ </button>
14
+ );
15
+ },
16
+ );
17
+
18
+ ButtonCard.displayName = "ButtonCard";
@@ -0,0 +1,19 @@
1
+ .center {
2
+ width: var(--width);
3
+ min-width: var(--min-width);
4
+ max-width: var(--max-width);
5
+ padding: var(--padding-top) var(--padding-right) var(--padding-bottom) var(--padding-left);
6
+ margin-inline: auto;
7
+ margin-top: var(--margin-top);
8
+ margin-bottom: var(--margin-bottom);
9
+ }
10
+
11
+ .center.textCenter {
12
+ text-align: center;
13
+ }
14
+
15
+ .center.childrenCenter {
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: center;
19
+ }