@rovula/ui 0.0.30 → 0.0.31

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 (59) hide show
  1. package/dist/cjs/bundle.css +99 -69
  2. package/dist/cjs/bundle.js +2 -2
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +12 -2
  5. package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +12 -2
  6. package/dist/cjs/types/components/InputFilter/InputFilter.d.ts +63 -4
  7. package/dist/cjs/types/components/InputFilter/InputFilter.stories.d.ts +54 -18
  8. package/dist/cjs/types/components/InputFilter/InputFilter.styles.d.ts +1 -0
  9. package/dist/cjs/types/components/Search/Search.stories.d.ts +7 -1
  10. package/dist/components/ActionButton/ActionButton.stories.js +1 -1
  11. package/dist/components/Checkbox/Checkbox.js +3 -3
  12. package/dist/components/Checkbox/Checkbox.stories.js +1 -1
  13. package/dist/components/Dropdown/Dropdown.js +12 -8
  14. package/dist/components/Dropdown/Dropdown.styles.js +1 -1
  15. package/dist/components/InputFilter/InputFilter.js +118 -12
  16. package/dist/components/InputFilter/InputFilter.stories.js +5 -4
  17. package/dist/components/InputFilter/InputFilter.styles.js +9 -4
  18. package/dist/components/RadioGroup/RadioGroup.js +5 -2
  19. package/dist/components/RadioGroup/RadioGroup.stories.js +1 -1
  20. package/dist/components/Search/Search.stories.js +2 -1
  21. package/dist/components/TextInput/TextInput.js +2 -1
  22. package/dist/components/TextInput/TextInput.styles.js +10 -9
  23. package/dist/esm/bundle.css +99 -69
  24. package/dist/esm/bundle.js +2 -2
  25. package/dist/esm/bundle.js.map +1 -1
  26. package/dist/esm/types/components/Dropdown/Dropdown.d.ts +12 -2
  27. package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +12 -2
  28. package/dist/esm/types/components/InputFilter/InputFilter.d.ts +63 -4
  29. package/dist/esm/types/components/InputFilter/InputFilter.stories.d.ts +54 -18
  30. package/dist/esm/types/components/InputFilter/InputFilter.styles.d.ts +1 -0
  31. package/dist/esm/types/components/Search/Search.stories.d.ts +7 -1
  32. package/dist/index.d.ts +83 -14
  33. package/dist/src/theme/global.css +225 -173
  34. package/dist/theme/presets/colors.js +21 -0
  35. package/dist/theme/themes/xspector/color.css +13 -0
  36. package/dist/theme/themes/xspector/components/action-button.css +44 -42
  37. package/dist/theme/themes/xspector/state.css +1 -1
  38. package/dist/theme/tokens/color.css +13 -0
  39. package/dist/theme/tokens/components/action-button.css +42 -55
  40. package/package.json +1 -1
  41. package/src/components/ActionButton/ActionButton.stories.tsx +1 -1
  42. package/src/components/Checkbox/Checkbox.stories.tsx +1 -1
  43. package/src/components/Checkbox/Checkbox.tsx +4 -4
  44. package/src/components/Dropdown/Dropdown.styles.ts +1 -1
  45. package/src/components/Dropdown/Dropdown.tsx +22 -8
  46. package/src/components/InputFilter/InputFilter.stories.tsx +9 -8
  47. package/src/components/InputFilter/InputFilter.styles.ts +9 -4
  48. package/src/components/InputFilter/InputFilter.tsx +301 -42
  49. package/src/components/RadioGroup/RadioGroup.stories.tsx +1 -1
  50. package/src/components/RadioGroup/RadioGroup.tsx +7 -9
  51. package/src/components/Search/Search.stories.tsx +2 -1
  52. package/src/components/TextInput/TextInput.styles.ts +10 -9
  53. package/src/components/TextInput/TextInput.tsx +11 -10
  54. package/src/theme/presets/colors.js +21 -0
  55. package/src/theme/themes/xspector/color.css +13 -0
  56. package/src/theme/themes/xspector/components/action-button.css +44 -42
  57. package/src/theme/themes/xspector/state.css +1 -1
  58. package/src/theme/tokens/color.css +13 -0
  59. package/src/theme/tokens/components/action-button.css +42 -55
