zds-pickers 4.0.3 → 4.0.6

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 (101) hide show
  1. package/.editorconfig +19 -0
  2. package/.github/workflows/codeql-analysis.yml +67 -0
  3. package/.nvmrc +1 -0
  4. package/.prettierignore +0 -0
  5. package/.prettierrc +13 -0
  6. package/.storybook/main.ts +42 -0
  7. package/.storybook/manager.ts +12 -0
  8. package/.storybook/preview.tsx +26 -0
  9. package/.vscode/extensions.json +32 -0
  10. package/.vscode/javascript.code-snippets +229 -0
  11. package/.vscode/settings.json +358 -0
  12. package/biome.jsonc +158 -0
  13. package/dist/index.cjs.js +15 -32
  14. package/dist/index.es.js +4144 -10129
  15. package/dist/types/hooks/useComponentSize.d.ts +5 -0
  16. package/dist/types/hooks/useStateWithDynamicDefault.d.ts +2 -0
  17. package/dist/types/index.d.ts +266 -2
  18. package/dist/types/main.d.ts +19 -0
  19. package/dist/types/midi/ccValues.d.ts +3 -0
  20. package/dist/types/midi/export.d.ts +22 -0
  21. package/dist/types/other/DefaultTooltip.d.ts +10 -0
  22. package/dist/types/other/OctavePlayer.d.ts +20 -0
  23. package/dist/types/other/SVGText.d.ts +17 -0
  24. package/dist/types/other/SoundFontProvider.d.ts +17 -0
  25. package/dist/types/pickers/CCPicker.d.ts +7 -0
  26. package/dist/types/pickers/ChannelMappingPicker.d.ts +14 -0
  27. package/dist/types/pickers/ChannelPicker.d.ts +8 -0
  28. package/dist/types/pickers/Knob.d.ts +12 -0
  29. package/dist/types/pickers/KnobPicker.d.ts +27 -0
  30. package/dist/types/pickers/LatchPicker.d.ts +8 -0
  31. package/dist/types/pickers/MappingPicker.d.ts +11 -0
  32. package/dist/types/pickers/NotePicker.d.ts +20 -0
  33. package/dist/types/pickers/PianoPicker.d.ts +9 -0
  34. package/dist/types/pickers/PolarityPicker.d.ts +14 -0
  35. package/dist/types/pickers/ResponseCurve.d.ts +40 -0
  36. package/dist/types/pickers/ResponseCurvePicker.d.ts +15 -0
  37. package/dist/types/pickers/Select.d.ts +29 -0
  38. package/dist/types/pickers/StatusPicker.d.ts +12 -0
  39. package/dist/types/pickers/ValuePicker.d.ts +15 -0
  40. package/dist/types/pickers/knobSkin10.d.ts +14 -0
  41. package/dist/types/rotaryKnob/InternalInput.d.ts +18 -0
  42. package/dist/types/rotaryKnob/Types.d.ts +5 -0
  43. package/dist/types/rotaryKnob/helpers/DrawCircle.d.ts +18 -0
  44. package/dist/types/rotaryKnob/helpers/DrawLine.d.ts +15 -0
  45. package/dist/types/rotaryKnob/helpers/HelpersOverlay.d.ts +11 -0
  46. package/dist/types/rotaryKnob/helpers/KnobVisualHelpers.d.ts +30 -0
  47. package/dist/types/rotaryKnob/index.d.ts +49 -0
  48. package/dist/types/rotaryKnob/knobdefaultskin.d.ts +6 -0
  49. package/dist/types/rotaryKnob/utils.d.ts +49 -0
  50. package/dist/types/utils.d.ts +9 -0
  51. package/lib/@types/nebiru-react-rotary-knob.d.ts +20 -0
  52. package/lib/@types/react-svgmt.d.ts +74 -0
  53. package/lib/@types/zds-mappings.d.ts +55 -0
  54. package/lib/@types/zds-react-piano.d.ts +17 -0
  55. package/lib/hooks/useComponentSize.ts +52 -0
  56. package/lib/hooks/useStateWithDynamicDefault.ts +13 -0
  57. package/lib/main.ts +34 -0
  58. package/lib/midi/ccValues.ts +139 -0
  59. package/lib/midi/export.ts +54 -0
  60. package/lib/other/DefaultTooltip.ts +14 -0
  61. package/lib/other/OctavePlayer.tsx +141 -0
  62. package/lib/other/SVGText.tsx +221 -0
  63. package/lib/other/SoundFontProvider.tsx +130 -0
  64. package/lib/pickers/CCPicker.tsx +28 -0
  65. package/lib/pickers/ChannelMappingPicker.tsx +135 -0
  66. package/lib/pickers/ChannelPicker.tsx +76 -0
  67. package/lib/pickers/Knob.tsx +93 -0
  68. package/lib/pickers/KnobPicker.tsx +93 -0
  69. package/lib/pickers/LatchPicker.tsx +33 -0
  70. package/lib/pickers/MappingPicker.tsx +68 -0
  71. package/lib/pickers/NotePicker.tsx +95 -0
  72. package/lib/pickers/PianoPicker.tsx +77 -0
  73. package/lib/pickers/PolarityPicker.tsx +39 -0
  74. package/lib/pickers/ResponseCurve.tsx +304 -0
  75. package/lib/pickers/ResponseCurvePicker.tsx +74 -0
  76. package/lib/pickers/Select.tsx +248 -0
  77. package/lib/pickers/StatusPicker.tsx +28 -0
  78. package/lib/pickers/ValuePicker.tsx +67 -0
  79. package/lib/pickers/knobSkin10.tsx +94 -0
  80. package/lib/rotaryKnob/InternalInput.tsx +43 -0
  81. package/lib/rotaryKnob/Types.ts +6 -0
  82. package/lib/rotaryKnob/helpers/DrawCircle.tsx +41 -0
  83. package/lib/rotaryKnob/helpers/DrawLine.tsx +32 -0
  84. package/lib/rotaryKnob/helpers/HelpersOverlay.tsx +43 -0
  85. package/lib/rotaryKnob/helpers/KnobVisualHelpers.tsx +138 -0
  86. package/lib/rotaryKnob/index.tsx +417 -0
  87. package/lib/rotaryKnob/knobdefaultskin.ts +56 -0
  88. package/lib/rotaryKnob/utils.ts +112 -0
  89. package/lib/temp/acoustic_grand_piano-mp3.js +94 -0
  90. package/lib/utils.ts +41 -0
  91. package/package.json +3 -7
  92. package/stories/assets/sampleStyles.css +349 -0
  93. package/stories/assets/utils.js +7 -0
  94. package/stories/picker.stories.tsx +313 -0
  95. package/tsconfig.app.json +26 -0
  96. package/tsconfig.json +34 -0
  97. package/tsconfig.node.json +24 -0
  98. package/vite.config.ts +45 -0
  99. package/dist/index.cjs.js.map +0 -1
  100. package/dist/index.d.ts +0 -244
  101. package/dist/index.es.js.map +0 -1
