@iota/apps-ui-kit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/index.d.ts +1636 -0
  3. package/dist/index.es.js +8909 -0
  4. package/dist/index.es.js.map +1 -0
  5. package/dist/index.umd.js +198 -0
  6. package/dist/index.umd.js.map +1 -0
  7. package/dist/style.css +1 -0
  8. package/package.json +6 -3
  9. package/src/lib/components/atoms/badge/Badge.tsx +37 -0
  10. package/src/lib/components/atoms/badge/badge.classes.ts +24 -0
  11. package/src/lib/components/atoms/badge/badge.enums.ts +8 -0
  12. package/src/lib/components/atoms/badge/index.ts +6 -0
  13. package/src/lib/components/atoms/button/Button.tsx +99 -0
  14. package/src/lib/components/atoms/button/button-variants/ButtonPill.tsx +23 -0
  15. package/src/lib/components/atoms/button/button-variants/ButtonUnstyled.tsx +27 -0
  16. package/src/lib/components/atoms/button/button-variants/buttonVariants.types.ts +11 -0
  17. package/src/lib/components/atoms/button/button-variants/index.ts +5 -0
  18. package/src/lib/components/atoms/button/button.classes.ts +53 -0
  19. package/src/lib/components/atoms/button/button.enums.ts +21 -0
  20. package/src/lib/components/atoms/button/index.ts +7 -0
  21. package/src/lib/components/atoms/button-segment/ButtonSegment.tsx +86 -0
  22. package/src/lib/components/atoms/button-segment/buttonSegment.classes.ts +45 -0
  23. package/src/lib/components/atoms/button-segment/buttonSegment.enums.ts +7 -0
  24. package/src/lib/components/atoms/button-segment/index.ts +5 -0
  25. package/src/lib/components/atoms/checkbox/Checkbox.tsx +114 -0
  26. package/src/lib/components/atoms/checkbox/index.ts +4 -0
  27. package/src/lib/components/atoms/divider/Divider.tsx +48 -0
  28. package/src/lib/components/atoms/divider/divider.classes.ts +11 -0
  29. package/src/lib/components/atoms/divider/divider.enums.ts +7 -0
  30. package/src/lib/components/atoms/divider/index.ts +6 -0
  31. package/src/lib/components/atoms/header/Header.tsx +73 -0
  32. package/src/lib/components/atoms/header/index.ts +4 -0
  33. package/src/lib/components/atoms/index.ts +22 -0
  34. package/src/lib/components/atoms/info-box/InfoBox.tsx +73 -0
  35. package/src/lib/components/atoms/info-box/index.ts +5 -0
  36. package/src/lib/components/atoms/info-box/infoBox.classes.ts +19 -0
  37. package/src/lib/components/atoms/info-box/infoBox.enums.ts +14 -0
  38. package/src/lib/components/atoms/key-value-info/KeyValueInfo.tsx +156 -0
  39. package/src/lib/components/atoms/key-value-info/index.ts +6 -0
  40. package/src/lib/components/atoms/key-value-info/keyValue.enums.ts +7 -0
  41. package/src/lib/components/atoms/label-text/LabelText.tsx +93 -0
  42. package/src/lib/components/atoms/label-text/index.ts +5 -0
  43. package/src/lib/components/atoms/label-text/labelText.classes.ts +22 -0
  44. package/src/lib/components/atoms/label-text/labelText.enums.ts +8 -0
  45. package/src/lib/components/atoms/list-item/ListItem.tsx +89 -0
  46. package/src/lib/components/atoms/list-item/index.ts +4 -0
  47. package/src/lib/components/atoms/loading-indicator/LoadingIndicator.tsx +38 -0
  48. package/src/lib/components/atoms/loading-indicator/index.ts +4 -0
  49. package/src/lib/components/atoms/panel/Panel.tsx +30 -0
  50. package/src/lib/components/atoms/panel/index.ts +4 -0
  51. package/src/lib/components/atoms/placeholder/Placeholder.tsx +27 -0
  52. package/src/lib/components/atoms/placeholder/index.ts +4 -0
  53. package/src/lib/components/atoms/radio-button/RadioButton.tsx +68 -0
  54. package/src/lib/components/atoms/radio-button/index.ts +4 -0
  55. package/src/lib/components/atoms/secondary-text/SecondaryText.tsx +28 -0
  56. package/src/lib/components/atoms/secondary-text/index.ts +4 -0
  57. package/src/lib/components/atoms/skeleton/Skeleton.tsx +47 -0
  58. package/src/lib/components/atoms/skeleton/index.ts +4 -0
  59. package/src/lib/components/atoms/snackbar/Snackbar.tsx +69 -0
  60. package/src/lib/components/atoms/snackbar/index.ts +5 -0
  61. package/src/lib/components/atoms/snackbar/snackbar.classes.ts +18 -0
  62. package/src/lib/components/atoms/snackbar/snackbar.enums.tsx +9 -0
  63. package/src/lib/components/atoms/tooltip/Tooltip.tsx +37 -0
  64. package/src/lib/components/atoms/tooltip/index.ts +5 -0
  65. package/src/lib/components/atoms/tooltip/tooltip.classes.ts +11 -0
  66. package/src/lib/components/atoms/tooltip/tooltip.enums.ts +9 -0
  67. package/src/lib/components/atoms/visual-asset-card/VisualAssetCard.tsx +101 -0
  68. package/src/lib/components/atoms/visual-asset-card/index.ts +5 -0
  69. package/src/lib/components/atoms/visual-asset-card/visualAssetCard.enums.ts +7 -0
  70. package/src/lib/components/index.ts +8 -0
  71. package/src/lib/components/molecules/account/Account.tsx +174 -0
  72. package/src/lib/components/molecules/account/index.ts +4 -0
  73. package/src/lib/components/molecules/address/Address.tsx +101 -0
  74. package/src/lib/components/molecules/address/index.ts +4 -0
  75. package/src/lib/components/molecules/card/Card.tsx +70 -0
  76. package/src/lib/components/molecules/card/CardAction.tsx +80 -0
  77. package/src/lib/components/molecules/card/CardBody.tsx +61 -0
  78. package/src/lib/components/molecules/card/CardImage.tsx +43 -0
  79. package/src/lib/components/molecules/card/CardImagePlaceholder.tsx +146 -0
  80. package/src/lib/components/molecules/card/card.classes.ts +26 -0
  81. package/src/lib/components/molecules/card/card.enums.ts +25 -0
  82. package/src/lib/components/molecules/card/index.ts +8 -0
  83. package/src/lib/components/molecules/chip/Chip.tsx +105 -0
  84. package/src/lib/components/molecules/chip/chip.classes.ts +32 -0
  85. package/src/lib/components/molecules/chip/chip.enums.ts +7 -0
  86. package/src/lib/components/molecules/chip/index.ts +4 -0
  87. package/src/lib/components/molecules/display-stats/DisplayStats.tsx +104 -0
  88. package/src/lib/components/molecules/display-stats/displayStats.classes.ts +36 -0
  89. package/src/lib/components/molecules/display-stats/displayStats.enums.ts +13 -0
  90. package/src/lib/components/molecules/display-stats/index.ts +5 -0
  91. package/src/lib/components/molecules/dropdown/Dropdown.tsx +10 -0
  92. package/src/lib/components/molecules/dropdown/dropdownPosition.enums.ts +7 -0
  93. package/src/lib/components/molecules/dropdown/index.ts +5 -0
  94. package/src/lib/components/molecules/index.ts +17 -0
  95. package/src/lib/components/molecules/input/Input.tsx +245 -0
  96. package/src/lib/components/molecules/input/InputWrapper.tsx +97 -0
  97. package/src/lib/components/molecules/input/TextArea.tsx +149 -0
  98. package/src/lib/components/molecules/input/index.ts +8 -0
  99. package/src/lib/components/molecules/input/input.classes.ts +14 -0
  100. package/src/lib/components/molecules/input/input.enums.ts +14 -0
  101. package/src/lib/components/molecules/input/input.types.ts +29 -0
  102. package/src/lib/components/molecules/navbar-item/NavbarItem.tsx +48 -0
  103. package/src/lib/components/molecules/navbar-item/NavbarItemHorizontal.tsx +65 -0
  104. package/src/lib/components/molecules/navbar-item/NavbarItemVertical.tsx +54 -0
  105. package/src/lib/components/molecules/navbar-item/index.ts +5 -0
  106. package/src/lib/components/molecules/navbar-item/navbarItem.classes.ts +16 -0
  107. package/src/lib/components/molecules/navbar-item/navbarItem.enums.ts +7 -0
  108. package/src/lib/components/molecules/search/Search.tsx +220 -0
  109. package/src/lib/components/molecules/search/index.ts +5 -0
  110. package/src/lib/components/molecules/search/search.classes.ts +20 -0
  111. package/src/lib/components/molecules/search/search.enums.ts +7 -0
  112. package/src/lib/components/molecules/segmented-button/SegmentedButton.tsx +34 -0
  113. package/src/lib/components/molecules/segmented-button/index.ts +6 -0
  114. package/src/lib/components/molecules/segmented-button/segmentedButton.classes.ts +12 -0
  115. package/src/lib/components/molecules/segmented-button/segmentedButton.enums.ts +8 -0
  116. package/src/lib/components/molecules/select/Select.tsx +223 -0
  117. package/src/lib/components/molecules/select/index.ts +5 -0
  118. package/src/lib/components/molecules/select/select.enums.ts +7 -0
  119. package/src/lib/components/molecules/table-cell/TableCell.tsx +62 -0
  120. package/src/lib/components/molecules/table-cell/index.ts +4 -0
  121. package/src/lib/components/molecules/table-header-cell/TableHeaderCell.tsx +140 -0
  122. package/src/lib/components/molecules/table-header-cell/index.ts +5 -0
  123. package/src/lib/components/molecules/table-header-cell/tableHeaderCell.enums.ts +7 -0
  124. package/src/lib/components/molecules/title/Title.tsx +69 -0
  125. package/src/lib/components/molecules/title/index.ts +5 -0
  126. package/src/lib/components/molecules/title/titleClasses.constants.ts +14 -0
  127. package/src/lib/components/molecules/title/titleSize.enums.ts +7 -0
  128. package/src/lib/components/organisms/accordion/Accordion.tsx +109 -0
  129. package/src/lib/components/organisms/accordion/accordion.classes.ts +4 -0
  130. package/src/lib/components/organisms/accordion/index.ts +4 -0
  131. package/src/lib/components/organisms/dialog/Dialog.tsx +123 -0
  132. package/src/lib/components/organisms/dialog/dialog.enums.ts +7 -0
  133. package/src/lib/components/organisms/dialog/index.ts +5 -0
  134. package/src/lib/components/organisms/index.ts +7 -0
  135. package/src/lib/components/organisms/navbar/Navbar.tsx +100 -0
  136. package/src/lib/components/organisms/navbar/NavbarSlideout.tsx +58 -0
  137. package/src/lib/components/organisms/navbar/index.ts +5 -0
  138. package/src/lib/components/organisms/table/Table.tsx +224 -0
  139. package/src/lib/components/organisms/table/TableContext.tsx +55 -0
  140. package/src/lib/components/organisms/table/index.ts +4 -0
  141. package/src/lib/constants/colors.constants.ts +132 -0
  142. package/src/lib/constants/index.ts +4 -0
  143. package/src/lib/enums/index.ts +4 -0
  144. package/src/lib/enums/screenSize.enums.ts +9 -0
  145. package/src/lib/index.ts +8 -0
  146. package/src/lib/styles/fonts.css +5 -0
  147. package/src/lib/styles/index.css +149 -0
  148. package/src/lib/tailwind/base.presets.ts +49 -0
  149. package/src/lib/tailwind/constants/fontSizes.constants.ts +201 -0
  150. package/src/lib/tailwind/constants/index.ts +6 -0
  151. package/src/lib/tailwind/constants/scaling.constants.ts +41 -0
  152. package/src/lib/tailwind/constants/variableSpacing.constants.ts +66 -0
  153. package/src/lib/tailwind/helpers.ts +20 -0
  154. package/src/lib/tailwind/index.ts +5 -0
  155. package/src/lib/tailwind/plugins.ts +8 -0
  156. package/src/lib/tailwind/responsive.presets.ts +36 -0
  157. package/src/lib/tailwind/static.presets.ts +25 -0
  158. package/src/lib/tailwind/types.ts +6 -0
  159. package/src/lib/utils/format.ts +8 -0
  160. package/src/lib/utils/index.ts +4 -0
  161. package/src/storybook/blocks/Showcase.tsx +20 -0
  162. package/src/storybook/blocks/TypeSet.tsx +34 -0
  163. package/src/storybook/blocks/TypeSetItem.tsx +74 -0
  164. package/src/storybook/blocks/index.ts +6 -0
  165. package/src/storybook/constants/index.ts +5 -0
  166. package/src/storybook/constants/typeset.constants.ts +39 -0
  167. package/src/storybook/constants/variableSpacing.constants.ts +6 -0
  168. package/src/storybook/helpers/getFontSizeLabel.ts +15 -0
  169. package/src/storybook/helpers/index.ts +4 -0
  170. package/src/storybook/stories/atoms/Badge.stories.tsx +32 -0
  171. package/src/storybook/stories/atoms/Button.stories.tsx +45 -0
  172. package/src/storybook/stories/atoms/ButtonSegment.stories.tsx +45 -0
  173. package/src/storybook/stories/atoms/Checkbox.stories.tsx +34 -0
  174. package/src/storybook/stories/atoms/CustomScrollbar.stories.tsx +22 -0
  175. package/src/storybook/stories/atoms/Divider.stories.tsx +41 -0
  176. package/src/storybook/stories/atoms/Header.stories.tsx +58 -0
  177. package/src/storybook/stories/atoms/InfoBox.stories.tsx +67 -0
  178. package/src/storybook/stories/atoms/KeyValueInfo.stories.tsx +59 -0
  179. package/src/storybook/stories/atoms/LabelText.stories.tsx +55 -0
  180. package/src/storybook/stories/atoms/ListItem.stories.tsx +41 -0
  181. package/src/storybook/stories/atoms/LoadingIndicator.stories.tsx +32 -0
  182. package/src/storybook/stories/atoms/Panel.stories.tsx +36 -0
  183. package/src/storybook/stories/atoms/Placeholder.stories.tsx +33 -0
  184. package/src/storybook/stories/atoms/RadioButton.stories.tsx +51 -0
  185. package/src/storybook/stories/atoms/Skeleton.stories.tsx +33 -0
  186. package/src/storybook/stories/atoms/Snackbar.stories.tsx +42 -0
  187. package/src/storybook/stories/atoms/Tooltip.stories.tsx +57 -0
  188. package/src/storybook/stories/atoms/VisualAssetCard.stories.tsx +56 -0
  189. package/src/storybook/stories/components/VariableSpacingShowcase.tsx +46 -0
  190. package/src/storybook/stories/components/index.ts +4 -0
  191. package/src/storybook/stories/design-tokens/colors.stories.mdx +23 -0
  192. package/src/storybook/stories/design-tokens/icons.stories.mdx +17 -0
  193. package/src/storybook/stories/design-tokens/typeset.stories.mdx +15 -0
  194. package/src/storybook/stories/design-tokens/variable-spacing.stories.mdx +10 -0
  195. package/src/storybook/stories/molecules/Account.stories.tsx +69 -0
  196. package/src/storybook/stories/molecules/Address.stories.tsx +39 -0
  197. package/src/storybook/stories/molecules/Card.stories.tsx +115 -0
  198. package/src/storybook/stories/molecules/Chip.stories.tsx +122 -0
  199. package/src/storybook/stories/molecules/DisplayStats.stories.tsx +63 -0
  200. package/src/storybook/stories/molecules/Dropdown.stories.tsx +38 -0
  201. package/src/storybook/stories/molecules/Input.stories.tsx +161 -0
  202. package/src/storybook/stories/molecules/NavbarItem.stories.tsx +45 -0
  203. package/src/storybook/stories/molecules/Search.stories.tsx +100 -0
  204. package/src/storybook/stories/molecules/SegmentedButton.stories.tsx +107 -0
  205. package/src/storybook/stories/molecules/Select.stories.tsx +102 -0
  206. package/src/storybook/stories/molecules/TableCell.stories.tsx +46 -0
  207. package/src/storybook/stories/molecules/TableHeaderCell.stories.tsx +34 -0
  208. package/src/storybook/stories/molecules/TextArea.stories.tsx +80 -0
  209. package/src/storybook/stories/molecules/Title.stories.tsx +76 -0
  210. package/src/storybook/stories/organisms/Accordion.stories.tsx +70 -0
  211. package/src/storybook/stories/organisms/Dialog.stories.tsx +103 -0
  212. package/src/storybook/stories/organisms/Navbar.stories.tsx +77 -0
  213. package/src/storybook/stories/organisms/Table.stories.tsx +154 -0
