@ippon-ui/ui 0.0.2

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 (246) hide show
  1. package/.agents/agents/component-library-create-from-pattern-library.agent.md +37 -0
  2. package/.agents/agents/patten-library-create-component.agent.md +30 -0
  3. package/.agents/skills/component-library/SKILL.md +169 -0
  4. package/.agents/skills/pattern-library/SKILL.md +277 -0
  5. package/.github/workflows/build.yml +34 -0
  6. package/.gitlab-ci.yml +12 -0
  7. package/.prettierignore +6 -0
  8. package/AGENTS.md +50 -0
  9. package/LICENSE +202 -0
  10. package/ci/build.yml +15 -0
  11. package/ci/common.yml +42 -0
  12. package/ci/deploy.yml +20 -0
  13. package/icons/LICENCE +202 -0
  14. package/icons/index.ts +69 -0
  15. package/icons/package.json +25 -0
  16. package/icons/tsconfig.json +11 -0
  17. package/lefthook.yml +10 -0
  18. package/mise.toml +55 -0
  19. package/package.json +26 -0
  20. package/pnpm-workspace.yaml +9 -0
  21. package/prettier.config.mts +8 -0
  22. package/react/LICENCE +202 -0
  23. package/react/README.md +75 -0
  24. package/react/eslint.config.js +22 -0
  25. package/react/package.json +63 -0
  26. package/react/src/CAP.ts +14 -0
  27. package/react/src/Card.ts +2 -0
  28. package/react/src/DataSelectable.ts +7 -0
  29. package/react/src/Grid.ts +33 -0
  30. package/react/src/IpponBadge.tsx +62 -0
  31. package/react/src/IpponButton.tsx +93 -0
  32. package/react/src/IpponButtonCard.tsx +34 -0
  33. package/react/src/IpponCard.tsx +30 -0
  34. package/react/src/IpponContainer.tsx +15 -0
  35. package/react/src/IpponGrid.tsx +56 -0
  36. package/react/src/IpponHSpace.tsx +56 -0
  37. package/react/src/IpponIcon.tsx +15 -0
  38. package/react/src/IpponImportFile.tsx +128 -0
  39. package/react/src/IpponIon.tsx +45 -0
  40. package/react/src/IpponMeter.tsx +43 -0
  41. package/react/src/IpponProgress.tsx +45 -0
  42. package/react/src/IpponText.tsx +56 -0
  43. package/react/src/IpponTitle.tsx +45 -0
  44. package/react/src/IpponVSpace.tsx +43 -0
  45. package/react/src/Optional.ts +177 -0
  46. package/react/src/Tokens.ts +36 -0
  47. package/react/src/index.ts +16 -0
  48. package/react/test/File.fixture.ts +13 -0
  49. package/react/test/IpponBadge.spec.tsx +245 -0
  50. package/react/test/IpponButton.spec.tsx +666 -0
  51. package/react/test/IpponButtonCard.spec.tsx +162 -0
  52. package/react/test/IpponCard.spec.tsx +133 -0
  53. package/react/test/IpponContainer.spec.tsx +56 -0
  54. package/react/test/IpponGrid.spec.tsx +140 -0
  55. package/react/test/IpponHSpace.spec.tsx +107 -0
  56. package/react/test/IpponIcon.spec.tsx +37 -0
  57. package/react/test/IpponImportFile.spec.tsx +431 -0
  58. package/react/test/IpponIon.spec.tsx +52 -0
  59. package/react/test/IpponMeter.spec.tsx +59 -0
  60. package/react/test/IpponProgress.spec.tsx +68 -0
  61. package/react/test/IpponText.spec.tsx +149 -0
  62. package/react/test/IpponTitle.spec.tsx +242 -0
  63. package/react/test/IpponVSpace.spec.tsx +91 -0
  64. package/react/tsconfig.app.json +24 -0
  65. package/react/tsconfig.json +4 -0
  66. package/react/tsconfig.node.json +23 -0
  67. package/react/vite.config.ts +30 -0
  68. package/react/vitest.config.ts +21 -0
  69. package/styles/.editorconfig +12 -0
  70. package/styles/.stylelintrc.json +75 -0
  71. package/styles/LICENCE +202 -0
  72. package/styles/README.md +107 -0
  73. package/styles/logo.svg +26 -0
  74. package/styles/package.json +67 -0
  75. package/styles/src/atom/_atom.scss +9 -0
  76. package/styles/src/atom/atom.pug +34 -0
  77. package/styles/src/atom/badge/_badge.scss +108 -0
  78. package/styles/src/atom/badge/badge.code.pug +29 -0
  79. package/styles/src/atom/badge/badge.md +1 -0
  80. package/styles/src/atom/badge/badge.mixin.pug +24 -0
  81. package/styles/src/atom/badge/badge.render.pug +7 -0
  82. package/styles/src/atom/button/_button.scss +242 -0
  83. package/styles/src/atom/button/button.code.pug +38 -0
  84. package/styles/src/atom/button/button.md +31 -0
  85. package/styles/src/atom/button/button.mixin.pug +30 -0
  86. package/styles/src/atom/button/button.render.pug +13 -0
  87. package/styles/src/atom/icon/_icon.scss +8 -0
  88. package/styles/src/atom/icon/icon.code.pug +5 -0
  89. package/styles/src/atom/icon/icon.md +11 -0
  90. package/styles/src/atom/icon/icon.mixin.pug +8 -0
  91. package/styles/src/atom/icon/icon.render.pug +7 -0
  92. package/styles/src/atom/ion/ion.code.pug +8 -0
  93. package/styles/src/atom/ion/ion.md +11 -0
  94. package/styles/src/atom/ion/ion.mixin.pug +8 -0
  95. package/styles/src/atom/ion/ion.render.pug +7 -0
  96. package/styles/src/atom/meter/_meter.scss +23 -0
  97. package/styles/src/atom/meter/meter.code.pug +8 -0
  98. package/styles/src/atom/meter/meter.md +7 -0
  99. package/styles/src/atom/meter/meter.mixin.pug +12 -0
  100. package/styles/src/atom/meter/meter.render.pug +5 -0
  101. package/styles/src/atom/progress/_progress.scss +23 -0
  102. package/styles/src/atom/progress/progress.code.pug +8 -0
  103. package/styles/src/atom/progress/progress.md +7 -0
  104. package/styles/src/atom/progress/progress.mixin.pug +14 -0
  105. package/styles/src/atom/progress/progress.render.pug +5 -0
  106. package/styles/src/atom/tab/_tab.scss +48 -0
  107. package/styles/src/atom/tab/tab.code.pug +5 -0
  108. package/styles/src/atom/tab/tab.md +1 -0
  109. package/styles/src/atom/tab/tab.mixin.pug +14 -0
  110. package/styles/src/atom/tab/tab.render.pug +4 -0
  111. package/styles/src/atom/text/_text.scss +74 -0
  112. package/styles/src/atom/text/text.code.pug +19 -0
  113. package/styles/src/atom/text/text.md +5 -0
  114. package/styles/src/atom/text/text.mixin.pug +12 -0
  115. package/styles/src/atom/text/text.render.pug +7 -0
  116. package/styles/src/atom/title/_title.scss +68 -0
  117. package/styles/src/atom/title/title.code.pug +25 -0
  118. package/styles/src/atom/title/title.md +9 -0
  119. package/styles/src/atom/title/title.mixin.pug +12 -0
  120. package/styles/src/atom/title/title.render.pug +5 -0
  121. package/styles/src/atom/title-display/_title-display.scss +26 -0
  122. package/styles/src/atom/title-display/title-display.code.pug +9 -0
  123. package/styles/src/atom/title-display/title-display.md +5 -0
  124. package/styles/src/atom/title-display/title-display.mixin.pug +6 -0
  125. package/styles/src/atom/title-display/title-display.render.pug +4 -0
  126. package/styles/src/doc.scss +2 -0
  127. package/styles/src/favicon.ico +0 -0
  128. package/styles/src/function/_conversion.scss +9 -0
  129. package/styles/src/index.pug +59 -0
  130. package/styles/src/layout-documentation.pug +14 -0
  131. package/styles/src/layout.pug +17 -0
  132. package/styles/src/molecule/_molecule.scss +2 -0
  133. package/styles/src/molecule/import-file/_import-file.scss +38 -0
  134. package/styles/src/molecule/import-file/import-file.code.pug +4 -0
  135. package/styles/src/molecule/import-file/import-file.md +1 -0
  136. package/styles/src/molecule/import-file/import-file.mixin.pug +15 -0
  137. package/styles/src/molecule/import-file/import-file.render.pug +5 -0
  138. package/styles/src/molecule/molecule.pug +20 -0
  139. package/styles/src/molecule/tabs/_tabs.scss +4 -0
  140. package/styles/src/molecule/tabs/tabs.code.pug +9 -0
  141. package/styles/src/molecule/tabs/tabs.md +1 -0
  142. package/styles/src/molecule/tabs/tabs.mixin.pug +4 -0
  143. package/styles/src/molecule/tabs/tabs.render.pug +4 -0
  144. package/styles/src/molecule/toggle/_toggle.scss +68 -0
  145. package/styles/src/molecule/toggle/toggle.code.pug +26 -0
  146. package/styles/src/molecule/toggle/toggle.md +1 -0
  147. package/styles/src/molecule/toggle/toggle.mixin.pug +36 -0
  148. package/styles/src/molecule/toggle/toggle.render.pug +5 -0
  149. package/styles/src/organism/_abstract-card.scss +36 -0
  150. package/styles/src/organism/_docorganism.scss +1 -0
  151. package/styles/src/organism/_organism.scss +8 -0
  152. package/styles/src/organism/button-card/_button-card.scss +22 -0
  153. package/styles/src/organism/button-card/button-card.code.pug +31 -0
  154. package/styles/src/organism/button-card/button-card.md +28 -0
  155. package/styles/src/organism/button-card/button-card.mixin.pug +8 -0
  156. package/styles/src/organism/button-card/button-card.render.pug +7 -0
  157. package/styles/src/organism/card/_card.scss +6 -0
  158. package/styles/src/organism/card/card.code.pug +9 -0
  159. package/styles/src/organism/card/card.md +23 -0
  160. package/styles/src/organism/card/card.mixin.pug +7 -0
  161. package/styles/src/organism/card/card.render.pug +7 -0
  162. package/styles/src/organism/container/_container.scss +3 -0
  163. package/styles/src/organism/container/container.code.pug +13 -0
  164. package/styles/src/organism/container/container.md +5 -0
  165. package/styles/src/organism/container/container.mixin.pug +3 -0
  166. package/styles/src/organism/container/container.render.pug +4 -0
  167. package/styles/src/organism/grid/_docgrid.scss +11 -0
  168. package/styles/src/organism/grid/_grid.scss +84 -0
  169. package/styles/src/organism/grid/grid.code.pug +25 -0
  170. package/styles/src/organism/grid/grid.md +1 -0
  171. package/styles/src/organism/grid/grid.mixin.pug +7 -0
  172. package/styles/src/organism/grid/grid.render.pug +5 -0
  173. package/styles/src/organism/h-space/_h-space.scss +49 -0
  174. package/styles/src/organism/h-space/h-space.code.pug +56 -0
  175. package/styles/src/organism/h-space/h-space.md +22 -0
  176. package/styles/src/organism/h-space/h-space.mixin.pug +14 -0
  177. package/styles/src/organism/h-space/h-space.render.pug +5 -0
  178. package/styles/src/organism/header/_header.scss +8 -0
  179. package/styles/src/organism/header/header.code.pug +14 -0
  180. package/styles/src/organism/header/header.md +1 -0
  181. package/styles/src/organism/header/header.mixin.pug +7 -0
  182. package/styles/src/organism/header/header.render.pug +4 -0
  183. package/styles/src/organism/modal/_modal.scss +58 -0
  184. package/styles/src/organism/modal/modal.code.pug +68 -0
  185. package/styles/src/organism/modal/modal.md +1 -0
  186. package/styles/src/organism/modal/modal.mixin.pug +25 -0
  187. package/styles/src/organism/modal/modal.render.pug +4 -0
  188. package/styles/src/organism/organism.pug +30 -0
  189. package/styles/src/organism/v-space/_v-space.scss +45 -0
  190. package/styles/src/organism/v-space/v-space.code.pug +41 -0
  191. package/styles/src/organism/v-space/v-space.md +20 -0
  192. package/styles/src/organism/v-space/v-space.mixin.pug +7 -0
  193. package/styles/src/organism/v-space/v-space.render.pug +5 -0
  194. package/styles/src/quark/_breakpoint.scss +12 -0
  195. package/styles/src/quark/_font.scss +38 -0
  196. package/styles/src/quark/_gap.scss +34 -0
  197. package/styles/src/quark/_placeholder.scss +27 -0
  198. package/styles/src/quark/_shadow.scss +13 -0
  199. package/styles/src/quark/_typography.scss +146 -0
  200. package/styles/src/template/_template.scss +1 -0
  201. package/styles/src/template/layout/_layout.scss +20 -0
  202. package/styles/src/template/layout/layout.code.pug +11 -0
  203. package/styles/src/template/layout/layout.md +1 -0
  204. package/styles/src/template/layout/layout.mixin.pug +11 -0
  205. package/styles/src/template/layout/layout.render.pug +4 -0
  206. package/styles/src/template/template.pug +16 -0
  207. package/styles/src/tikui.scss +5 -0
  208. package/styles/src/token/_doctable.scss +14 -0
  209. package/styles/src/token/_doctoken.scss +1 -0
  210. package/styles/src/token/_size.scss +9 -0
  211. package/styles/src/token/_token.scss +5 -0
  212. package/styles/src/token/color/_color.scss +9 -0
  213. package/styles/src/token/color/color/_base.scss +65 -0
  214. package/styles/src/token/color/color/_brand.scss +13 -0
  215. package/styles/src/token/color/color/_error.scss +13 -0
  216. package/styles/src/token/color/color/_information-2.scss +13 -0
  217. package/styles/src/token/color/color/_information.scss +13 -0
  218. package/styles/src/token/color/color/_neutral.scss +20 -0
  219. package/styles/src/token/color/color/_semantic.scss +69 -0
  220. package/styles/src/token/color/color/_success.scss +13 -0
  221. package/styles/src/token/color/color/_warning.scss +13 -0
  222. package/styles/src/token/color/color.js +31 -0
  223. package/styles/src/token/color/color.mixin.pug +19 -0
  224. package/styles/src/token/color/color.pug +9 -0
  225. package/styles/src/token/color/color.render.pug +13 -0
  226. package/styles/src/token/radius/_radius.scss +8 -0
  227. package/styles/src/token/radius/radius.js +54 -0
  228. package/styles/src/token/radius/radius.mixin.pug +14 -0
  229. package/styles/src/token/radius/radius.pug +9 -0
  230. package/styles/src/token/radius/radius.render.pug +11 -0
  231. package/styles/src/token/shadow/_shadow.scss +22 -0
  232. package/styles/src/token/shadow/shadow.js +45 -0
  233. package/styles/src/token/shadow/shadow.mixin.pug +13 -0
  234. package/styles/src/token/shadow/shadow.pug +9 -0
  235. package/styles/src/token/shadow/shadow.render.pug +9 -0
  236. package/styles/src/token/token.js +38 -0
  237. package/styles/src/token/token.pug +25 -0
  238. package/styles/src/token/typography/_typography.scss +103 -0
  239. package/styles/src/token/typography/typography.js +32 -0
  240. package/styles/src/token/typography/typography.mixin.pug +17 -0
  241. package/styles/src/token/typography/typography.pug +9 -0
  242. package/styles/src/token/typography/typography.render.pug +19 -0
  243. package/styles/test/function/conversion.test.scss +20 -0
  244. package/styles/test/function/sass.spec.ts +6 -0
  245. package/styles/tikuiconfig.json +14 -0
  246. package/styles/tsconfig.json +10 -0
