@stack-spot/citric-react 0.41.1 → 0.42.0-beta.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 (206) hide show
  1. package/CHANGELOG.md +13 -13
  2. package/dist/citric.css +3090 -2846
  3. package/dist/components/Accordion.d.ts +1 -1
  4. package/dist/components/Accordion.js +1 -1
  5. package/dist/components/Alert.d.ts +1 -1
  6. package/dist/components/Alert.js +1 -1
  7. package/dist/components/AsyncContent.d.ts +1 -1
  8. package/dist/components/AsyncContent.js +1 -1
  9. package/dist/components/Autocomplete/Autocomplete.d.ts +211 -0
  10. package/dist/components/Autocomplete/Autocomplete.d.ts.map +1 -0
  11. package/dist/components/Autocomplete/Autocomplete.js +409 -0
  12. package/dist/components/Autocomplete/Autocomplete.js.map +1 -0
  13. package/dist/components/Autocomplete/index.d.ts +3 -0
  14. package/dist/components/Autocomplete/index.d.ts.map +1 -0
  15. package/dist/components/Autocomplete/index.js +2 -0
  16. package/dist/components/Autocomplete/index.js.map +1 -0
  17. package/dist/components/Avatar.d.ts +1 -1
  18. package/dist/components/Avatar.js +1 -1
  19. package/dist/components/AvatarGroup.d.ts +1 -1
  20. package/dist/components/AvatarGroup.js +1 -1
  21. package/dist/components/Badge.d.ts +1 -1
  22. package/dist/components/Badge.js +1 -1
  23. package/dist/components/Blockquote.d.ts +1 -1
  24. package/dist/components/Blockquote.js +1 -1
  25. package/dist/components/Breadcrumb.d.ts +1 -1
  26. package/dist/components/Breadcrumb.js +1 -1
  27. package/dist/components/Button.d.ts +1 -1
  28. package/dist/components/Button.js +1 -1
  29. package/dist/components/ButtonLink.d.ts +1 -1
  30. package/dist/components/ButtonLink.js +1 -1
  31. package/dist/components/Card.d.ts +1 -1
  32. package/dist/components/Card.js +1 -1
  33. package/dist/components/Checkbox.d.ts +1 -1
  34. package/dist/components/Checkbox.d.ts.map +1 -1
  35. package/dist/components/Checkbox.js +2 -2
  36. package/dist/components/Checkbox.js.map +1 -1
  37. package/dist/components/CheckboxGroup.d.ts +1 -1
  38. package/dist/components/CheckboxGroup.js +1 -1
  39. package/dist/components/Circle.d.ts +1 -1
  40. package/dist/components/Circle.js +1 -1
  41. package/dist/components/CitricComponent.d.ts +1 -1
  42. package/dist/components/CitricComponent.d.ts.map +1 -1
  43. package/dist/components/Divider.d.ts +1 -1
  44. package/dist/components/Divider.js +1 -1
  45. package/dist/components/ErrorBoundary.d.ts +1 -1
  46. package/dist/components/ErrorBoundary.js +1 -1
  47. package/dist/components/ErrorMessage.d.ts +1 -1
  48. package/dist/components/ErrorMessage.js +1 -1
  49. package/dist/components/FallbackBoundary.d.ts +1 -1
  50. package/dist/components/FallbackBoundary.js +1 -1
  51. package/dist/components/Favorite.d.ts +1 -1
  52. package/dist/components/Favorite.js +1 -1
  53. package/dist/components/FieldGroup.d.ts +1 -1
  54. package/dist/components/FieldGroup.js +1 -1
  55. package/dist/components/Form.d.ts +2 -2
  56. package/dist/components/Form.js +1 -1
  57. package/dist/components/FormGroup.d.ts +1 -1
  58. package/dist/components/FormGroup.js +1 -1
  59. package/dist/components/Icon.d.ts +1 -1
  60. package/dist/components/Icon.js +1 -1
  61. package/dist/components/IconBox.d.ts +3 -3
  62. package/dist/components/IconBox.js +1 -1
  63. package/dist/components/ImageBox.d.ts +3 -3
  64. package/dist/components/ImageBox.js +1 -1
  65. package/dist/components/ImageWithFallback.d.ts +1 -1
  66. package/dist/components/ImageWithFallback.js +1 -1
  67. package/dist/components/Input.d.ts +1 -1
  68. package/dist/components/Input.js +1 -1
  69. package/dist/components/Link.d.ts +1 -1
  70. package/dist/components/Link.js +1 -1
  71. package/dist/components/LoadingPanel.d.ts +1 -1
  72. package/dist/components/LoadingPanel.js +1 -1
  73. package/dist/components/MenuOverlay/Menu.d.ts +1 -1
  74. package/dist/components/MenuOverlay/Menu.js +1 -1
  75. package/dist/components/MenuOverlay/index.d.ts +1 -1
  76. package/dist/components/MenuOverlay/index.js +1 -1
  77. package/dist/components/Overlay/index.d.ts +1 -1
  78. package/dist/components/Overlay/index.d.ts.map +1 -1
  79. package/dist/components/Overlay/index.js +7 -6
  80. package/dist/components/Overlay/index.js.map +1 -1
  81. package/dist/components/Pagination.d.ts +1 -1
  82. package/dist/components/Pagination.js +1 -1
  83. package/dist/components/ProgressBar.d.ts +1 -1
  84. package/dist/components/ProgressBar.js +1 -1
  85. package/dist/components/ProgressCircular.d.ts +1 -1
  86. package/dist/components/ProgressCircular.js +1 -1
  87. package/dist/components/RadioGroup.d.ts +1 -1
  88. package/dist/components/RadioGroup.js +1 -1
  89. package/dist/components/Rating.d.ts +1 -1
  90. package/dist/components/Rating.js +1 -1
  91. package/dist/components/Select/MultiSelect.d.ts +1 -1
  92. package/dist/components/Select/MultiSelect.js +1 -1
  93. package/dist/components/Select/RichSelect.d.ts +1 -1
  94. package/dist/components/Select/RichSelect.js +1 -1
  95. package/dist/components/Select/SimpleSelect.d.ts +1 -1
  96. package/dist/components/Select/SimpleSelect.js +1 -1
  97. package/dist/components/Select/index.d.ts +1 -1
  98. package/dist/components/Select/index.js +1 -1
  99. package/dist/components/SelectBox.d.ts +1 -1
  100. package/dist/components/SelectBox.js +1 -1
  101. package/dist/components/Skeleton.d.ts +1 -1
  102. package/dist/components/Skeleton.js +1 -1
  103. package/dist/components/Slider.d.ts +1 -1
  104. package/dist/components/Slider.js +1 -1
  105. package/dist/components/SmartTable.d.ts +1 -1
  106. package/dist/components/SmartTable.js +1 -1
  107. package/dist/components/Stepper.d.ts +1 -1
  108. package/dist/components/Stepper.js +1 -1
  109. package/dist/components/Table.d.ts +3 -3
  110. package/dist/components/Table.js +1 -1
  111. package/dist/components/Tabs/index.d.ts +1 -1
  112. package/dist/components/Tabs/index.js +1 -1
  113. package/dist/components/Textarea.d.ts +1 -1
  114. package/dist/components/Textarea.js +1 -1
  115. package/dist/components/Tooltip.d.ts +1 -1
  116. package/dist/components/Tooltip.js +1 -1
  117. package/dist/context/CitricProvider.d.ts +1 -1
  118. package/dist/context/CitricProvider.js +1 -1
  119. package/dist/index.d.ts +1 -0
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +1 -0
  122. package/dist/index.js.map +1 -1
  123. package/dist/overlay.js +1 -1
  124. package/dist/theme.css +415 -415
  125. package/package.json +1 -1
  126. package/scripts/build-css.ts +49 -49
  127. package/src/components/Accordion.tsx +130 -130
  128. package/src/components/Alert.tsx +24 -24
  129. package/src/components/AsyncContent.tsx +75 -75
  130. package/src/components/Autocomplete/Autocomplete.tsx +794 -0
  131. package/src/components/Autocomplete/index.ts +3 -0
  132. package/src/components/Avatar.tsx +45 -45
  133. package/src/components/AvatarGroup.tsx +49 -49
  134. package/src/components/Badge.tsx +47 -47
  135. package/src/components/Blockquote.tsx +18 -18
  136. package/src/components/Breadcrumb.tsx +33 -33
  137. package/src/components/Button.tsx +105 -105
  138. package/src/components/ButtonLink.tsx +45 -45
  139. package/src/components/Card.tsx +68 -68
  140. package/src/components/Checkbox.tsx +52 -51
  141. package/src/components/CheckboxGroup.tsx +153 -153
  142. package/src/components/Circle.tsx +43 -43
  143. package/src/components/CitricComponent.ts +47 -47
  144. package/src/components/Divider.tsx +24 -24
  145. package/src/components/ErrorBoundary.tsx +75 -75
  146. package/src/components/ErrorMessage.tsx +11 -11
  147. package/src/components/FallbackBoundary.tsx +40 -40
  148. package/src/components/Favorite.tsx +57 -57
  149. package/src/components/FieldGroup.tsx +46 -46
  150. package/src/components/Form.tsx +36 -36
  151. package/src/components/FormGroup.tsx +57 -57
  152. package/src/components/Icon.tsx +35 -35
  153. package/src/components/IconBox.tsx +134 -134
  154. package/src/components/ImageBox.tsx +125 -125
  155. package/src/components/ImageWithFallback.tsx +65 -65
  156. package/src/components/Input.tsx +49 -49
  157. package/src/components/Link.tsx +55 -55
  158. package/src/components/LoadingPanel.tsx +12 -12
  159. package/src/components/MenuOverlay/Menu.tsx +158 -158
  160. package/src/components/MenuOverlay/context.ts +20 -20
  161. package/src/components/MenuOverlay/index.tsx +55 -55
  162. package/src/components/MenuOverlay/keyboard.ts +60 -60
  163. package/src/components/MenuOverlay/types.ts +171 -171
  164. package/src/components/Overlay/context.ts +10 -10
  165. package/src/components/Overlay/index.tsx +182 -181
  166. package/src/components/Overlay/types.ts +75 -75
  167. package/src/components/Pagination.tsx +133 -133
  168. package/src/components/ProgressBar.tsx +45 -45
  169. package/src/components/ProgressCircular.tsx +45 -45
  170. package/src/components/RadioGroup.tsx +147 -147
  171. package/src/components/Rating.tsx +98 -98
  172. package/src/components/Select/MultiSelect.tsx +217 -217
  173. package/src/components/Select/RichSelect.tsx +128 -128
  174. package/src/components/Select/SimpleSelect.tsx +73 -73
  175. package/src/components/Select/hooks.ts +133 -133
  176. package/src/components/Select/index.tsx +35 -35
  177. package/src/components/Select/types.ts +134 -134
  178. package/src/components/SelectBox.tsx +167 -167
  179. package/src/components/Skeleton.tsx +53 -53
  180. package/src/components/Slider.tsx +89 -89
  181. package/src/components/SmartTable.tsx +227 -227
  182. package/src/components/Stepper.tsx +163 -163
  183. package/src/components/Table.tsx +234 -234
  184. package/src/components/Tabs/TabController.ts +54 -54
  185. package/src/components/Tabs/index.tsx +106 -106
  186. package/src/components/Tabs/types.ts +67 -67
  187. package/src/components/Tabs/utils.ts +6 -6
  188. package/src/components/Text.ts +111 -111
  189. package/src/components/Textarea.tsx +27 -27
  190. package/src/components/Tooltip.tsx +83 -83
  191. package/src/components/layout.tsx +101 -101
  192. package/src/context/CitricContext.tsx +4 -4
  193. package/src/context/CitricProvider.tsx +14 -14
  194. package/src/context/hooks.ts +6 -6
  195. package/src/index.ts +59 -58
  196. package/src/overlay.ts +348 -348
  197. package/src/types.ts +235 -235
  198. package/src/utils/ValueController.ts +28 -28
  199. package/src/utils/acessibility.ts +92 -92
  200. package/src/utils/checkbox.ts +121 -121
  201. package/src/utils/css.ts +119 -119
  202. package/src/utils/options.ts +9 -9
  203. package/src/utils/radio.ts +93 -93
  204. package/src/utils/react.ts +6 -6
  205. package/src/utils/time.ts +5 -5
  206. package/tsconfig.json +10 -10
