@clickhouse/click-ui 0.0.0 → 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 (236) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/.prettierrc +14 -0
  3. package/.storybook/main.ts +1 -0
  4. package/.storybook/manager.ts +11 -0
  5. package/.storybook/preview-head.html +2 -4
  6. package/.storybook/preview.tsx +13 -4
  7. package/.storybook/theme.ts +16 -0
  8. package/build-tokens.js +6 -6
  9. package/package.json +25 -5
  10. package/public/favicon.ico +0 -0
  11. package/public/logo.svg +17 -0
  12. package/src/App.tsx +102 -39
  13. package/src/components/Accordion/Accordion.stories.tsx +4 -54
  14. package/src/components/Accordion/Accordion.tsx +1 -0
  15. package/src/components/Alert/Alert.stories.tsx +14 -0
  16. package/src/components/Alert/Alert.test.tsx +27 -0
  17. package/src/components/Alert/Alert.tsx +130 -0
  18. package/src/components/Avatar/Avatar.stories.tsx +13 -0
  19. package/src/components/Avatar/Avatar.tsx +64 -0
  20. package/src/components/Badge/Badge.stories.ts +19 -3
  21. package/src/components/Badge/Badge.test.tsx +6 -1
  22. package/src/components/Badge/Badge.tsx +67 -13
  23. package/src/components/BigStat/BigStat.stories.ts +10 -5
  24. package/src/components/BigStat/BigStat.tsx +36 -24
  25. package/src/components/Button/Button.stories.ts +12 -68
  26. package/src/components/Button/Button.test.tsx +2 -2
  27. package/src/components/Button/Button.tsx +59 -41
  28. package/src/components/ButtonGroup/ButtonGroup.stories.ts +3 -3
  29. package/src/components/Card/Card.stories.ts +3 -3
  30. package/src/components/Checkbox/Checkbox.stories.tsx +35 -0
  31. package/src/components/Checkbox/Checkbox.test.tsx +41 -0
  32. package/src/components/Checkbox/Checkbox.tsx +89 -0
  33. package/src/components/ContextMenu/ContextMenu.stories.tsx +73 -0
  34. package/src/components/ContextMenu/ContextMenu.test.tsx +152 -0
  35. package/src/components/ContextMenu/ContextMenu.tsx +155 -0
  36. package/src/components/Dropdown/Dropdown.stories.tsx +64 -0
  37. package/src/components/Dropdown/Dropdown.test.tsx +141 -0
  38. package/src/components/Dropdown/Dropdown.tsx +149 -0
  39. package/src/components/FormField/Label.stories.tsx +39 -0
  40. package/src/components/FormField/Label.tsx +47 -0
  41. package/src/components/FormField/Select.stories.tsx +48 -0
  42. package/src/components/FormField/Select.test.tsx +216 -0
  43. package/src/components/FormField/Select.tsx +574 -0
  44. package/src/components/FormField/SelectContext.tsx +101 -0
  45. package/src/components/FormField/commonElement.tsx +42 -0
  46. package/src/components/GenericMenu.tsx +114 -0
  47. package/src/components/HoverCard/HoverCard.stories.tsx +64 -0
  48. package/src/components/HoverCard/HoverCard.test.tsx +85 -0
  49. package/src/components/HoverCard/HoverCard.tsx +65 -0
  50. package/src/components/Icon/Icon.stories.ts +4 -35
  51. package/src/components/Icon/Icon.tsx +166 -18
  52. package/src/components/Icon/types.ts +78 -1
  53. package/src/components/IconButton/IconButton.stories.ts +25 -7
  54. package/src/components/IconButton/IconButton.test.tsx +32 -0
  55. package/src/components/IconButton/IconButton.tsx +60 -71
  56. package/src/components/Panel/Panel.stories.tsx +25 -0
  57. package/src/components/Panel/Panel.tsx +33 -0
  58. package/src/components/Popover/Popover.stories.tsx +67 -0
  59. package/src/components/Popover/Popover.test.tsx +46 -0
  60. package/src/components/Popover/Popover.tsx +115 -0
  61. package/src/components/RadioGroup/RadioGroup.stories.tsx +43 -0
  62. package/src/components/RadioGroup/RadioGroup.test.tsx +59 -0
  63. package/src/components/RadioGroup/RadioGroup.tsx +149 -0
  64. package/src/components/Separator/Separator.stories.tsx +24 -0
  65. package/src/components/Separator/Separator.tsx +29 -0
  66. package/src/components/SidebarNavigationItem/SidebarNavigationItem.stories.tsx +2 -2
  67. package/src/components/Spacer/Spacer.stories.tsx +20 -0
  68. package/src/components/Spacer/Spacer.tsx +15 -0
  69. package/src/components/Switch/Switch.stories.ts +3 -3
  70. package/src/components/Switch/Switch.tsx +3 -2
  71. package/src/components/Table/Table.stories.tsx +29 -0
  72. package/src/components/Table/Table.tsx +109 -0
  73. package/src/components/Tabs/Tabs.stories.tsx +2 -37
  74. package/src/components/Tooltip/Tooltip.stories.tsx +68 -0
  75. package/src/components/Tooltip/Tooltip.test.tsx +44 -0
  76. package/src/components/Tooltip/Tooltip.tsx +67 -0
  77. package/src/components/Typography/Text/Text.stories.tsx +22 -0
  78. package/src/components/Typography/Text/Text.test.tsx +16 -0
  79. package/src/components/Typography/Text/Text.tsx +30 -0
  80. package/src/components/Typography/Title/Title.stories.tsx +31 -0
  81. package/src/components/Typography/Title/Title.test.tsx +16 -0
  82. package/src/components/Typography/Title/Title.tsx +36 -0
  83. package/src/components/icons/Activity.tsx +30 -0
  84. package/src/components/icons/ArrowDown.tsx +22 -0
  85. package/src/components/icons/ArrowRight.tsx +22 -0
  86. package/src/components/icons/ArrowTriangle.tsx +36 -0
  87. package/src/components/icons/ArrowUp.tsx +22 -0
  88. package/src/components/icons/Backups.tsx +29 -0
  89. package/src/components/icons/Blog.tsx +38 -0
  90. package/src/components/icons/Book.tsx +30 -0
  91. package/src/components/icons/Brackets.tsx +22 -0
  92. package/src/components/icons/Briefcase.tsx +30 -0
  93. package/src/components/icons/Building.tsx +30 -0
  94. package/src/components/icons/BurgerMenu.tsx +22 -0
  95. package/src/components/icons/Cards.tsx +30 -0
  96. package/src/components/icons/CellTower.tsx +21 -0
  97. package/src/components/icons/CheckIcon.tsx +21 -0
  98. package/src/components/icons/CheckInCircle.tsx +39 -0
  99. package/src/components/icons/ChevronDown.tsx +19 -5
  100. package/src/components/icons/ChevronLeft.tsx +22 -0
  101. package/src/components/icons/ChevronRight.tsx +3 -3
  102. package/src/components/icons/ChevronUp.tsx +22 -0
  103. package/src/components/icons/Clock.tsx +37 -0
  104. package/src/components/icons/Cloud.tsx +23 -0
  105. package/src/components/icons/Code.tsx +22 -0
  106. package/src/components/icons/CodeInSquare.tsx +30 -0
  107. package/src/components/icons/Connect.tsx +22 -0
  108. package/src/components/icons/ConnectAlt.tsx +30 -0
  109. package/src/components/icons/Console.tsx +30 -0
  110. package/src/components/icons/Copy.tsx +33 -0
  111. package/src/components/icons/CrossIcon.tsx +29 -0
  112. package/src/components/icons/Data.tsx +36 -0
  113. package/src/components/icons/DatabaseIcon.tsx +27 -29
  114. package/src/components/icons/Disk.tsx +30 -0
  115. package/src/components/icons/Display.tsx +30 -0
  116. package/src/components/icons/Document.tsx +30 -0
  117. package/src/components/icons/DotsHorizontal.tsx +36 -0
  118. package/src/components/icons/DotsVertical.tsx +33 -0
  119. package/src/components/icons/Email.tsx +33 -0
  120. package/src/components/icons/Empty.tsx +14 -0
  121. package/src/components/icons/FilterIcon.tsx +29 -16
  122. package/src/components/icons/Fire.tsx +23 -0
  123. package/src/components/icons/Folder.tsx +20 -0
  124. package/src/components/icons/Gift.tsx +21 -0
  125. package/src/components/icons/HistoryIcon.tsx +13 -13
  126. package/src/components/icons/Home.tsx +29 -0
  127. package/src/components/icons/Http.tsx +22 -0
  128. package/src/components/icons/Icons.mdx +31 -28
  129. package/src/components/icons/InfoInCircleIcon.tsx +37 -0
  130. package/src/components/icons/InformationIcon.tsx +34 -0
  131. package/src/components/icons/InsertRowIcon.tsx +30 -32
  132. package/src/components/icons/Integrations.tsx +29 -0
  133. package/src/components/icons/LightBulb.tsx +40 -0
  134. package/src/components/icons/Lightening.tsx +30 -0
  135. package/src/components/icons/Loading.tsx +57 -0
  136. package/src/components/icons/Metrics.tsx +38 -0
  137. package/src/components/icons/MetricsAlt.tsx +30 -0
  138. package/src/components/icons/Payment.tsx +23 -0
  139. package/src/components/icons/Payments/Amex.tsx +44 -0
  140. package/src/components/icons/Payments/MasterCard.tsx +48 -0
  141. package/src/components/icons/Payments/Paypal.tsx +41 -0
  142. package/src/components/icons/Payments/Visa.tsx +36 -0
  143. package/src/components/icons/Payments/index.tsx +30 -0
  144. package/src/components/icons/Pencil.tsx +30 -0
  145. package/src/components/icons/PieChart.tsx +30 -0
  146. package/src/components/icons/Play.tsx +30 -0
  147. package/src/components/icons/Plus.tsx +22 -0
  148. package/src/components/icons/Popout.tsx +22 -0
  149. package/src/components/icons/PopoverArrow.tsx +22 -0
  150. package/src/components/icons/Question.tsx +30 -0
  151. package/src/components/icons/Refresh.tsx +29 -0
  152. package/src/components/icons/Search.tsx +22 -0
  153. package/src/components/icons/Secure.tsx +30 -0
  154. package/src/components/icons/Services.tsx +23 -0
  155. package/src/components/icons/Settings.tsx +22 -0
  156. package/src/components/icons/Share.tsx +29 -0
  157. package/src/components/icons/SlideIn.tsx +28 -0
  158. package/src/components/icons/SlideOut.tsx +28 -0
  159. package/src/components/icons/SortAltIcon.tsx +18 -20
  160. package/src/components/icons/SortIcon.tsx +24 -0
  161. package/src/components/icons/Sparkle.tsx +23 -0
  162. package/src/components/icons/Speaker.tsx +30 -0
  163. package/src/components/icons/Speed.tsx +29 -0
  164. package/src/components/icons/Star.tsx +23 -0
  165. package/src/components/icons/Support.tsx +37 -0
  166. package/src/components/icons/Table.tsx +30 -0
  167. package/src/components/icons/Taxi.tsx +120 -0
  168. package/src/components/icons/Trash.tsx +22 -0
  169. package/src/components/icons/Upload.tsx +29 -0
  170. package/src/components/icons/Url.tsx +22 -0
  171. package/src/components/icons/UsersIcon.tsx +27 -27
  172. package/src/components/icons/WarningIcon.tsx +30 -0
  173. package/src/components/index.ts +31 -10
  174. package/src/index.ts +2 -2
  175. package/src/styles/types.ts +715 -295
  176. package/src/styles/variables.classic.json +171 -0
  177. package/src/styles/variables.dark.json +447 -129
  178. package/src/styles/variables.json +830 -410
  179. package/src/styles/variables.light.json +339 -179
  180. package/tokens/themes/$themes.json +3657 -1
  181. package/tokens/themes/classic.json +492 -0
  182. package/tokens/themes/component.json +1126 -441
  183. package/tokens/themes/dark.json +1871 -758
  184. package/tokens/themes/light.json +852 -266
  185. package/tokens/themes/primitives.json +294 -210
  186. package/vite.config.ts +6 -4
  187. package/app/.babelrc +0 -27
  188. package/app/.eslintrc.json +0 -6
  189. package/app/.storybook/main.ts +0 -17
  190. package/app/.storybook/preview.tsx +0 -26
  191. package/app/README.md +0 -38
  192. package/app/next.config.js +0 -6
  193. package/app/package-lock.json +0 -28711
  194. package/app/package.json +0 -44
  195. package/app/public/favicon.ico +0 -0
  196. package/app/public/next.svg +0 -1
  197. package/app/public/vercel.svg +0 -1
  198. package/app/src/assets/RightArrow/right-arrow.tsx +0 -17
  199. package/app/src/assets/S3Logo/s3-logo.tsx +0 -31
  200. package/app/src/assets/amazon_s3.svg +0 -9
  201. package/app/src/assets/arrow.svg +0 -3
  202. package/app/src/globals.d.ts +0 -4
  203. package/app/src/pages/_app.tsx +0 -8
  204. package/app/src/pages/_document.tsx +0 -17
  205. package/app/src/pages/api/hello.ts +0 -13
  206. package/app/src/pages/index.tsx +0 -141
  207. package/app/src/pages/label.tsx +0 -27
  208. package/app/src/stories/assets/code-brackets.svg +0 -1
  209. package/app/src/stories/assets/colors.svg +0 -1
  210. package/app/src/stories/assets/comments.svg +0 -1
  211. package/app/src/stories/assets/direction.svg +0 -1
  212. package/app/src/stories/assets/flow.svg +0 -1
  213. package/app/src/stories/assets/plugin.svg +0 -1
  214. package/app/src/stories/assets/repo.svg +0 -1
  215. package/app/src/stories/assets/stackalt.svg +0 -1
  216. package/app/src/styles/Home.module.css +0 -235
  217. package/app/src/styles/globals.css +0 -111
  218. package/app/src/styles/types.ts +0 -1031
  219. package/app/src/styles/variables.classic.css +0 -16
  220. package/app/src/styles/variables.classic.json +0 -31
  221. package/app/src/styles/variables.css +0 -763
  222. package/app/src/styles/variables.dark.css +0 -135
  223. package/app/src/styles/variables.dark.json +0 -339
  224. package/app/src/styles/variables.json +0 -1029
  225. package/app/src/styles/variables.light.css +0 -203
  226. package/app/src/styles/variables.light.json +0 -478
  227. package/app/tokens/themes/$metadata.json +0 -9
  228. package/app/tokens/themes/$themes.json +0 -1
  229. package/app/tokens/themes/classic.json +0 -58
  230. package/app/tokens/themes/component.json +0 -868
  231. package/app/tokens/themes/dark.json +0 -937
  232. package/app/tokens/themes/light.json +0 -1380
  233. package/app/tokens/themes/primitives.json +0 -859
  234. package/app/tsconfig.json +0 -23
  235. package/src/components/FormField/FormField.stories.ts +0 -14
  236. package/src/components/FormField/FormField.tsx +0 -22