@@ -0,0 +1,220 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { useEffect, useRef, useState } from 'react';
5
+ import cx from 'classnames';
6
+ import { Loader, Search as SearchIcon } from '@iota/apps-ui-icons';
7
+ import { Divider, SearchBarType } from '@/components';
8
+ import {
9
+ BACKGROUND_COLORS,
10
+ SUGGESTIONS_WRAPPER_STYLE,
11
+ SEARCH_WRAPPER_STYLE,
12
+ } from './search.classes';
13
+
14
+ export interface Suggestion {
15
+ id: string;
16
+ label: string;
17
+ supportingText?: string;
18
+ type?: string;
19
+ }
20
+
21
+ export interface SearchProps {
22
+ /**
23
+ * The value of the search input.
24
+ */
25
+ searchValue: string;
26
+ /**
27
+ * Callback when the search input value changes.
28
+ */
29
+ onSearchValueChange: (value: string) => void;
30
+ /**
31
+ * List of suggestions to display (optional).
32
+ */
33
+ suggestions?: Suggestion[];
34
+ /**
35
+ * Callback when a suggestion is clicked.
36
+ */
37
+ onSuggestionClick?: (suggestion: Suggestion) => void;
38
+ /**
39
+ * Placeholder text for the search input.
40
+ */
41
+ placeholder: string;
42
+ /**
43
+ * Are the suggestions loading.
44
+ */
45
+ isLoading: boolean;
46
+ /**
47
+ * Callback when a key is pressed.
48
+ */
49
+ onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
50
+ /**
51
+ * The type of the search bar. Can be 'outlined' or 'filled'.
52
+ */
53
+ type?: SearchBarType;
54
+ /**
55
+ * Render suggestion.
56
+ */
57
+ renderSuggestion: (suggestion: Suggestion, index: number) => React.ReactNode;
58
+ }
59
+
60
+ export function Search({
61
+ searchValue,
62
+ suggestions,
63
+ onSearchValueChange,
64
+ onSuggestionClick,
65
+ placeholder,
66
+ isLoading = false,
67
+ onKeyDown,
68
+ type = SearchBarType.Outlined,
69
+ renderSuggestion,
70
+ }: SearchProps): React.JSX.Element {
71
+ const inputRef = useRef<HTMLInputElement>(null);
72
+ const suggestionsListRef = useRef<HTMLDivElement>(null);
73
+ const [isSuggestionsVisible, setIsSuggestionsVisible] = useState(true);
74
+ const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
75
+
76
+ function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
77
+ const value = event.target.value;
78
+ onSearchValueChange(value);
79
+ }
80
+
81
+ // Hide suggestions on escape key press
82
+ useEffect(() => {
83
+ const handler = (e: KeyboardEvent) => {
84
+ if (e.key === 'Escape') {
85
+ setIsSuggestionsVisible(false);
86
+ inputRef.current?.blur();
87
+ }
88
+ };
89
+
90
+ document.addEventListener('keydown', handler);
91
+
92
+ return () => {
93
+ document.removeEventListener('keydown', handler);
94
+ };
95
+ }, []);
96
+
97
+ // Hide suggestions on click outside
98
+ useEffect(() => {
99
+ const listener = (event: MouseEvent | TouchEvent) => {
100
+ const el = inputRef?.current;
101
+ if (!el || el.contains(event?.target as Node)) {
102
+ return;
103
+ }
104
+
105
+ if (suggestionsListRef.current?.contains(event.target as Node)) {
106
+ return;
107
+ }
108
+ setIsSuggestionsVisible(false);
109
+ };
110
+
111
+ document.addEventListener('click', listener, true);
112
+ document.addEventListener('touchstart', listener, true);
113
+
114
+ return () => {
115
+ document.removeEventListener('click', listener, true);
116
+ document.removeEventListener('touchstart', listener, true);
117
+ };
118
+ }, [inputRef]);
119
+
120
+ const showSuggestions = isSuggestionsVisible && suggestions && suggestions.length > 0;
121
+
122
+ const roundedStyleWithSuggestions = showSuggestions
123
+ ? 'rounded-t-3xl border-b border-b-transparent'
124
+ : type === SearchBarType.Outlined
125
+ ? 'rounded-3xl border-b'
126
+ : 'rounded-full';
127
+ const searchTypeClass = SEARCH_WRAPPER_STYLE[type];
128
+ const backgroundColorClass = BACKGROUND_COLORS[type];
129
+ const suggestionsStyle = SUGGESTIONS_WRAPPER_STYLE[type];
130
+
131
+ const handleOnSuggestionClick = (suggestion: Suggestion) => {
132
+ onSuggestionClick?.(suggestion);
133
+ onSearchValueChange('');
134
+ setIsSuggestionsVisible(false);
135
+ setSelectedIndex(null);
136
+ inputRef.current?.blur();
137
+ };
138
+
139
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
140
+ if (suggestions && suggestions?.length > 0) {
141
+ if (event.key === 'ArrowDown') {
142
+ event.preventDefault();
143
+ setSelectedIndex((prev) =>
144
+ prev === null || prev >= suggestions.length - 1 ? 0 : prev + 1,
145
+ );
146
+ } else if (event.key === 'ArrowUp') {
147
+ event.preventDefault();
148
+ setSelectedIndex((prev) =>
149
+ prev === null || prev <= 0 ? suggestions.length - 1 : prev - 1,
150
+ );
151
+ } else if (event.key === 'Enter') {
152
+ event.preventDefault();
153
+ if (selectedIndex !== null && suggestions[selectedIndex]) {
154
+ handleOnSuggestionClick(suggestions[selectedIndex]);
155
+ } else if (suggestions.length === 1) {
156
+ handleOnSuggestionClick(suggestions[0]);
157
+ }
158
+ }
159
+ }
160
+ };
161
+
162
+ return (
163
+ <div className="relative w-full">
164
+ <div
165
+ className={cx(
166
+ 'flex items-center overflow-hidden px-md py-sm text-neutral-10 dark:text-neutral-92 [&_svg]:h-6 [&_svg]:w-6',
167
+ roundedStyleWithSuggestions,
168
+ searchTypeClass,
169
+ )}
170
+ >
171
+ <input
172
+ ref={inputRef}
173
+ type="text"
174
+ value={searchValue}
175
+ onChange={handleChange}
176
+ onKeyDown={handleKeyDown}
177
+ onFocus={() => setIsSuggestionsVisible(true)}
178
+ placeholder={placeholder}
179
+ className={cx(
180
+ 'w-full flex-1 outline-none placeholder:text-neutral-40 placeholder:dark:text-neutral-60',
181
+ backgroundColorClass,
182
+ )}
183
+ />
184
+ <SearchIcon />
185
+ </div>
186
+ {showSuggestions && (
187
+ <div
188
+ ref={suggestionsListRef}
189
+ className={cx(
190
+ 'absolute left-0 top-full flex w-full flex-col items-center overflow-hidden bg-neutral-100 dark:bg-neutral-10',
191
+ suggestionsStyle,
192
+ )}
193
+ >
194
+ <Divider width="w-11/12" />
195
+ {isLoading ? (
196
+ <div className="px-md py-sm">
197
+ <Loader className="animate-spin" />
198
+ </div>
199
+ ) : (
200
+ suggestions.map((suggestion, index) => (
201
+ <div
202
+ key={suggestion.id}
203
+ onClick={() => handleOnSuggestionClick(suggestion)}
204
+ onMouseEnter={() => setSelectedIndex(index)}
205
+ className={cx(
206
+ 'w-full cursor-pointer px-md py-sm',
207
+ selectedIndex === index
208
+ ? 'bg-shader-primary-dark-16 dark:bg-shader-inverted-dark-16'
209
+ : '',
210
+ )}
211
+ >
212
+ {renderSuggestion(suggestion, index)}
213
+ </div>
214
+ ))
215
+ )}
216
+ </div>
217
+ )}
218
+ </div>
219
+ );
220
+ }
@@ -0,0 +1,5 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export * from './Search';
5
+ export * from './search.enums';
@@ -0,0 +1,20 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { SearchBarType } from './search.enums';
5
+
6
+ export const BACKGROUND_COLORS: Record<SearchBarType, string> = {
7
+ [SearchBarType.Outlined]: 'bg-transparent',
8
+ [SearchBarType.Filled]: 'bg-neutral-92 dark:bg-neutral-12',
9
+ };
10
+
11
+ export const SEARCH_WRAPPER_STYLE: Record<SearchBarType, string> = {
12
+ [SearchBarType.Outlined]: 'border-l border-r border-t border-neutral-70 dark:border-neutral-40',
13
+ [SearchBarType.Filled]: 'bg-neutral-92 dark:bg-neutral-12',
14
+ };
15
+
16
+ export const SUGGESTIONS_WRAPPER_STYLE: Record<SearchBarType, string> = {
17
+ [SearchBarType.Outlined]:
18
+ 'rounded-b-3xl border-b border-l border-r border-neutral-70 dark:border-neutral-40',
19
+ [SearchBarType.Filled]: 'rounded-b-3xl bg-neutral-92 dark:bg-neutral-12',
20
+ };
@@ -0,0 +1,7 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export enum SearchBarType {
5
+ Outlined = 'outlined',
6
+ Filled = 'filled',
7
+ }
@@ -0,0 +1,34 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { PropsWithChildren } from 'react';
5
+ import { BACKGROUND_COLORS, OUTLINED_BORDER } from './segmentedButton.classes';
6
+ import cx from 'classnames';
7
+ import { SegmentedButtonType } from './segmentedButton.enums';
8
+ import { ButtonSegmentType } from '../../atoms';
9
+
10
+ interface SegmentedButtonProps {
11
+ /**
12
+ * The type of the button
13
+ */
14
+ type?: SegmentedButtonType;
15
+ /**
16
+ * The shape of the button
17
+ */
18
+ shape?: ButtonSegmentType;
19
+ }
20
+
21
+ export function SegmentedButton({
22
+ type = SegmentedButtonType.Filled,
23
+ children,
24
+ shape = ButtonSegmentType.Rounded,
25
+ }: PropsWithChildren<SegmentedButtonProps>): React.JSX.Element {
26
+ const backgroundColors = BACKGROUND_COLORS[type];
27
+ const borderColors = type === SegmentedButtonType.Outlined ? OUTLINED_BORDER : '';
28
+ const borderShape = shape === ButtonSegmentType.Rounded ? 'rounded-full gap-1 p-xxs' : '';
29
+ return (
30
+ <div className={cx('flex flex-row flex-wrap', backgroundColors, borderColors, borderShape)}>
31
+ {children}
32
+ </div>
33
+ );
34
+ }
@@ -0,0 +1,6 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export * from './SegmentedButton';
5
+
6
+ export * from './segmentedButton.enums';
@@ -0,0 +1,12 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { SegmentedButtonType } from './segmentedButton.enums';
5
+
6
+ export const BACKGROUND_COLORS: Record<SegmentedButtonType, string> = {
7
+ [SegmentedButtonType.Outlined]: 'bg-transparent',
8
+ [SegmentedButtonType.Filled]: 'bg-neutral-96 dark:bg-neutral-10',
9
+ [SegmentedButtonType.Transparent]: 'bg-transparent',
10
+ };
11
+
12
+ export const OUTLINED_BORDER = 'border border-neutral-70 dark:border-neutral-40';
@@ -0,0 +1,8 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export enum SegmentedButtonType {
5
+ Outlined = 'outlined',
6
+ Filled = 'filled',
7
+ Transparent = 'transparent',
8
+ }
@@ -0,0 +1,223 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { TriangleDown } from '@iota/apps-ui-icons';
5
+ import cx from 'classnames';
6
+ import { forwardRef, useEffect, useState } from 'react';
7
+ import { Dropdown } from '../dropdown/Dropdown';
8
+ import { SecondaryText } from '../../atoms/secondary-text';
9
+ import { InputWrapper, LabelHtmlTag } from '../input/InputWrapper';
10
+ import { ButtonUnstyled } from '../../atoms/button';
11
+ import { ListItem } from '../../atoms';
12
+ import { DropdownPosition } from '../dropdown';
13
+ import { SelectSize } from './select.enums';
14
+
15
+ export type SelectOption =
16
+ | string
17
+ | { id: string; renderLabel: () => React.JSX.Element }
18
+ | { id: string; label: React.ReactNode };
19
+
20
+ interface SelectProps extends Pick<React.HTMLProps<HTMLSelectElement>, 'disabled'> {
21
+ /**
22
+ * The selected option value.
23
+ */
24
+ value?: string;
25
+ /**
26
+ * The field label.
27
+ */
28
+ label?: string;
29
+ /**
30
+ * The field caption.
31
+ */
32
+ caption?: string;
33
+ /**
34
+ * The dropdown elements to render.
35
+ */
36
+ options: SelectOption[];
37
+ /**
38
+ * The icon to show on the left of the field.
39
+ */
40
+ leadingIcon?: React.ReactNode;
41
+ /**
42
+ * The supporting text to shown at the end of the selector.
43
+ */
44
+ supportingText?: string;
45
+ /**
46
+ * The error message to show under the field.
47
+ */
48
+ errorMessage?: string;
49
+ /**
50
+ * Placeholder for the selector
51
+ */
52
+ placeholder?: SelectOption;
53
+ /**
54
+ * The callback to call when the value changes.
55
+ */
56
+ onValueChange?: (id: string) => void;
57
+ /**
58
+ * The callback to call when the option is clicked.
59
+ */
60
+ onOptionClick?: (id: string) => void;
61
+ /**
62
+ * The dropdown position
63
+ */
64
+ dropdownPosition?: DropdownPosition;
65
+ /**
66
+ * The size of the select.
67
+ */
68
+ size?: SelectSize;
69
+ }
70
+
71
+ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
72
+ (
73
+ {
74
+ disabled,
75
+ label,
76
+ leadingIcon,
77
+ supportingText,
78
+ errorMessage,
79
+ caption,
80
+ options,
81
+ placeholder,
82
+ onValueChange,
83
+ onOptionClick,
84
+ value,
85
+ dropdownPosition = DropdownPosition.Bottom,
86
+ size = SelectSize.Default,
87
+ },
88
+ ref,
89
+ ) => {
90
+ const [isOpen, setIsOpen] = useState<boolean>(false);
91
+ const selectedValue = findValueByProps(value, options);
92
+
93
+ const selectorText = selectedValue || placeholder;
94
+ const selectPadding = size === SelectSize.Small ? 'px-sm' : 'px-md';
95
+ const textSize = size === SelectSize.Small ? 'text-body-md' : 'text-body-lg';
96
+ useEffect(() => {
97
+ if (disabled && isOpen) {
98
+ closeDropdown();
99
+ }
100
+ }, [disabled, isOpen]);
101
+
102
+ function findValueByProps(value: SelectProps['value'], options: SelectOption[] = []) {
103
+ return (
104
+ options.find((option) =>
105
+ typeof option === 'string' ? option === value : option.id === value,
106
+ ) ?? options[0]
107
+ );
108
+ }
109
+
110
+ function onSelectorClick() {
111
+ setIsOpen((prev) => !prev);
112
+ }
113
+
114
+ function handleOptionClick(option: SelectOption) {
115
+ closeDropdown();
116
+ const clickedOption = typeof option === 'string' ? option : option.id;
117
+ onOptionClick?.(clickedOption);
118
+
119
+ if (option !== selectedValue) {
120
+ onValueChange?.(clickedOption);
121
+ }
122
+ }
123
+
124
+ function closeDropdown() {
125
+ setIsOpen(false);
126
+ }
127
+ return (
128
+ <InputWrapper
129
+ label={label}
130
+ caption={caption}
131
+ disabled={disabled}
132
+ errorMessage={errorMessage}
133
+ labelHtmlTag={LabelHtmlTag.Div}
134
+ >
135
+ <div className="relative flex w-full flex-col">
136
+ <ButtonUnstyled
137
+ ref={ref}
138
+ onClick={onSelectorClick}
139
+ disabled={disabled}
140
+ className={cx(
141
+ 'flex flex-row items-center gap-x-3 rounded-lg border border-neutral-80 py-sm hover:enabled:border-neutral-50 focus-visible:enabled:border-primary-30 active:enabled:border-primary-30 disabled:cursor-not-allowed group-[.errored]:border-error-30 group-[.opened]:border-primary-30 dark:border-neutral-20 dark:hover:enabled:border-neutral-60 dark:group-[.errored]:border-error-80 dark:group-[.opened]:border-primary-80 [&:is(:focus,_:focus-visible,_:active)]:enabled:border-primary-30 dark:[&:is(:focus,_:focus-visible,_:active)]:enabled:border-primary-80 [&_svg]:h-5 [&_svg]:w-5',
142
+ selectPadding,
143
+ )}
144
+ >
145
+ {leadingIcon && (
146
+ <span className="text-neutral-10 dark:text-neutral-92">
147
+ {leadingIcon}
148
+ </span>
149
+ )}
150
+
151
+ <div className="flex w-full flex-row items-baseline gap-x-3">
152
+ {selectorText && (
153
+ <div
154
+ className={cx(
155
+ 'block w-full text-start text-neutral-10 dark:text-neutral-92',
156
+ textSize,
157
+ )}
158
+ >
159
+ <OptionLabel option={selectorText} />
160
+ </div>
161
+ )}
162
+
163
+ {supportingText && (
164
+ <div className="ml-auto">
165
+ <SecondaryText>{supportingText}</SecondaryText>
166
+ </div>
167
+ )}
168
+ </div>
169
+
170
+ <TriangleDown
171
+ className={cx(
172
+ 'text-neutral-10 transition-transform dark:text-neutral-92',
173
+ {
174
+ ' rotate-180': isOpen,
175
+ },
176
+ )}
177
+ />
178
+ </ButtonUnstyled>
179
+
180
+ {isOpen && (
181
+ <div
182
+ className="fixed left-0 top-0 z-[49] h-screen w-screen bg-transparent"
183
+ onClick={closeDropdown}
184
+ />
185
+ )}
186
+ <div
187
+ className={cx('absolute z-50 min-w-full', {
188
+ hidden: !isOpen,
189
+ 'top-full':
190
+ !dropdownPosition || dropdownPosition === DropdownPosition.Bottom,
191
+ 'bottom-full': dropdownPosition === DropdownPosition.Top,
192
+ })}
193
+ >
194
+ <Dropdown>
195
+ {options.map((option) => {
196
+ const optionIsString = typeof option === 'string';
197
+ return (
198
+ <ListItem
199
+ onClick={() => handleOptionClick(option)}
200
+ hideBottomBorder
201
+ key={optionIsString ? option : option.id}
202
+ >
203
+ <OptionLabel option={option} />
204
+ </ListItem>
205
+ );
206
+ })}
207
+ </Dropdown>
208
+ </div>
209
+ </div>
210
+ </InputWrapper>
211
+ );
212
+ },
213
+ );
214
+
215
+ function OptionLabel({ option }: { option: SelectOption }) {
216
+ if (typeof option === 'string') {
217
+ return option;
218
+ } else if ('renderLabel' in option) {
219
+ return option.renderLabel();
220
+ } else {
221
+ return option.label;
222
+ }
223
+ }
@@ -0,0 +1,5 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export * from './Select';
5
+ export * from './select.enums';
@@ -0,0 +1,7 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export enum SelectSize {
5
+ Small = 'small',
6
+ Default = 'default',
7
+ }
@@ -0,0 +1,62 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { PropsWithChildren } from 'react';
4
+ import cx from 'classnames';
5
+ import { Placeholder } from '../../atoms';
6
+
7
+ interface TableCellBaseProps {
8
+ /**
9
+ * The label of the cell.
10
+ */
11
+ label?: string;
12
+ /**
13
+ * If the cell is the last in the row and should not have a border.
14
+ */
15
+ hasLastBorderNoneClass?: boolean;
16
+ /**
17
+ * Whether the cell content should be centered.
18
+ */
19
+ isContentCentered?: boolean;
20
+ }
21
+
22
+ export function TableCellBase({
23
+ children,
24
+ hasLastBorderNoneClass,
25
+ isContentCentered,
26
+ }: PropsWithChildren<TableCellBaseProps>) {
27
+ return (
28
+ <td
29
+ className={cx(
30
+ 'h-14 border-b border-shader-neutral-light-8 px-md dark:border-shader-neutral-dark-8',
31
+ { 'last:border-none': hasLastBorderNoneClass },
32
+ { 'flex items-center justify-center': isContentCentered },
33
+ )}
34
+ >
35
+ {children}
36
+ </td>
37
+ );
38
+ }
39
+
40
+ export interface TableCellTextProps {
41
+ supportingLabel?: string;
42
+ }
43
+
44
+ export function TableCellText({
45
+ children,
46
+ supportingLabel,
47
+ }: PropsWithChildren<TableCellTextProps>) {
48
+ return (
49
+ <div className="flex flex-row items-baseline gap-1">
50
+ <span>{children}</span>
51
+ {supportingLabel && (
52
+ <span className="text-body-sm text-neutral-60 dark:text-neutral-40">
53
+ {supportingLabel}
54
+ </span>
55
+ )}
56
+ </div>
57
+ );
58
+ }
59
+
60
+ export function TableCellPlaceholder() {
61
+ return <Placeholder />;
62
+ }
@@ -0,0 +1,4 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export * from './TableCell';