nova-design-model-testing 0.0.2 → 0.0.3

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/guidelines/components/action-sheet.md +30 -0
  2. package/guidelines/components/avatar.md +30 -0
  3. package/guidelines/components/badge.md +45 -0
  4. package/guidelines/components/bottom-navigation.md +30 -0
  5. package/guidelines/components/bottom-sheet.md +30 -0
  6. package/guidelines/components/button-group.md +56 -0
  7. package/guidelines/components/button.md +76 -0
  8. package/guidelines/components/card.md +30 -0
  9. package/guidelines/components/checkbox.md +30 -0
  10. package/guidelines/components/chips.md +30 -0
  11. package/guidelines/components/confirm-sheet.md +30 -0
  12. package/guidelines/components/date-picker.md +30 -0
  13. package/guidelines/components/date-wheel.md +30 -0
  14. package/guidelines/components/icon.md +29 -0
  15. package/guidelines/components/inline-notification.md +30 -0
  16. package/guidelines/components/list-item.md +30 -0
  17. package/guidelines/components/list.md +30 -0
  18. package/guidelines/components/loading-indicator.md +30 -0
  19. package/guidelines/components/menu.md +57 -0
  20. package/guidelines/components/passcode.md +35 -0
  21. package/guidelines/components/radio.md +62 -0
  22. package/guidelines/components/tabs.md +30 -0
  23. package/guidelines/components/text-field.md +68 -0
  24. package/guidelines/components/toast.md +30 -0
  25. package/guidelines/components/toggle.md +30 -0
  26. package/guidelines/components/top-navigation.md +30 -0
  27. package/guidelines/foundations/colors.md +43 -0
  28. package/guidelines/foundations/radius.md +16 -0
  29. package/guidelines/foundations/spacing.md +16 -0
  30. package/guidelines/guidelines.md +36 -0
  31. package/guidelines/overview-components.md +16 -0
  32. package/guidelines/overview-icons.md +18 -0
  33. package/guidelines/overview-setup.md +15 -0
  34. package/package.json +1 -1
  35. package/public/vite.svg +1 -0
  36. package/src/App.css +42 -0
  37. package/src/App.jsx +6328 -0
  38. package/src/assets/fonts/inter_bold.otf +0 -0
  39. package/src/assets/fonts/inter_italic.otf +0 -0
  40. package/src/assets/fonts/inter_medium.otf +0 -0
  41. package/src/assets/fonts/inter_regular.otf +0 -0
  42. package/src/assets/fonts/inter_semi_bold.otf +0 -0
  43. package/src/assets/fonts/space_grotesk_bold.otf +0 -0
  44. package/src/assets/fonts/space_grotesk_medium.otf +0 -0
  45. package/src/assets/fonts/space_grotesk_regular.otf +0 -0
  46. package/src/assets/fonts/space_grotesk_semi_bold.otf +0 -0
  47. package/src/assets/icons/Style=Line-1.svg +5 -0
  48. package/src/assets/icons/Style=Line-2.svg +5 -0
  49. package/src/assets/icons/Style=Line-3.svg +5 -0
  50. package/src/assets/icons/Style=Line.svg +5 -0
  51. package/src/assets/icons/Style=Solid.svg +7 -0
  52. package/src/assets/icons/line/Style=Line-1.svg +5 -0
  53. package/src/assets/icons/line/Style=Line-2.svg +5 -0
  54. package/src/assets/icons/line/Style=Line-3.svg +5 -0
  55. package/src/assets/icons/line/Style=Line.svg +5 -0
  56. package/src/assets/icons/line/arrow_circle_down.svg +5 -0
  57. package/src/assets/icons/line/arrow_circle_down_left_ltr.svg +5 -0
  58. package/src/assets/icons/line/arrow_circle_down_right_ltr.svg +5 -0
  59. package/src/assets/icons/line/arrow_circle_left_ltr.svg +5 -0
  60. package/src/assets/icons/line/arrow_circle_right.svg +3 -0
  61. package/src/assets/icons/line/arrow_circle_right_ltr.svg +5 -0
  62. package/src/assets/icons/line/arrow_circle_up.svg +5 -0
  63. package/src/assets/icons/line/arrow_circle_up_left_ltr.svg +5 -0
  64. package/src/assets/icons/line/arrow_circle_up_right_ltr.svg +5 -0
  65. package/src/assets/icons/line/arrow_narrow_down.svg +5 -0
  66. package/src/assets/icons/line/arrow_narrow_down_left_ltr.svg +5 -0
  67. package/src/assets/icons/line/arrow_narrow_down_left_vturn.svg +5 -0
  68. package/src/assets/icons/line/arrow_narrow_down_right_ltr.svg +5 -0
  69. package/src/assets/icons/line/arrow_narrow_left_ltr.svg +5 -0
  70. package/src/assets/icons/line/arrow_narrow_up.svg +5 -0
  71. package/src/assets/icons/line/arrow_narrow_up_left_ltr.svg +5 -0
  72. package/src/assets/icons/line/arrow_narrow_up_right_ltr.svg +5 -0
  73. package/src/assets/icons/line/arrow_turn_down_right_ltr.svg +5 -0
  74. package/src/assets/icons/line/arrows_down.svg +5 -0
  75. package/src/assets/icons/line/arrows_left_ltr.svg +5 -0
  76. package/src/assets/icons/line/arrows_right_ltr.svg +5 -0
  77. package/src/assets/icons/line/arrows_up.svg +5 -0
  78. package/src/assets/icons/line/chevron-selector-vertical.svg +5 -0
  79. package/src/assets/icons/line/chevron_down.svg +5 -0
  80. package/src/assets/icons/line/chevron_down_double.svg +5 -0
  81. package/src/assets/icons/line/chevron_left_double_ltr.svg +5 -0
  82. package/src/assets/icons/line/chevron_left_ltr.svg +5 -0
  83. package/src/assets/icons/line/chevron_right_double_ltr.svg +5 -0
  84. package/src/assets/icons/line/chevron_right_ltr.svg +5 -0
  85. package/src/assets/icons/line/chevron_selector_vertical.svg +5 -0
  86. package/src/assets/icons/line/chevron_up.svg +5 -0
  87. package/src/assets/icons/line/chevron_up_double.svg +5 -0
  88. package/src/assets/icons/line/coin_swap.svg +3 -0
  89. package/src/assets/icons/line/credit_card_edit.svg +5 -0
  90. package/src/assets/icons/line/dev.svg +5 -0
  91. package/src/assets/icons/line/expand_01.svg +5 -0
  92. package/src/assets/icons/line/expand_02.svg +5 -0
  93. package/src/assets/icons/line/flip_backward_ltr.svg +5 -0
  94. package/src/assets/icons/line/flip_forward_ltr.svg +5 -0
  95. package/src/assets/icons/line/home_05.svg +3 -0
  96. package/src/assets/icons/line/infinity.svg +5 -0
  97. package/src/assets/icons/line/minimise.svg +6 -0
  98. package/src/assets/icons/line/moon_01.svg +3 -0
  99. package/src/assets/icons/line/paint.svg +3 -0
  100. package/src/assets/icons/line/plus_circle.svg +3 -0
  101. package/src/assets/icons/line/refresh.svg +5 -0
  102. package/src/assets/icons/line/reset.svg +5 -0
  103. package/src/assets/icons/line/reverse_left_ltr.svg +5 -0
  104. package/src/assets/icons/line/reverse_right_ltr.svg +5 -0
  105. package/src/assets/icons/line/safe_01.svg +3 -0
  106. package/src/assets/icons/line/search.svg +3 -0
  107. package/src/assets/icons/line/shield_star.svg +3 -0
  108. package/src/assets/icons/line/sun.svg +3 -0
  109. package/src/assets/icons/line/switch_horizontal.svg +5 -0
  110. package/src/assets/icons/line/switch_vertical.svg +5 -0
  111. package/src/assets/icons/solid/arrow_circle_right.svg +3 -0
  112. package/src/assets/icons/solid/coin_swap.svg +6 -0
  113. package/src/assets/icons/solid/credit_card_edit.svg +6 -0
  114. package/src/assets/icons/solid/safe_01.svg +5 -0
  115. package/src/assets/images/New user_gold.png +0 -0
  116. package/src/assets/images/Sheet-1.png +0 -0
  117. package/src/assets/images/Sheet.png +0 -0
  118. package/src/assets/logos/botim_credit_dark.svg +22 -0
  119. package/src/assets/logos/botim_credit_light.svg +31 -0
  120. package/src/assets/logos/botim_invest_dark.svg +22 -0
  121. package/src/assets/logos/botim_invest_light.svg +31 -0
  122. package/src/assets/logos/botim_main_dark.svg +18 -0
  123. package/src/assets/logos/botim_main_light.svg +17 -0
  124. package/src/assets/logos/botim_money_dark.svg +26 -0
  125. package/src/assets/logos/botim_money_light.svg +28 -0
  126. package/src/assets/logos/botim_pay_dark.svg +24 -0
  127. package/src/assets/logos/botim_pay_light.svg +35 -0
  128. package/src/assets/react.svg +1 -0
  129. package/src/design-system/components/action-sheet/ActionSheet.jsx +3 -0
  130. package/src/design-system/components/avatar/Avatar.jsx +3 -0
  131. package/src/design-system/components/badge/Badge.jsx +68 -0
  132. package/src/design-system/components/bottom-navigation/BottomNavigation.jsx +3 -0
  133. package/src/design-system/components/bottom-sheet/BottomSheet.jsx +3 -0
  134. package/src/design-system/components/button/Button.jsx +310 -0
  135. package/src/design-system/components/button-group/ButtonGroup.jsx +150 -0
  136. package/src/design-system/components/card/Card.jsx +3 -0
  137. package/src/design-system/components/checkbox/Checkbox.jsx +3 -0
  138. package/src/design-system/components/chips/Chips.jsx +3 -0
  139. package/src/design-system/components/confirm-sheet/ConfirmSheet.jsx +3 -0
  140. package/src/design-system/components/date-picker/DatePicker.jsx +3 -0
  141. package/src/design-system/components/date-wheel/DateWheel.jsx +3 -0
  142. package/src/design-system/components/digit-field/DigitField.jsx +191 -0
  143. package/src/design-system/components/icon/Icon.jsx +154 -0
  144. package/src/design-system/components/inline-notification/InlineNotification.jsx +3 -0
  145. package/src/design-system/components/list/List.jsx +3 -0
  146. package/src/design-system/components/list-item/ListItem.jsx +3 -0
  147. package/src/design-system/components/loading-indicator/LoadingIndicator.jsx +3 -0
  148. package/src/design-system/components/logo/AppLogo.jsx +35 -0
  149. package/src/design-system/components/logo/BrandingLogo.jsx +39 -0
  150. package/src/design-system/components/logo/Logo.jsx +39 -0
  151. package/src/design-system/components/menu/Menu.jsx +100 -0
  152. package/src/design-system/components/menu/MenuItem.jsx +184 -0
  153. package/src/design-system/components/passcode/Passcode.jsx +165 -0
  154. package/src/design-system/components/radio/Radio.jsx +135 -0
  155. package/src/design-system/components/section-header-footer/SectionHeaderFooter.jsx +7 -0
  156. package/src/design-system/components/tabs/Tabs.jsx +3 -0
  157. package/src/design-system/components/text-field/TextField.jsx +308 -0
  158. package/src/design-system/components/toast/Toast.jsx +3 -0
  159. package/src/design-system/components/toggle/Toggle.jsx +3 -0
  160. package/src/design-system/components/top-navigation/TopNavigation.jsx +3 -0
  161. package/src/design-system/theme/ThemeContext.jsx +11 -0
  162. package/src/design-system/tokens/primitives/colors.json +122 -0
  163. package/src/design-system/tokens/primitives/radius.json +9 -0
  164. package/src/design-system/tokens/primitives/sizes.json +10 -0
  165. package/src/design-system/tokens/primitives/spacing.json +10 -0
  166. package/src/design-system/tokens/semantic/colors.dark.json +139 -0
  167. package/src/design-system/tokens/semantic/colors.light.json +139 -0
  168. package/src/design-system/tokens/semantic/spacing.json +18 -0
  169. package/src/index.css +99 -0
  170. package/src/main.jsx +10 -0