@@ -0,0 +1,304 @@
1
+ import { useRef } from 'react'
2
+ import useComponentSize from '../hooks/useComponentSize'
3
+ import DefaultTooltip, { type TooltipProps } from '../other/DefaultTooltip'
4
+ import SVGText from '../other/SVGText'
5
+ import type { Option } from './Select'
6
+
7
+ const RESPONSE_CURVE_0 = 0
8
+ const RESPONSE_CURVE_1 = 1
9
+ const RESPONSE_CURVE_2 = 2
10
+ const RESPONSE_CURVE_3 = 3
11
+ const RESPONSE_CURVE_4 = 4
12
+ const RESPONSE_CURVE_5 = 5
13
+ const RESPONSE_CURVE_6 = 6
14
+ const RESPONSE_CURVE_7 = 7
15
+
16
+ type Curve =
17
+ | typeof RESPONSE_CURVE_0
18
+ | typeof RESPONSE_CURVE_1
19
+ | typeof RESPONSE_CURVE_2
20
+ | typeof RESPONSE_CURVE_3
21
+ | typeof RESPONSE_CURVE_4
22
+ | typeof RESPONSE_CURVE_5
23
+ | typeof RESPONSE_CURVE_6
24
+ | typeof RESPONSE_CURVE_7
25
+
26
+ type ResponseCurve = {
27
+ value: Curve
28
+ label: React.ReactNode
29
+ c1: number
30
+ c2: number
31
+ x1: number
32
+ y1: number
33
+ x2: number
34
+ y2: number
35
+ labelX: number
36
+ labelY: number
37
+ }
38
+
39
+ const RESPONSE_CURVES: Option<Curve>[] = [
40
+ {
41
+ value: RESPONSE_CURVE_7,
42
+ label: 'Always maxed out.',
43
+ },
44
+ {
45
+ value: RESPONSE_CURVE_6,
46
+ label: 'Most sensitive.',
47
+ },
48
+ {
49
+ value: RESPONSE_CURVE_5,
50
+ label: 'Moderately sensitive.',
51
+ },
52
+ {
53
+ value: RESPONSE_CURVE_4,
54
+ label: 'A little sensitive.',
55
+ },
56
+ {
57
+ value: RESPONSE_CURVE_0,
58
+ label: 'Linear, no change.',
59
+ },
60
+ {
61
+ value: RESPONSE_CURVE_1,
62
+ label: 'A little less sensitive.',
63
+ },
64
+ {
65
+ value: RESPONSE_CURVE_2,
66
+ label: 'Even less sensitive.',
67
+ },
68
+ {
69
+ value: RESPONSE_CURVE_3,
70
+ label: 'Least sensitive.',
71
+ },
72
+ ]
73
+
74
+ // const width = 218
75
+ // const height = 150
76
+ const axisOffset = 20
77
+ const buttonSize = 15
78
+
79
+ const axisX1 = axisOffset
80
+ const axisY1 = axisOffset
81
+
82
+ type ResponseCurveProps = {
83
+ autosize: boolean
84
+ disabled?: boolean
85
+ inverted: boolean
86
+ onChange: (event: { target: { value: Curve } }) => void
87
+ Tooltip: React.FC<TooltipProps>
88
+ value: Curve
89
+ }
90
+
91
+ const ResponseCurve = (props: ResponseCurveProps) => {
92
+ const {
93
+ Tooltip = DefaultTooltip,
94
+ autosize,
95
+ disabled,
96
+ inverted,
97
+ onChange,
98
+ value: responseCurve,
99
+ } = props
100
+
101
+ const ref = useRef(null)
102
+ const svgRef = useRef(null)
103
+ const { height: ourHeight, width: ourWidth } = useComponentSize(ref)
104
+
105
+ const width = autosize ? ourWidth || 218 : 218
106
+ const height = autosize ? ourHeight || 150 : 150
107
+
108
+ const axisX2 = width - axisOffset
109
+ const axisY2 = height - axisOffset
110
+
111
+ const xMid = width / 2
112
+ const yMid = height / 2
113
+
114
+ const svgProps = { ref: svgRef }
115
+
116
+ const responseCurves = RESPONSE_CURVES.map(
117
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
118
+ ({ value, label }): ResponseCurve => {
119
+ let c1 = 0
120
+ let c2 = 0
121
+ let x1 = axisX1
122
+ let y1 = inverted ? axisY1 : axisY2
123
+ let x2 = axisX2
124
+ let y2 = inverted ? axisY2 : axisY1
125
+ let labelX = xMid
126
+ let labelY = yMid
127
+ switch (value) {
128
+ case RESPONSE_CURVE_0:
129
+ c1 = xMid
130
+ c2 = yMid
131
+ labelY += 3
132
+
133
+ break
134
+ case RESPONSE_CURVE_1:
135
+ c1 = inverted ? xMid + width * 0.13 : xMid - width * 0.13
136
+ c2 = yMid - height * 0.13
137
+ labelX = inverted ? xMid + width * 0.1 : xMid - width * 0.1
138
+ labelY = yMid - height * 0.09
139
+ break
140
+
141
+ case RESPONSE_CURVE_2:
142
+ c1 = inverted ? xMid + width * 0.25 : xMid - width * 0.25
143
+ c2 = yMid - height * 0.25
144
+ labelX = inverted ? xMid + width * 0.2 : xMid - width * 0.2
145
+ labelY = yMid - height * 0.17
146
+ break
147
+
148
+ case RESPONSE_CURVE_3:
149
+ c1 = inverted ? xMid + width * 0.35 : xMid - width * 0.35
150
+ c2 = yMid - height * 0.35
151
+ labelX = inverted ? xMid + width * 0.28 : xMid - width * 0.28
152
+ labelY = yMid - height * 0.25
153
+ break
154
+
155
+ case RESPONSE_CURVE_4:
156
+ c1 = inverted ? xMid - width * 0.13 : xMid + width * 0.13
157
+ c2 = yMid + height * 0.13
158
+ labelX = inverted ? xMid - width * 0.1 : xMid + width * 0.1
159
+ labelY = yMid + height * 0.1
160
+ break
161
+
162
+ case RESPONSE_CURVE_5:
163
+ c1 = inverted ? xMid - width * 0.25 : xMid + width * 0.25
164
+ c2 = yMid + height * 0.25
165
+ labelX = inverted ? xMid - width * 0.2 : xMid + width * 0.2
166
+ labelY = yMid + height * 0.18
167
+ break
168
+
169
+ case RESPONSE_CURVE_6:
170
+ c1 = inverted ? xMid - width * 0.35 : xMid + width * 0.35
171
+ c2 = yMid + height * 0.35
172
+ labelX = inverted ? xMid - width * 0.28 : xMid + width * 0.28
173
+ labelY = yMid + height * 0.25
174
+ break
175
+
176
+ case RESPONSE_CURVE_7:
177
+ x1 = inverted ? axisX1 : axisX2
178
+ y1 = axisY1
179
+ x2 = x1
180
+ y2 = axisY2
181
+ c1 = x1
182
+ c2 = yMid
183
+ labelX = x1 - 5
184
+ labelY = height - 30
185
+ break
186
+
187
+ default:
188
+ break
189
+ }
190
+ return {
191
+ value,
192
+ label,
193
+ c1,
194
+ c2,
195
+ x1,
196
+ y1,
197
+ x2,
198
+ y2,
199
+ labelX,
200
+ labelY,
201
+ }
202
+ },
203
+ )
204
+
205
+ return (
206
+ <div
207
+ className="response-curve-picker"
208
+ ref={ref}>
209
+ <svg {...svgProps}>
210
+ <rect
211
+ className="container"
212
+ height={height}
213
+ rx={3}
214
+ ry={3}
215
+ width={width}
216
+ x={0}
217
+ y={0}
218
+ />
219
+
220
+ <SVGText
221
+ className="axis"
222
+ textAnchor="middle"
223
+ transform={`translate(${axisX1 - 14} ${height / 2}) rotate(90)`}
224
+ x={0}
225
+ y={0}
226
+ text="Applied force"
227
+ />
228
+
229
+ <SVGText
230
+ className="axis"
231
+ textAnchor="middle"
232
+ transform={`translate(${width / 2} ${axisY2 + 13})`}
233
+ x={0}
234
+ y={0}
235
+ text="Output (velocity)"
236
+ />
237
+
238
+ <line
239
+ className="axis"
240
+ x1={axisX1}
241
+ x2={axisX1}
242
+ y1={axisY1}
243
+ y2={axisY2}
244
+ />
245
+ <line
246
+ className="axis"
247
+ x1={axisX1}
248
+ x2={axisX2}
249
+ y1={axisY2}
250
+ y2={axisY2}
251
+ />
252
+
253
+ {responseCurves.map(
254
+ ({ value, label, x1, y1, x2, y2, c1, c2, labelX, labelY }) => (
255
+ <g key={value}>
256
+ <path
257
+ className={value === responseCurve ? 'curve-selected' : 'curve'}
258
+ d={`M${x1} ${y1} C ${c1} ${c2}, ${c1} ${c2}, ${x2} ${y2}`}
259
+ />
260
+
261
+ <Tooltip
262
+ placement="left"
263
+ title={label}>
264
+ <rect
265
+ className={
266
+ value === responseCurve ? 'button-selected' : 'button'
267
+ }
268
+ height={buttonSize}
269
+ onClick={
270
+ disabled
271
+ ? f => f
272
+ : () =>
273
+ onChange({
274
+ target: {
275
+ value: Number.parseInt(String(value)) as Curve,
276
+ },
277
+ })
278
+ }
279
+ transform={`translate(${labelX + 3} ${labelY - buttonSize / 1.05}) rotate(45)`}
280
+ width={buttonSize}
281
+ />
282
+ </Tooltip>
283
+
284
+ <text
285
+ className={
286
+ value === responseCurve ? 'button-selected' : 'button'
287
+ }
288
+ transform={`translate(${labelX} ${labelY})`}>
289
+ {value}
290
+ <title>{label}</title>
291
+ </text>
292
+ </g>
293
+ ),
294
+ )}
295
+ </svg>
296
+ </div>
297
+ )
298
+ }
299
+
300
+ export default ResponseCurve
301
+
302
+ export { RESPONSE_CURVES }
303
+
304
+ export type { Curve, ResponseCurve, ResponseCurveProps }
@@ -0,0 +1,74 @@
1
+ import { forwardRef, useCallback } from 'react'
2
+ import type { GroupBase, SelectInstance } from 'react-select'
3
+ import DefaultTooltip, { type TooltipProps } from '../other/DefaultTooltip'
4
+ import { findObj } from '../utils'
5
+ import ResponseCurve, { type Curve, RESPONSE_CURVES } from './ResponseCurve'
6
+ import Select from './Select'
7
+ import type { Option, SelectProps } from './Select'
8
+
9
+ type ResponseCurvePickerProps = SelectProps<Curve> & {
10
+ inverted?: boolean
11
+ Tooltip?: React.FC<TooltipProps>
12
+ }
13
+
14
+ type ResponseCurvePickerRef = SelectInstance<
15
+ Option<Curve>,
16
+ false,
17
+ GroupBase<Option<Curve>>
18
+ >
19
+
20
+ const ResponseCurvePicker = forwardRef<
21
+ SelectInstance<Option<Curve>, false, GroupBase<Option<Curve>>>,
22
+ ResponseCurvePickerProps
23
+ >((props, ref) => {
24
+ const {
25
+ Tooltip = DefaultTooltip,
26
+ inverted = false,
27
+ onChange,
28
+ value,
29
+ ...rest
30
+ } = props
31
+
32
+ const Placeholder = useCallback(
33
+ () => (
34
+ <div className="singleValue">
35
+ {(findObj('value', value)(RESPONSE_CURVES) as Option<Curve>)?.label}
36
+ </div>
37
+ ),
38
+ [value],
39
+ )
40
+
41
+ const MenuList = useCallback(
42
+ ({ setValue }: { setValue: (v: Curve) => void }) => (
43
+ <ResponseCurve
44
+ autosize
45
+ inverted={inverted}
46
+ onChange={({ target: { value: v } }) => {
47
+ setValue(v)
48
+ onChange(v)
49
+ }}
50
+ Tooltip={Tooltip}
51
+ value={Number.parseInt(String(value)) as Curve}
52
+ />
53
+ ),
54
+ [Tooltip, inverted, onChange, value],
55
+ )
56
+
57
+ return (
58
+ <Select
59
+ {...rest}
60
+ components={{
61
+ Placeholder,
62
+ MenuList,
63
+ }}
64
+ isSearchable={false}
65
+ onChange={f => f}
66
+ ref={ref}
67
+ value={value}
68
+ />
69
+ )
70
+ })
71
+
72
+ export default ResponseCurvePicker
73
+
74
+ export type { ResponseCurvePickerProps, ResponseCurvePickerRef }
@@ -0,0 +1,248 @@
1
+ import {
2
+ Children,
3
+ type JSX,
4
+ forwardRef,
5
+ isValidElement,
6
+ useCallback,
7
+ useMemo,
8
+ } from 'react'
9
+ import ReactSelect, {
10
+ type GroupBase,
11
+ type IndicatorsContainerProps,
12
+ components as originalComponents,
13
+ type PlaceholderProps,
14
+ type SelectInstance,
15
+ type SingleValue,
16
+ type StylesConfig,
17
+ type ValueContainerProps,
18
+ } from 'react-select'
19
+ import { omit } from '../utils'
20
+
21
+ const { IndicatorsContainer, Placeholder, ValueContainer } = originalComponents
22
+
23
+ type Option<T> = {
24
+ label: React.ReactNode
25
+ value: T
26
+ }
27
+
28
+ interface CustomIndicatorsContainerProps<T>
29
+ extends Omit<IndicatorsContainerProps<Option<T>, false>, 'selectProps'> {
30
+ children: React.ReactNode
31
+ selectProps: IndicatorsContainerProps<Option<T>, false>['selectProps'] & {
32
+ actionButton?: React.ReactNode
33
+ inputValue?: string
34
+ placeholder?: React.ReactNode
35
+ }
36
+ }
37
+
38
+ const CustomIndicatorsContainer = <T,>({
39
+ children,
40
+ ...rest
41
+ }: CustomIndicatorsContainerProps<T>) => {
42
+ const actionButton = rest.selectProps?.actionButton
43
+
44
+ return (
45
+ <IndicatorsContainer {...rest}>
46
+ <div
47
+ onMouseDown={e => {
48
+ e.stopPropagation()
49
+ e.preventDefault()
50
+ }}
51
+ style={{ display: 'flex' }}>
52
+ {actionButton}
53
+ {children}
54
+ </div>
55
+ </IndicatorsContainer>
56
+ )
57
+ }
58
+
59
+ type CustomValueContainerProps<T> = (
60
+ | ValueContainerProps<Option<T>, false>
61
+ | PlaceholderProps<Option<T>, false>
62
+ ) & {
63
+ children: React.ReactNode
64
+ hasValue?: boolean
65
+ isFocused?: boolean
66
+ }
67
+
68
+ const CustomValueContainer = <T,>({
69
+ children,
70
+ ...props
71
+ }: CustomValueContainerProps<T>) => (
72
+ <ValueContainer {...props}>
73
+ <Placeholder
74
+ {...props}
75
+ innerProps={props.innerProps || {}}
76
+ className={props.hasValue || props.selectProps.inputValue ? 'shrunk' : ''}
77
+ isFocused={props.isFocused ?? false}>
78
+ {props.selectProps.placeholder}
79
+ </Placeholder>
80
+ {Children.map(children, child =>
81
+ isValidElement(child) && child.type !== Placeholder ? child : null,
82
+ )}
83
+ </ValueContainer>
84
+ )
85
+
86
+ type SelectProps<T> = {
87
+ className?: string
88
+ components?: object
89
+ disabled?: boolean
90
+ filterOptions?: (candidate: Option<T>, inputValue: string) => boolean
91
+ formatOptionLabel?: (
92
+ option: Option<unknown>,
93
+ { context }: { context: 'menu' | 'value' },
94
+ ) => React.ReactNode
95
+ isSearchable?: boolean
96
+ label?: string
97
+ onChange: (value: Option<T>['value']) => void
98
+ options: Option<T>[] | { entries: Option<T>[] }[]
99
+ preserveMenuWidth?: boolean
100
+ shrinkLabel?: boolean
101
+ value: Option<T>['value']
102
+ }
103
+
104
+ const Select = forwardRef(
105
+ <T,>(
106
+ props: SelectProps<T>,
107
+ ref: React.ForwardedRef<
108
+ SelectInstance<Option<T>, false, GroupBase<Option<T>>>
109
+ >,
110
+ ) => {
111
+ const {
112
+ components,
113
+ disabled,
114
+ label,
115
+ onChange,
116
+ options = [],
117
+ preserveMenuWidth,
118
+ shrinkLabel,
119
+ value,
120
+ ...rest
121
+ } = props
122
+
123
+ const handleChange = useCallback(
124
+ (option: SingleValue<Option<unknown>>) => {
125
+ if (option) {
126
+ onChange((option as Option<T>).value)
127
+ }
128
+ },
129
+ [onChange],
130
+ )
131
+
132
+ const flatOptionsList = options.reduce<Option<T>[]>((acc, entry) => {
133
+ if ('entries' in entry) {
134
+ return acc.concat(entry.entries)
135
+ }
136
+ return acc.concat(entry)
137
+ }, [])
138
+
139
+ const selectedOption =
140
+ flatOptionsList.find(option => value === option.value) || undefined
141
+
142
+ const styles: unknown = useMemo(() => {
143
+ const base = preserveMenuWidth
144
+ ? {
145
+ menu: (base: Record<string, unknown>) => omit('width')(base),
146
+ menuList: (base: object) => ({
147
+ ...base,
148
+ overflowX: 'hidden',
149
+ }),
150
+ option: (base: object) => ({
151
+ ...base,
152
+ whiteSpace: 'nowrap',
153
+ paddingRight: 10,
154
+ }),
155
+ }
156
+ : {}
157
+
158
+ return shrinkLabel
159
+ ? {
160
+ ...base,
161
+ container: (provided: object /* , state */) => ({
162
+ ...provided,
163
+ marginTop: 10,
164
+ }),
165
+ placeholder: (
166
+ _provided: object,
167
+ state: PlaceholderProps<Option<T>, false>,
168
+ ) => ({
169
+ position: 'absolute',
170
+ left: state.hasValue || state.selectProps.inputValue ? -2 : '4%',
171
+ top: state.hasValue || state.selectProps.inputValue ? -12 : '50%',
172
+ transition: 'top 0.1s, font-size 0.1s, color 0.1s',
173
+ fontSize: (state.hasValue || state.selectProps.inputValue) && 13,
174
+ }),
175
+ valueContainer: (provided: object /* , state */) => ({
176
+ ...provided,
177
+ overflow: 'visible',
178
+ }),
179
+ }
180
+ : {
181
+ ...base,
182
+ }
183
+ }, [preserveMenuWidth, shrinkLabel])
184
+
185
+ return shrinkLabel ? (
186
+ <div className="zds-pickers__container">
187
+ <ReactSelect
188
+ {...rest}
189
+ classNamePrefix="zds-pickers"
190
+ components={{
191
+ IndicatorsContainer: CustomIndicatorsContainer,
192
+ ValueContainer: CustomValueContainer,
193
+ ...components,
194
+ }}
195
+ isDisabled={disabled}
196
+ onChange={handleChange}
197
+ options={flatOptionsList}
198
+ placeholder={label}
199
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
200
+ ref={ref as React.Ref<any>}
201
+ styles={
202
+ styles as StylesConfig<
203
+ Option<unknown>,
204
+ false,
205
+ GroupBase<Option<unknown>>
206
+ >
207
+ }
208
+ value={selectedOption}
209
+ />
210
+ </div>
211
+ ) : (
212
+ <div className="zds-pickers__container">
213
+ {Boolean(label) && <span className="zds-pickers__label">{label}</span>}
214
+ <ReactSelect
215
+ {...rest}
216
+ classNamePrefix="zds-pickers"
217
+ components={{
218
+ IndicatorsContainer: CustomIndicatorsContainer,
219
+ ...components,
220
+ }}
221
+ isDisabled={disabled}
222
+ onChange={handleChange}
223
+ options={flatOptionsList}
224
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
225
+ ref={ref as React.Ref<any>}
226
+ styles={
227
+ styles as StylesConfig<
228
+ Option<unknown>,
229
+ false,
230
+ GroupBase<Option<unknown>>
231
+ >
232
+ }
233
+ value={selectedOption}
234
+ />
235
+ </div>
236
+ )
237
+ },
238
+ ) as <T>(
239
+ props: SelectProps<T> & {
240
+ ref?: React.ForwardedRef<
241
+ SelectInstance<Option<T>, false, GroupBase<Option<T>>>
242
+ >
243
+ },
244
+ ) => JSX.Element
245
+
246
+ export default Select
247
+
248
+ export type { Option, SelectProps }
@@ -0,0 +1,28 @@
1
+ import type { Status } from 'lib/midi/export'
2
+ import { forwardRef } from 'react'
3
+ import type { GroupBase, SelectInstance } from 'react-select'
4
+ import Select from './Select'
5
+ import type { Option, SelectProps } from './Select'
6
+
7
+ type StatusPickerProps = SelectProps<Status> & { statuses: Option<Status>[] }
8
+
9
+ type StatusPickerRef = SelectInstance<
10
+ Option<Status>,
11
+ false,
12
+ GroupBase<Option<Status>>
13
+ >
14
+
15
+ const StatusPicker = forwardRef<
16
+ SelectInstance<Option<Status>, false, GroupBase<Option<Status>>>,
17
+ StatusPickerProps
18
+ >(({ statuses, ...rest }, ref) => (
19
+ <Select<Status>
20
+ {...rest}
21
+ options={statuses}
22
+ ref={ref}
23
+ />
24
+ ))
25
+
26
+ export default StatusPicker
27
+
28
+ export type { StatusPickerProps, StatusPickerRef }
@@ -0,0 +1,67 @@
1
+ import { forwardRef, useCallback, useMemo } from 'react'
2
+ import type { GroupBase, SelectInstance } from 'react-select'
3
+ import useStateWithDynamicDefault from '../hooks/useStateWithDynamicDefault'
4
+ import { arraySequence, assertRange } from '../utils'
5
+ import Select from './Select'
6
+ import type { Option, SelectProps } from './Select'
7
+
8
+ type ValuePickerProps = SelectProps<number> & {
9
+ highToLow?: boolean
10
+ max?: number
11
+ min?: number
12
+ }
13
+
14
+ type ValuePickerRef = SelectInstance<
15
+ Option<number>,
16
+ false,
17
+ GroupBase<Option<number>>
18
+ >
19
+
20
+ const ValuePicker = forwardRef<
21
+ SelectInstance<Option<number>, false, GroupBase<Option<number>>>,
22
+ ValuePickerProps
23
+ >((props, ref) => {
24
+ const {
25
+ disabled,
26
+ highToLow,
27
+ max = 127,
28
+ min = 0,
29
+ onChange,
30
+ value: initialValue = 0,
31
+ ...rest
32
+ } = props
33
+ const options = useMemo(() => {
34
+ const result = arraySequence(max - min + 1)
35
+ .map(i => min + i)
36
+ .map(value => ({ value, label: value }))
37
+ return (highToLow
38
+ ? result.reverse()
39
+ : result) as unknown as Option<number>[]
40
+ }, [highToLow, max, min])
41
+
42
+ const [value, setValue] = useStateWithDynamicDefault(initialValue)
43
+
44
+ const handleChange = useCallback(
45
+ (v: number | string) => {
46
+ onChange(assertRange(v, max, min))
47
+
48
+ setValue(Number.parseInt(String(v), 10))
49
+ },
50
+ [max, min, onChange, setValue],
51
+ )
52
+
53
+ return (
54
+ <Select
55
+ {...rest}
56
+ disabled={disabled}
57
+ onChange={handleChange}
58
+ options={options}
59
+ ref={ref}
60
+ value={value}
61
+ />
62
+ )
63
+ })
64
+
65
+ export default ValuePicker
66
+
67
+ export type { ValuePickerProps, ValuePickerRef }