@@ -67,5 +67,5 @@ export type AccordionProps = Omit<React.JSX.IntrinsicElements['div'], 'onChange'
67
67
  * )
68
68
  * ```
69
69
  */
70
- export declare const Accordion: ({ id, appearance, expanded, onChange, header, maxHeight, startExpanded, className, style, children, ...props }: AccordionProps) => import("react/jsx-runtime").JSX.Element;
70
+ export declare const Accordion: ({ id, appearance, expanded, onChange, header, maxHeight, startExpanded, className, style, children, ...props }: AccordionProps) => import("react/jsx-runtime.js").JSX.Element;
71
71
  //# sourceMappingURL=Accordion.d.ts.map
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime.js";
2
2
  import { listToClass } from '@stack-spot/portal-theme';
3
3
  import { useTranslate } from '@stack-spot/portal-translate';
4
4
  import { useEffect, useMemo, useState } from 'react';
@@ -14,5 +14,5 @@ export type AlertProps = React.JSX.IntrinsicElements['div'] & BaseAlertProps;
14
14
  * <Alert type="warning">My warning!</Alert>
15
15
  * ```
16
16
  */
17
- export declare const Alert: ({ type, className, children, ...props }: AlertProps) => import("react/jsx-runtime").JSX.Element;
17
+ export declare const Alert: ({ type, className, children, ...props }: AlertProps) => import("react/jsx-runtime.js").JSX.Element;
18
18
  //# sourceMappingURL=Alert.d.ts.map
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime.js";
2
2
  import { listToClass } from '@stack-spot/portal-theme';
