react-science 19.0.0 → 19.2.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 (159) hide show
  1. package/lib/components/color-picker/react-color/helpers/color.d.ts +1 -1
  2. package/lib/components/color-picker/react-color/helpers/color.d.ts.map +1 -1
  3. package/lib/components/color-picker/react-color/helpers/color.js +1 -1
  4. package/lib/components/color-picker/react-color/helpers/color.js.map +1 -1
  5. package/lib/components/form/components/hooks/use_input_id.d.ts +8 -0
  6. package/lib/components/form/components/hooks/use_input_id.d.ts.map +1 -0
  7. package/lib/components/form/components/hooks/use_input_id.js +15 -0
  8. package/lib/components/form/components/hooks/use_input_id.js.map +1 -0
  9. package/lib/components/form/components/input/checkbox.d.ts +5 -0
  10. package/lib/components/form/components/input/checkbox.d.ts.map +1 -0
  11. package/lib/components/form/components/input/checkbox.js +19 -0
  12. package/lib/components/form/components/input/checkbox.js.map +1 -0
  13. package/lib/components/form/components/input/input.d.ts +9 -0
  14. package/lib/components/form/components/input/input.d.ts.map +1 -0
  15. package/lib/components/form/components/input/input.js +16 -0
  16. package/lib/components/form/components/input/input.js.map +1 -0
  17. package/lib/components/form/components/input/numeric_input.d.ts +8 -0
  18. package/lib/components/form/components/input/numeric_input.d.ts.map +1 -0
  19. package/lib/components/form/components/input/numeric_input.js +20 -0
  20. package/lib/components/form/components/input/numeric_input.js.map +1 -0
  21. package/lib/components/form/components/input/reset_button.d.ts +7 -0
  22. package/lib/components/form/components/input/reset_button.d.ts.map +1 -0
  23. package/lib/components/form/components/input/reset_button.js +9 -0
  24. package/lib/components/form/components/input/reset_button.js.map +1 -0
  25. package/lib/components/form/components/input/select.d.ts +12 -0
  26. package/lib/components/form/components/input/select.d.ts.map +1 -0
  27. package/lib/components/form/components/input/select.js +25 -0
  28. package/lib/components/form/components/input/select.js.map +1 -0
  29. package/lib/components/form/components/input/submit_button.d.ts +5 -0
  30. package/lib/components/form/components/input/submit_button.d.ts.map +1 -0
  31. package/lib/components/form/components/input/submit_button.js +11 -0
  32. package/lib/components/form/components/input/submit_button.js.map +1 -0
  33. package/lib/components/form/components/input/switch.d.ts +6 -0
  34. package/lib/components/form/components/input/switch.d.ts.map +1 -0
  35. package/lib/components/form/components/input/switch.js +16 -0
  36. package/lib/components/form/components/input/switch.js.map +1 -0
  37. package/lib/components/form/components/input_groups/input.d.ts +20 -0
  38. package/lib/components/form/components/input_groups/input.d.ts.map +1 -0
  39. package/lib/components/form/components/input_groups/input.js +9 -0
  40. package/lib/components/form/components/input_groups/input.js.map +1 -0
  41. package/lib/components/form/components/input_groups/select.d.ts +59 -0
  42. package/lib/components/form/components/input_groups/select.d.ts.map +1 -0
  43. package/lib/components/form/components/input_groups/select.js +29 -0
  44. package/lib/components/form/components/input_groups/select.js.map +1 -0
  45. package/lib/components/form/components/util/select.d.ts +20 -0
  46. package/lib/components/form/components/util/select.d.ts.map +1 -0
  47. package/lib/components/form/components/util/select.js +20 -0
  48. package/lib/components/form/components/util/select.js.map +1 -0
  49. package/lib/components/form/context/use_ts_form.d.ts +41 -0
  50. package/lib/components/form/context/use_ts_form.d.ts.map +1 -0
  51. package/lib/components/form/context/use_ts_form.js +27 -0
  52. package/lib/components/form/context/use_ts_form.js.map +1 -0
  53. package/lib/components/form/utils/use_field_id.d.ts +2 -0
  54. package/lib/components/form/utils/use_field_id.d.ts.map +1 -0
  55. package/lib/components/form/utils/use_field_id.js +5 -0
  56. package/lib/components/form/utils/use_field_id.js.map +1 -0
  57. package/lib/components/form/utils/use_intent.d.ts +3 -0
  58. package/lib/components/form/utils/use_intent.d.ts.map +1 -0
  59. package/lib/components/form/utils/use_intent.js +4 -0
  60. package/lib/components/form/utils/use_intent.js.map +1 -0
  61. package/lib/components/hooks/useDoubleClick.js +1 -1
  62. package/lib/components/hooks/useDoubleClick.js.map +1 -1
  63. package/lib/components/hooks/useOnOff.js +1 -1
  64. package/lib/components/hooks/useOnOff.js.map +1 -1
  65. package/lib/components/index.d.ts +2 -1
  66. package/lib/components/index.d.ts.map +1 -1
  67. package/lib/components/index.js +2 -1
  68. package/lib/components/index.js.map +1 -1
  69. package/lib/components/info-panel/InfoPanel.js +2 -2
  70. package/lib/components/info-panel/InfoPanel.js.map +1 -1
  71. package/lib/components/input/index.d.ts.map +1 -0
  72. package/lib/components/input/index.js.map +1 -0
  73. package/lib/components/{forms → input}/radio-button-group/RadioButton.d.ts.map +1 -1
  74. package/lib/components/{forms → input}/radio-button-group/RadioButton.js.map +1 -1
  75. package/lib/components/{forms → input}/radio-button-group/RadioButtonGroup.d.ts.map +1 -1
  76. package/lib/components/{forms → input}/radio-button-group/RadioButtonGroup.js.map +1 -1
  77. package/lib/components/{forms → input}/radio-button-group/index.d.ts.map +1 -1
  78. package/lib/components/input/radio-button-group/index.js.map +1 -0
  79. package/lib/components/input/styles.d.ts.map +1 -0
  80. package/lib/components/{forms → input}/styles.js.map +1 -1
  81. package/lib/components/{forms → input}/utils/SubText.d.ts.map +1 -1
  82. package/lib/components/{forms → input}/utils/SubText.js.map +1 -1
  83. package/lib/components/input/utils/index.d.ts.map +1 -0
  84. package/lib/components/input/utils/index.js.map +1 -0
  85. package/lib/components/{forms → input}/utils/use_input_id.d.ts.map +1 -1
  86. package/lib/components/{forms → input}/utils/use_input_id.js.map +1 -1
  87. package/lib/components/logger/FifoLoggerDialog.js +1 -1
  88. package/lib/components/logger/FifoLoggerDialog.js.map +1 -1
  89. package/lib/components/pdnd.cjs +2 -2
  90. package/lib/components/pdnd.cjs.map +1 -1
  91. package/lib/components/pdnd.d.cts +1 -1
  92. package/lib/components/pdnd.d.cts.map +1 -1
  93. package/lib/components/split_pane/split_pane.d.ts +5 -0
  94. package/lib/components/split_pane/split_pane.d.ts.map +1 -1
  95. package/lib/components/split_pane/split_pane.js +23 -2
  96. package/lib/components/split_pane/split_pane.js.map +1 -1
  97. package/lib/components/table/table_body.d.ts.map +1 -1
  98. package/lib/components/toolbar/Toolbar.d.ts.map +1 -1
  99. package/lib/components/toolbar/Toolbar.js +2 -1
  100. package/lib/components/toolbar/Toolbar.js.map +1 -1
  101. package/package.json +32 -30
  102. package/src/components/color-picker/react-color/helpers/color.ts +2 -2
  103. package/src/components/form/components/hooks/use_input_id.ts +19 -0
  104. package/src/components/form/components/input/checkbox.tsx +36 -0
  105. package/src/components/form/components/input/input.tsx +37 -0
  106. package/src/components/form/components/input/numeric_input.tsx +49 -0
  107. package/src/components/form/components/input/reset_button.tsx +19 -0
  108. package/src/components/form/components/input/select.tsx +47 -0
  109. package/src/components/form/components/input/submit_button.tsx +23 -0
  110. package/src/components/form/components/input/switch.tsx +33 -0
  111. package/src/components/form/components/input_groups/input.tsx +63 -0
  112. package/src/components/form/components/input_groups/select.tsx +196 -0
  113. package/src/components/form/components/util/select.tsx +67 -0
  114. package/src/components/form/context/use_ts_form.ts +31 -0
  115. package/src/components/form/utils/use_field_id.ts +5 -0
  116. package/src/components/form/utils/use_intent.ts +5 -0
  117. package/src/components/hooks/useDoubleClick.ts +1 -1
  118. package/src/components/hooks/useOnOff.ts +1 -1
  119. package/src/components/index.ts +2 -1
  120. package/src/components/info-panel/InfoPanel.tsx +2 -2
  121. package/src/components/logger/FifoLoggerDialog.tsx +1 -1
  122. package/src/components/pdnd.cts +1 -1
  123. package/src/components/split_pane/split_pane.tsx +43 -2
  124. package/src/components/table/table_body.tsx +1 -1
  125. package/src/components/toolbar/Toolbar.tsx +2 -1
  126. package/lib/components/doi/__tests__/parseDoi.test.d.ts +0 -2
  127. package/lib/components/doi/__tests__/parseDoi.test.d.ts.map +0 -1
  128. package/lib/components/doi/__tests__/parseDoi.test.js +0 -19
  129. package/lib/components/doi/__tests__/parseDoi.test.js.map +0 -1
  130. package/lib/components/forms/index.d.ts.map +0 -1
  131. package/lib/components/forms/index.js.map +0 -1
  132. package/lib/components/forms/radio-button-group/index.js.map +0 -1
  133. package/lib/components/forms/styles.d.ts.map +0 -1
  134. package/lib/components/forms/utils/index.d.ts.map +0 -1
  135. package/lib/components/forms/utils/index.js.map +0 -1
  136. /package/lib/components/{forms → input}/index.d.ts +0 -0
  137. /package/lib/components/{forms → input}/index.js +0 -0
  138. /package/lib/components/{forms → input}/radio-button-group/RadioButton.d.ts +0 -0
  139. /package/lib/components/{forms → input}/radio-button-group/RadioButton.js +0 -0
  140. /package/lib/components/{forms → input}/radio-button-group/RadioButtonGroup.d.ts +0 -0
  141. /package/lib/components/{forms → input}/radio-button-group/RadioButtonGroup.js +0 -0
  142. /package/lib/components/{forms → input}/radio-button-group/index.d.ts +0 -0
  143. /package/lib/components/{forms → input}/radio-button-group/index.js +0 -0
  144. /package/lib/components/{forms → input}/styles.d.ts +0 -0
  145. /package/lib/components/{forms → input}/styles.js +0 -0
  146. /package/lib/components/{forms → input}/utils/SubText.d.ts +0 -0
  147. /package/lib/components/{forms → input}/utils/SubText.js +0 -0
  148. /package/lib/components/{forms → input}/utils/index.d.ts +0 -0
  149. /package/lib/components/{forms → input}/utils/index.js +0 -0
  150. /package/lib/components/{forms → input}/utils/use_input_id.d.ts +0 -0
  151. /package/lib/components/{forms → input}/utils/use_input_id.js +0 -0
  152. /package/src/components/{forms → input}/index.ts +0 -0
  153. /package/src/components/{forms → input}/radio-button-group/RadioButton.tsx +0 -0
  154. /package/src/components/{forms → input}/radio-button-group/RadioButtonGroup.tsx +0 -0
  155. /package/src/components/{forms → input}/radio-button-group/index.ts +0 -0
  156. /package/src/components/{forms → input}/styles.ts +0 -0
  157. /package/src/components/{forms → input}/utils/SubText.tsx +0 -0
  158. /package/src/components/{forms → input}/utils/index.ts +0 -0
  159. /package/src/components/{forms → input}/utils/use_input_id.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-science",
