@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,30 @@
1
+ import type { DataSelectableWithChildren } from './DataSelectable.ts';
2
+ import { clsx } from 'clsx';
3
+ import {
4
+ optionalToPrefixedAlternativeClass,
5
+ optionalToAlternativeClass,
6
+ toAlternativeClass,
7
+ } from './CAP.ts';
8
+ import type { IpponCardShadowLevel, IpponCardSize } from './Card.ts';
9
+
10
+ type IpponCardProps = DataSelectableWithChildren<{
11
+ shadow?: IpponCardShadowLevel;
12
+ border?: boolean;
13
+ size?: IpponCardSize;
14
+ }>;
15
+
16
+ export const IpponCard = (props: IpponCardProps) => (
17
+ <div
18
+ className={clsx(
19
+ 'ippon-card',
20
+ optionalToPrefixedAlternativeClass('shadow')(props.shadow),
21
+ {
22
+ [toAlternativeClass('border')]: props.border,
23
+ },
24
+ optionalToAlternativeClass(props.size),
25
+ )}
26
+ data-selector={props.dataSelector}
27
+ >
28
+ {props.children}
29
+ </div>
30
+ );
@@ -0,0 +1,15 @@
1
+ import type { DataSelectableWithChildren } from './DataSelectable.ts';
2
+ import type { JSX } from 'react';
3
+
4
+ type IpponContainerProps = DataSelectableWithChildren<{
5
+ tag?: keyof JSX.IntrinsicElements;
6
+ }>;
7
+
8
+ export const IpponContainer = (props: IpponContainerProps) => {
9
+ const CustomTag: keyof JSX.IntrinsicElements = props.tag || 'div';
10
+ return (
11
+ <CustomTag className="ippon-container" data-selector={props.dataSelector}>
12
+ {props.children}
13
+ </CustomTag>
14
+ );
15
+ };
@@ -0,0 +1,56 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { DataSelectableWithChildren } from './DataSelectable.ts';
3
+ import type { IpponGridCol, IpponGridContainer, IpponGridGap, IpponGridMedia } from './Grid.ts';
4
+ import { clsx } from 'clsx';
5
+ import { toAlternativeClass } from './CAP.ts';
6
+ import { Optional } from './Optional.ts';
7
+
8
+ const toResponsiveClasses = (
9
+ prefix: string,
10
+ value: string | number | (string | number)[],
11
+ ): string =>
12
+ clsx(
13
+ Array.isArray(value)
14
+ ? value.map((v) => toAlternativeClass(`${prefix}-${v}`))
15
+ : toAlternativeClass(`${prefix}-${value}`),
16
+ );
17
+
18
+ const optionalToResponsiveClasses =
19
+ (prefix: string) =>
20
+ (value?: string | number | (string | number)[]): string | undefined =>
21
+ Optional.ofFalsifiable(value)
22
+ .map((v) => toResponsiveClasses(prefix, v))
23
+ .orUndefined();
24
+
25
+ type IpponGridProps = DataSelectableWithChildren<{
26
+ media?: IpponGridMedia;
27
+ container?: IpponGridContainer;
28
+ gap?: IpponGridGap;
29
+ }>;
30
+
31
+ export const IpponGrid = (props: IpponGridProps): ReactNode => (
32
+ <div
33
+ className={clsx(
34
+ 'ippon-grid',
35
+ optionalToResponsiveClasses('media')(props.media),
36
+ optionalToResponsiveClasses('container')(props.container),
37
+ optionalToResponsiveClasses('gap')(props.gap),
38
+ )}
39
+ data-selector={props.dataSelector}
40
+ >
41
+ {props.children}
42
+ </div>
43
+ );
44
+
45
+ type IpponGridSlotProps = DataSelectableWithChildren<{
46
+ col?: IpponGridCol;
47
+ }>;
48
+
49
+ export const IpponGridSlot = (props: IpponGridSlotProps): ReactNode => (
50
+ <div
51
+ className={clsx('ippon-grid--slot', optionalToResponsiveClasses('col')(props.col))}
52
+ data-selector={props.dataSelector}
53
+ >
54
+ {props.children}
55
+ </div>
56
+ );
@@ -0,0 +1,56 @@
1
+ import type { ReactNode, JSX } from 'react';
2
+ import type { IpponTokenSize } from './Tokens.ts';
3
+ import { clsx } from 'clsx';
4
+ import type { DataSelectableWithChildren } from './DataSelectable.ts';
5
+ import { Optional } from './Optional.ts';
6
+ import { optionalToPrefixedAlternativeClass, toAlternativeClass } from './CAP.ts';
7
+
8
+ type Align = 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom' | 'space-between';
9
+
10
+ type IpponHSpaceProps = DataSelectableWithChildren<{
11
+ gap?: IpponTokenSize;
12
+ align?: Align | Align[];
13
+ className?: string;
14
+ wrap?: boolean;
15
+ tag?: keyof JSX.IntrinsicElements;
16
+ }>;
17
+
18
+ const alignSetToClass = (alignSet: Set<Align>): string =>
19
+ clsx([...alignSet].map(toAlternativeClass));
20
+
21
+ const alignToClass = (align: Align | Align[]): string => {
22
+ if (typeof align === 'string') {
23
+ return toAlternativeClass(align);
24
+ }
25
+ return alignSetToClass(new Set<Align>(align));
26
+ };
27
+
28
+ export const IpponHSpace = (props: IpponHSpaceProps): ReactNode => {
29
+ const CustomTag: keyof JSX.IntrinsicElements = props.tag || 'div';
30
+ const className = clsx(
31
+ 'ippon-h-space',
32
+ optionalToPrefixedAlternativeClass('gap')(props.gap),
33
+ Optional.ofFalsifiable(props.align).map(alignToClass).orUndefined(),
34
+ props.className,
35
+ { '-wrap': props.wrap },
36
+ );
37
+
38
+ return (
39
+ <CustomTag className={className} data-selector={props.dataSelector}>
40
+ {props.children}
41
+ </CustomTag>
42
+ );
43
+ };
44
+
45
+ type IpponHSpaceSlotProps = DataSelectableWithChildren<{
46
+ expand?: boolean;
47
+ }>;
48
+
49
+ export const IpponHSpaceSlot = (props: IpponHSpaceSlotProps): ReactNode => (
50
+ <div
51
+ className={clsx('ippon-h-space--slot', { '-expand': props.expand })}
52
+ data-selector={props.dataSelector}
53
+ >
54
+ {props.children}
55
+ </div>
56
+ );
@@ -0,0 +1,15 @@
1
+ import { IpponIon, type IpponIonProps } from './IpponIon.tsx';
2
+ import { clsx } from 'clsx';
3
+ import type { IpponTokenTextColor, IpponTokenSize } from './Tokens.ts';
4
+
5
+ export type IpponIconProps = IpponIonProps & {
6
+ color?: IpponTokenTextColor;
7
+ size?: IpponTokenSize;
8
+ };
9
+
10
+ export const IpponIcon = (props: IpponIconProps) => {
11
+ const colorClass = props.color ? `-color-${props.color}` : null;
12
+ const sizeClass = props.size ? `-size-${props.size}` : null;
13
+ const className = clsx('ippon-icon', colorClass, sizeClass, props.className);
14
+ return <IpponIon {...props} className={className} />;
15
+ };
@@ -0,0 +1,128 @@
1
+ import { clsx } from 'clsx';
2
+ import type { ChangeEvent, DragEvent } from 'react';
3
+ import { useRef, useState } from 'react';
4
+ import { Optional } from './Optional.ts';
5
+ import type { DataSelectable } from './DataSelectable.ts';
6
+ import { IpponIcon } from './IpponIcon.tsx';
7
+ import { IpponText } from './IpponText.tsx';
8
+ import { IpponVSpace } from './IpponVSpace.tsx';
9
+ import { toAlternativeClass } from './CAP.ts';
10
+
11
+ type IpponImportFileCommonProps = {
12
+ accept?: string;
13
+ title: string;
14
+ description: string;
15
+ };
16
+
17
+ type IpponImportFileSingleProps = IpponImportFileCommonProps & {
18
+ multiple?: false;
19
+ onChange?: (file: File) => void;
20
+ };
21
+
22
+ type IpponImportFileMultipleProps = IpponImportFileCommonProps & {
23
+ multiple: true;
24
+ onChange?: (files: File[]) => void;
25
+ };
26
+
27
+ type IpponImportFileVanillaProps = IpponImportFileSingleProps | IpponImportFileMultipleProps;
28
+
29
+ type IpponImportFileProps = DataSelectable<IpponImportFileVanillaProps>;
30
+
31
+ const getFileForOnChange = (files: FileList): File => files[0];
32
+
33
+ const getFilesForOnChange = (files: FileList): File[] => Array.from(files);
34
+
35
+ const importOnChange = (props: IpponImportFileProps) => (files: FileList) => {
36
+ if (!props.onChange) {
37
+ return;
38
+ }
39
+ if (props.multiple) {
40
+ props.onChange(getFilesForOnChange(files));
41
+ return;
42
+ }
43
+ props.onChange(getFileForOnChange(files));
44
+ };
45
+
46
+ export const IpponImportFile = (props: IpponImportFileProps) => {
47
+ const [isDragover, setIsDragover] = useState(false);
48
+ const dragCounterRef = useRef(0);
49
+ const inputRef = useRef<HTMLInputElement>(null);
50
+
51
+ const resetFile = () => {
52
+ if (inputRef.current) {
53
+ inputRef.current.value = '';
54
+ }
55
+ };
56
+
57
+ const importFiles = (files: FileList) => {
58
+ importOnChange(props)(files);
59
+ resetFile();
60
+ };
61
+
62
+ const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
63
+ Optional.ofNullable(event.target.files)
64
+ .filter((files) => files.length > 0)
65
+ .ifPresent(importFiles);
66
+ };
67
+
68
+ const handleDragEnter = () => {
69
+ dragCounterRef.current += 1;
70
+ setIsDragover(true);
71
+ };
72
+
73
+ const handleDragLeave = () => {
74
+ dragCounterRef.current -= 1;
75
+ if (dragCounterRef.current === 0) {
76
+ setIsDragover(false);
77
+ }
78
+ };
79
+
80
+ const handleDragOver = (event: DragEvent<HTMLLabelElement>) => {
81
+ event.preventDefault();
82
+ };
83
+
84
+ const handleDrop = (event: DragEvent<HTMLLabelElement>) => {
85
+ event.preventDefault();
86
+ dragCounterRef.current = 0;
87
+ setIsDragover(false);
88
+ Optional.ofFalsifiable(event.dataTransfer?.files)
89
+ .filter((files) => files.length > 0)
90
+ .ifPresent(importFiles);
91
+ };
92
+
93
+ return (
94
+ <label
95
+ className={clsx('ippon-import-file', {
96
+ [toAlternativeClass('dragover')]: isDragover,
97
+ })}
98
+ data-selector={props.dataSelector}
99
+ onDragEnter={handleDragEnter}
100
+ onDragLeave={handleDragLeave}
101
+ onDragOver={handleDragOver}
102
+ onDrop={handleDrop}
103
+ >
104
+ <IpponVSpace gap={8} align="center">
105
+ <div className="ippon-import-file--icon">
106
+ <IpponIcon name="cloud-upload" size={24} />
107
+ </div>
108
+ <IpponVSpace gap={4} align="center">
109
+ <IpponText variant="body" weight="bold">
110
+ {props.title}
111
+ </IpponText>
112
+ <IpponText variant="body" size="small" color="neutral-tertiary-inversed">
113
+ {props.description}
114
+ </IpponText>
115
+ </IpponVSpace>
116
+ </IpponVSpace>
117
+ <input
118
+ ref={inputRef}
119
+ type="file"
120
+ className="ippon-import-file--input"
121
+ multiple={props.multiple}
122
+ accept={props.accept}
123
+ onChange={handleChange}
124
+ data-selector={props.dataSelector ? `${props.dataSelector}.input` : undefined}
125
+ />
126
+ </label>
127
+ );
128
+ };
@@ -0,0 +1,45 @@
1
+ import type { IconClassic, IconLogo, IconVariant } from '@ippon-ui/icons';
2
+ import { clsx } from 'clsx';
3
+ import type { DataSelectable } from './DataSelectable.ts';
4
+
5
+ type IpponIconClassic = {
6
+ name: IconClassic;
7
+ variant?: IconVariant;
8
+ };
9
+
10
+ type IpponIconLogo = {
11
+ name: IconLogo;
12
+ };
13
+
14
+ type IpponIconBase = {
15
+ className?: string;
16
+ onClick?: () => void;
17
+ };
18
+
19
+ export type IpponIonProps = DataSelectable<(IpponIconClassic | IpponIconLogo) & IpponIconBase>;
20
+
21
+ type IpponIconInfo = {
22
+ name: string;
23
+ variant?: string;
24
+ };
25
+
26
+ const toClassName = (props: IpponIconInfo): string => {
27
+ const { name, variant } = props;
28
+ const prefix = 'ippon-ion';
29
+ const variantSuffix = variant ? `-${variant}` : '';
30
+ return `${prefix}-${name}${variantSuffix}`;
31
+ };
32
+
33
+ export const IpponIon = (props: IpponIonProps) => {
34
+ const CustomTag = props.onClick ? 'button' : 'span';
35
+
36
+ return (
37
+ /* NOSONAR */ <CustomTag
38
+ role={props.onClick ? undefined : 'presentation'}
39
+ type={props.onClick ? 'button' : undefined}
40
+ className={clsx(toClassName(props), props.className)}
41
+ data-selector={props.dataSelector}
42
+ onClick={props.onClick}
43
+ ></CustomTag>
44
+ );
45
+ };
@@ -0,0 +1,43 @@
1
+ import { clsx } from 'clsx';
2
+ import type { DataSelectable } from './DataSelectable.ts';
3
+
4
+ declare module 'react' {
5
+ interface CSSProperties {
6
+ '--ippon-meter-percentage'?: string;
7
+ }
8
+ }
9
+
10
+ type IpponVanillaMeterProps = {
11
+ value: number;
12
+ min: number;
13
+ max: number;
14
+ label: string;
15
+ };
16
+
17
+ type IpponMeterProps = DataSelectable<IpponVanillaMeterProps>;
18
+
19
+ const toPercentage = (props: IpponVanillaMeterProps) => {
20
+ const range = props.max - props.min;
21
+ if (range === 0) {
22
+ return 0;
23
+ }
24
+ return ((props.value - props.min) / range) * 100;
25
+ };
26
+
27
+ export const IpponMeter = (props: IpponMeterProps) => {
28
+ const percentage = toPercentage(props);
29
+ const clampedPercentage = Math.min(Math.max(percentage, 0), 100);
30
+
31
+ return (
32
+ /* NOSONAR */ <div
33
+ className={clsx('ippon-meter')}
34
+ role="meter"
35
+ aria-valuenow={props.value}
36
+ aria-valuemin={props.min}
37
+ aria-valuemax={props.max}
38
+ aria-label={props.label}
39
+ data-selector={props.dataSelector}
40
+ style={{ '--ippon-meter-percentage': `${clampedPercentage}%` }}
41
+ ></div>
42
+ );
43
+ };
@@ -0,0 +1,45 @@
1
+ import { clsx } from 'clsx';
2
+ import type { DataSelectable } from './DataSelectable.ts';
3
+
4
+ declare module 'react' {
5
+ interface CSSProperties {
6
+ '--ippon-progress-percentage'?: string;
7
+ }
8
+ }
9
+
10
+ type IpponVanillaProgressProps = {
11
+ value: number;
12
+ min: number;
13
+ max: number;
14
+ label: string;
15
+ };
16
+
17
+ type IpponProgressProps = DataSelectable<IpponVanillaProgressProps>;
18
+
19
+ const toPercentage = (props: IpponVanillaProgressProps) => {
20
+ const range = props.max - props.min;
21
+ if (range === 0) {
22
+ return 0;
23
+ }
24
+ return ((props.value - props.min) / range) * 100;
25
+ };
26
+
27
+ export const IpponProgress = (props: IpponProgressProps) => {
28
+ const percentage = toPercentage(props);
29
+ const clampedPercentage = Math.min(Math.max(percentage, 0), 100);
30
+ const busy = props.value < props.max;
31
+
32
+ return (
33
+ /* NOSONAR */ <div
34
+ className={clsx('ippon-progress')}
35
+ role="progressbar"
36
+ aria-valuenow={props.value}
37
+ aria-valuemin={props.min}
38
+ aria-valuemax={props.max}
39
+ aria-label={props.label}
40
+ aria-busy={busy}
41
+ data-selector={props.dataSelector}
42
+ style={{ '--ippon-progress-percentage': `${clampedPercentage}%` }}
43
+ ></div>
44
+ );
45
+ };
@@ -0,0 +1,56 @@
1
+ import type { IpponTokenTextColor } from './Tokens.ts';
2
+ import type { DataSelectableWithChildren } from './DataSelectable.ts';
3
+ import { clsx } from 'clsx';
4
+ import { optionalToAlternativeClass, optionalToPrefixedAlternativeClass } from './CAP.ts';
5
+ import type { JSX } from 'react';
6
+
7
+ type IpponTextBodyProps = {
8
+ variant: 'body';
9
+ weight?: 'bold';
10
+ size?: 'small' | 'large';
11
+ };
12
+
13
+ type IpponTextLabelProps = {
14
+ variant: 'label';
15
+ size?: 'small' | 'large';
16
+ };
17
+
18
+ type IpponVariantProps = IpponTextBodyProps | IpponTextLabelProps;
19
+
20
+ type IpponVanillaTextProps = {
21
+ color?: IpponTokenTextColor;
22
+ tag?: keyof JSX.IntrinsicElements;
23
+ placeholder?: boolean;
24
+ } & IpponVariantProps;
25
+
26
+ type IpponTextProps = DataSelectableWithChildren<IpponVanillaTextProps>;
27
+
28
+ const toBodyVariantClasses = (props: IpponTextBodyProps) =>
29
+ clsx('-body', optionalToAlternativeClass(props.weight), optionalToAlternativeClass(props.size));
30
+
31
+ const toLabelVariantClasses = (props: IpponTextLabelProps) =>
32
+ clsx('-label', optionalToAlternativeClass(props.size));
33
+
34
+ const toVariantClasses = (props: IpponVariantProps) => {
35
+ if (props.variant === 'body') {
36
+ return toBodyVariantClasses(props);
37
+ }
38
+ return toLabelVariantClasses(props);
39
+ };
40
+
41
+ export const IpponText = (props: IpponTextProps) => {
42
+ const CustomTag: keyof JSX.IntrinsicElements = props.tag || 'span';
43
+ return (
44
+ <CustomTag
45
+ className={clsx(
46
+ 'ippon-text',
47
+ toVariantClasses(props),
48
+ optionalToPrefixedAlternativeClass('color')(props.color),
49
+ props.placeholder && '-placeholder',
50
+ )}
51
+ data-selector={props.dataSelector}
52
+ >
53
+ {props.children}
54
+ </CustomTag>
55
+ );
56
+ };
@@ -0,0 +1,45 @@
1
+ import type { DataSelectableWithChildren } from './DataSelectable.ts';
2
+ import { clsx } from 'clsx';
3
+ import type { JSX } from 'react';
4
+ import { Optional } from './Optional.ts';
5
+
6
+ type Level = 1 | 2 | 3 | 4 | 5;
7
+
8
+ type HeadingTag = `h${Level}`;
9
+
10
+ type IpponTitleWithHeadingProps = DataSelectableWithChildren<{
11
+ tag: HeadingTag;
12
+ level?: Level;
13
+ placeholder?: boolean;
14
+ }>;
15
+
16
+ type IpponTitleWithNonHeadingProps = DataSelectableWithChildren<{
17
+ tag?: Exclude<keyof JSX.IntrinsicElements, HeadingTag>;
18
+ level: Level;
19
+ placeholder?: boolean;
20
+ }>;
21
+
22
+ type IpponTitleProps = IpponTitleWithHeadingProps | IpponTitleWithNonHeadingProps;
23
+
24
+ const levelToClass = (level?: Level): string | undefined =>
25
+ Optional.ofFalsifiable(level)
26
+ .map((l) => `-l${l}`)
27
+ .orUndefined();
28
+
29
+ export const IpponTitle = (props: IpponTitleProps) => {
30
+ const CustomTag: keyof JSX.IntrinsicElements = (props.tag ||
31
+ 'div') as keyof JSX.IntrinsicElements;
32
+
33
+ return (
34
+ <CustomTag
35
+ className={clsx(
36
+ 'ippon-title',
37
+ levelToClass(props.level),
38
+ props.placeholder && '-placeholder',
39
+ )}
40
+ data-selector={props.dataSelector}
41
+ >
42
+ {props.children}
43
+ </CustomTag>
44
+ );
45
+ };
@@ -0,0 +1,43 @@
1
+ import type { JSX, ReactNode } from 'react';
2
+ import type { IpponTokenSize } from './Tokens.ts';
3
+ import { clsx } from 'clsx';
4
+ import type { DataSelectableWithChildren } from './DataSelectable.ts';
5
+ import { optionalToAlternativeClass, optionalToPrefixedAlternativeClass } from './CAP.ts';
6
+
7
+ type Align = 'left' | 'center' | 'justify' | 'right';
8
+
9
+ type IpponVSpaceProps = DataSelectableWithChildren<{
10
+ gap?: IpponTokenSize;
11
+ align?: Align;
12
+ className?: string;
13
+ tag?: keyof JSX.IntrinsicElements;
14
+ }>;
15
+
16
+ export const IpponVSpace = (props: IpponVSpaceProps): ReactNode => {
17
+ const CustomTag: keyof JSX.IntrinsicElements = props.tag || 'div';
18
+ const className = clsx(
19
+ 'ippon-v-space',
20
+ optionalToPrefixedAlternativeClass('gap')(props.gap),
21
+ optionalToAlternativeClass(props.align),
22
+ props.className,
23
+ );
24
+
25
+ return (
26
+ <CustomTag className={className} data-selector={props.dataSelector}>
27
+ {props.children}
28
+ </CustomTag>
29
+ );
30
+ };
31
+
32
+ type IpponVSpaceSlotProps = DataSelectableWithChildren<{
33
+ align?: Align;
34
+ }>;
35
+
36
+ export const IpponVSpaceSlot = (props: IpponVSpaceSlotProps): ReactNode => (
37
+ <div
38
+ className={clsx('ippon-v-space--slot', optionalToAlternativeClass(props.align))}
39
+ data-selector={props.dataSelector}
40
+ >
41
+ {props.children}
42
+ </div>
43
+ );