3
3
  import { withRef } from '../utils/react.js';
4
4
  import { CitricComponent } from './CitricComponent.js';
@@ -45,6 +45,6 @@ interface Props {
45
45
  * )
46
46
  * ```
47
47
  */
48
- export declare const AsyncContent: ({ loading, error, autofocus, children, loadingStyle }: Props) => string | number | boolean | Iterable<import("react").ReactNode> | import("react/jsx-runtime").JSX.Element | null | undefined;
48
+ export declare const AsyncContent: ({ loading, error, autofocus, children, loadingStyle }: Props) => string | number | boolean | Iterable<import("react").ReactNode> | import("react/jsx-runtime.js").JSX.Element | null | undefined;
49
49
  export {};
50
50
  //# sourceMappingURL=AsyncContent.d.ts.map
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime.js";
2
2
  import { useEffect, useLayoutEffect } from 'react';
3
3
  import { useCitricController } from '../context/hooks.js';
4
4
  import { withRef } from '../utils/react.js';
@@ -0,0 +1,211 @@
1
+ import { ColorPaletteName, ColorSchemeName } from '@stack-spot/portal-theme';
2
+ export interface BaseAutocompleteProps<T, Multiple extends boolean = false> {
3
+ /**
4
+ * The list of options available for selection.
5
+ */
6
+ options: T[];
7
+ /**
8
+ * The current value(s) selected.
9
+ * - Single selection: T | null
10
+ * - Multiple selection: T[]
11
+ */
12
+ value: Multiple extends true ? T[] : (T | null);
13
+ /**
14
+ * Callback fired when the value changes.
15
+ */
16
+ onChange: Multiple extends true ? (value: T[]) => void : (value: T | null) => void;
17
+ /**
18
+ * If true, enables multiple selection mode.
19
+ * @default false
20
+ */
21
+ multiple?: Multiple;
22
+ /**
23
+ * If true, allows the user to enter values that are not in the options list.
24
+ * @default false
25
+ */
26
+ freeSolo?: boolean;
27
+ /**
28
+ * If true, allows creating new options when no match is found.
29
+ * Shows an "Add [value]" option at the top of the list.
30
+ * @default false
31
+ */
32
+ creatable?: boolean;
33
+ /**
34
+ * Callback fired when a new option is created.
35
+ * Required when creatable is true and you want manual control.
36
+ */
37
+ onCreate?: (inputValue: string) => void;
38
+ /**
39
+ * Function to create a new option object from the input value.
40
+ * Required when creatable is true without onCreate and working with objects.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * getOptionFromInput={(inputValue) => ({ id: Date.now(), name: inputValue })}
45
+ * ```
46
+ */
47
+ getOptionFromInput?: (inputValue: string) => T;
48
+ /**
49
+ * The input value (controlled).
50
+ */
51
+ inputValue?: string;
52
+ /**
53
+ * Callback fired when the input value changes.
54
+ */
55
+ onInputChange?: (value: string) => void;
56
+ /**
57
+ * A function to render the item label.
58
+ * @default "the item's toString() result."
59
+ */
60
+ renderLabel?: (option: T) => string;
61
+ /**
62
+ * A function to generate a unique key for each option.
63
+ */
64
+ renderKey?: (option: T) => string | number;
65
+ /**
66
+ * A function to render an option in the dropdown.
67
+ */
68
+ renderOption?: (option: T) => React.ReactNode;
69
+ /**
70
+ * A function to render a tag in multiple mode.
71
+ */
72
+ renderTag?: (option: T, onRemove: () => void) => React.ReactNode;
73
+ /**
74
+ * Custom filter function for options.
75
+ * @default filters by label includes input (case insensitive)
76
+ */
77
+ filterOptions?: (options: T[], inputValue: string) => T[];
78
+ /**
79
+ * If true, shows a loading indicator.
80
+ * @default false
81
+ */
82
+ loading?: boolean;
83
+ /**
84
+ * If true, the component is disabled.
85
+ * @default false
86
+ */
87
+ disabled?: boolean;
88
+ /**
89
+ * Placeholder text for the input.
90
+ */
91
+ placeholder?: string;
92
+ /**
93
+ * Maximum height for the dropdown panel in pixels.
94
+ */
95
+ maxHeight?: number;
96
+ /**
97
+ * Maximum number of tags to show before truncating.
98
+ * Only applies when multiple is true.
99
+ */
100
+ maxTagsToShow?: number;
101
+ /**
102
+ * If true, automatically highlights the first option.
103
+ * @default false
104
+ */
105
+ autoHighlight?: boolean;
106
+ /**
107
+ * If true, clears the input value when an option is selected.
108
+ * Only applies when multiple is true.
109
+ * @default true (for multiple)
110
+ */
111
+ clearOnSelect?: boolean;
112
+ /**
113
+ * If true, the popup will open on input focus.
114
+ * @default true
115
+ */
116
+ openOnFocus?: boolean;
117
+ /**
118
+ * Callback fired when an option is selected (before onChange).
119
+ */
120
+ onSelect?: (option: T | null) => void;
121
+ /**
122
+ * Text to display when no options are available.
123
+ */
124
+ noOptionsText?: string;
125
+ /**
126
+ * Text to display when loading.
127
+ */
128
+ loadingText?: string;
129
+ /**
130
+ * Callback fired when the user scrolls to the end of the options list.
131
+ * Useful for implementing infinite scroll/pagination.
132
+ *
133
+ * @example
134
+ * ```tsx
135
+ * <Autocomplete
136
+ * options={options}
137
+ * onScrollEnd={() => fetchMoreOptions()}
138
+ * loading={isFetchingMore}
139
+ * />
140
+ * ```
141
+ */
142
+ onScrollEnd?: () => void;
143
+ /**
144
+ * Margin in pixels before the end of the list to trigger onScrollEnd.
145
+ * @default 200
146
+ */
147
+ scrollEndMargin?: number;
148
+ /**
149
+ * Color scheme for the tags (badges) in multiple mode.
150
+ * @example 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info'
151
+ */
152
+ tagColorScheme?: ColorSchemeName;
153
+ /**
154
+ * Color palette for the tags (badges) in multiple mode.
155
+ * @example 'blue' | 'green' | 'red' | 'yellow' | 'purple'
156
+ */
157
+ tagColorPalette?: ColorPaletteName;
158
+ /**
159
+ * Appearance of the tags (badges) in multiple mode.
160
+ * @default 'circle'
161
+ */
162
+ tagAppearance?: 'square' | 'circle';
163
+ }
164
+ export type AutocompleteProps<T, Multiple extends boolean = false> = Omit<React.JSX.IntrinsicElements['div'], 'ref' | 'onChange' | 'onSelect'> & BaseAutocompleteProps<T, Multiple>;
165
+ /**
166
+ * A combination of a text input and a dropdown that suggests options as the user types.
167
+ * Supports both single and multiple selection modes, similar to Material-UI Autocomplete.
168
+ *
169
+ * @example
170
+ * Basic usage (single selection):
171
+ * ```tsx
172
+ * const [value, setValue] = useState<Option | null>(null)
173
+ *
174
+ * <Autocomplete
175
+ * options={options}
176
+ * value={value}
177
+ * onChange={setValue}
178
+ * renderLabel={o => o.name}
179
+ * renderKey={o => o.id}
180
+ * />
181
+ * ```
182
+ *
183
+ * @example
184
+ * Multiple selection with tags:
185
+ * ```tsx
186
+ * const [value, setValue] = useState<Option[]>([])
187
+ *
188
+ * <Autocomplete
189
+ * multiple
190
+ * options={options}
191
+ * value={value}
192
+ * onChange={setValue}
193
+ * renderLabel={o => o.name}
194
+ * renderKey={o => o.id}
195
+ * />
196
+ * ```
197
+ *
198
+ * @example
199
+ * Free solo (allow custom values):
200
+ * ```tsx
201
+ * <Autocomplete
202
+ * freeSolo
203
+ * options={options}
204
+ * value={value}
205
+ * onChange={setValue}
206
+ * renderLabel={o => o.name}
207
+ * />
208
+ * ```
209
+ */
210
+ export declare const Autocomplete: <T, Multiple extends boolean = false>(props: AutocompleteProps<T, Multiple>) => React.ReactElement;
211
+ //# sourceMappingURL=Autocomplete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Autocomplete.d.ts","sourceRoot":"","sources":["../../../src/components/Autocomplete/Autocomplete.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAe,MAAM,0BAA0B,CAAA;AAczF,MAAM,WAAW,qBAAqB,CAAC,CAAC,EAAE,QAAQ,SAAS,OAAO,GAAG,KAAK;IACxE;;OAEG;IACH,OAAO,EAAE,CAAC,EAAE,CAAC;IACb;;;;OAIG;IACH,KAAK,EAAE,QAAQ,SAAS,IAAI,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChD;;OAEG;IACH,QAAQ,EAAE,QAAQ,SAAS,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IACnF;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,CAAC,CAAC;IAC/C;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACpC;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC;IAC3C;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9C;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,KAAK,CAAC,SAAS,CAAC;IACjE;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;IAC1D;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC;;;OAGG;IACH,eAAe,CAAC,EAAE,gBAAgB,CAAC;IACnC;;;OAGG;IACH,aAAa,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;CACrC;AAED,MAAM,MAAM,iBAAiB,CAAC,CAAC,EAAE,QAAQ,SAAS,OAAO,GAAG,KAAK,IAC/D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,UAAU,GAAG,UAAU,CAAC,GACzE,qBAAqB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,eAAO,MAAM,YAAY,EAgiBpB,CAAC,CAAC,EAAE,QAAQ,SAAS,OAAO,GAAG,KAAK,EACvC,KAAK,EAAE,iBAAiB,CAAC,CAAC,EAAE,QAAQ,CAAC,KAClC,KAAK,CAAC,YAAY,CAAA"}
@@ -0,0 +1,409 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime.js";
2
+ import { listToClass } from '@stack-spot/portal-theme';
3
+ import { useTranslate } from '@stack-spot/portal-translate';
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
+ import { applyCSSVariable } from '../../utils/css.js';
6
+ import { defaultRenderKey, defaultRenderLabel } from '../../utils/options.js';
7
+ import { withRef } from '../../utils/react.js';
8
+ import { Badge } from '../Badge.js';
9
+ import { Checkbox } from '../Checkbox.js';
10
+ import { CitricComponent } from '../CitricComponent.js';
11
+ import { IconButton } from '../IconBox.js';
12
+ import { ProgressCircular } from '../ProgressCircular.js';
13
+ import { useDisabledEffect, useFocusEffect } from '../Select/hooks.js';
14
+ import { Row } from '../layout.js';
15
+ /**
16
+ * A combination of a text input and a dropdown that suggests options as the user types.
17
+ * Supports both single and multiple selection modes, similar to Material-UI Autocomplete.
18
+ *
19
+ * @example
20
+ * Basic usage (single selection):
21
+ * ```tsx
22
+ * const [value, setValue] = useState<Option | null>(null)
23
+ *
24
+ * <Autocomplete
25
+ * options={options}
26
+ * value={value}
27
+ * onChange={setValue}
28
+ * renderLabel={o => o.name}
29
+ * renderKey={o => o.id}
30
+ * />
31
+ * ```
32
+ *
33
+ * @example
34
+ * Multiple selection with tags:
35
+ * ```tsx
36
+ * const [value, setValue] = useState<Option[]>([])
37
+ *
38
+ * <Autocomplete
39
+ * multiple
40
+ * options={options}
41
+ * value={value}
42
+ * onChange={setValue}
43
+ * renderLabel={o => o.name}
44
+ * renderKey={o => o.id}
45
+ * />
46
+ * ```
47
+ *
48
+ * @example
49
+ * Free solo (allow custom values):
50
+ * ```tsx
51
+ * <Autocomplete
52
+ * freeSolo
53
+ * options={options}
54
+ * value={value}
55
+ * onChange={setValue}
56
+ * renderLabel={o => o.name}
57
+ * />
58
+ * ```
59
+ */
60
+ export const Autocomplete = withRef(function Autocomplete({ options, value, onChange, multiple = false, freeSolo = false, creatable = false, onCreate, getOptionFromInput, inputValue: controlledInputValue, onInputChange, renderLabel = defaultRenderLabel, renderKey = defaultRenderKey, renderOption, renderTag, filterOptions, loading = false, disabled = false, placeholder, maxHeight, maxTagsToShow, autoHighlight = false, clearOnSelect = multiple, openOnFocus = true, onSelect, noOptionsText, loadingText, onScrollEnd, scrollEndMargin = 200, tagColorScheme, tagColorPalette, tagAppearance = 'circle', style, className, ...props }, ref) {
61
+ const t = useTranslate(dictionary);
62
+ const _element = useRef(null);
63
+ const inputRef = useRef(null);
64
+ const dropdownRef = useRef(null);
65
+ const element = ref ?? _element;
66
+ const [open, setOpen] = useState(false);
67
+ const [focused, setFocused] = useState(false);
68
+ const [internalInputValue, setInternalInputValue] = useState('');
69
+ const [highlightedIndex, setHighlightedIndex] = useState(-1);
70
+ useFocusEffect({ element, focused, setFocused, setOpen });
71
+ useDisabledEffect({ disabled, setOpen, setFocused });
72
+ useEffect(() => {
73
+ if (!open)
74
+ return;
75
+ const handleClickOutside = (event) => {
76
+ if (element.current && !element.current.contains(event.target)) {
77
+ setOpen(false);
78
+ setFocused(false);
79
+ }
80
+ };
81
+ setTimeout(() => {
82
+ document.addEventListener('click', handleClickOutside);
83
+ }, 10);
84
+ return () => {
85
+ document.removeEventListener('click', handleClickOutside);
86
+ };
87
+ }, [open, element]);
88
+ const inputValue = controlledInputValue ?? internalInputValue;
89
+ const setInputValue = useCallback((newValue) => {
90
+ if (onInputChange) {
91
+ onInputChange(newValue);
92
+ }
93
+ else {
94
+ setInternalInputValue(newValue);
95
+ }
96
+ }, [onInputChange]);
97
+ const defaultFilter = useCallback((opts, input) => {
98
+ if (!input)
99
+ return opts;
100
+ return opts.filter(option => renderLabel(option).toLowerCase().includes(input.toLowerCase()));
101
+ }, [renderLabel]);
102
+ const filter = filterOptions ?? defaultFilter;
103
+ const filteredOptions = useMemo(() => filter(options, inputValue), [options, inputValue, filter]);
104
+ const showCreateOption = useMemo(() => {
105
+ if (!creatable || !onCreate || !inputValue.trim())
106
+ return false;
107
+ const hasExactMatch = filteredOptions.some(option => renderLabel(option).toLowerCase() === inputValue.toLowerCase());
108
+ return !hasExactMatch;
109
+ }, [creatable, onCreate, inputValue, filteredOptions, renderLabel]);
110
+ const handleCreate = useCallback(() => {
111
+ if (!onCreate || !inputValue.trim())
112
+ return;
113
+ onCreate(inputValue.trim());
114
+ setInputValue('');
115
+ if (inputRef.current) {
116
+ inputRef.current.focus();
117
+ }
118
+ }, [onCreate, inputValue, setInputValue]);
119
+ const isSelected = useCallback((option) => {
120
+ if (multiple) {
121
+ return value.some(v => renderKey(v) === renderKey(option));
122
+ }
123
+ return value !== null && renderKey(value) === renderKey(option);
124
+ }, [value, multiple, renderKey]);
125
+ const handleSelect = useCallback((option) => {
126
+ if (onSelect)
127
+ onSelect(option);
128
+ if (multiple) {
129
+ const currentValue = value;
130
+ const isAlreadySelected = currentValue.some(v => renderKey(v) === renderKey(option));
131
+ if (isAlreadySelected) {
132
+ const newValue = currentValue.filter(v => renderKey(v) !== renderKey(option));
133
+ onChange(newValue);
134
+ }
135
+ else {
136
+ onChange([...currentValue, option]);
137
+ }
138
+ if (clearOnSelect) {
139
+ setInputValue('');
140
+ }
141
+ }
142
+ else {
143
+ onChange(option);
144
+ setInputValue(renderLabel(option));
145
+ setOpen(false);
146
+ }
147
+ }, [multiple, value, onChange, renderKey, renderLabel, clearOnSelect, setInputValue, onSelect]);
148
+ const handleRemoveTag = useCallback((optionToRemove) => {
149
+ if (!multiple)
150
+ return;
151
+ const newValue = value.filter(v => renderKey(v) !== renderKey(optionToRemove));
152
+ onChange(newValue);
153
+ }, [multiple, value, onChange, renderKey]);
154
+ const handleInputChange = (newValue) => {
155
+ setInputValue(newValue);
156
+ if (!open && newValue) {
157
+ setOpen(true);
158
+ }
159
+ setHighlightedIndex(autoHighlight ? 0 : -1);
160
+ };
161
+ const handleFocus = () => {
162
+ setFocused(true);
163
+ if (openOnFocus) {
164
+ setOpen(true);
165
+ }
166
+ };
167
+ const handleBlur = (e) => {
168
+ if (element.current?.contains(e.relatedTarget)) {
169
+ return;
170
+ }
171
+ setFocused(false);
172
+ setOpen(false);
173
+ if (freeSolo && inputValue && !multiple) {
174
+ if (creatable && !onCreate) {
175
+ if (getOptionFromInput) {
176
+ const newOption = getOptionFromInput(inputValue.trim());
177
+ onChange(newOption);
178
+ }
179
+ else {
180
+ onChange(inputValue);
181
+ }
182
+ }
183
+ else {
184
+ const exactMatch = options.find(o => renderLabel(o).toLowerCase() === inputValue.toLowerCase());
185
+ if (exactMatch) {
186
+ handleSelect(exactMatch);
187
+ }
188
+ }
189
+ }
190
+ };
191
+ const handleKeyDown = (e) => {
192
+ if (disabled)
193
+ return;
194
+ switch (e.key) {
195
+ case 'ArrowDown':
196
+ e.preventDefault();
197
+ if (!open) {
198
+ setOpen(true);
199
+ }
200
+ else {
201
+ setHighlightedIndex(prev => prev < filteredOptions.length - 1 ? prev + 1 : prev);
202
+ }
203
+ break;
204
+ case 'ArrowUp':
205
+ e.preventDefault();
206
+ if (open) {
207
+ setHighlightedIndex(prev => prev > 0 ? prev - 1 : 0);
208
+ }
209
+ break;
210
+ case 'Enter':
211
+ e.preventDefault();
212
+ if (open && highlightedIndex >= 0 && filteredOptions[highlightedIndex]) {
213
+ handleSelect(filteredOptions[highlightedIndex]);
214
+ }
215
+ else if (creatable && inputValue.trim()) {
216
+ if (onCreate) {
217
+ handleCreate();
218
+ }
219
+ else if (freeSolo && getOptionFromInput) {
220
+ const newOption = getOptionFromInput(inputValue.trim());
221
+ if (multiple) {
222
+ const currentValue = value;
223
+ const isDuplicate = currentValue.some(v => renderKey(v) === renderKey(newOption));
224
+ if (!isDuplicate) {
225
+ onChange([...currentValue, newOption]);
226
+ }
227
+ setInputValue('');
228
+ }
229
+ else {
230
+ onChange(newOption);
231
+ setInputValue(renderLabel(newOption));
232
+ setOpen(false);
233
+ }
234
+ }
235
+ else if (freeSolo) {
236
+ if (multiple) {
237
+ const currentValue = value;
238
+ const inputAsOption = inputValue;
239
+ const isDuplicate = currentValue.some(v => renderLabel(v).toLowerCase() === inputValue.toLowerCase());
240
+ if (!isDuplicate) {
241
+ onChange([...currentValue, inputAsOption]);
242
+ }
243
+ setInputValue('');
244
+ }
245
+ else {
246
+ onChange(inputValue);
247
+ setOpen(false);
248
+ }
249
+ }
250
+ }
251
+ else if (freeSolo && inputValue && !multiple) {
252
+ const exactMatch = options.find(o => renderLabel(o).toLowerCase() === inputValue.toLowerCase());
253
+ if (exactMatch) {
254
+ handleSelect(exactMatch);
255
+ }
256
+ }
257
+ break;
258
+ case 'Escape':
259
+ e.preventDefault();
260
+ setOpen(false);
261
+ if (inputRef.current) {
262
+ inputRef.current.blur();
263
+ }
264
+ break;
265
+ case 'Backspace':
266
+ if (multiple && !inputValue && value.length > 0) {
267
+ const lastTag = value[value.length - 1];
268
+ handleRemoveTag(lastTag);
269
+ }
270
+ break;
271
+ default:
272
+ break;
273
+ }
274
+ };
275
+ const handleClear = () => {
276
+ if (multiple) {
277
+ onChange([]);
278
+ }
279
+ else {
280
+ onChange(null);
281
+ }
282
+ setInputValue('');
283
+ if (inputRef.current) {
284
+ inputRef.current.focus();
285
+ }
286
+ };
287
+ useEffect(() => {
288
+ if (!multiple && value && !focused) {
289
+ setInternalInputValue(renderLabel(value));
290
+ }
291
+ }, [value, multiple, renderLabel, focused]);
292
+ useEffect(() => {
293
+ if (autoHighlight && open && filteredOptions.length > 0) {
294
+ setHighlightedIndex(0);
295
+ }
296
+ }, [autoHighlight, open, filteredOptions.length]);
297
+ useEffect(() => {
298
+ if (highlightedIndex < 0 || !open)
299
+ return;
300
+ const optionsContainer = dropdownRef.current?.querySelector('.options');
301
+ if (!optionsContainer)
302
+ return;
303
+ const highlightedOption = optionsContainer.children[highlightedIndex];
304
+ if (!highlightedOption)
305
+ return;
306
+ const containerRect = optionsContainer.getBoundingClientRect();
307
+ const optionRect = highlightedOption.getBoundingClientRect();
308
+ if (optionRect.bottom > containerRect.bottom) {
309
+ highlightedOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
310
+ }
311
+ else if (optionRect.top < containerRect.top) {
312
+ highlightedOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
313
+ }
314
+ }, [highlightedIndex, open]);
315
+ useEffect(() => {
316
+ if (!onScrollEnd || !open)
317
+ return;
318
+ const optionsContainer = dropdownRef.current?.querySelector('.options');
319
+ if (!optionsContainer)
320
+ return;
321
+ const handleScroll = () => {
322
+ if (loading)
323
+ return;
324
+ const { scrollTop, scrollHeight, clientHeight } = optionsContainer;
325
+ const scrollBottom = scrollHeight - scrollTop - clientHeight;
326
+ if (scrollBottom <= scrollEndMargin) {
327
+ onScrollEnd();
328
+ }
329
+ };
330
+ optionsContainer.addEventListener('scroll', handleScroll);
331
+ handleScroll();
332
+ return () => {
333
+ optionsContainer.removeEventListener('scroll', handleScroll);
334
+ };
335
+ }, [onScrollEnd, open, filteredOptions.length, loading, scrollEndMargin]);
336
+ const renderTags = () => {
337
+ if (!multiple || value.length === 0)
338
+ return null;
339
+ const tags = value;
340
+ const visibleTags = maxTagsToShow && tags.length > maxTagsToShow
341
+ ? tags.slice(0, maxTagsToShow)
342
+ : tags;
343
+ const remainingCount = maxTagsToShow && tags.length > maxTagsToShow
344
+ ? tags.length - maxTagsToShow
345
+ : 0;
346
+ return (_jsxs(_Fragment, { children: [visibleTags.map(tag => {
347
+ if (renderTag) {
348
+ return renderTag(tag, () => handleRemoveTag(tag));
349
+ }
350
+ return (_jsxs(Badge, { colorScheme: tagColorScheme, colorPalette: tagColorPalette, appearance: tagAppearance, children: [renderLabel(tag), !disabled && (_jsx(IconButton, { icon: "Times", type: "button", appearance: "none", size: "xs", style: { color: 'inherit' }, onClick: (e) => {
351
+ e.stopPropagation();
352
+ if (!disabled)
353
+ handleRemoveTag(tag);
354
+ }, "aria-label": `${t.removeTag} ${renderLabel(tag)}`, disabled: disabled, tabIndex: 0 }))] }, renderKey(tag)));
355
+ }), remainingCount > 0 && (_jsxs(Badge, { colorScheme: tagColorScheme, colorPalette: tagColorPalette, appearance: tagAppearance, children: ["+", remainingCount] }))] }));
356
+ };
357
+ const showClearButton = !disabled && ((!multiple && value !== null) ||
358
+ (multiple && value.length > 0));
359
+ return (_jsxs(CitricComponent, { tag: "div", component: "autocomplete", "data-citric": "autocomplete", style: maxHeight ? applyCSSVariable(style, 'max-height', `${maxHeight}px`) : style, className: listToClass([
360
+ className,
361
+ open && 'open',
362
+ focused && 'focused',
363
+ disabled && 'disabled',
364
+ multiple && 'multiple',
365
+ ]), ref: element, "aria-busy": loading, ...props, children: [_jsxs("header", { onClick: () => {
366
+ if (disabled)
367
+ return;
368
+ setFocused(true);
369
+ setOpen(true);
370
+ if (inputRef.current) {
371
+ inputRef.current.focus();
372
+ }
373
+ }, onFocus: () => setFocused(true), tabIndex: disabled ? undefined : 0, children: [_jsxs(Row, { gap: "4px", className: "input-container", children: [multiple && renderTags(), _jsx("input", { ref: inputRef, type: "text", value: inputValue, onChange: (e) => handleInputChange(e.target.value), onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, disabled: disabled, placeholder: multiple && value.length > 0 ? '' : placeholder, autoComplete: "off", "aria-autocomplete": "list", "aria-expanded": open, "aria-controls": "autocomplete-listbox" })] }), _jsxs("div", { className: "end-adornment", children: [loading && _jsx(ProgressCircular, { size: "xs", className: "loader" }), showClearButton && (_jsx(IconButton, { icon: "Times", appearance: "none", size: "sm", type: "button", onClick: (e) => {
374
+ e.stopPropagation();
375
+ handleClear();
376
+ }, disabled: disabled, "aria-label": t.clear, tabIndex: 1, style: { width: '12px', height: '12px' } })), _jsx(IconButton, { icon: open ? 'ChevronUp' : 'ChevronDown', appearance: "none", size: "md", type: "button", onClick: (e) => {
377
+ e.stopPropagation();
378
+ setOpen((prev) => !prev);
379
+ }, disabled: disabled, "aria-label": open ? t.collapse : t.expand, tabIndex: 1, style: { width: '12px', height: '12px' } })] })] }), _jsx("div", { className: "dropdown-panel", ref: dropdownRef, id: "autocomplete-listbox", role: "listbox", "aria-hidden": !open, ...(open ? {} : { inert: 'true' }), children: loading && !filteredOptions.length ? (_jsx("div", { className: "message", children: loadingText || t.loading })) : filteredOptions.length === 0 && !showCreateOption ? (_jsx("div", { className: "message", children: noOptionsText || t.noOptions })) : (_jsxs("div", { className: "options", children: [showCreateOption && (_jsxs("div", { role: "option", className: "option create-option", onMouseDown: (e) => {
380
+ e.preventDefault();
381
+ }, onClick: handleCreate, onMouseEnter: () => setHighlightedIndex(-1), children: [_jsx("i", { "data-citric": "icon", className: "citric-icon outline Plus" }), t.addOption.replace('{value}', inputValue)] }, "create-option")), filteredOptions.map((option, index) => (_jsxs("div", { role: "option", "aria-selected": isSelected(option), className: listToClass([
382
+ 'option',
383
+ isSelected(option) && 'selected',
384
+ highlightedIndex === index && 'highlighted',
385
+ ]), onMouseDown: (e) => {
386
+ e.preventDefault();
387
+ }, onClick: () => handleSelect(option), onMouseEnter: () => setHighlightedIndex(index), children: [multiple && _jsx(Checkbox, { value: isSelected(option), readOnly: true }), renderOption ? renderOption(option) : renderLabel(option)] }, renderKey(option))))] })) })] }));
388
+ });
389
+ const dictionary = {
390
+ en: {
391
+ removeTag: 'Remove',
392
+ clear: 'Clear',
393
+ loading: 'Loading...',
394
+ noOptions: 'No options',
395
+ collapse: 'Collapse',
396
+ expand: 'Expand',
397
+ addOption: 'Add "{value}"',
398
+ },
399
+ pt: {
400
+ removeTag: 'Remover',
401
+ clear: 'Limpar',
402
+ loading: 'Carregando...',
403
+ noOptions: 'Sem opções',
404
+ collapse: 'Recolher',
405
+ expand: 'Expandir',
406
+ addOption: 'Adicionar "{value}"',
407
+ },
408
+ };
409
+ //# sourceMappingURL=Autocomplete.js.map