3
- "version": "19.0.0",
3
+ "version": "19.2.0",
4
4
  "description": "React components to build scientific applications UI",
5
5
  "type": "module",
6
6
  "exports": {
@@ -51,64 +51,66 @@
51
51
  "@blueprintjs/core": "^6.0.0",
52
52
  "@blueprintjs/icons": "^6.0.0",
53
53
  "@blueprintjs/select": "^6.0.0",
54
- "fifo-logger": "^1.0.0",
54
+ "fifo-logger": "^1.0.0 || ^2.0.0",
55
55
  "react": ">=18.0.0",
56
56
  "react-dom": ">=18.0.0"
57
57
  },
58
58
  "dependencies": {
59
- "@atlaskit/pragmatic-drag-and-drop": "^1.7.2",
59
+ "@atlaskit/pragmatic-drag-and-drop": "^1.7.4",
60
60
  "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.1",
61
61
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
62
- "@emotion/styled": "^11.14.0",
62
+ "@emotion/styled": "^11.14.1",
63
63
  "@radix-ui/react-use-controllable-state": "^1.2.2",
64
+ "@tanstack/react-form": "^1.19.2",
64
65
  "@tanstack/react-table": "^8.21.3",
65
- "@tanstack/react-virtual": "^3.13.10",
66
+ "@tanstack/react-virtual": "^3.13.12",
66
67
  "d3-scale-chromatic": "^3.1.0",
67
- "react-d3-utils": "^3.1.0",
68
+ "react-d3-utils": "^3.1.1",
68
69
  "react-dropzone": "^14.3.8",
69
70
  "react-full-screen": "^1.1.1",
70
71
  "react-icons": "^5.5.0",
71
72
  "react-inspector": "^6.0.2",
72
73
  "tinycolor2": "^1.6.0",
73
- "ts-pattern": "^5.7.1"
74
+ "ts-pattern": "^5.7.1",
75
+ "zod": "^4.0.17"
74
76
  },