@@ -1,53 +1,312 @@
1
- import React, { forwardRef } from "react";
2
- import Dropdown, { DropdownProps } from "../Dropdown/Dropdown";
1
+ import React, {
2
+ Fragment,
3
+ ReactNode,
4
+ forwardRef,
5
+ useCallback,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from "react";
11
+
12
+ import TextInput, { InputProps } from "../TextInput/TextInput";
13
+ import { cn } from "@/utils/cn";
14
+ import { customInputVariant } from "../Dropdown/Dropdown.styles";
3
15
  import Icon from "../Icon/Icon";
4
16
  import { filterIconVariant } from "./InputFilter.styles";
5
- import { AdjustmentsHorizontalIcon } from "@heroicons/react/16/solid";
6
-
7
- export type InputFilterProps = Omit<
8
- DropdownProps,
9
- | "isFloatingLabel"
10
- | "keepCloseIconOnValue"
11
- | "hasClearIcon"
12
- | "hasSearchIcon"
13
- | "endIcon"
14
- | "filterMode"
15
- | "isFloatingLabel"
16
- >;
17
+ import { Checkbox } from "../Checkbox/Checkbox";
18
+
19
+ type RenderLabelCallbackArg = {
20
+ value: string;
21
+ label: string;
22
+ handleOnClick: () => void;
23
+ className: string;
24
+ };
25
+
26
+ export type Options = {
27
+ value: string;
28
+ label: string;
29
+ renderLabel?: (config: RenderLabelCallbackArg) => ReactNode;
30
+ };
31
+
32
+ export type InputFilterProps = {
33
+ id?: string;
34
+ label?: string;
35
+ size?: "sm" | "md" | "lg";
36
+ rounded?: "none" | "normal" | "full";
37
+ variant?: "flat" | "outline" | "underline";
38
+ helperText?: string;
39
+ errorMessage?: string;
40
+ filterMode?: boolean;
41
+ fullwidth?: boolean;
42
+ disabled?: boolean;
43
+ error?: boolean;
44
+ required?: boolean;
45
+ className?: string;
46
+ optionContainerClassName?: string;
47
+ options: Options[];
48
+ values?: Options[];
49
+ onChangeText?: InputProps["onChange"];
50
+ onSelect?: (values: Options[]) => void;
51
+ renderOptions?: (value: {
52
+ optionsFiltered: Options[];
53
+ selectedOptions: Options[];
54
+ onClick: (option: Options) => void;
55
+ }) => ReactNode;
56
+ } & Omit<InputProps, "value" | "onSelect">;
17
57
 
18
58
  const InputFilter = forwardRef<HTMLInputElement, InputFilterProps>(
19
- (props, ref) => {
59
+ (
60
+ {
61
+ id,
62
+ options = [],
63
+ values = [],
64
+ label,
65
+ size = "md",
66
+ rounded = "normal",
67
+ variant = "outline",
68
+ helperText,
69
+ errorMessage,
70
+ fullwidth = true,
71
+ disabled = false,
72
+ error = false,
73
+ filterMode = false,
74
+ required = true,
75
+ onChangeText,
76
+ onSelect,
77
+ renderOptions: customRenderOptions,
78
+ optionContainerClassName,
79
+ ...props
80
+ },
81
+ ref
82
+ ) => {
83
+ const _id = id || `${label}-select`;
84
+
85
+ const [isFocused, setIsFocused] = useState(false);
86
+ const [selectedOptions, setSelectedOptions] = useState<Options[]>(values);
87
+ const [textValue, setTextValue] = useState("");
88
+ const keyCode = useRef("");
89
+ const containerRef = useRef<HTMLDivElement>(null);
90
+
91
+ useEffect(() => {
92
+ setSelectedOptions(values ?? []);
93
+ setTextValue(values?.map((option) => option.label).join(", ") ?? "");
94
+ }, [values]);
95
+
96
+ useEffect(() => {
97
+ const handleClickOutside = (event: MouseEvent) => {
98
+ if (
99
+ containerRef.current &&
100
+ !containerRef.current.contains(event.target as Node)
101
+ ) {
102
+ setIsFocused(false);
103
+ }
104
+ };
105
+
106
+ document.addEventListener("mousedown", handleClickOutside);
107
+ return () =>
108
+ document.removeEventListener("mousedown", handleClickOutside);
109
+ }, []);
110
+
111
+ const handleOnChangeText = useCallback(
112
+ (event: React.ChangeEvent<HTMLInputElement>) => {
113
+ onChangeText?.(event);
114
+ setTextValue(event.target.value);
115
+
116
+ if (!event.target.value) {
117
+ clearMismatchValues(event as any);
118
+ }
119
+ },
120
+ [onChangeText]
121
+ );
122
+
123
+ const handleOptionClick = useCallback(
124
+ (option: Options) => {
125
+ const isSelected = selectedOptions.some(
126
+ (selected) => selected.value === option.value
127
+ );
128
+ let newSelectedOptions = [...selectedOptions];
129
+
130
+ if (isSelected) {
131
+ newSelectedOptions = newSelectedOptions.filter(
132
+ (selected) => selected.value !== option.value
133
+ );
134
+ } else {
135
+ newSelectedOptions.push(option);
136
+ }
137
+
138
+ setSelectedOptions(newSelectedOptions);
139
+ setTextValue(
140
+ newSelectedOptions.map((option) => option.label).join(", ")
141
+ );
142
+ onSelect?.(newSelectedOptions);
143
+ },
144
+ [selectedOptions, onSelect]
145
+ );
146
+
147
+ const optionsFiltered = useMemo(() => {
148
+ const lastText = textValue?.split(",").pop();
149
+ const filterText = lastText ?? "";
150
+
151
+ return options.filter(
152
+ (option) =>
153
+ !filterMode ||
154
+ option.label?.toLowerCase().includes(filterText?.toLowerCase())
155
+ );
156
+ }, [options, filterMode, textValue]);
157
+
158
+ const renderOptions = () => {
159
+ if (customRenderOptions) {
160
+ return customRenderOptions({
161
+ optionsFiltered,
162
+ selectedOptions,
163
+ onClick: handleOptionClick,
164
+ });
165
+ }
166
+
167
+ return (
168
+ <ul
169
+ className={cn(
170
+ "absolute mt-1 w-full bg-base-popup border border-base-popup text-base-popup-foreground rounded-md shadow-md z-10 max-h-60 overflow-y-auto",
171
+ optionContainerClassName
172
+ )}
173
+ >
174
+ {optionsFiltered.map((option) => {
175
+ if (option.renderLabel) {
176
+ return (
177
+ <Fragment key={option.value}>
178
+ {option.renderLabel({
179
+ value: option.value,
180
+ label: option.label,
181
+ handleOnClick: () => handleOptionClick(option),
182
+ className: `p-4 typography-subtitile4 hover:bg-gray-100 cursor-pointer flex items-center gap-3 ${
183
+ selectedOptions.some(
184
+ (selected) => selected.value === option.value
185
+ )
186
+ ? "bg-gray-200"
187
+ : ""
188
+ }`,
189
+ })}
190
+ </Fragment>
191
+ );
192
+ }
193
+ return (
194
+ <li
195
+ key={option.value}
196
+ onMouseDown={() => handleOptionClick(option)}
197
+ className={`p-4 typography-subtitile4 hover:bg-primary-hover-bg cursor-pointer flex items-center gap-3 ${
198
+ selectedOptions.some(
199
+ (selected) => selected.value === option.value
200
+ )
201
+ ? "bg-base-popup-highlight"
202
+ : ""
203
+ }`}
204
+ >
205
+ <Checkbox
206
+ checked={selectedOptions.some(
207
+ (selected) => selected.value === option.value
208
+ )}
209
+ />
210
+ {option.label}
211
+ </li>
212
+ );
213
+ })}
214
+ {optionsFiltered.length === 0 && (
215
+ <li className="px-4 py-14 text-center text-input-text">
216
+ Not found
217
+ </li>
218
+ )}
219
+ </ul>
220
+ );
221
+ };
222
+
223
+ const handleOnFocus = useCallback(
224
+ (e: React.FocusEvent<HTMLInputElement, Element>) => {
225
+ setIsFocused(true);
226
+ props?.onFocus?.(e);
227
+ },
228
+ [props?.onFocus]
229
+ );
230
+
231
+ const clearMismatchValues = useCallback(
232
+ (e: React.FocusEvent<HTMLInputElement, Element>) => {
233
+ const matchSelectedValues = optionsFiltered.filter(
234
+ (opt) =>
235
+ opt.value === e.target?.value || opt.label === e.target?.value
236
+ );
237
+
238
+ if (keyCode.current === "Enter") {
239
+ return;
240
+ }
241
+
242
+ setSelectedOptions(matchSelectedValues);
243
+ setTextValue(
244
+ matchSelectedValues.map((option) => option.label).join(", ")
245
+ );
246
+ onSelect?.(matchSelectedValues);
247
+ },
248
+ [optionsFiltered, textValue]
249
+ );
250
+
251
+ const handleOnKeyDown = useCallback(
252
+ (e: React.KeyboardEvent<HTMLInputElement>) => {
253
+ keyCode.current = e.code;
254
+ props?.onKeyDown?.(e);
255
+ },
256
+ [props?.onKeyDown]
257
+ );
258
+
20
259
  const filterIconClassName = filterIconVariant({
21
- size: props.size,
22
- rounded: props.rounded,
23
- error: props.error,
24
- active: !!props.value?.value,
260
+ size: size,
261
+ rounded: rounded,
262
+ error: error,
263
+ active: !!values.length,
264
+ disabled,
25
265
  });
26
266
 
27
267
  return (
28
- <Dropdown
29
- label="Placeholder Text"
30
- required={false}
31
- {...props}
32
- ref={ref}
33
- renderEndIcon={() => (
34
- <div className={filterIconClassName}>
35
- <Icon
36
- type="heroicons"
37
- name="adjustments-horizontal"
38
- variant="outline"
39
- color="inherit"
40
- stroke="inherit"
41
- fill="transparent"
42
- />
43
- </div>
44
- )}
45
- renderOptions={(optionsFiltered) => {
46
- return "";
47
- }}
48
- filterMode
49
- isFloatingLabel={false}
50
- />
268
+ <div
269
+ ref={containerRef}
270
+ className={`relative ${fullwidth ? "w-full" : ""}`}
271
+ >
272
+ <TextInput
273
+ hasClearIcon={false}
274
+ endIcon={
275
+ <div className={filterIconClassName}>
276
+ <Icon
277
+ type="heroicons"
278
+ name="adjustments-horizontal"
279
+ variant="outline"
280
+ color="inherit"
281
+ stroke="inherit"
282
+ fill="transparent"
283
+ />
284
+ </div>
285
+ }
286
+ {...props}
287
+ ref={ref}
288
+ readOnly={!filterMode}
289
+ value={textValue}
290
+ onChange={handleOnChangeText}
291
+ label={label}
292
+ placeholder=" "
293
+ type="text"
294
+ rounded={rounded}
295
+ variant={variant}
296
+ helperText={helperText}
297
+ errorMessage={errorMessage}
298
+ fullwidth={fullwidth}
299
+ error={error}
300
+ required={required}
301
+ id={_id}
302
+ disabled={disabled}
303
+ size={size}
304
+ className={customInputVariant({ size })}
305
+ onFocus={handleOnFocus}
306
+ onKeyDown={handleOnKeyDown}
307
+ />
308
+ {isFocused && renderOptions()}
309
+ </div>
51
310
  );
52
311
  }
53
312
  );
@@ -11,7 +11,7 @@ const meta = {
11
11
  },
12
12
  decorators: [
13
13
  (Story) => (
14
- <div className="p-5 flex w-full">
14
+ <div className="p-5 flex w-full bg-base-bg2">
15
15
  <Story />
16
16
  </div>
17
17
  ),
@@ -1,8 +1,5 @@
1
- "use client";
2
-
3
1
  import * as React from "react";
4
2
  import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
5
-
6
3
  import { cn } from "@/utils/cn";
7
4
 
8
5
  const RadioGroup = React.forwardRef<
@@ -27,18 +24,19 @@ const RadioGroupItem = React.forwardRef<
27
24
  <RadioGroupPrimitive.Item
28
25
  ref={ref}
29
26
  className={cn(
30
- "aspect-square box-border size-4 rounded-full border border-primary text-primary",
31
- "hover:border-primary-hover",
27
+ "aspect-square box-border size-4 rounded-full border border-function-default-solid text-function-default-solid",
28
+ "hover:border-function-default-hover",
32
29
  "focus:outline-none",
33
- "data-[state=checked]:border-secondary data-[state=checked]:text-secondary",
34
- "hover:data-[state=checked]:border-secondary-hover hover:data-[state=checked]:text-secondary-hover",
35
- "data-[disabled]:border-state-disable-solid data-[disabled]:fill-state-disable-solid data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none data-[disabled]:text-state-disable-solid",
30
+ // Disabled state styles
31
+ "data-[disabled]:!border-state-disable-solid data-[disabled]:!fill-state-disable-solid data-[disabled]:!cursor-not-allowed data-[disabled]:!pointer-events-none data-[disabled]:!text-state-disable-solid",
32
+ // Checked state styles
33
+ "data-[state=checked]:border-function-active-solid data-[state=checked]:text-function-active-solid",
34
+ "hover:data-[state=checked]:border-function-active-hover hover:data-[state=checked]:text-function-active-hover",
36
35
  className
37
36
  )}
38
37
  {...props}
39
38
  >
40
39
  <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
41
- {/* <div className="bg-primary size-2.5 rounded-full" /> */}
42
40
  <svg
43
41
  width="10"
44
42
  height="10"
@@ -12,7 +12,7 @@ const meta = {
12
12
  },
13
13
  decorators: [
14
14
  (Story) => (
15
- <div className="p-5 flex w-full">
15
+ <div className="p-5 flex w-full bg-base-bg2">
16
16
  <Story />
17
17
  </div>
18
18
  ),
@@ -30,6 +30,7 @@ export const Default = {
30
30
  args: {
31
31
  label: "Choose an option:",
32
32
  fullwidth: true,
33
+ size: "md",
33
34
  options,
34
35
  },
35
36
  render: (args) => {
@@ -2,6 +2,7 @@ import { cva } from "class-variance-authority";
2
2
 
3
3
  export const inputVariant = cva(
4
4
  [
5
+ "truncate",
5
6
  "border-0 outline-none",
6
7
  "p-1 flex w-auto box-border",
7
8
  "peer text-input-filled-text placeholder:text-transparent bg-transparent caret-primary",
@@ -76,47 +77,47 @@ export const inputVariant = cva(
76
77
  {
77
78
  hasSearchIcon: true,
78
79
  size: "sm",
79
- class: "ps-6",
80
+ class: "!ps-6",
80
81
  },
81
82
  {
82
83
  hasSearchIcon: true,
83
84
  size: "md",
84
- class: "ps-9",
85
+ class: "!ps-9",
85
86
  },
86
87
  {
87
88
  hasSearchIcon: true,
88
89
  size: "lg",
89
- class: "ps-11",
90
+ class: "!ps-11",
90
91
  },
91
92
  {
92
93
  leftSectionIcon: true,
93
94
  size: "sm",
94
- class: "ps-[38px]",
95
+ class: "!ps-[38px]",
95
96
  },
96
97
  {
97
98
  leftSectionIcon: true,
98
99
  size: "md",
99
- class: "ps-[46px]",
100
+ class: "!ps-[46px]",
100
101
  },
101
102
  {
102
103
  leftSectionIcon: true,
103
104
  size: "lg",
104
- class: "ps-[72px]",
105
+ class: "!ps-[72px]",
105
106
  },
106
107
  {
107
108
  rightSectionIcon: true,
108
109
  size: "sm",
109
- class: "pe-[38px]",
110
+ class: "!pe-[38px]",
110
111
  },
111
112
  {
112
113
  rightSectionIcon: true,
113
114
  size: "md",
114
- class: "pe-[46px]",
115
+ class: "!pe-[46px]",
115
116
  },
116
117
  {
117
118
  rightSectionIcon: true,
118
119
  size: "lg",
119
- class: "pe-[72px]",
120
+ class: "!pe-[72px]",
120
121
  },
121
122
  ],
122
123
  defaultVariants: {
@@ -165,16 +165,17 @@ export const TextInput = forwardRef<HTMLInputElement, InputProps>(
165
165
  />
166
166
  </div>
167
167
  )}
168
- {hasRightSectionIcon && renderEndIcon ? (
169
- renderEndIcon()
170
- ) : (
171
- <div
172
- className={endIconWrapperClassname}
173
- onClick={handleOnClickRightSectionIcon}
174
- >
175
- {endIcon}
176
- </div>
177
- )}
168
+ {hasRightSectionIcon &&
169
+ (renderEndIcon ? (
170
+ renderEndIcon()
171
+ ) : (
172
+ <div
173
+ className={endIconWrapperClassname}
174
+ onClick={handleOnClickRightSectionIcon}
175
+ >
176
+ {endIcon}
177
+ </div>
178
+ ))}
178
179
 
179
180
  <label htmlFor={_id} className={cn(labelClassname, labelClassName)}>
180
181
  {label}{" "}
@@ -71,6 +71,27 @@ module.exports = {
71
71
  "input-label-bg": withColorMixin("--input-color-label-bg"),
72
72
  "input-error": withColorMixin("--input-color-error"),
73
73
 
74
+ "function-default-solid": withColorMixin("--function-default-solid"),
75
+ "function-default-hover": withColorMixin("--function-default-hover"),
76
+ "function-default-hover-bg": withColorMixin(
77
+ "--function-default-hover-bg"
78
+ ),
79
+ "function-default-hover-bg": withColorMixin(
80
+ "--function-default-hover-bg"
81
+ ),
82
+ "function-default-stroke": withColorMixin("--function-default-stroke"),
83
+ "function-default-icon": withColorMixin("--function-default-icon"),
84
+ "function-default-outline": withColorMixin(
85
+ "--function-default-outline-icon"
86
+ ),
87
+ "function-active-solid": withColorMixin("--function-active-solid"),
88
+ "function-active-hover": withColorMixin("--function-active-hover"),
89
+ "function-active-hover-bg": withColorMixin(
90
+ "--function-active-hover-bg"
91
+ ),
92
+ "function-active-stroke": withColorMixin("--function-active-stroke"),
93
+ "function-active-icon": withColorMixin("--function-active-icon"),
94
+
74
95
  "base-bg": withColorMixin("--base-color-bg"),
75
96
  "base-bg2": withColorMixin("--base-color-bg2"),
76
97
  "base-bg3": withColorMixin("--base-color-bg3"),
@@ -11,6 +11,19 @@
11
11
  --input-color-label-bg: #2d2e30;
12
12
  --input-color-error: #ff4d35;
13
13
 
14
+ /* Function button */
15
+ --function-default-solid: #ececec;
16
+ --function-default-hover: #fafafa;
17
+ --function-default-hover-bg: rgba(250 250 250 / 0.08);
18
+ --function-default-stroke: rgba(158 158 158 / 0.24);
19
+ --function-default-icon: #212b36;
20
+ --function-default-outline-icon: #9e9e9e;
21
+ --function-active-solid: #b1a400;
22
+ --function-active-hover: #ddcd00;
23
+ --function-active-hover-bg: rgba(221 205 0 / 0.08);
24
+ --function-active-stroke: rgba(177 164 0 / 0.48);
25
+ --function-active-icon: #212b36;
26
+
14
27
  --text-dark: #212b36;
15
28
  --text-medium: #637381;
16
29
  --text-light: #919eab;