@tremolo-ui/react 0.0.5 → 0.0.7

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 (77) hide show
  1. package/dist/cjs/index.js +512 -93
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/AnimationCanvas/index.d.ts +3 -4
  4. package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +1 -1
  5. package/dist/cjs/types/components/Knob/index.d.ts +6 -7
  6. package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
  7. package/dist/cjs/types/components/NumberInput/index.d.ts +10 -7
  8. package/dist/cjs/types/components/NumberInput/index.d.ts.map +1 -1
  9. package/dist/cjs/types/components/Piano/BlackKey.d.ts +4 -0
  10. package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +1 -0
  11. package/dist/cjs/types/components/Piano/KeyLabel.d.ts +20 -0
  12. package/dist/cjs/types/components/Piano/KeyLabel.d.ts.map +1 -0
  13. package/dist/cjs/types/components/Piano/WhiteKey.d.ts +4 -0
  14. package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +1 -0
  15. package/dist/cjs/types/components/Piano/index.d.ts +35 -0
  16. package/dist/cjs/types/components/Piano/index.d.ts.map +1 -0
  17. package/dist/cjs/types/components/Piano/key.d.ts +39 -0
  18. package/dist/cjs/types/components/Piano/key.d.ts.map +1 -0
  19. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +12 -0
  20. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts.map +1 -0
  21. package/dist/cjs/types/components/Slider/Thumb.d.ts +17 -2
  22. package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
  23. package/dist/cjs/types/components/Slider/Track.d.ts +21 -2
  24. package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
  25. package/dist/cjs/types/components/Slider/index.d.ts +18 -10
  26. package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
  27. package/dist/cjs/types/components/_util/index.d.ts +4 -0
  28. package/dist/cjs/types/components/_util/index.d.ts.map +1 -0
  29. package/dist/cjs/types/index.d.ts +1 -0
  30. package/dist/cjs/types/index.d.ts.map +1 -1
  31. package/dist/esm/index.js +509 -95
  32. package/dist/esm/index.js.map +1 -1
  33. package/dist/esm/types/components/AnimationCanvas/index.d.ts +3 -4
  34. package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
  35. package/dist/esm/types/components/Knob/index.d.ts +6 -7
  36. package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
  37. package/dist/esm/types/components/NumberInput/index.d.ts +10 -7
  38. package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
  39. package/dist/esm/types/components/Piano/BlackKey.d.ts +4 -0
  40. package/dist/esm/types/components/Piano/BlackKey.d.ts.map +1 -0
  41. package/dist/esm/types/components/Piano/KeyLabel.d.ts +20 -0
  42. package/dist/esm/types/components/Piano/KeyLabel.d.ts.map +1 -0
  43. package/dist/esm/types/components/Piano/WhiteKey.d.ts +4 -0
  44. package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +1 -0
  45. package/dist/esm/types/components/Piano/index.d.ts +35 -0
  46. package/dist/esm/types/components/Piano/index.d.ts.map +1 -0
  47. package/dist/esm/types/components/Piano/key.d.ts +39 -0
  48. package/dist/esm/types/components/Piano/key.d.ts.map +1 -0
  49. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +12 -0
  50. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts.map +1 -0
  51. package/dist/esm/types/components/Slider/Thumb.d.ts +17 -2
  52. package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
  53. package/dist/esm/types/components/Slider/Track.d.ts +21 -2
  54. package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
  55. package/dist/esm/types/components/Slider/index.d.ts +18 -10
  56. package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
  57. package/dist/esm/types/components/_util/index.d.ts +4 -0
  58. package/dist/esm/types/components/_util/index.d.ts.map +1 -0
  59. package/dist/esm/types/index.d.ts +1 -0
  60. package/dist/esm/types/index.d.ts.map +1 -1
  61. package/dist/styles/{variables.css → index.css} +4 -0
  62. package/package.json +7 -7
  63. package/src/components/AnimationCanvas/index.tsx +3 -7
  64. package/src/components/Knob/index.tsx +15 -19
  65. package/src/components/NumberInput/index.tsx +18 -11
  66. package/src/components/Piano/BlackKey.tsx +108 -0
  67. package/src/components/Piano/KeyLabel.tsx +69 -0
  68. package/src/components/Piano/WhiteKey.tsx +109 -0
  69. package/src/components/Piano/index.tsx +222 -0
  70. package/src/components/Piano/key.ts +43 -0
  71. package/src/components/Piano/keyboardShortcuts.ts +12 -0
  72. package/src/components/Slider/Thumb.tsx +56 -15
  73. package/src/components/Slider/Track.tsx +62 -3
  74. package/src/components/Slider/index.tsx +125 -89
  75. package/src/components/_util/index.ts +11 -0
  76. package/src/index.ts +1 -0
  77. package/src/styles/{variables.css → index.css} +4 -0