75
77
  "devDependencies": {
76
78
  "@blueprintjs/core": "6.0.0",
77
79
  "@blueprintjs/icons": "6.0.0",
78
80
  "@blueprintjs/select": "6.0.0",
79
- "@floating-ui/react": "^0.27.12",
80
- "@playwright/experimental-ct-react": "^1.53.0",
81
- "@playwright/test": "^1.53.0",
82
- "@storybook/addon-docs": "^9.0.8",
83
- "@storybook/react-vite": "^9.0.8",
84
- "@tanstack/react-query": "^5.80.7",
81
+ "@floating-ui/react": "^0.27.13",
82
+ "@playwright/experimental-ct-react": "^1.54.1",
83
+ "@playwright/test": "^1.54.1",
84
+ "@storybook/addon-docs": "^9.0.16",
85
+ "@storybook/react-vite": "^9.0.16",
86
+ "@tanstack/react-query": "^5.83.0",
85
87
  "@types/babel__core": "^7.20.5",
86
88
  "@types/d3-scale-chromatic": "^3.1.0",
87
- "@types/node": "^22.15.29",
89
+ "@types/node": "^24.0.13",
88
90
  "@types/react": "^18.3.12",
89
91
  "@types/react-dom": "^18.3.1",
90
92
  "@types/tinycolor2": "^1.4.6",
91
- "@vitejs/plugin-react": "^4.5.2",
92
- "@vitest/coverage-v8": "^3.2.3",
93
+ "@vitejs/plugin-react": "^4.6.0",
94
+ "@vitest/coverage-v8": "^3.2.4",
93
95
  "biologic-converter": "^0.6.0",
94
96
  "cheminfo-font": "^1.13.1",
95
97
  "cheminfo-types": "^1.8.1",
96
98
  "cross-env": "^7.0.3",
97
- "eslint": "^9.28.0",
98
- "eslint-config-zakodium": "^15.0.1",
99
- "eslint-plugin-storybook": "^9.0.8",
100
- "fifo-logger": "^1.0.0",
99
+ "eslint": "^9.31.0",
100
+ "eslint-config-zakodium": "^16.0.0",
101
+ "eslint-plugin-storybook": "^9.0.16",
102
+ "fifo-logger": "^2.0.0",
101
103
  "filelist-utils": "^1.11.3",
102
104
  "immer": "^10.1.1",
103
105
  "jcampconverter": "^11.0.3",
104
- "ml-gsd": "^12.1.8",
105
- "ml-peak-shape-generator": "^4.1.4",
106
- "ml-signal-processing": "^1.2.0",
107
- "ml-spectra-processing": "^14.12.0",
106
+ "ml-gsd": "^13.0.1",
107
+ "ml-peak-shape-generator": "^4.2.0",
108
+ "ml-signal-processing": "^2.0.0",
109
+ "ml-spectra-processing": "^14.13.0",
108
110
  "ms-spectrum": "^3.9.0",
109
111
  "netcdfjs": "^3.0.0",
110
112
  "postcss-styled-syntax": "^0.7.1",
111
- "prettier": "^3.5.3",
113
+ "prettier": "^3.6.2",
112
114
  "react": "^18.3.1",
113
115
  "react-dom": "^18.3.1",
114
116
  "react-error-boundary": "^6.0.0",
@@ -117,16 +119,16 @@
117
119
  "react-plot": "^3.1.0",
118
120
  "rimraf": "^6.0.1",
119
121
  "spc-parser": "^1.0.0",
120
- "storybook": "^9.0.8",
121
- "stylelint": "^16.20.0",
122
+ "storybook": "^9.0.16",
123
+ "stylelint": "^16.21.1",
122
124
  "stylelint-config-standard": "^38.0.0",
123
125
  "typescript": "^5.8.3",
124
- "vite": "^6.3.5",
125
- "vitest": "^3.2.3",
126
+ "vite": "^7.0.4",
127
+ "vitest": "^3.2.4",
126
128
  "wdf-parser": "^0.3.0"
127
129
  },
