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.
- package/.editorconfig +19 -0
- package/.github/workflows/codeql-analysis.yml +67 -0
- package/.nvmrc +1 -0
- package/.prettierignore +0 -0
- package/.prettierrc +13 -0
- package/.storybook/main.ts +42 -0
- package/.storybook/manager.ts +12 -0
- package/.storybook/preview.tsx +26 -0
- package/.vscode/extensions.json +32 -0
- package/.vscode/javascript.code-snippets +229 -0
- package/.vscode/settings.json +358 -0
- package/biome.jsonc +158 -0
- package/dist/index.cjs.js +15 -32
- package/dist/index.es.js +4144 -10129
- package/dist/types/hooks/useComponentSize.d.ts +5 -0
- package/dist/types/hooks/useStateWithDynamicDefault.d.ts +2 -0
- package/dist/types/index.d.ts +266 -2
- package/dist/types/main.d.ts +19 -0
- package/dist/types/midi/ccValues.d.ts +3 -0
- package/dist/types/midi/export.d.ts +22 -0
- package/dist/types/other/DefaultTooltip.d.ts +10 -0
- package/dist/types/other/OctavePlayer.d.ts +20 -0
- package/dist/types/other/SVGText.d.ts +17 -0
- package/dist/types/other/SoundFontProvider.d.ts +17 -0
- package/dist/types/pickers/CCPicker.d.ts +7 -0
- package/dist/types/pickers/ChannelMappingPicker.d.ts +14 -0
- package/dist/types/pickers/ChannelPicker.d.ts +8 -0
- package/dist/types/pickers/Knob.d.ts +12 -0
- package/dist/types/pickers/KnobPicker.d.ts +27 -0
- package/dist/types/pickers/LatchPicker.d.ts +8 -0
- package/dist/types/pickers/MappingPicker.d.ts +11 -0
- package/dist/types/pickers/NotePicker.d.ts +20 -0
- package/dist/types/pickers/PianoPicker.d.ts +9 -0
- package/dist/types/pickers/PolarityPicker.d.ts +14 -0
- package/dist/types/pickers/ResponseCurve.d.ts +40 -0
- package/dist/types/pickers/ResponseCurvePicker.d.ts +15 -0
- package/dist/types/pickers/Select.d.ts +29 -0
- package/dist/types/pickers/StatusPicker.d.ts +12 -0
- package/dist/types/pickers/ValuePicker.d.ts +15 -0
- package/dist/types/pickers/knobSkin10.d.ts +14 -0
- package/dist/types/rotaryKnob/InternalInput.d.ts +18 -0
- package/dist/types/rotaryKnob/Types.d.ts +5 -0
- package/dist/types/rotaryKnob/helpers/DrawCircle.d.ts +18 -0
- package/dist/types/rotaryKnob/helpers/DrawLine.d.ts +15 -0
- package/dist/types/rotaryKnob/helpers/HelpersOverlay.d.ts +11 -0
- package/dist/types/rotaryKnob/helpers/KnobVisualHelpers.d.ts +30 -0
- package/dist/types/rotaryKnob/index.d.ts +49 -0
- package/dist/types/rotaryKnob/knobdefaultskin.d.ts +6 -0
- package/dist/types/rotaryKnob/utils.d.ts +49 -0
- package/dist/types/utils.d.ts +9 -0
- package/lib/@types/nebiru-react-rotary-knob.d.ts +20 -0
- package/lib/@types/react-svgmt.d.ts +74 -0
- package/lib/@types/zds-mappings.d.ts +55 -0
- package/lib/@types/zds-react-piano.d.ts +17 -0
- package/lib/hooks/useComponentSize.ts +52 -0
- package/lib/hooks/useStateWithDynamicDefault.ts +13 -0
- package/lib/main.ts +34 -0
- package/lib/midi/ccValues.ts +139 -0
- package/lib/midi/export.ts +54 -0
- package/lib/other/DefaultTooltip.ts +14 -0
- package/lib/other/OctavePlayer.tsx +141 -0
- package/lib/other/SVGText.tsx +221 -0
- package/lib/other/SoundFontProvider.tsx +130 -0
- package/lib/pickers/CCPicker.tsx +28 -0
- package/lib/pickers/ChannelMappingPicker.tsx +135 -0
- package/lib/pickers/ChannelPicker.tsx +76 -0
- package/lib/pickers/Knob.tsx +93 -0
- package/lib/pickers/KnobPicker.tsx +93 -0
- package/lib/pickers/LatchPicker.tsx +33 -0
- package/lib/pickers/MappingPicker.tsx +68 -0
- package/lib/pickers/NotePicker.tsx +95 -0
- package/lib/pickers/PianoPicker.tsx +77 -0
- package/lib/pickers/PolarityPicker.tsx +39 -0
- package/lib/pickers/ResponseCurve.tsx +304 -0
- package/lib/pickers/ResponseCurvePicker.tsx +74 -0
- package/lib/pickers/Select.tsx +248 -0
- package/lib/pickers/StatusPicker.tsx +28 -0
- package/lib/pickers/ValuePicker.tsx +67 -0
- package/lib/pickers/knobSkin10.tsx +94 -0
- package/lib/rotaryKnob/InternalInput.tsx +43 -0
- package/lib/rotaryKnob/Types.ts +6 -0
- package/lib/rotaryKnob/helpers/DrawCircle.tsx +41 -0
- package/lib/rotaryKnob/helpers/DrawLine.tsx +32 -0
- package/lib/rotaryKnob/helpers/HelpersOverlay.tsx +43 -0
- package/lib/rotaryKnob/helpers/KnobVisualHelpers.tsx +138 -0
- package/lib/rotaryKnob/index.tsx +417 -0
- package/lib/rotaryKnob/knobdefaultskin.ts +56 -0
- package/lib/rotaryKnob/utils.ts +112 -0
- package/lib/temp/acoustic_grand_piano-mp3.js +94 -0
- package/lib/utils.ts +41 -0
- package/package.json +3 -7
- package/stories/assets/sampleStyles.css +349 -0
- package/stories/assets/utils.js +7 -0
- package/stories/picker.stories.tsx +313 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.json +34 -0
- package/tsconfig.node.json +24 -0
- package/vite.config.ts +45 -0
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.d.ts +0 -244
- 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 }
|