@@ -0,0 +1,574 @@
1
+ import {
2
+ Children,
3
+ HTMLAttributes,
4
+ MouseEvent,
5
+ ReactElement,
6
+ ReactNode,
7
+ forwardRef,
8
+ useId,
9
+ useRef,
10
+ useState,
11
+ } from "react";
12
+ import * as RadixPopover from "@radix-ui/react-popover";
13
+ import { Command, useCommandState } from "cmdk";
14
+ import { Icon } from "../Icon/Icon";
15
+ import { Error, FormRoot } from "./commonElement";
16
+ import styled from "styled-components";
17
+ import { Label } from "./Label";
18
+ import { SelectContextProvider, useSelect } from "./SelectContext";
19
+ import Separator from "../Separator/Separator";
20
+
21
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
22
+ placeholder?: string;
23
+ label: ReactNode;
24
+ children: ReactNode;
25
+ error?: ReactNode;
26
+ showSearch?: boolean;
27
+ disabled?: boolean;
28
+ defaultValue?: string;
29
+ onChange?: (value: string) => void;
30
+ name?: string;
31
+ required?: boolean;
32
+ isFormControl?: boolean;
33
+ value?: string;
34
+ }
35
+
36
+ declare type DivProps = HTMLAttributes<HTMLDivElement>;
37
+ export type SelectProps = RadixPopover.PopoverProps & Props;
38
+
39
+ const SelectPopoverRoot = styled(RadixPopover.Root)`
40
+ width: 100%;
41
+ `;
42
+
43
+ const SelectTrigger = styled(RadixPopover.Trigger)<{ $error: boolean }>`
44
+ width: 100%;
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: space-between;
48
+ align-items: center;
49
+ cursor: pointer;
50
+
51
+ span:first-of-type {
52
+ max-width: 100%;
53
+ overflow: hidden;
54
+ white-space: nowrap;
55
+ text-overflow: ellipsis;
56
+ }
57
+
58
+ ${({ theme, $error }) => `
59
+ border-radius: ${theme.click.field.radii.all};
60
+ padding: ${theme.click.field.space.y} ${theme.click.field.space.x};
61
+ gap: ${theme.click.field.space.gap};
62
+ font: ${theme.click.field.typography.fieldText.default};
63
+ color: ${theme.click.field.color.text.default};
64
+ border: 1px solid ${theme.click.field.color.stroke.default};
65
+ background: ${theme.click.field.color.background.default};
66
+ &:hover {
67
+ border: 1px solid ${theme.click.field.color.stroke.hover};
68
+ background: ${theme.click.field.color.background.hover};
69
+ color: ${theme.click.field.color.text.hover};
70
+ }
71
+ ${
72
+ $error
73
+ ? `
74
+ font: ${theme.click.field.typography.fieldText.error};
75
+ border: 1px solid ${theme.click.field.color.stroke.error};
76
+ background: ${theme.click.field.color.background.active};
77
+ color: ${theme.click.field.color.text.error};
78
+ &:hover {
79
+ border: 1px solid ${theme.click.field.color.stroke.error};
80
+ color: ${theme.click.field.color.text.error};
81
+ }
82
+ `
83
+ : `
84
+ &:focus,
85
+ &[data-state="open"] {
86
+ font: ${theme.click.field.typography.fieldText.active};
87
+ border: 1px solid ${theme.click.field.color.stroke.active};
88
+ background: ${theme.click.field.color.background.active};
89
+ color: ${theme.click.field.color.text.active};
90
+ & ~ label {
91
+ color: ${theme.click.field.color.label.active};
92
+ font: ${theme.click.field.typography.label.active};;
93
+ }
94
+ }
95
+ `
96
+ };
97
+ &:disabled {
98
+ font: ${theme.click.field.typography.fieldText.disabled};
99
+ border: 1px solid ${theme.click.field.color.stroke.disabled};
100
+ background: ${theme.click.field.color.background.disabled};
101
+ color: ${theme.click.field.color.text.disabled};
102
+ }
103
+ `}
104
+ `;
105
+ const SelectContent = styled(RadixPopover.Content)`
106
+ width: var(--radix-popover-trigger-width);
107
+ max-height: var(--radix-popover-content-available-height);
108
+ border-radius: 0.25rem;
109
+
110
+ ${({ theme }) => `
111
+ border: 1px solid ${theme.click.genericMenu.item.color.stroke.default};
112
+ background: ${theme.click.genericMenu.item.color.background.default};
113
+ box-shadow: 0px 1px 3px 0px rgba(16, 24, 40, 0.1),
114
+ 0px 1px 2px 0px rgba(16, 24, 40, 0.06);
115
+ border-radius: 0.25rem;
116
+ `}
117
+
118
+ overflow: hidden;
119
+ display: flex;
120
+ padding: 0.5rem 0rem;
121
+ align-items: flex-start;
122
+ gap: 0.625rem;
123
+ [cmdk-root] {
124
+ width: 100%;
125
+ }
126
+ `;
127
+
128
+ const SearchBarContainer = styled.div<{ $showSearch: boolean }>`
129
+ width: fill-available;
130
+ display: grid;
131
+ grid-template-columns: 1fr auto;
132
+ ${({ theme }) => `
133
+ border-bottom: 1px solid ${theme.click.genericMenu.button.color.stroke.default};
134
+ padding: ${theme.click.genericMenu.item.space.y} ${theme.click.genericMenu.item.space.x};
135
+ color: ${theme.click.genericMenu.autocomplete.color.searchTerm.default};
136
+ font: ${theme.click.genericMenu.autocomplete.typography.search.term.default};
137
+ `}
138
+ ${({ $showSearch }) =>
139
+ $showSearch
140
+ ? undefined
141
+ : `
142
+ border: none;
143
+ height: 0;
144
+ padding:0;
145
+ `}
146
+ `;
147
+
148
+ const SearchBar = styled(Command.Input)<{ $showSearch: boolean }>`
149
+ background: transparent;
150
+ border: none;
151
+ width: 100%;
152
+ outline: none;
153
+ ${({ theme, $showSearch }) => `
154
+ min-height: ${$showSearch ? "21px" : "0"};
155
+ gap: ${theme.click.genericMenu.item.space.gap};
156
+ font: ${theme.click.genericMenu.item.typography.label};
157
+ border-bottom: 1px solid ${theme.click.genericMenu.button.color.stroke.default};
158
+ color: inherit;
159
+ font: inherit;
160
+ &::placeholder {
161
+ color: ${theme.click.genericMenu.autocomplete.color.placeholder.default};
162
+ font: ${theme.click.genericMenu.autocomplete.typography.search.placeholder.default};
163
+ }
164
+ ${
165
+ $showSearch
166
+ ? undefined
167
+ : `
168
+ height: 0;
169
+ opacity: 0;
170
+ `
171
+ }
172
+ `}
173
+ `;
174
+
175
+ const SearchClose = styled.button`
176
+ background: transparent;
177
+ border: none;
178
+ padding: 0;
179
+ outline: none;
180
+ cursor: pointer;
181
+ color: inherit;
182
+ `;
183
+
184
+ const NoDataContainer = styled.button<{ clickable: boolean }>`
185
+ border: none;
186
+ display: flex;
187
+ justify-content: flex-start;
188
+ width: 100%;
189
+ ${({ theme, clickable }) => `
190
+ font: ${theme.click.genericMenu.button.typography.label.default}
191
+ padding: ${theme.click.genericMenu.button.space.y} ${
192
+ theme.click.genericMenu.item.space.x
193
+ };
194
+ background: ${theme.click.genericMenu.button.color.background.default};
195
+ color: ${theme.click.genericMenu.button.color.label.default};
196
+ &:hover {
197
+ font: ${theme.click.genericMenu.button.typography.label.hover};
198
+ }
199
+ cursor: ${clickable ? "pointer" : "default"}
200
+ `}
201
+ `;
202
+ declare type State = {
203
+ search: string;
204
+ value: string;
205
+ filtered: {
206
+ count: number;
207
+ items: Map<string, number>;
208
+ groups: Set<string>;
209
+ };
210
+ };
211
+
212
+ interface SelectRootProps {
213
+ open?: boolean;
214
+ id: string;
215
+ placeholder: string;
216
+ disabled?: boolean;
217
+ children: ReactNode;
218
+ hasError: boolean;
219
+ showSearch?: boolean;
220
+ }
221
+
222
+ const SelectRoot = ({
223
+ open,
224
+ id,
225
+ placeholder,
226
+ disabled,
227
+ children,
228
+ hasError,
229
+ showSearch = false,
230
+ }: SelectRootProps) => {
231
+ const { valueNode, popperOpen, onOpenChange } = useSelect();
232
+ const inputRef = useRef<HTMLInputElement>(null);
233
+ const [search, setSearch] = useState("");
234
+ const onFocus = () => {
235
+ inputRef.current?.focus();
236
+ };
237
+
238
+ const clearSearch = () => {
239
+ setSearch("");
240
+ };
241
+
242
+ return (
243
+ <SelectPopoverRoot
244
+ open={open ?? popperOpen}
245
+ onOpenChange={onOpenChange}
246
+ >
247
+ <SelectTrigger
248
+ id={id}
249
+ $error={hasError}
250
+ disabled={disabled}
251
+ >
252
+ {valueNode ?? placeholder}
253
+ <Icon
254
+ name="sort"
255
+ size="small"
256
+ />
257
+ </SelectTrigger>
258
+ <RadixPopover.Portal>
259
+ <SelectContent
260
+ sideOffset={5}
261
+ onFocus={onFocus}
262
+ >
263
+ <Command
264
+ onValueChange={setSearch}
265
+ loop
266
+ >
267
+ <SearchBarContainer $showSearch={showSearch}>
268
+ <SearchBar
269
+ ref={inputRef}
270
+ value={search}
271
+ onValueChange={setSearch}
272
+ $showSearch={showSearch}
273
+ data-testid="select-search-input"
274
+ />
275
+ {search.length > 0 && (
276
+ <SearchClose
277
+ onClick={clearSearch}
278
+ data-testid="select-search-close"
279
+ >
280
+ <Icon
281
+ name="cross"
282
+ size="small"
283
+ />
284
+ </SearchClose>
285
+ )}
286
+ </SearchBarContainer>
287
+ <Command.List>{children}</Command.List>
288
+ </Command>
289
+ </SelectContent>
290
+ </RadixPopover.Portal>
291
+ </SelectPopoverRoot>
292
+ );
293
+ };
294
+
295
+ const findChildWithSpecificProp =
296
+ (children: ReactNode): ((value?: string) => ReactElement | null) =>
297
+ (value?: string): ReactElement | null => {
298
+ if (!value) {
299
+ return null;
300
+ }
301
+
302
+ let foundChild: ReactNode | null = null;
303
+
304
+ Children.forEach(children, (child: ReactNode) => {
305
+ const childProps =
306
+ child && typeof child === "object" && "props" in child ? child.props : null;
307
+ if (childProps?.value === value) {
308
+ foundChild = child;
309
+ return; // Break the loop if the child is found
310
+ }
311
+
312
+ if (childProps?.children) {
313
+ const nestedChild = findChildWithSpecificProp(childProps.children)(value);
314
+ if (nestedChild) {
315
+ foundChild = nestedChild;
316
+ return; // Break the loop if the nested child is found
317
+ }
318
+ }
319
+ });
320
+
321
+ return foundChild;
322
+ };
323
+
324
+ export const Select = ({
325
+ placeholder = "Select an option",
326
+ label,
327
+ children,
328
+ disabled,
329
+ id,
330
+ error,
331
+ value,
332
+ defaultValue,
333
+ onChange,
334
+ open,
335
+ defaultOpen,
336
+ onOpenChange,
337
+ name,
338
+ required,
339
+ isFormControl,
340
+ showSearch = false,
341
+ ...props
342
+ }: SelectProps) => {
343
+ const defaultId = useId();
344
+ return (
345
+ <FormRoot {...props}>
346
+ {error && <Error>{error}</Error>}
347
+ {isFormControl && (
348
+ <input
349
+ type="hidden"
350
+ name={name}
351
+ required={required}
352
+ />
353
+ )}
354
+ <SelectContextProvider
355
+ value={value}
356
+ defaultValue={defaultValue}
357
+ updateValueNode={findChildWithSpecificProp(children)}
358
+ defaultOpen={defaultOpen}
359
+ onOpenChange={onOpenChange}
360
+ onChange={onChange}
361
+ >
362
+ <SelectRoot
363
+ hasError={typeof error !== "undefined"}
364
+ open={open}
365
+ id={id ?? defaultId}
366
+ placeholder={placeholder}
367
+ disabled={disabled}
368
+ showSearch={showSearch}
369
+ >
370
+ {children}
371
+ </SelectRoot>
372
+ </SelectContextProvider>
373
+ {label && (
374
+ <Label
375
+ htmlFor={id ?? defaultId}
376
+ disabled={disabled}
377
+ error={typeof error !== "undefined"}
378
+ >
379
+ {label}
380
+ </Label>
381
+ )}
382
+ </FormRoot>
383
+ );
384
+ };
385
+ interface GroupProps extends Omit<DivProps, "value" | "heading"> {
386
+ heading?: ReactNode;
387
+ value?: string;
388
+ }
389
+
390
+ const SelectGroup = styled(Command.Group)`
391
+ display: flex;
392
+ flex-direction: column;
393
+ align-items: flex-start;
394
+ justify-content: center;
395
+ width: var(--radix-popover-trigger-width);
396
+ padding: 0;
397
+ gap: 0.5rem;
398
+ &[aria-selected] {
399
+ outline: none;
400
+ }
401
+
402
+ ${({ theme }) => `
403
+ font: ${theme.click.genericMenu.item.typography.label.default};
404
+ background: ${theme.click.genericMenu.item.color.background.default};
405
+ color: ${theme.click.genericMenu.item.color.text.default};
406
+ &[data-highlighted] {
407
+ font: ${theme.click.genericMenu.item.typography.label.hover};
408
+ background: ${theme.click.genericMenu.item.color.background.hover};
409
+ color:${theme.click.genericMenu.item.color.text.hover};
410
+ }
411
+ &[data-state="checked"] {
412
+ background:${theme.click.genericMenu.item.color.background.active};
413
+ color:${theme.click.genericMenu.item.color.text.active};
414
+ font: ${theme.click.genericMenu.item.typography.label.active};
415
+ }
416
+ &[data-disabled] {
417
+ background:${theme.click.genericMenu.item.color.background.disabled};
418
+ color:${theme.click.genericMenu.item.color.text.disabled};
419
+ font: ${theme.click.genericMenu.item.typography.label.disabled};
420
+ pointer-events: none;
421
+ }
422
+ `};
423
+ [cmdk-group-heading] {
424
+ display: flex;
425
+ width: 100%;
426
+ flex-direction: column;
427
+ max-width: calc(var(--radix-popover-trigger-width) - 24px);
428
+ overflow: hidden;
429
+ white-space: nowrap;
430
+ text-overflow: ellipsis;
431
+ ${({ theme }) => `
432
+ font: ${theme.click.genericMenu.item.typography.sectionHeader.default};
433
+ color: ${theme.click.genericMenu.item.color.text.muted};
434
+ padding: ${theme.click.genericMenu.item.space.y} ${theme.click.genericMenu.item.space.x};
435
+ gap: ${theme.click.genericMenu.item.space.gap};
436
+ border-bottom: 1px solid ${theme.click.genericMenu.item.color.stroke.default};
437
+ `}
438
+ }
439
+ [cmdk-group-items] {
440
+ width: 100%;
441
+ }
442
+ &[hidden] {
443
+ display: none;
444
+ [cmdk-group-heading] {
445
+ display: none;
446
+ }
447
+ }
448
+ `;
449
+
450
+ const Group = forwardRef<HTMLDivElement, GroupProps>(
451
+ ({ children, ...props }, forwardedRef) => {
452
+ return (
453
+ <SelectGroup
454
+ {...props}
455
+ ref={forwardedRef}
456
+ >
457
+ {children}
458
+ </SelectGroup>
459
+ );
460
+ }
461
+ );
462
+ Group.displayName = "Select.Group";
463
+
464
+ const SelectItem = styled(Command.Item)`
465
+ display: flex;
466
+ width: 100%;
467
+ align-items: center;
468
+ cursor: default;
469
+ max-width: calc(var(--radix-popover-trigger-width) - 24px);
470
+ overflow: hidden;
471
+ white-space: nowrap;
472
+ text-overflow: ellipsis;
473
+ &[aria-selected] {
474
+ outline: none;
475
+ }
476
+
477
+ ${({ theme }) => `
478
+ padding: ${theme.click.genericMenu.item.space.y} ${theme.click.genericMenu.item.space.x};
479
+ gap: ${theme.click.genericMenu.item.space.gap};
480
+ font: ${theme.click.genericMenu.item.typography.label.default};
481
+ background: ${theme.click.genericMenu.item.color.background.default};
482
+ color: ${theme.click.genericMenu.item.color.text.default};
483
+ &[data-selected="true"] {
484
+ font: ${theme.click.genericMenu.item.typography.label.hover};
485
+ background: ${theme.click.genericMenu.item.color.background.hover};
486
+ color:${theme.click.genericMenu.item.color.text.hover};
487
+ cursor: pointer;
488
+ }
489
+ &[data-state="checked"] {
490
+ background:${theme.click.genericMenu.item.color.background.active};
491
+ color:${theme.click.genericMenu.item.color.text.active};
492
+ font: ${theme.click.genericMenu.item.typography.label.active};
493
+ }
494
+ &[data-disabled] {
495
+ background:${theme.click.genericMenu.item.color.background.disabled};
496
+ color:${theme.click.genericMenu.item.color.text.disabled};
497
+ font: ${theme.click.genericMenu.item.typography.label.disabled};
498
+ pointer-events: none;
499
+ }
500
+ `};
501
+ `;
502
+
503
+ interface ItemProps extends Omit<DivProps, "disabled" | "onSelect" | "value"> {
504
+ separator?: boolean;
505
+ disabled?: boolean;
506
+ onSelect?: (value: string) => void;
507
+ value?: string;
508
+ }
509
+
510
+ const Item = forwardRef<HTMLDivElement, ItemProps>(
511
+ (
512
+ { children, separator, onSelect: onSelectProp, value = "", ...props },
513
+ forwardedRef
514
+ ) => {
515
+ const { selectedValue, onSelect } = useSelect();
516
+ const onSelectValue = () => {
517
+ onSelect(value);
518
+ if (typeof onSelectProp == "function") {
519
+ onSelectProp(value);
520
+ }
521
+ };
522
+ return (
523
+ <>
524
+ <SelectItem
525
+ {...props}
526
+ onSelect={onSelectValue}
527
+ ref={forwardedRef}
528
+ data-state={selectedValue == value ? "checked" : "unchecked"}
529
+ >
530
+ {children}
531
+ </SelectItem>
532
+ {separator && <Separator size="sm" />}
533
+ </>
534
+ );
535
+ }
536
+ );
537
+ Item.displayName = "Select.Item";
538
+
539
+ Select.Group = Group;
540
+ Select.Item = Item;
541
+
542
+ type SelectNoDataProps = Omit<HTMLAttributes<HTMLButtonElement>, "children"> & {
543
+ children?: (props: { search: string }) => ReactNode;
544
+ };
545
+ const SelectNoData = ({ children, onClick, ...props }: SelectNoDataProps): ReactNode => {
546
+ const clickable = typeof onClick === "function";
547
+ const search = useCommandState((state: State) => state.search);
548
+ const { onOpenChange } = useSelect();
549
+ const onSelect = (e: MouseEvent<HTMLButtonElement>) => {
550
+ e.preventDefault();
551
+ if (clickable) {
552
+ onClick(e);
553
+ onOpenChange(false);
554
+ }
555
+ };
556
+ return (
557
+ <Command.Empty>
558
+ <NoDataContainer
559
+ onClick={onSelect}
560
+ clickable={clickable}
561
+ {...props}
562
+ >
563
+ {typeof children === "function"
564
+ ? children({ search })
565
+ : `
566
+ No Options found${search.length > 0 ? ` for "${search}" ` : ""}
567
+ `}
568
+ </NoDataContainer>
569
+ </Command.Empty>
570
+ );
571
+ };
572
+
573
+ SelectNoData.displayName = "SelectNoData";
574
+ Select.NoData = SelectNoData;
@@ -0,0 +1,101 @@
1
+ import {
2
+ createContext,
3
+ HTMLAttributes,
4
+ ReactElement,
5
+ ReactNode,
6
+ useContext,
7
+ useEffect,
8
+ useState,
9
+ } from "react";
10
+
11
+ const SelectValue = (props: HTMLAttributes<HTMLDivElement>) => <div {...props} />;
12
+
13
+ type ContextProps = {
14
+ selectedValue?: string | null;
15
+ onSelect: (value: string) => void;
16
+ popperOpen: boolean;
17
+ valueNode: ReactNode | null;
18
+ onOpenChange: (value: boolean) => void;
19
+ };
20
+
21
+ const SelectContext = createContext<ContextProps>({
22
+ selectedValue: undefined,
23
+ onSelect: () => null,
24
+ popperOpen: false,
25
+ valueNode: null,
26
+ onOpenChange: () => null,
27
+ });
28
+
29
+ type Props = {
30
+ children: ReactNode;
31
+ value?: string;
32
+ defaultValue?: string;
33
+ updateValueNode: (value?: string) => ReactElement | null;
34
+ defaultOpen?: boolean;
35
+ onOpenChange?: (value: boolean) => void;
36
+ onChange?: (value: string) => void;
37
+ };
38
+
39
+ export const SelectProvider = ({
40
+ value,
41
+ children,
42
+ }: {
43
+ children: ReactNode;
44
+ value: ContextProps;
45
+ }) => {
46
+ return <SelectContext.Provider value={value}>{children}</SelectContext.Provider>;
47
+ };
48
+
49
+ export const SelectContextProvider = ({
50
+ children,
51
+ value,
52
+ defaultValue,
53
+ updateValueNode,
54
+ defaultOpen = false,
55
+ onOpenChange: onOpenChangeProp,
56
+ onChange,
57
+ }: Props) => {
58
+ const [popperOpen, setPopperOpen] = useState<boolean>(defaultOpen);
59
+ const [valueNode, setValueNode] = useState<JSX.Element | null>(null);
60
+ const [selectedValue, setSelectedValue] = useState<string | undefined>(
61
+ value ?? defaultValue
62
+ );
63
+
64
+ const onSelect = (value: string) => {
65
+ setSelectedValue(value);
66
+ onOpenChange(false);
67
+ if (typeof onChange === "function") {
68
+ onChange(value);
69
+ }
70
+ };
71
+
72
+ const onOpenChange = (open: boolean) => {
73
+ setPopperOpen(open);
74
+ if (typeof onOpenChangeProp === "function") {
75
+ onOpenChangeProp(open);
76
+ }
77
+ };
78
+
79
+ const selectValue = {
80
+ selectedValue,
81
+ valueNode,
82
+ onSelect,
83
+ popperOpen,
84
+ onOpenChange,
85
+ };
86
+
87
+ useEffect(() => {
88
+ const element = updateValueNode(selectedValue);
89
+ setValueNode(element ? <SelectValue {...element.props} /> : null);
90
+ }, [selectedValue, updateValueNode]);
91
+
92
+ return <SelectProvider value={selectValue}>{children}</SelectProvider>;
93
+ };
94
+
95
+ export const useSelect = () => {
96
+ const result = useContext(SelectContext);
97
+ if (!result) {
98
+ throw new Error("Context used outside of its Provider!");
99
+ }
100
+ return result;
101
+ };
@@ -0,0 +1,42 @@
1
+ import styled, { css } from "styled-components";
2
+
3
+ export const FormRoot = styled.div`
4
+ display: flex;
5
+ flex-direction: column-reverse;
6
+ width: fill-available;
7
+ align-items: flex-start;
8
+ gap: 0.5rem;
9
+ * {
10
+ box-shadow: none;
11
+ outline: none;
12
+ }
13
+ `;
14
+
15
+ export const Error = styled.div`
16
+ ${({ theme }) => `
17
+ font: ${theme.click.field.typography.label.error};
18
+ color: ${theme.click.field.color.label.error};
19
+ `};
20
+ `;
21
+
22
+ export const ItemSeparator = css`
23
+ height: 1px;
24
+ background-color: ${({ theme }) => theme.click.genericMenu.item.color.stroke.default};
25
+ `;
26
+
27
+ export const EmptyButton = styled.button`
28
+ background: transparent;
29
+ border: none;
30
+ cursor: pointer;
31
+ outline: none;
32
+ &:disabled {
33
+ cursor: not-allowed;
34
+ }
35
+ `;
36
+
37
+ export const GridCenter = styled.div`
38
+ display: grid;
39
+ place-items: center;
40
+ width: 100%;
41
+ height: 100%;
42
+ `;