128
130
  "volta": {
129
- "node": "22.16.0"
131
+ "node": "24.4.0"
130
132
  },
131
133
  "overrides": {
132
134
  "storybook": "$storybook"
@@ -91,10 +91,10 @@ const red = {
91
91
  };
92
92
 
93
93
  export {
94
- red,
95
- isvalidColorString,
96
94
  getContrastingColor,
97
95
  isValidHex,
96
+ isvalidColorString,
97
+ red,
98
98
  simpleCheckForValidColor,
99
99
  toState,
100
100
  };
@@ -0,0 +1,19 @@
1
+ import { useId } from 'react';
2
+
3
+ /**
4
+ * Generate an automatic id if needed, joined with name if truthy
5
+ *
6
+ * @param id
7
+ * @param name
8
+ */
9
+ export function useInputId(
10
+ id: string | null | undefined,
11
+ name: string | null | undefined,
12
+ ) {
13
+ const reactId = useId();
14
+
15
+ // If id is defined, keep id as finalId for predictable behavior.
16
+ // If name is defined, join name with reactId to simplify debug in devtools.
17
+ // Else use reactId.
18
+ return id ?? (name ? `${name}_${reactId}` : reactId);
19
+ }
@@ -0,0 +1,36 @@
1
+ import type { CheckboxProps as BPCheckboxProps } from '@blueprintjs/core';
2
+ import { Checkbox as BPCheckbox, FormGroup } from '@blueprintjs/core';
3
+ import type { ChangeEvent } from 'react';
4
+
5
+ import { useFieldContext } from '../../context/use_ts_form.js';
6
+ import { getIntent } from '../../utils/use_intent.js';
7
+
8
+ type CheckboxProps = Omit<BPCheckboxProps, 'defaultChecked' | 'name'>;
9
+
10
+ export function Checkbox(props: CheckboxProps) {
11
+ const { ...rest } = props;
12
+ const field = useFieldContext<boolean>();
13
+ const error = field
14
+ .getMeta()
15
+ .errors.map((e) => e.message)
16
+ .at(0);
17
+
18
+ const intent = getIntent(error);
19
+
20
+ function onChange(event: ChangeEvent<HTMLInputElement>) {
21
+ const checked = event.target.checked;
22
+ return field.handleChange(checked);
23
+ }
24
+
25
+ return (
26
+ <FormGroup intent={intent} helperText={error}>
27
+ <BPCheckbox
28
+ {...rest}
29
+ name={field.name}
30
+ value={String(field.state.value)}
31
+ onChange={onChange}
32
+ onBlur={field.handleBlur}
33
+ />
34
+ </FormGroup>
35
+ );
36
+ }
@@ -0,0 +1,37 @@
1
+ import type { ChangeEvent } from 'react';
2
+
3
+ import { useFieldContext } from '../../context/use_ts_form.js';
4
+ import { Input as FormGroupInput } from '../input_groups/input.js';
5
+
6
+ interface InputProps {
7
+ label?: string;
8
+ inline?: boolean;
9
+ required?: boolean;
10
+ placeholder?: string;
11
+ }
12
+
13
+ export function Input(props: InputProps) {
14
+ const { label, required, inline, placeholder } = props;
15
+ const field = useFieldContext<string>();
16
+ const error = field
17
+ .getMeta()
18
+ .errors.map((e) => e.message)
19
+ .at(0);
20
+
21
+ function onChange(event: ChangeEvent<HTMLInputElement>) {
22
+ return field.handleChange(event.target.value);
23
+ }
24
+
25
+ return (
26
+ <FormGroupInput
27
+ required={required}
28
+ error={error}
29
+ formGroupProps={{ label, inline }}
30
+ value={field.state.value}
31
+ onChange={onChange}
32
+ name={field.name}
33
+ placeholder={placeholder}
34
+ onBlur={field.handleBlur}
35
+ />
36
+ );
37
+ }
@@ -0,0 +1,49 @@
1
+ import type { NumericInputProps as BPNumericInputProps } from '@blueprintjs/core';
2
+ import { FormGroup, NumericInput as BPNumericInput } from '@blueprintjs/core';
3
+
4
+ import { useFieldContext } from '../../context/use_ts_form.js';
5
+ import { useFieldId } from '../../utils/use_field_id.js';
6
+ import { getIntent } from '../../utils/use_intent.js';
7
+
8
+ interface NumericInputProps
9
+ extends Omit<BPNumericInputProps, 'defaultValue' | 'name'> {
10
+ label?: string;
11
+ required?: boolean;
12
+ }
13
+
14
+ export function NumericInput(props: NumericInputProps) {
15
+ const { label, required, ...rest } = props;
16
+ const field = useFieldContext<string>();
17
+ const id = useFieldId(field.name);
18
+ const error = field
19
+ .getMeta()
20
+ .errors.map((e) => e.message)
21
+ .at(0);
22
+
23
+ const intent = getIntent(error);
24
+
25
+ function onChange(_: number, valueAsString: string) {
26
+ return field.handleChange(valueAsString);
27
+ }
28
+
29
+ return (
30
+ <FormGroup
31
+ helperText={error}
32
+ label={label}
33
+ labelFor={id}
34
+ intent={intent}
35
+ labelInfo={label && required && <span style={{ color: 'red' }}>*</span>}
36
+ >
37
+ <BPNumericInput
38
+ {...rest}
39
+ name={field.name}
40
+ value={field.state.value ?? ''}
41
+ onValueChange={onChange}
42
+ intent={intent}
43
+ required={required}
44
+ id={id}
45
+ onBlur={field.handleBlur}
46
+ />
47
+ </FormGroup>
48
+ );
49
+ }
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { Button } from '../../../button/index.js';
4
+ import { useFormContext } from '../../context/use_ts_form.js';
5
+
6
+ interface ResetButtonProps {
7
+ children: ReactNode;
8
+ }
9
+
10
+ export function ResetButton(props: ResetButtonProps) {
11
+ const { children } = props;
12
+ const form = useFormContext();
13
+
14
+ return (
15
+ <Button intent="danger" type="reset" onClick={() => form.reset()}>
16
+ {children}
17
+ </Button>
18
+ );
19
+ }
@@ -0,0 +1,47 @@
1
+ import { useFieldContext } from '../../context/use_ts_form.js';
2
+ import { getIntent } from '../../utils/use_intent.js';
3
+ import { Select as FormGroupSelect } from '../input_groups/select.js';
4
+ import type { SelectId } from '../util/select.js';
5
+
6
+ interface SelectOptionType {
7
+ label: string;
8
+ value: string;
9
+ }
10
+
11
+ interface SelectProps {
12
+ label?: string;
13
+ items: SelectOptionType[];
14
+ required?: boolean;
15
+ }
16
+
17
+ export function Select(props: SelectProps) {
18
+ const { label, items, required } = props;
19
+
20
+ const field = useFieldContext<SelectId>();
21
+ const error = field
22
+ .getMeta()
23
+ .errors.map((e) => e.message)
24
+ .at(0);
25
+
26
+ const intent = getIntent(error);
27
+
28
+ function onItemSelect(selected: SelectId | undefined) {
29
+ if (!selected) return;
30
+ return field.handleChange(selected);
31
+ }
32
+
33
+ return (
34
+ <FormGroupSelect
35
+ formGroupProps={{
36
+ required,
37
+ label,
38
+ intent,
39
+ helperText: error ?? undefined,
40
+ }}
41
+ items={items}
42
+ onChange={onItemSelect}
43
+ selected={field.state.value}
44
+ onBlur={field.handleBlur}
45
+ />
46
+ );
47
+ }
@@ -0,0 +1,23 @@
1
+ import { useStore } from '@tanstack/react-form';
2
+
3
+ import type { ButtonProps } from '../../../button/index.js';
4
+ import { Button } from '../../../button/index.js';
5
+ import { useFormContext } from '../../context/use_ts_form.js';
6
+
7
+ type SubmitButtonProps = ButtonProps;
8
+
9
+ export function SubmitButton(props: SubmitButtonProps) {
10
+ const { intent, ...otherProps } = props;
11
+
12
+ const form = useFormContext();
13
+ const isSubmitting = useStore(form.store, (state) => state.isSubmitting);
14
+
15
+ return (
16
+ <Button
17
+ {...otherProps}
18
+ intent={intent ?? 'primary'}
19
+ type="submit"
20
+ disabled={isSubmitting}
21
+ />
22
+ );
23
+ }
@@ -0,0 +1,33 @@
1
+ import { FormGroup, Switch as BPSwitch } from '@blueprintjs/core';
2
+ import type { ChangeEvent } from 'react';
3
+
4
+ import { useFieldContext } from '../../context/use_ts_form.js';
5
+
6
+ interface SwitchProps {
7
+ label?: string;
8
+ }
9
+
10
+ export function Switch(props: SwitchProps) {
11
+ const { label } = props;
12
+
13
+ const field = useFieldContext<boolean>();
14
+ const error = field
15
+ .getMeta()
16
+ .errors.map((e) => e.message)
17
+ .at(0);
18
+
19
+ function onChange(event: ChangeEvent<HTMLInputElement>) {
20
+ return field.handleChange(event.target.checked);
21
+ }
22
+
23
+ return (
24
+ <FormGroup helperText={error ?? undefined} intent="danger">
25
+ <BPSwitch
26
+ checked={field.state.value}
27
+ onChange={onChange}
28
+ onBlur={field.handleBlur}
29
+ labelElement={label}
30
+ />
31
+ </FormGroup>
32
+ );
33
+ }
@@ -0,0 +1,63 @@
1
+ import { FormGroup, InputGroup } from '@blueprintjs/core';
2
+ import type { ChangeEvent } from 'react';
3
+
4
+ import { useInputId } from '../hooks/use_input_id.js';
5
+
6
+ interface InputProps {
7
+ error?: string;
8
+ required?: boolean;
9
+ id?: string;
10
+ name: string;
11
+ type?: string;
12
+ onChange: (event: ChangeEvent<HTMLInputElement>) => void;
13
+ onBlur: () => void;
14
+ value: string;
15
+ placeholder?: string;
16
+ formGroupProps: {
17
+ label?: string;
18
+ className?: string;
19
+ inline?: boolean;
20
+ };
21
+ }
22
+
23
+ export function Input(props: InputProps) {
24
+ const {
25
+ id,
26
+ required = false,
27
+ error,
28
+ formGroupProps: { className, inline = false, label },
29
+ name,
30
+ onBlur,
31
+ onChange,
32
+ type = 'text',
33
+ value,
34
+ placeholder,
35
+ } = props;
36
+
37
+ const finalId = useInputId(id, name);
38
+
39
+ return (
40
+ <FormGroup
41
+ helperText={error ?? undefined}
42
+ label={label}
43
+ labelFor={finalId}
44
+ intent="danger"
45
+ style={{ margin: 0, position: 'relative' }}
46
+ className={className}
47
+ inline={inline}
48
+ labelInfo={required && <span style={{ color: 'red' }}>*</span>}
49
+ >
50
+ <InputGroup
51
+ id={finalId}
52
+ name={name}
53
+ required={required}
54
+ type={type}
55
+ onChange={onChange}
56
+ onBlur={onBlur}
57
+ intent={error ? 'danger' : 'none'}
58
+ value={value}
59
+ placeholder={placeholder}
60
+ />
61
+ </FormGroup>
62
+ );
63
+ }
@@ -0,0 +1,196 @@
1
+ import type { FormGroupProps } from '@blueprintjs/core';
2
+ import { FormGroup } from '@blueprintjs/core';
3
+ import type { SelectProps as BPSelectProps } from '@blueprintjs/select';
4
+ import { Select as BPSelect } from '@blueprintjs/select';
5
+ import type { ComponentProps, ReactElement, ReactNode } from 'react';
6
+ import { useCallback, useMemo } from 'react';
7
+
8
+ import { Button } from '../../../button/index.js';
9
+ import { useInputId } from '../hooks/use_input_id.js';
10
+ import type {
11
+ GetOptionLabel,
12
+ GetOptionValue,
13
+ SelectId,
14
+ SelectOption,
15
+ SelectOptionLabel,
16
+ } from '../util/select.js';
17
+ import {
18
+ getItemRenderer,
19
+ getSelectLabel,
20
+ getSelectValue,
21
+ } from '../util/select.js';
22
+
23
+ type FieldGroupProps = Pick<FormGroupProps, 'label' | 'inline'>;
24
+
25
+ export interface SelectPropsRenderButtonState<OptionType> {
26
+ error: string | undefined;
27
+ selectedOption: OptionType | undefined;
28
+ }
29
+
30
+ interface SelectCustomProps<OptionType, ID extends SelectId> {
31
+ renderButton?: (state: SelectPropsRenderButtonState<OptionType>) => ReactNode;
32
+ onChange?: (selected: ID | undefined, option: OptionType | undefined) => void;
33
+ id?: string;
34
+ selected?: ID;
35
+
36
+ required?: boolean;
37
+ }
38
+
39
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
40
+ type SelectGetValue<OptionType, ID extends SelectId> = {
41
+ /**
42
+ * Optional if OptionType extends SelectOption<ID>
43
+ * @default getSelectValue
44
+ */
45
+ getValue: GetOptionValue<OptionType, ID>;
46
+ };
47
+
48
+ type SelectGetValueProps<OptionType, ID extends SelectId> =
49
+ OptionType extends SelectOption<ID>
50
+ ? Partial<SelectGetValue<OptionType, ID>>
51
+ : SelectGetValue<OptionType, ID>;
52
+
53
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
54
+ type SelectGetLabel<OptionType> = {
55
+ /**
56
+ * Optional if OptionType extends SelectOptionLabel
57
+ * @default getSelectLabel
58
+ */
59
+ getLabel: GetOptionLabel<OptionType>;
60
+ };
61
+
62
+ type SelectGetLabelProps<OptionType> = OptionType extends SelectOptionLabel
63
+ ? Partial<SelectGetLabel<OptionType>>
64
+ : SelectGetLabel<OptionType>;
65
+
66
+ export type SelectProps<OptionType, ID extends SelectId> = FieldGroupProps &
67
+ Pick<
68
+ BPSelectProps<OptionType>,
69
+ | 'placeholder'
70
+ | 'items'
71
+ | 'className'
72
+ | 'disabled'
73
+ | 'popoverProps'
74
+ | 'filterable'
75
+ | 'itemDisabled'
76
+ | 'itemListPredicate'
77
+ | 'resetOnClose'
78
+ > &
79
+ Pick<ComponentProps<typeof FormGroup>, 'helperText' | 'intent'> &
80
+ SelectCustomProps<OptionType, ID> &
81
+ SelectGetValueProps<OptionType, ID> &
82
+ SelectGetLabelProps<OptionType>;
83
+
84
+ /**
85
+ * A wrapper for the BlueprintJS Select component that integrates with react-hook-form.
86
+ * Store in form the selected value.
87
+ * Find selected option getValue.
88
+ * Display options with getLabel.
89
+ *
90
+ * @param props
91
+ * @constructor
92
+ */
93
+
94
+ interface RealSelectProps<OptionType, ID extends SelectId>
95
+ extends Pick<BPSelectProps<OptionType>, 'filterable' | 'items'> {
96
+ onBlur: () => void;
97
+ id?: string;
98
+ renderButton?: (
99
+ state: SelectPropsRenderButtonState<OptionType>,
100
+ onBlur: () => void,
101
+ ) => ReactNode;
102
+ disabled?: boolean;
103
+ selected?: ID;
104
+ onChange?: (selected: ID | undefined, option: OptionType | undefined) => void;
105
+ getLabel?: GetOptionLabel<OptionType>;
106
+ getValue?: GetOptionValue<OptionType, ID>;
107
+ formGroupProps: Pick<
108
+ ComponentProps<typeof FormGroup>,
109
+ 'helperText' | 'intent' | 'label' | 'className' | 'inline'
110
+ > & {
111
+ required?: boolean;
112
+ };
113
+ }
114
+
115
+ export function Select<
116
+ OptionType extends SelectOption<ID>,
117
+ ID extends SelectId,
118
+ >(props: RealSelectProps<OptionType, ID>): ReactElement {
119
+ const {
120
+ renderButton,
121
+ id,
122
+ disabled,
123
+ selected,
124
+ getLabel: _getLabel,
125
+ getValue: _getValue,
126
+ formGroupProps: { className, helperText, inline, intent, label, required },
127
+ // Weirdly, setting the filterable prop on BP's Select component activates the filter input
128
+ filterable = false,
129
+ items,
130
+ onChange,
131
+ onBlur,
132
+ } = props;
133
+
134
+ const getValue: GetOptionValue<OptionType, ID> = _getValue ?? getSelectValue;
135
+ const getLabel: GetOptionLabel<OptionType> = _getLabel ?? getSelectLabel;
136
+
137
+ const selectedOption = useMemo(
138
+ () => items.find((item) => getValue(item) === selected),
139
+ [getValue, items, selected],
140
+ );
141
+
142
+ const itemRenderer = useMemo(() => {
143
+ return getItemRenderer<OptionType, ID>({
144
+ selected,
145
+ getValue,
146
+ getLabel,
147
+ });
148
+ }, [getLabel, getValue, selected]);
149
+
150
+ const onItemSelect = useCallback(
151
+ (option: OptionType | undefined) => {
152
+ const value = getValue(option);
153
+ onChange?.(value, option);
154
+ },
155
+ [getValue, onChange],
156
+ );
157
+
158
+ const inputId = useInputId(id, null);
159
+
160
+ return (
161
+ <FormGroup
162
+ label={label}
163
+ labelFor={inputId}
164
+ helperText={helperText}
165
+ intent={intent}
166
+ style={{ margin: 0, position: 'relative' }}
167
+ className={className}
168
+ inline={inline}
169
+ disabled={disabled}
170
+ labelInfo={required && <span style={{ color: 'red' }}>*</span>}
171
+ >
172
+ <BPSelect<OptionType>
173
+ filterable={filterable}
174
+ items={items}
175
+ onItemSelect={onItemSelect}
176
+ itemRenderer={itemRenderer}
177
+ disabled={disabled}
178
+ >
179
+ {renderButton ? (
180
+ renderButton({ selectedOption, error: undefined }, onBlur)
181
+ ) : (
182
+ <Button
183
+ id={inputId}
184
+ text={getLabel(selectedOption) || 'Select ...'}
185
+ endIcon="double-caret-vertical"
186
+ variant="outlined"
187
+ intent={intent}
188
+ disabled={disabled}
189
+ style={{ minWidth: 180 }}
190
+ onBlur={onBlur}
191
+ />
192
+ )}
193
+ </BPSelect>
194
+ </FormGroup>
195
+ );
196
+ }