@@ -1,7 +1,7 @@
1
1
  import { css, CSSObject, Global } from '@emotion/react'
2
+ import clsx from 'clsx'
2
3
  import {
3
4
  Direction,
4
- gradientDirection,
5
5
  isHorizontal,
6
6
  isReversed,
7
7
  isVertical,
@@ -9,21 +9,20 @@ import {
9
9
  ScaleOrderList,
10
10
  ScaleType,
11
11
  } from '@tremolo-ui/functions/Slider'
12
- import { clamp, normalizeValue, rawValue, stepValue } from '@tremolo-ui/functions'
13
- import { WheelOption } from '@tremolo-ui/functions'
14
- import { styleHelper } from '@tremolo-ui/functions'
15
- import React, { ReactElement, useRef } from 'react'
16
- import clsx from 'clsx'
12
+ import { clamp, normalizeValue, rawValue, stepValue, toFixed, InputEventOption, styleHelper } from '@tremolo-ui/functions'
13
+ import React, { forwardRef, ReactElement, useCallback, useImperativeHandle, useRef } from 'react'
17
14
 
18
15
  import { useEventListener } from '../../hooks/useEventListener'
19
16
  import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
20
17
  import { UserActionPseudoProps } from '../../system/pseudo'
18
+ import { addNoSelect, removeNoSelect } from '../_util'
21
19
 
22
- import { SliderThumb } from './Thumb'
20
+ import { SliderThumb, SliderThumbMethods } from './Thumb'
23
21
  import { SliderTrack } from './Track'
24
22
  import { ScaleOption } from './type'
25
23
 
26
- interface SliderProps {
24
+ export interface SliderProps {
25
+ // TODO: property docs
27
26
  // required
28
27
  value: number
29
28
  min: number
@@ -31,16 +30,22 @@ interface SliderProps {
31
30
 
32
31
  // optional
33
32
  step?: number
34
- skew?: number // | SkewFunction
35
- length?: number | string
36
- thickness?: number | string
37
- direction?: Direction
33
+ skew?: number // TODO | SkewFunction
34
+ direction?: Direction // TODO: vertical & reverse
35
+ // TODO: scales component
38
36
  scale?: ['step', ScaleType] | [number, ScaleType] | ScaleOrderList[]
39
37
  scaleOption?: ScaleOption
40
- color?: string
41
- bg?: string
42
38
  bodyNoSelect?: boolean
43
- enableWheel?: WheelOption
39
+ /**
40
+ * wheel control option
41
+ */
42
+ wheel?: InputEventOption
43
+ /**
44
+ * keyboard control option
45
+ */
46
+ keyboard?: InputEventOption
47
+ disabled?: boolean
48
+ readonly?: boolean
44
49
  className?: string
45
50
  style?: CSSObject
46
51
  onChange?: (value: number) => void
@@ -48,45 +53,62 @@ interface SliderProps {
48
53
  // ReactElement<typeof SliderThumb, typeof SliderTrack>[]
49
54
  }
50
55
 
56
+ export interface SliderMethods {
57
+ focus: () => void
58
+ blur: () => void
59
+ }
60
+
51
61
  /**
52
62
  * Customizable slider
53
63
  */
54
- export function Slider({
64
+ export const Slider = forwardRef<SliderMethods, SliderProps>(({
55
65
  value,
56
66
  min,
57
67
  max,
58
68
  step = 1,
59
69
  skew = 1,
60
- length = 140,
61
- thickness = 10,
62
70
  direction = 'right',
63
71
  scale,
64
72
  scaleOption,
65
- color = '#4e76e6',
66
- bg = '#eee',
67
73
  className,
68
74
  style,
69
75
  bodyNoSelect = true,
70
- enableWheel,
76
+ wheel = ['raw', 1],
77
+ keyboard = ['raw', 1],
78
+ disabled = false,
79
+ readonly = false,
71
80
  onChange,
72
81
  children,
73
82
  ...pseudo
74
- }: SliderProps & UserActionPseudoProps) {
83
+ }: SliderProps & UserActionPseudoProps, ref) => {
75
84
  // -- state and ref ---
76
85
  const trackElementRef = useRef<HTMLDivElement>(null)
86
+ const thumbRef = useRef<SliderThumbMethods>(null)
87
+ const thumbDragged = useRef(false)
77
88
 
78
89
  // --- interpret props ---
79
90
  const { _active, _focus, _hover } = pseudo
80
- const percent = normalizeValue(value, min, max, skew) * 100
81
- const percentRev = isReversed(direction) ? 100 - percent : percent
91
+ const percent = toFixed(normalizeValue(value, min, max, skew) * 100)
92
+ const percentRev = isReversed(direction) ? toFixed(100 - percent) : percent
93
+ const calcPercent = (v: number) => {
94
+ return toFixed(
95
+ isReversed(direction)
96
+ ? 100 - normalizeValue(v, min, max, skew) * 100
97
+ : normalizeValue(v, min, max, skew) * 100
98
+ )
99
+ }
100
+ if (wheel.length != 2) {
101
+ throw new Error(`Slider.wheel expect ['normalized' | 'raw', number]`)
102
+ }
103
+ if (keyboard.length != 2) {
104
+ throw new Error(`Slider.keyboard expect ['normalized' | 'raw', number]`)
105
+ }
82
106
 
83
107
  const scalesList = parseScaleOrderList(scale, min, max, step)
84
108
  if (isReversed(direction)) scalesList.reverse()
85
109
 
86
110
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
- let trackProps: any
88
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
- let thumbProps: any
111
+ let trackProps: any, thumbProps: any
90
112
  if (children != undefined) {
91
113
  React.Children.forEach(children, (child) => {
92
114
  if (React.isValidElement(child)) {
@@ -103,14 +125,14 @@ export function Slider({
103
125
  })
104
126
  }
105
127
 
106
- const thumbDragged = useRef(false)
128
+ // --- internal functions ---
107
129
  const handleValue = (
108
130
  event: MouseEvent | React.PointerEvent<HTMLDivElement> | TouchEvent,
109
131
  ) => {
110
- if (!trackElementRef.current || !thumbDragged.current) return
132
+ if (!trackElementRef.current || !thumbDragged.current || !onChange || readonly) return
111
133
  const isTouch = event instanceof TouchEvent
112
134
  if (isTouch && event.cancelable) event.preventDefault()
113
- if (bodyNoSelect) document.body.classList.add('no-select')
135
+ if (bodyNoSelect) addNoSelect()
114
136
  const {
115
137
  left: x1,
116
138
  top: y1,
@@ -123,38 +145,55 @@ export function Slider({
123
145
  ? normalizeValue(mouseX, x1, x2)
124
146
  : normalizeValue(mouseY, y1, y2)
125
147
  const v = rawValue(isReversed(direction) ? 1 - n : n, min, max, skew)
126
- const v2 = clamp(stepValue(v, step), min, max)
127
- if (onChange) onChange(v2)
148
+ onChange(clamp(stepValue(v, step), min, max))
128
149
  }
129
150
 
151
+ const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {
152
+ if (!keyboard || !onChange || readonly) return
153
+ const key = event.key
154
+ if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key)) {
155
+ event.preventDefault()
156
+ let x = (key == 'ArrowRight' || key == 'ArrowUp') ? keyboard[1] : -keyboard[1]
157
+ if (direction == 'down' || direction == 'left') x *= -1
158
+ let v
159
+ if (keyboard[0] == 'normalized') {
160
+ const n = normalizeValue(value, min, max, skew)
161
+ v = rawValue(n + x, min, max, skew)
162
+ } else {
163
+ v = value + x
164
+ }
165
+ onChange(clamp(stepValue(v, step), min, max))
166
+ }
167
+ }, [value, min, max, step, skew, direction, keyboard, onChange, readonly])
168
+
130
169
  // --- hooks ---
131
170
  const touchMoveRefCallback = useRefCallbackEvent(
132
171
  'touchmove',
133
172
  handleValue,
134
173
  { passive: false },
135
- [min, max, skew, step, direction, onChange],
174
+ [min, max, skew, step, direction, onChange, readonly],
136
175
  )
137
176
 
138
177
  const wheelRefCallback = useRefCallbackEvent(
139
178
  'wheel',
140
179
  (event) => {
141
- if (!enableWheel) return
180
+ if (!wheel || !onChange || readonly) return
142
181
  event.preventDefault()
143
- let x = event.deltaY > 0 ? enableWheel[1] : -enableWheel[1]
182
+ let x = event.deltaY > 0 ? wheel[1] : -wheel[1]
144
183
  if (isReversed(direction) || isHorizontal(direction)) x *= -1
145
184
  let v
146
- if (enableWheel[0] == 'normalized') {
185
+ if (wheel[0] == 'normalized') {
147
186
  const n = normalizeValue(value, min, max, skew)
148
187
  v = rawValue(n + x, min, max, skew)
149
188
  } else {
150
189
  v = value + x
151
190
  }
152
- if (onChange) onChange(clamp(stepValue(v, step), min, max))
191
+ onChange(clamp(stepValue(v, step), min, max))
153
192
  },
154
193
  {
155
194
  passive: false,
156
195
  },
157
- [enableWheel, value, min, max, skew, step],
196
+ [wheel, value, min, max, skew, step],
158
197
  )
159
198
 
160
199
  useEventListener(window, 'mousemove', (event) => {
@@ -163,9 +202,20 @@ export function Slider({
163
202
 
164
203
  useEventListener(window, 'mouseup', () => {
165
204
  thumbDragged.current = false
166
- if (bodyNoSelect) document.body.classList.remove('no-select')
205
+ if (bodyNoSelect) removeNoSelect()
167
206
  })
168
207
 
208
+ useImperativeHandle(ref, () => {
209
+ return {
210
+ focus() {
211
+ thumbRef.current?.focus()
212
+ },
213
+ blur() {
214
+ thumbRef.current?.blur()
215
+ },
216
+ }
217
+ }, [])
218
+
169
219
  return (
170
220
  <div
171
221
  className={clsx('tremolo-slider', className)}
@@ -176,21 +226,25 @@ export function Slider({
176
226
  tabIndex={-1}
177
227
  role="slider"
178
228
  aria-valuenow={value}
179
- aria-valuemax={max}
180
229
  aria-valuemin={min}
230
+ aria-valuemax={max}
181
231
  aria-orientation={isHorizontal(direction) ? 'horizontal' : 'vertical'}
232
+ aria-disabled={disabled}
233
+ aria-readonly={readonly}
182
234
  css={css({
183
235
  display: 'inline-block',
184
- boxSizing: 'border-box',
185
- margin: '0.7rem',
186
- padding: 0,
187
- cursor: 'pointer',
236
+ margin: `calc(${styleHelper(thumbProps?.size ?? 22)} / 2)`, // half thumb size
237
+ cursor: readonly ? 'not-allowed' : 'pointer',
238
+ outline: 0,
188
239
  ...style,
189
240
  })}
190
241
  onPointerDown={(event) => {
191
242
  thumbDragged.current = true
192
243
  handleValue(event)
193
244
  }}
245
+ onKeyDown={handleKeyDown}
246
+ onFocus={thumbRef.current?.focus}
247
+ onBlur={thumbRef.current?.blur}
194
248
  >
195
249
  <Global
196
250
  styles={{
@@ -205,49 +259,43 @@ export function Slider({
205
259
  flexDirection: isHorizontal(direction) ? 'column' : 'row',
206
260
  })}
207
261
  >
208
- {/* trackProps */}
209
262
  <div
210
263
  className="tremolo-slider-track-wrapper"
211
264
  ref={trackElementRef}
212
- css={css({ position: 'relative' })}
213
265
  >
214
266
  <SliderTrack
215
- __css={{
216
- background: `linear-gradient(to ${gradientDirection(direction)}, ${color} ${percent}%, ${bg} ${percent}%)`,
217
- borderRadius: styleHelper(thickness, '/', 2),
218
- borderStyle: 'solid',
219
- borderColor: '#ccc',
220
- borderWidth: 2,
221
- width: isHorizontal(direction) ? length : thickness,
222
- height: isHorizontal(direction) ? thickness : length,
223
- zIndex: 1,
224
- }}
267
+ __direction={direction}
268
+ __disabled={disabled}
269
+ __percent={percent}
270
+ __thumb={
271
+ <SliderThumb
272
+ ref={thumbRef}
273
+ __disabled={disabled}
274
+ __css={{
275
+ top: isHorizontal(direction) ? '50%' : `${percentRev}%`,
276
+ left: isHorizontal(direction) ? `${percentRev}%` : '50%',
277
+ }}
278
+ {...thumbProps}
279
+ />
280
+ }
225
281
  {...trackProps}
226
282
  />
227
- <SliderThumb
228
- color={color}
229
- __css={{
230
- top: isHorizontal(direction) ? '50%' : `${percentRev}%`,
231
- left: isHorizontal(direction) ? `${percentRev}%` : '50%',
232
- }}
233
- {...thumbProps}
234
- />
235
283
  </div>
284
+ {/* TODO: scales component */}
236
285
  {scale && (
237
286
  <div
238
287
  className="tremolo-slider-scale-wrapper"
239
288
  css={css({
289
+ display: 'block',
240
290
  position: 'relative',
241
- width: isHorizontal(direction) ? length : undefined,
242
- height: isVertical(direction) ? length : undefined,
243
- marginLeft: isHorizontal(direction)
244
- ? 2
245
- : (scaleOption?.gap ??
246
- `calc((1.4rem - ${styleHelper(thickness)}) / 2)`),
247
- marginTop: isVertical(direction)
248
- ? 2
249
- : (scaleOption?.gap ??
250
- `calc((1.4rem - ${styleHelper(thickness)}) / 2)`),
291
+ marginLeft: isVertical(direction)
292
+ ? (scaleOption?.gap ??
293
+ `calc((22px - ${styleHelper(trackProps?.thickness ?? 10)}) / 2)`)
294
+ : undefined,
295
+ marginTop: isHorizontal(direction)
296
+ ? (scaleOption?.gap ??
297
+ `calc((22px - ${styleHelper(trackProps?.thickness ?? 10)}) / 2)`)
298
+ : undefined,
251
299
  ...scaleOption?.style,
252
300
  // zIndex: 10,
253
301
  })}
@@ -261,20 +309,8 @@ export function Slider({
261
309
  justifyContent: 'center',
262
310
  alignItems: 'center',
263
311
  position: 'absolute',
264
- left: isHorizontal(direction)
265
- ? `${
266
- isReversed(direction)
267
- ? 100 - normalizeValue(item.at, min, max, skew) * 100
268
- : normalizeValue(item.at, min, max, skew) * 100
269
- }%`
270
- : undefined,
271
- top: isVertical(direction)
272
- ? `${
273
- isReversed(direction)
274
- ? 100 - normalizeValue(item.at, min, max, skew) * 100
275
- : normalizeValue(item.at, min, max, skew) * 100
276
- }%`
277
- : undefined,
312
+ left: isHorizontal(direction) ? `${calcPercent(item.at)}%` : undefined,
313
+ top: isVertical(direction) ? `${calcPercent(item.at)}%` : undefined,
278
314
  translate: isHorizontal(direction) ? '-50% 0' : '0 -50%',
279
315
  zIndex: 10,
280
316
  })}
@@ -319,7 +355,7 @@ export function Slider({
319
355
  </div>
320
356
  </div>
321
357
  )
322
- }
358
+ })
323
359
 
324
360
  export * from './Thumb'
325
361
  export * from './Track'
@@ -0,0 +1,11 @@
1
+ export function addNoSelect() {
2
+ document.body.classList.add('tremolo-no-select')
3
+ }
4
+
5
+ export function removeNoSelect() {
6
+ document.body.classList.remove('tremolo-no-select')
7
+ }
8
+
9
+ export function toggleNoSelect() {
10
+ document.body.classList.toggle('tremolo-no-select')
11
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './components/AnimationCanvas'
2
2
  export * from './components/Knob'
3
3
  export * from './components/NumberInput'
4
+ export * from './components/Piano'
4
5
  export * from './components/Slider'
@@ -2,3 +2,7 @@
2
2
  --tremolo-theme-color: #4e76e6;
3
3
  --tremolo-theme-color-rgb: 78, 118, 230;
4
4
  }
5
+
6
+ .tremolo-no-select {
7
+ user-select: none;
8
+ }