@@ -0,0 +1,177 @@
1
+ const checkIsNaN = <Value>(value: Value) => typeof value === 'number' && Number.isNaN(value);
2
+
3
+ export abstract class Optional<Value> {
4
+ static empty<Value>(): Optional<Value> {
5
+ return new EmptyOptional();
6
+ }
7
+
8
+ static of<Value>(value: Value): Optional<Value> {
9
+ return new ValuatedOptional(value);
10
+ }
11
+
12
+ static ofNullable<Value>(value: Value | null): Optional<Value> {
13
+ if (value === null) {
14
+ return Optional.empty();
15
+ }
16
+ return Optional.of(value);
17
+ }
18
+
19
+ static ofUndefinable<Value>(value?: Value): Optional<Value> {
20
+ if (value === undefined) {
21
+ return Optional.empty();
22
+ }
23
+ return Optional.of(value);
24
+ }
25
+
26
+ static ofFalsifiable<Value>(value?: Value | null): Optional<Value> {
27
+ if (value === undefined || value === null || checkIsNaN(value)) {
28
+ return Optional.empty();
29
+ }
30
+ if (typeof value === 'string' && value.trim() === '') {
31
+ return Optional.empty();
32
+ }
33
+ return Optional.of(value);
34
+ }
35
+
36
+ static every<Structure extends Record<string, unknown>>(
37
+ optionalStructure: OptionalStructure<Structure>,
38
+ ): Optional<Structure> {
39
+ const keys = Object.keys(optionalStructure) as Array<keyof Structure>;
40
+ if (keys.some((key) => optionalStructure[key].isEmpty())) {
41
+ return Optional.empty();
42
+ }
43
+ const entries = keys.map((key) => [key, optionalStructure[key].orElseThrow()]);
44
+ return Optional.of(Object.fromEntries(entries));
45
+ }
46
+
47
+ abstract map<Output>(mapper: (value: Value) => Output): Optional<Output>;
48
+ abstract flatMap<Output>(mapper: (feature: Value) => Optional<Output>): Optional<Output>;
49
+ abstract or(factory: () => Optional<Value>): Optional<Value>;
50
+ abstract orElse(value: Value): Value;
51
+ abstract orElseGet(factory: () => Value): Value;
52
+ abstract orElseThrow<U = Error>(throwable?: () => U): Value;
53
+ abstract filter<To extends Value>(predicate: (value: Value) => boolean): Optional<To>;
54
+ abstract isEmpty(): boolean;
55
+ abstract isPresent(): boolean;
56
+ abstract ifAbsent(consumer: () => void): void;
57
+ abstract ifPresent(consumer: (feature: Value) => void): void;
58
+ abstract orUndefined(): Value | undefined;
59
+ }
60
+
61
+ class EmptyOptional<Value> extends Optional<Value> {
62
+ map<Output>(): Optional<Output> {
63
+ return Optional.empty();
64
+ }
65
+
66
+ flatMap<Output>(): Optional<Output> {
67
+ return Optional.empty();
68
+ }
69
+
70
+ or(factory: () => Optional<Value>): Optional<Value> {
71
+ return factory();
72
+ }
73
+
74
+ orElse(value: Value): Value {
75
+ return value;
76
+ }
77
+
78
+ orElseGet(factory: () => Value): Value {
79
+ return factory();
80
+ }
81
+
82
+ orElseThrow<U>(throwable?: () => U): Value {
83
+ if (throwable === undefined) {
84
+ throw new Error("Can't get value from an empty optional");
85
+ }
86
+
87
+ throw throwable();
88
+ }
89
+
90
+ filter<To extends Value>(): Optional<To> {
91
+ return Optional.empty();
92
+ }
93
+
94
+ isEmpty(): boolean {
95
+ return true;
96
+ }
97
+
98
+ isPresent(): boolean {
99
+ return false;
100
+ }
101
+
102
+ ifAbsent(consumer: () => void): void {
103
+ consumer();
104
+ }
105
+
106
+ ifPresent(): void {
107
+ // Nothing to do
108
+ }
109
+
110
+ orUndefined(): Value | undefined {
111
+ return undefined;
112
+ }
113
+ }
114
+
115
+ class ValuatedOptional<Value> extends Optional<Value> {
116
+ constructor(private readonly value: Value) {
117
+ super();
118
+ }
119
+
120
+ map<Output>(mapper: (value: Value) => Output): Optional<Output> {
121
+ return Optional.of(mapper(this.value));
122
+ }
123
+
124
+ flatMap<Output>(mapper: (feature: Value) => Optional<Output>): Optional<Output> {
125
+ return mapper(this.value);
126
+ }
127
+
128
+ or(): Optional<Value> {
129
+ return Optional.of(this.value);
130
+ }
131
+
132
+ orElse(): Value {
133
+ return this.value;
134
+ }
135
+
136
+ orElseGet(): Value {
137
+ return this.value;
138
+ }
139
+
140
+ orElseThrow(): Value {
141
+ return this.value;
142
+ }
143
+
144
+ filter<To extends Value>(predicate: (value: Value) => value is To): Optional<To> {
145
+ if (predicate(this.value)) {
146
+ return Optional.of(this.value);
147
+ }
148
+
149
+ return Optional.empty();
150
+ }
151
+
152
+ isEmpty(): boolean {
153
+ return false;
154
+ }
155
+
156
+ isPresent(): boolean {
157
+ return true;
158
+ }
159
+
160
+ ifAbsent(): void {
161
+ // Nothing to do
162
+ }
163
+
164
+ ifPresent(consumer: (feature: Value) => void): void {
165
+ consumer(this.value);
166
+ }
167
+
168
+ orUndefined(): Value | undefined {
169
+ return this.value;
170
+ }
171
+ }
172
+
173
+ type OptionalValue<Structure, K extends keyof Structure> = Optional<Structure[K]>;
174
+
175
+ type OptionalStructure<Structure> = {
176
+ [key in keyof Structure]: OptionalValue<Structure, key>;
177
+ };
@@ -0,0 +1,36 @@
1
+ export type IpponTokenSize =
2
+ | 2
3
+ | 4
4
+ | 8
5
+ | 10
6
+ | 12
7
+ | 16
8
+ | 20
9
+ | 24
10
+ | 28
11
+ | 32
12
+ | 36
13
+ | 40
14
+ | 44
15
+ | 48
16
+ | 52
17
+ | 56
18
+ | 64
19
+ | 886
20
+ | 999;
21
+
22
+ type SemanticColor =
23
+ | 'neutral'
24
+ | 'brand'
25
+ | 'success'
26
+ | 'error'
27
+ | 'warning'
28
+ | 'information'
29
+ | 'information-2';
30
+
31
+ type VariantColor = 'primary' | 'secondary' | 'on-primary' | 'on-secondary';
32
+
33
+ export type IpponTokenTextColor =
34
+ | `${SemanticColor}-${VariantColor}`
35
+ | 'neutral-tertiary'
36
+ | 'neutral-tertiary-inversed';
@@ -0,0 +1,16 @@
1
+ export { IpponBadge } from './IpponBadge.tsx';
2
+ export { IpponButton } from './IpponButton.tsx';
3
+ export { IpponButtonCard } from './IpponButtonCard.tsx';
4
+ export { IpponCard } from './IpponCard.tsx';
5
+ export { IpponContainer } from './IpponContainer.tsx';
6
+ export { IpponGrid, IpponGridSlot } from './IpponGrid.tsx';
7
+ export { IpponHSpace, IpponHSpaceSlot } from './IpponHSpace.tsx';
8
+ export { IpponIcon } from './IpponIcon.tsx';
9
+ export { IpponImportFile } from './IpponImportFile.tsx';
10
+ export { IpponIon } from './IpponIon.tsx';
11
+ export { IpponMeter } from './IpponMeter.tsx';
12
+ export { IpponProgress } from './IpponProgress.tsx';
13
+ export { IpponText } from './IpponText.tsx';
14
+ export { IpponTitle } from './IpponTitle.tsx';
15
+ export { IpponVSpace, IpponVSpaceSlot } from './IpponVSpace.tsx';
16
+ export { type DataSelectableWithChildren } from './DataSelectable.ts';
@@ -0,0 +1,13 @@
1
+ import { File as FileNode } from 'buffer';
2
+
3
+ export const fakeTextFile = ({
4
+ content,
5
+ name,
6
+ type,
7
+ }: {
8
+ content: string;
9
+ name: string;
10
+ type: string;
11
+ }): File => {
12
+ return new FileNode([content], name, { type }) as unknown as File;
13
+ };
@@ -0,0 +1,245 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { render, screen, configure, cleanup } from '@testing-library/react';
3
+ import '@testing-library/jest-dom/vitest';
4
+ import { IpponBadge } from '../src';
5
+
6
+ configure({
7
+ testIdAttribute: 'data-selector',
8
+ });
9
+
10
+ const getIpponBadge = () => screen.getByTestId('ippon-badge');
11
+
12
+ const expectToHaveClasses = (...classes: string[]) =>
13
+ expect(getIpponBadge()).toHaveClass('ippon-badge', ...classes);
14
+
15
+ const expectToHaveTextContent = (text: string) => expect(getIpponBadge()).toHaveTextContent(text);
16
+
17
+ const getTextPart = () => getIpponBadge().querySelector('.ippon-badge--text');
18
+
19
+ const expectToHaveTextPart = () => expect(getTextPart()).toBeInTheDocument();
20
+
21
+ const getIconParts = () => getIpponBadge().querySelectorAll<HTMLElement>('.ippon-badge--icon');
22
+
23
+ const expectIconPartsCount = (count: number) => expect(getIconParts()).toHaveLength(count);
24
+
25
+ describe('IpponBadge', () => {
26
+ beforeEach(cleanup);
27
+
28
+ it('should be minimal', () => {
29
+ render(<IpponBadge dataSelector="ippon-badge">Default</IpponBadge>);
30
+
31
+ expectToHaveClasses();
32
+ expectToHaveTextContent('Default');
33
+ });
34
+
35
+ describe('Variant', () => {
36
+ it('should be secondary', () => {
37
+ render(
38
+ <IpponBadge variant="secondary" dataSelector="ippon-badge">
39
+ Secondary
40
+ </IpponBadge>,
41
+ );
42
+
43
+ expectToHaveClasses('-secondary');
44
+ expectToHaveTextContent('Secondary');
45
+ });
46
+ });
47
+
48
+ describe('Color', () => {
49
+ it('should be neutral', () => {
50
+ render(
51
+ <IpponBadge color="neutral" dataSelector="ippon-badge">
52
+ Neutral
53
+ </IpponBadge>,
54
+ );
55
+
56
+ expectToHaveClasses('-neutral');
57
+ expectToHaveTextContent('Neutral');
58
+ });
59
+
60
+ it('should be success', () => {
61
+ render(
62
+ <IpponBadge color="success" dataSelector="ippon-badge">
63
+ Success
64
+ </IpponBadge>,
65
+ );
66
+
67
+ expectToHaveClasses('-success');
68
+ expectToHaveTextContent('Success');
69
+ });
70
+
71
+ it('should be error', () => {
72
+ render(
73
+ <IpponBadge color="error" dataSelector="ippon-badge">
74
+ Error
75
+ </IpponBadge>,
76
+ );
77
+
78
+ expectToHaveClasses('-error');
79
+ expectToHaveTextContent('Error');
80
+ });
81
+
82
+ it('should be warning', () => {
83
+ render(
84
+ <IpponBadge color="warning" dataSelector="ippon-badge">
85
+ Warning
86
+ </IpponBadge>,
87
+ );
88
+
89
+ expectToHaveClasses('-warning');
90
+ expectToHaveTextContent('Warning');
91
+ });
92
+
93
+ it('should be information', () => {
94
+ render(
95
+ <IpponBadge color="information" dataSelector="ippon-badge">
96
+ Information
97
+ </IpponBadge>,
98
+ );
99
+
100
+ expectToHaveClasses('-information');
101
+ expectToHaveTextContent('Information');
102
+ });
103
+ });
104
+
105
+ describe('Icons', () => {
106
+ it('should have icon left', () => {
107
+ render(
108
+ <IpponBadge
109
+ iconLeft={{ name: 'alert-circle', variant: 'outline' }}
110
+ dataSelector="ippon-badge"
111
+ >
112
+ With left icon
113
+ </IpponBadge>,
114
+ );
115
+
116
+ expectToHaveClasses();
117
+ expectToHaveTextPart();
118
+ expectIconPartsCount(1);
119
+ });
120
+
121
+ it('should have icon right', () => {
122
+ render(
123
+ <IpponBadge iconRight={{ name: 'close', variant: 'outline' }} dataSelector="ippon-badge">
124
+ With right icon
125
+ </IpponBadge>,
126
+ );
127
+
128
+ expectToHaveClasses();
129
+ expectToHaveTextPart();
130
+ expectIconPartsCount(1);
131
+ });
132
+
133
+ it('should have both icons', () => {
134
+ render(
135
+ <IpponBadge
136
+ iconLeft={{ name: 'alert-circle', variant: 'outline' }}
137
+ iconRight={{ name: 'close', variant: 'outline' }}
138
+ dataSelector="ippon-badge"
139
+ >
140
+ With both icons
141
+ </IpponBadge>,
142
+ );
143
+
144
+ expectToHaveClasses();
145
+ expectToHaveTextPart();
146
+ expectIconPartsCount(2);
147
+ });
148
+ });
149
+
150
+ describe('Clickable', () => {
151
+ it('should have clickable icon left', () => {
152
+ const onClick = vi.fn();
153
+
154
+ render(
155
+ <IpponBadge
156
+ iconLeft={{ name: 'alert-circle', variant: 'outline', onClick }}
157
+ dataSelector="ippon-badge"
158
+ >
159
+ Clickable left
160
+ </IpponBadge>,
161
+ );
162
+
163
+ const icon = getIconParts()[0];
164
+ expect(icon.tagName).toBe('BUTTON');
165
+ expect(icon).toHaveClass('-clickable');
166
+ icon.click();
167
+ expect(onClick).toHaveBeenCalled();
168
+ });
169
+
170
+ it('should have clickable icon right', () => {
171
+ const onClick = vi.fn();
172
+
173
+ render(
174
+ <IpponBadge
175
+ iconRight={{ name: 'close', variant: 'outline', onClick }}
176
+ dataSelector="ippon-badge"
177
+ >
178
+ Clickable right
179
+ </IpponBadge>,
180
+ );
181
+
182
+ const icon = getIconParts()[0];
183
+ expect(icon.tagName).toBe('BUTTON');
184
+ expect(icon).toHaveClass('-clickable');
185
+ icon.click();
186
+ expect(onClick).toHaveBeenCalled();
187
+ });
188
+
189
+ it('should not have clickable class without onClick', () => {
190
+ render(
191
+ <IpponBadge
192
+ iconLeft={{ name: 'alert-circle', variant: 'outline' }}
193
+ dataSelector="ippon-badge"
194
+ >
195
+ Not clickable
196
+ </IpponBadge>,
197
+ );
198
+
199
+ const icon = getIconParts()[0];
200
+ expect(icon.tagName).toBe('SPAN');
201
+ expect(icon).not.toHaveClass('-clickable');
202
+ });
203
+ });
204
+
205
+ describe('Placeholder', () => {
206
+ it('should display placeholder class', () => {
207
+ render(
208
+ <IpponBadge placeholder dataSelector="ippon-badge">
209
+ Placeholder
210
+ </IpponBadge>,
211
+ );
212
+
213
+ expectToHaveClasses('-placeholder');
214
+ });
215
+ });
216
+
217
+ describe('Combinations', () => {
218
+ it('should combine color and variant', () => {
219
+ render(
220
+ <IpponBadge color="success" variant="secondary" dataSelector="ippon-badge">
221
+ Success Secondary
222
+ </IpponBadge>,
223
+ );
224
+
225
+ expectToHaveClasses('-secondary', '-success');
226
+ expectToHaveTextContent('Success Secondary');
227
+ });
228
+
229
+ it('should combine color and icon left', () => {
230
+ render(
231
+ <IpponBadge
232
+ color="error"
233
+ iconLeft={{ name: 'alert-circle', variant: 'outline' }}
234
+ dataSelector="ippon-badge"
235
+ >
236
+ Error with icon
237
+ </IpponBadge>,
238
+ );
239
+
240
+ expectToHaveClasses('-error');
241
+ expectToHaveTextPart();
242
+ expectIconPartsCount(1);
243
+ });
244
+ });
245
+ });