@@ -0,0 +1,154 @@
1
+ import primitiveColors from '../../tokens/primitives/colors.json'
2
+ import semanticLight from '../../tokens/semantic/colors.light.json'
3
+ import semanticDark from '../../tokens/semantic/colors.dark.json'
4
+ import sizeTokens from '../../tokens/primitives/sizes.json'
5
+ import { useTheme } from '../../theme/ThemeContext.jsx'
6
+
7
+ import arrowCircleRightLine from '../../../assets/icons/line/arrow_circle_right.svg?raw'
8
+ import chevronRightLine from '../../../assets/icons/line/chevron_right_ltr.svg?raw'
9
+ import coinSwapLine from '../../../assets/icons/line/coin_swap.svg?raw'
10
+ import creditCardEditLine from '../../../assets/icons/line/credit_card_edit.svg?raw'
11
+ import devLine from '../../../assets/icons/line/dev.svg?raw'
12
+ import expand01Line from '../../../assets/icons/line/expand_01.svg?raw'
13
+ import expand02Line from '../../../assets/icons/line/expand_02.svg?raw'
14
+ import home05Line from '../../../assets/icons/line/home_05.svg?raw'
15
+ import infinityLine from '../../../assets/icons/line/infinity.svg?raw'
16
+ import moon01Line from '../../../assets/icons/line/moon_01.svg?raw'
17
+ import paintLine from '../../../assets/icons/line/paint.svg?raw'
18
+ import plusCircleLine from '../../../assets/icons/line/plus_circle.svg?raw'
19
+ import safeLine from '../../../assets/icons/line/safe_01.svg?raw'
20
+ import searchLine from '../../../assets/icons/line/search.svg?raw'
21
+ import shieldStarLine from '../../../assets/icons/line/shield_star.svg?raw'
22
+ import sunLine from '../../../assets/icons/line/sun.svg?raw'
23
+
24
+ import arrowCircleRightSolid from '../../../assets/icons/solid/arrow_circle_right.svg?raw'
25
+ import coinSwapSolid from '../../../assets/icons/solid/coin_swap.svg?raw'
26
+ import creditCardEditSolid from '../../../assets/icons/solid/credit_card_edit.svg?raw'
27
+ import safeSolid from '../../../assets/icons/solid/safe_01.svg?raw'
28
+
29
+ const ICON_SET = {
30
+ line: {
31
+ arrow_circle_right: arrowCircleRightLine,
32
+ chevron_right_ltr: chevronRightLine,
33
+ coin_swap: coinSwapLine,
34
+ credit_card_edit: creditCardEditLine,
35
+ dev: devLine,
36
+ expand_01: expand01Line,
37
+ expand_02: expand02Line,
38
+ home_05: home05Line,
39
+ infinity: infinityLine,
40
+ moon_01: moon01Line,
41
+ paint: paintLine,
42
+ plus_circle: plusCircleLine,
43
+ safe_01: safeLine,
44
+ search: searchLine,
45
+ shield_star: shieldStarLine,
46
+ sun: sunLine,
47
+ },
48
+ solid: {
49
+ arrow_circle_right: arrowCircleRightSolid,
50
+ coin_swap: coinSwapSolid,
51
+ credit_card_edit: creditCardEditSolid,
52
+ safe_01: safeSolid,
53
+ },
54
+ }
55
+
56
+ const SIZE_MAP = {
57
+ xxs: sizeTokens['size-xxs'],
58
+ xs: sizeTokens['size-xs'],
59
+ sm: sizeTokens['size-xs'],
60
+ md: sizeTokens['size-md'],
61
+ lg: sizeTokens['size-lg'],
62
+ xl: sizeTokens['size-xl'],
63
+ '2xl': sizeTokens['size-2xl'],
64
+ '3xl': sizeTokens['size-3xl'],
65
+ '4xl': sizeTokens['size-4xl'],
66
+ }
67
+
68
+ function resolveColorToken(color, themeTokens) {
69
+ if (!color || typeof color !== 'string') {
70
+ return getSemanticToken(themeTokens, 'text icon/neutral/primary')
71
+ }
72
+
73
+ if (color === 'currentColor' || color.startsWith('#') || color.startsWith('rgb')) {
74
+ return color
75
+ }
76
+
77
+ if (color.includes('/')) {
78
+ return getSemanticToken(themeTokens, color)
79
+ }
80
+
81
+ return getSemanticToken(themeTokens, 'text icon/neutral/primary')
82
+ }
83
+
84
+ function resolvePrimitiveReference(reference) {
85
+ if (typeof reference !== 'string' || !reference.startsWith('primitives.')) {
86
+ return reference
87
+ }
88
+
89
+ const path = reference.replace('primitives.', '').split('.')
90
+ let current = primitiveColors
91
+
92
+ for (const key of path) {
93
+ if (!current || typeof current !== 'object') {
94
+ return reference
95
+ }
96
+ current = current[key]
97
+ }
98
+
99
+ return current ?? reference
100
+ }
101
+
102
+ function getSemanticToken(themeTokens, token) {
103
+ const reference = themeTokens[token]
104
+ return resolvePrimitiveReference(reference)
105
+ }
106
+
107
+ export default function Icon({
108
+ name = 'chevron_right_ltr',
109
+ variant = 'line',
110
+ size = 'xs',
111
+ color = 'text icon/neutral/primary',
112
+ className,
113
+ ...rest
114
+ }) {
115
+ const theme = useTheme()
116
+ const themeTokens = theme === 'dark' ? semanticDark : semanticLight
117
+ const resolvedSize =
118
+ typeof size === 'number' ? size : SIZE_MAP[size] || sizeTokens['size-xs']
119
+ const resolvedColor = resolveColorToken(color, themeTokens)
120
+ const iconSvg = ICON_SET[variant]?.[name] || ICON_SET.line.chevron_right_ltr
121
+
122
+ const tintedSvg = iconSvg
123
+ .replace(/<svg[^>]*>/i, (match) => {
124
+ // Remove hardcoded width/height from svg tag, we set them on the container
125
+ return match
126
+ .replace(/width="[^"]*"/gi, 'width="100%"')
127
+ .replace(/height="[^"]*"/gi, 'height="100%"')
128
+ .replace(/fill="(?!none|transparent)[^"]*"/gi, 'fill="none"') // SVG tag should usually not have a fill
129
+ })
130
+ .replace(/fill="(?!none|transparent)[^"]*"/gi, 'fill="currentColor"')
131
+ .replace(/stroke="(?!none|transparent)[^"]*"/gi, 'stroke="currentColor"')
132
+ .replace(/style="[^"]*"/gi, '')
133
+ .replace(/<path(?![^>]*fill=)/gi, '<path fill="none"')
134
+ .replace(/<rect(?![^>]*fill=)/gi, '<rect fill="none"')
135
+ .replace(/<circle(?![^>]*fill=)/gi, '<circle fill="none"')
136
+ .replace(/<ellipse(?![^>]*fill=)/gi, '<ellipse fill="none"')
137
+ .replace(/<polygon(?![^>]*fill=)/gi, '<polygon fill="none"')
138
+ .replace(/<polyline(?![^>]*fill=)/gi, '<polyline fill="none"')
139
+ .replace(/id="[^"]*"/gi, '')
140
+
141
+ return (
142
+ <span
143
+ className={className}
144
+ style={{
145
+ width: resolvedSize,
146
+ height: resolvedSize,
147
+ color: resolvedColor,
148
+ display: 'inline-flex',
149
+ }}
150
+ dangerouslySetInnerHTML={{ __html: tintedSvg }}
151
+ {...rest}
152
+ />
153
+ )
154
+ }
@@ -0,0 +1,3 @@
1
+ export const InlineNotification = () => {
2
+ return null;
3
+ };
@@ -0,0 +1,3 @@
1
+ export const List = () => {
2
+ return null;
3
+ };
@@ -0,0 +1,3 @@
1
+ export const ListItem = () => {
2
+ return null;
3
+ };
@@ -0,0 +1,3 @@
1
+ export const LoadingIndicator = () => {
2
+ return null;
3
+ };
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+
3
+ export const AppLogo = ({ size = 40, className = '' }) => {
4
+ return (
5
+ <div
6
+ className={`overflow-hidden relative rounded-[22%] ${className}`}
7
+ style={{
8
+ width: `${size}px`,
9
+ height: `${size}px`,
10
+ backgroundImage: "linear-gradient(120.8deg, #000000 9.76%, #011FE5 90.24%)"
11
+ }}
12
+ >
13
+ <div className="absolute inset-0 bg-[#011FE5]" />
14
+ {/* SVG Icon implementation based on Figma paths */}
15
+ <svg
16
+ viewBox="0 0 240 240"
17
+ className="absolute inset-0 w-full h-full p-[24%]"
18
+ fill="none"
19
+ xmlns="http://www.w3.org/2000/svg"
20
+ >
21
+ <path
22
+ d="M117.8 181.4C152.9 181.4 181.4 152.9 181.4 117.8C181.4 82.7 152.9 54.2 117.8 54.2C82.7 54.2 54.2 82.7 54.2 117.8C54.2 152.9 82.7 181.4 117.8 181.4Z"
23
+ fill="white"
24
+ />
25
+ <path
26
+ d="M117.8 117.8L117.8 54.2C100.8 54.2 84.5 60.9 72.5 72.9C60.5 84.9 53.8 101.2 53.8 118.2L117.8 117.8Z"
27
+ fill="white"
28
+ />
29
+ <circle cx="117.8" cy="117.8" r="15" fill="#011FE5" />
30
+ </svg>
31
+ </div>
32
+ );
33
+ };
34
+
35
+ export default AppLogo;
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { useTheme } from '../../theme/ThemeContext';
3
+
4
+ import botimMainLight from '../../../assets/logos/botim_main_light.svg';
5
+ import botimMainDark from '../../../assets/logos/botim_main_dark.svg';
6
+ import botimPayLight from '../../../assets/logos/botim_pay_light.svg';
7
+ import botimPayDark from '../../../assets/logos/botim_pay_dark.svg';
8
+ import botimMoneyLight from '../../../assets/logos/botim_money_light.svg';
9
+ import botimMoneyDark from '../../../assets/logos/botim_money_dark.svg';
10
+ import botimInvestLight from '../../../assets/logos/botim_invest_light.svg';
11
+ import botimInvestDark from '../../../assets/logos/botim_invest_dark.svg';
12
+ import botimCreditLight from '../../../assets/logos/botim_credit_light.svg';
13
+ import botimCreditDark from '../../../assets/logos/botim_credit_dark.svg';
14
+
15
+ const LOGO_MAP = {
16
+ 'Botim Main': { light: botimMainLight, dark: botimMainDark },
17
+ 'Botim Pay': { light: botimPayLight, dark: botimPayDark },
18
+ 'Botim Money': { light: botimMoneyLight, dark: botimMoneyDark },
19
+ 'Botim Invest': { light: botimInvestLight, dark: botimInvestDark },
20
+ 'Botim Credit': { light: botimCreditLight, dark: botimCreditDark },
21
+ };
22
+
23
+ export const BrandingLogo = ({ type = 'Botim Main', height = 24, className = '' }) => {
24
+ const theme = useTheme();
25
+ const isDark = theme === 'dark';
26
+ const logo = LOGO_MAP[type] || LOGO_MAP['Botim Main'];
27
+ const src = isDark ? logo.dark : logo.light;
28
+
29
+ return (
30
+ <img
31
+ src={src}
32
+ alt={`${type} logo`}
33
+ style={{ height: `${height}px` }}
34
+ className={className}
35
+ />
36
+ );
37
+ };
38
+
39
+ export default BrandingLogo;
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { useTheme } from '../../theme/ThemeContext';
3
+
4
+ import botimMainLight from '../../../assets/logos/botim_main_light.svg';
5
+ import botimMainDark from '../../../assets/logos/botim_main_dark.svg';
6
+ import botimPayLight from '../../../assets/logos/botim_pay_light.svg';
7
+ import botimPayDark from '../../../assets/logos/botim_pay_dark.svg';
8
+ import botimMoneyLight from '../../../assets/logos/botim_money_light.svg';
9
+ import botimMoneyDark from '../../../assets/logos/botim_money_dark.svg';
10
+ import botimInvestLight from '../../../assets/logos/botim_invest_light.svg';
11
+ import botimInvestDark from '../../../assets/logos/botim_invest_dark.svg';
12
+ import botimCreditLight from '../../../assets/logos/botim_credit_light.svg';
13
+ import botimCreditDark from '../../../assets/logos/botim_credit_dark.svg';
14
+
15
+ const LOGO_MAP = {
16
+ main: { light: botimMainLight, dark: botimMainDark },
17
+ pay: { light: botimPayLight, dark: botimPayDark },
18
+ money: { light: botimMoneyLight, dark: botimMoneyDark },
19
+ invest: { light: botimInvestLight, dark: botimInvestDark },
20
+ credit: { light: botimCreditLight, dark: botimCreditDark },
21
+ };
22
+
23
+ export const Logo = ({ type = 'main', height = 24, className = '' }) => {
24
+ const theme = useTheme();
25
+ const isDark = theme === 'dark';
26
+ const logo = LOGO_MAP[type] || LOGO_MAP.main;
27
+ const src = isDark ? logo.dark : logo.light;
28
+
29
+ return (
30
+ <img
31
+ src={src}
32
+ alt={`Botim ${type} logo`}
33
+ style={{ height: `${height}px` }}
34
+ className={className}
35
+ />
36
+ );
37
+ };
38
+
39
+ export default Logo;
@@ -0,0 +1,100 @@
1
+ import primitiveColors from '../../tokens/primitives/colors.json'
2
+ import semanticLight from '../../tokens/semantic/colors.light.json'
3
+ import semanticDark from '../../tokens/semantic/colors.dark.json'
4
+ import { useTheme } from '../../theme/ThemeContext.jsx'
5
+ import MenuItem from './MenuItem.jsx'
6
+
7
+ function resolvePrimitiveReference(reference) {
8
+ if (typeof reference !== 'string' || !reference.startsWith('primitives.')) {
9
+ return reference
10
+ }
11
+
12
+ const path = reference.replace('primitives.', '').split('.')
13
+ let current = primitiveColors
14
+
15
+ for (const key of path) {
16
+ if (!current || typeof current !== 'object') {
17
+ return reference
18
+ }
19
+ current = current[key]
20
+ }
21
+
22
+ return current ?? reference
23
+ }
24
+
25
+ function getSemanticToken(themeTokens, token) {
26
+ const reference = themeTokens[token]
27
+ return resolvePrimitiveReference(reference)
28
+ }
29
+
30
+ export default function Menu({
31
+ items = [],
32
+ count,
33
+ itemProps = {},
34
+ style: customStyle = {},
35
+ ...rest
36
+ }) {
37
+ const theme = useTheme()
38
+ const themeTokens = theme === 'dark' ? semanticDark : semanticLight
39
+ const background = getSemanticToken(themeTokens, 'background/neutral/action sheet')
40
+ const divider = getSemanticToken(themeTokens, 'background/neutral/secondary')
41
+ const shadowColor = theme === 'dark' ? 'rgba(5, 5, 5, 0.4)' : 'rgba(157, 173, 197, 0.5)'
42
+ const resolvedCount = count === undefined || count === null ? items.length : Number(count)
43
+ const normalizedCount = Number.isFinite(resolvedCount) ? resolvedCount : items.length
44
+ const safeCount = Math.max(0, normalizedCount)
45
+ const menuItems = items.slice(0, safeCount)
46
+ if (menuItems.length < safeCount) {
47
+ const placeholders = Array.from({ length: safeCount - menuItems.length }).map((_, index) => ({
48
+ id: `menu-placeholder-${index}`,
49
+ labelText: 'Label',
50
+ showTrailingIcon: true,
51
+ }))
52
+ menuItems.push(...placeholders)
53
+ }
54
+
55
+ return (
56
+ <div
57
+ style={{
58
+ width: 260,
59
+ display: 'flex',
60
+ flexDirection: 'column',
61
+ boxShadow: `0 8px 22px 0 ${shadowColor}`,
62
+ backgroundColor: background,
63
+ borderRadius: 12,
64
+ padding: 8,
65
+ overflow: 'hidden',
66
+ ...customStyle,
67
+ }}
68
+ {...rest}
69
+ >
70
+ <div
71
+ style={{
72
+ display: 'flex',
73
+ flexDirection: 'column',
74
+ gap: 0,
75
+ }}
76
+ >
77
+ {menuItems.map((item, index) => (
78
+ <div key={item.id || `${item.labelText}-${index}`}>
79
+ <MenuItem {...itemProps} {...item} />
80
+ {index < menuItems.length - 1 ? (
81
+ <div
82
+ style={{
83
+ padding: '0 16px',
84
+ }}
85
+ >
86
+ <div
87
+ style={{
88
+ height: 1,
89
+ width: '100%',
90
+ backgroundColor: divider,
91
+ }}
92
+ />
93
+ </div>
94
+ ) : null}
95
+ </div>
96
+ ))}
97
+ </div>
98
+ </div>
99
+ )
100
+ }
@@ -0,0 +1,184 @@
1
+ import { useState } from 'react'
2
+ import primitiveColors from '../../tokens/primitives/colors.json'
3
+ import semanticLight from '../../tokens/semantic/colors.light.json'
4
+ import semanticDark from '../../tokens/semantic/colors.dark.json'
5
+ import { useTheme } from '../../theme/ThemeContext.jsx'
6
+
7
+ const STATE_ALIASES = {
8
+ Default: 'default',
9
+ Pressed: 'pressed',
10
+ Error: 'error',
11
+ Disabled: 'disabled',
12
+ }
13
+
14
+ function resolvePrimitiveReference(reference) {
15
+ if (typeof reference !== 'string' || !reference.startsWith('primitives.')) {
16
+ return reference
17
+ }
18
+
19
+ const path = reference.replace('primitives.', '').split('.')
20
+ let current = primitiveColors
21
+
22
+ for (const key of path) {
23
+ if (!current || typeof current !== 'object') {
24
+ return reference
25
+ }
26
+ current = current[key]
27
+ }
28
+
29
+ return current ?? reference
30
+ }
31
+
32
+ function getSemanticToken(themeTokens, token) {
33
+ const reference = themeTokens[token]
34
+ return resolvePrimitiveReference(reference)
35
+ }
36
+
37
+ function getMenuItemColors(themeTokens, state, pressedActive) {
38
+ if (state === 'disabled') {
39
+ return {
40
+ background: getSemanticToken(themeTokens, 'button/secondary/bg-disabled'),
41
+ text: getSemanticToken(themeTokens, 'text icon/neutral/disabled'),
42
+ }
43
+ }
44
+
45
+ if (pressedActive) {
46
+ return {
47
+ background: getSemanticToken(themeTokens, 'background/neutral/secondary'),
48
+ text:
49
+ state === 'error'
50
+ ? getSemanticToken(themeTokens, 'text icon/functional/danger')
51
+ : getSemanticToken(themeTokens, 'text icon/neutral/primary'),
52
+ }
53
+ }
54
+
55
+ if (state === 'error') {
56
+ return {
57
+ background: 'transparent',
58
+ text: getSemanticToken(themeTokens, 'text icon/functional/danger'),
59
+ }
60
+ }
61
+
62
+ return {
63
+ background: 'transparent',
64
+ text: getSemanticToken(themeTokens, 'text icon/neutral/primary'),
65
+ }
66
+ }
67
+
68
+ function DotsIcon({ color }) {
69
+ return (
70
+ <svg aria-hidden="true" height="24" viewBox="0 0 24 24" width="24">
71
+ <rect
72
+ x="3"
73
+ y="3"
74
+ width="18"
75
+ height="18"
76
+ rx="4"
77
+ fill="none"
78
+ stroke={color}
79
+ strokeWidth="2"
80
+ />
81
+ <circle cx="9" cy="12" r="1" fill={color} />
82
+ <circle cx="12" cy="12" r="1" fill={color} />
83
+ <circle cx="15" cy="12" r="1" fill={color} />
84
+ </svg>
85
+ )
86
+ }
87
+
88
+ export default function MenuItem({
89
+ labelText = 'Label',
90
+ showTrailingIcon = true,
91
+ trailingIcon = null,
92
+ count = '',
93
+ state = 'default',
94
+ pressed = false,
95
+ enableInteraction = true,
96
+ ...rest
97
+ }) {
98
+ const [isPressed, setIsPressed] = useState(false)
99
+ const theme = useTheme()
100
+ const themeTokens = theme === 'dark' ? semanticDark : semanticLight
101
+ const normalizedState = STATE_ALIASES[state] || state
102
+ const allowPress = enableInteraction && normalizedState !== 'disabled'
103
+ const pressedActive =
104
+ allowPress &&
105
+ (normalizedState === 'default' || normalizedState === 'error') &&
106
+ (pressed || isPressed)
107
+ const colors = getMenuItemColors(themeTokens, normalizedState, pressedActive)
108
+ const resolvedTrailing =
109
+ trailingIcon || <DotsIcon color={colors.text} />
110
+ const hasCount = count !== undefined && count !== null && String(count).length > 0
111
+
112
+ return (
113
+ <div
114
+ onMouseDown={() => {
115
+ if (allowPress) {
116
+ setIsPressed(true)
117
+ }
118
+ }}
119
+ onMouseUp={() => setIsPressed(false)}
120
+ onMouseLeave={() => setIsPressed(false)}
121
+ onKeyDown={(event) => {
122
+ if (allowPress && (event.key === 'Enter' || event.key === ' ')) {
123
+ setIsPressed(true)
124
+ }
125
+ }}
126
+ onKeyUp={() => setIsPressed(false)}
127
+ role="button"
128
+ tabIndex={0}
129
+ style={{
130
+ width: '100%',
131
+ display: 'flex',
132
+ alignItems: 'center',
133
+ gap: 12,
134
+ padding: 16,
135
+ borderRadius: 16,
136
+ backgroundColor: colors.background,
137
+ cursor: normalizedState === 'disabled' ? 'not-allowed' : 'pointer',
138
+ }}
139
+ {...rest}
140
+ >
141
+ <span
142
+ style={{
143
+ flex: 1,
144
+ fontSize: 16,
145
+ fontWeight: 600,
146
+ lineHeight: 1,
147
+ color: colors.text,
148
+ whiteSpace: 'nowrap',
149
+ overflow: 'hidden',
150
+ textOverflow: 'ellipsis',
151
+ }}
152
+ >
153
+ {labelText}
154
+ </span>
155
+ {hasCount ? (
156
+ <span
157
+ style={{
158
+ fontSize: 14,
159
+ fontWeight: 600,
160
+ lineHeight: 1,
161
+ color: colors.text,
162
+ }}
163
+ >
164
+ {count}
165
+ </span>
166
+ ) : null}
167
+ {showTrailingIcon ? (
168
+ <span
169
+ aria-hidden="true"
170
+ style={{
171
+ width: 24,
172
+ height: 24,
173
+ display: 'inline-flex',
174
+ alignItems: 'center',
175
+ justifyContent: 'center',
176
+ color: colors.text,
177
+ }}
178
+ >
179
+ {resolvedTrailing}
180
+ </span>
181
+ ) : null}
182
+ </div>
183
+ )
184
+ }