@tremolo-ui/react 0.1.6 → 0.2.1

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 (68) hide show
  1. package/dist/index.cjs +1329 -1152
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +238 -183
  4. package/dist/index.d.cts +367 -366
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.ts +367 -366
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1313 -1125
  9. package/dist/index.js.map +1 -1
  10. package/package.json +17 -19
  11. package/src/components/AnimationCanvas/index.tsx +0 -7
  12. package/src/components/DragObserver/index.tsx +9 -7
  13. package/src/components/Knob/ActiveLine.tsx +29 -0
  14. package/src/components/Knob/InactiveLine.tsx +53 -0
  15. package/src/components/Knob/SVGRoot.tsx +39 -0
  16. package/src/components/Knob/Thumb.tsx +64 -0
  17. package/src/components/Knob/context.tsx +120 -0
  18. package/src/components/Knob/index.css +25 -27
  19. package/src/components/Knob/index.tsx +110 -164
  20. package/src/components/NumberInput/DecrementStepper.tsx +0 -2
  21. package/src/components/NumberInput/IncrementStepper.tsx +0 -2
  22. package/src/components/NumberInput/InternalInput.tsx +3 -1
  23. package/src/components/NumberInput/Stepper.tsx +0 -2
  24. package/src/components/NumberInput/context.tsx +0 -1
  25. package/src/components/NumberInput/index.css +31 -15
  26. package/src/components/NumberInput/index.tsx +21 -14
  27. package/src/components/Piano/KeyLabel.tsx +0 -2
  28. package/src/components/Piano/context.tsx +0 -2
  29. package/src/components/Piano/index.css +7 -14
  30. package/src/components/Piano/index.tsx +14 -15
  31. package/src/components/Piano/key.tsx +0 -10
  32. package/src/components/Piano/keyboardShortcuts.ts +0 -2
  33. package/src/components/PointsEditor/Background.tsx +17 -0
  34. package/src/components/PointsEditor/Container.tsx +26 -0
  35. package/src/components/PointsEditor/Point.tsx +137 -0
  36. package/src/components/PointsEditor/context.tsx +91 -0
  37. package/src/components/PointsEditor/index.css +30 -0
  38. package/src/components/PointsEditor/index.tsx +129 -0
  39. package/src/components/Slider/Scale.tsx +0 -2
  40. package/src/components/Slider/ScaleOption.tsx +0 -2
  41. package/src/components/Slider/Thumb.tsx +1 -5
  42. package/src/components/Slider/Track.tsx +1 -3
  43. package/src/components/Slider/context.tsx +0 -1
  44. package/src/components/Slider/index.css +33 -8
  45. package/src/components/Slider/index.tsx +61 -33
  46. package/src/components/WheelObserver/index.tsx +17 -19
  47. package/src/components/XYPad/Area.tsx +4 -5
  48. package/src/components/XYPad/Thumb.tsx +8 -7
  49. package/src/components/XYPad/index.css +18 -4
  50. package/src/components/XYPad/index.tsx +71 -38
  51. package/src/components/_util/composeRefs.tsx +63 -0
  52. package/src/components/_util/index.ts +23 -6
  53. package/src/components/_util/type.ts +1 -0
  54. package/src/hooks/useAnimationFrame.ts +0 -3
  55. package/src/hooks/useCallbackRef.ts +2 -2
  56. package/src/hooks/useDrag.ts +12 -11
  57. package/src/hooks/useDragWithElement.ts +12 -17
  58. package/src/hooks/useEventListener.ts +6 -9
  59. package/src/hooks/useInterval.ts +0 -3
  60. package/src/hooks/useLongPress.ts +0 -3
  61. package/src/hooks/useMIDIAccess.ts +0 -1
  62. package/src/hooks/useMIDIInput.ts +0 -1
  63. package/src/hooks/useMIDIMessage.ts +0 -1
  64. package/src/hooks/usePianoDrag.ts +75 -0
  65. package/src/index.ts +21 -30
  66. package/src/styles/global.css +16 -4
  67. package/dist/index.css.map +0 -1
  68. package/src/components/AnimationKnob/index.tsx +0 -314
@@ -5,22 +5,38 @@ import {
5
5
  useCallback,
6
6
  useImperativeHandle,
7
7
  useRef,
8
+ useState,
8
9
  } from 'react'
9
10
 
10
11
  import {
11
12
  clamp,
12
13
  InputEventOption,
13
14
  normalizeValue,
14
- radian,
15
15
  rawValue,
16
16
  stepValue,
17
17
  } from '@tremolo-ui/functions'
18
18
 
19
19
  import { useDrag } from '../../hooks/useDrag'
20
20
  import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
21
- import { addNoSelect, removeNoSelect } from '../_util'
21
+ import {
22
+ addUserSelectNone,
23
+ Cursor,
24
+ removeUserSelectNone,
25
+ resetCursorStyle,
26
+ setCursorStyle,
27
+ } from '../_util'
28
+
29
+ import { ActiveLine } from './ActiveLine'
30
+ import { KnobProvider } from './context'
31
+ import { InactiveLine } from './InactiveLine'
32
+ import { SVGRoot } from './SVGRoot'
33
+ import { Thumb } from './Thumb'
34
+
35
+ const defaultExternalStyles: KnobProps['externalStyles'] = {
36
+ userSelectNone: true,
37
+ cursor: 'grabbing',
38
+ }
22
39
 
23
- /** @category Knob */
24
40
  export interface KnobProps {
25
41
  // required
26
42
  value: number
@@ -29,9 +45,10 @@ export interface KnobProps {
29
45
 
30
46
  // optional
31
47
  step?: number
32
- skew?: number // | SkewFunction
48
+ skew?: number // | SkewFunction // TODO
33
49
  /**
34
50
  * value set when double-clicking
51
+ * restriction: enableDoubleClickDefault = true
35
52
  * @default min
36
53
  * @see enableDoubleClickDefault
37
54
  */
@@ -46,14 +63,22 @@ export interface KnobProps {
46
63
  /** width and height */
47
64
  size?: number | string
48
65
 
49
- /** Whether to apply `{use-select: none}` when dragging */
50
- bodyNoSelect?: boolean
66
+ /**
67
+ * Global style to apply when dragged
68
+ * @default defaultExternalStyles
69
+ */
70
+ externalStyles?: {
71
+ userSelectNone?: boolean
72
+ cursor?: Cursor
73
+ }
51
74
  /**
52
75
  * wheel control option
76
+ * If null, no event will be triggered
53
77
  */
54
78
  wheel?: InputEventOption | null
55
79
  /**
56
80
  * keyboard control option
81
+ * If null, no event will be triggered
57
82
  */
58
83
  keyboard?: InputEventOption | null
59
84
  enableDoubleClickDefault?: boolean
@@ -61,51 +86,20 @@ export interface KnobProps {
61
86
  disabled?: boolean
62
87
  readonly?: boolean
63
88
 
64
- /** color */
65
- activeLine?: string
66
- /** color */
67
- inactiveLine?: string
68
- /** color */
69
- thumb?: string
70
- /** color */
71
- thumbLine?: string
72
- /** percent (0-100) */
73
- thumbSize?: number
74
- /** percent (0-100) */
75
- thumbLineWeight?: number
76
- /** percent (0-100) */
77
- thumbLineLength?: number
78
- /** line weight [px] */
79
- lineWeight?: number
80
89
  /** angle range [degree] */
81
90
  angleRange?: number
82
91
 
83
- classes?: {
84
- activeLine?: string
85
- inactiveLine?: string
86
- thumb?: string
87
- thumbLine?: string
88
- }
89
-
90
92
  onChange?: (value: number) => void
91
93
  }
92
94
 
93
- /**
94
- * @category Knob
95
- */
96
95
  export interface KnobMethods {
97
96
  focus: () => void
98
97
  blur: () => void
99
98
  }
100
99
 
101
- type Props = KnobProps & Omit<ComponentPropsWithoutRef<'svg'>, keyof KnobProps>
100
+ type Props = KnobProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KnobProps>
102
101
 
103
- /**
104
- * Interactive rotary knob component implemented in SVG.
105
- *
106
- * @category Knob
107
- */
108
- export const Knob = forwardRef<KnobMethods, Props>(
102
+ const Root = forwardRef<KnobMethods, Props>(
109
103
  (
110
104
  {
111
105
  value,
@@ -115,34 +109,30 @@ export const Knob = forwardRef<KnobMethods, Props>(
115
109
  skew = 1,
116
110
  defaultValue = min,
117
111
  startValue = min,
118
- size = 50,
119
- bodyNoSelect = true,
112
+ size,
113
+ externalStyles: _externalStyles,
120
114
  wheel = ['raw', 1],
121
115
  keyboard = ['raw', 1],
122
116
  enableDoubleClickDefault = true,
123
117
  disabled = false,
124
118
  readonly = false,
125
- activeLine,
126
- inactiveLine,
127
- thumb,
128
- thumbLine,
129
- thumbSize = 84,
130
- thumbLineWeight = 6,
131
- thumbLineLength = 35,
132
- lineWeight = 6,
133
119
  angleRange = 270,
134
120
  onChange,
135
121
  onKeyDown,
136
122
  onPointerDown,
137
123
  onDoubleClick,
138
124
  className,
139
- classes,
125
+ style,
126
+ children,
140
127
  ...props
141
128
  }: Props,
142
129
  forwardedRef,
143
130
  ) => {
131
+ const [dragging, setDragging] = useState(false)
144
132
  const valueRef = useRef(0)
145
- const elmRef = useRef<HTMLOrSVGElement>(null)
133
+ const elmRef = useRef<HTMLElement | SVGElement>(null)
134
+
135
+ const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
146
136
 
147
137
  // --- internal functions ---
148
138
  const onDrag = useCallback(
@@ -184,14 +174,20 @@ export const Knob = forwardRef<KnobMethods, Props>(
184
174
  )
185
175
 
186
176
  // --- hooks ---
187
- const [touchMoveRefCallback, pointerDownHandler] = useDrag<SVGElement>({
177
+ const [touchMoveRefCallback, pointerDownHandler] = useDrag<
178
+ HTMLElement | SVGElement
179
+ >({
188
180
  onDrag: onDrag,
189
181
  onDragStart: () => {
182
+ setDragging(true)
190
183
  valueRef.current = normalizeValue(value, min, max, skew)
191
- if (bodyNoSelect) addNoSelect()
184
+ if (externalStyles.userSelectNone) addUserSelectNone()
185
+ if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
192
186
  },
193
187
  onDragEnd: () => {
194
- if (bodyNoSelect) removeNoSelect()
188
+ setDragging(false)
189
+ if (externalStyles.userSelectNone) removeUserSelectNone()
190
+ if (externalStyles.cursor) resetCursorStyle()
195
191
  },
196
192
  })
197
193
 
@@ -219,118 +215,68 @@ export const Knob = forwardRef<KnobMethods, Props>(
219
215
  }
220
216
  }, [])
221
217
 
222
- const viewBoxSize = 100
223
- const center = viewBoxSize / 2
224
- const padding = (viewBoxSize - clamp(thumbSize, 0, 100)) / 2 // %
225
- const p = normalizeValue(value, min, max, skew)
226
- const s = normalizeValue(startValue, min, max, skew)
227
-
228
- // 時計回りで描画していく
229
- const r1 = -angleRange / 2 // ロータリー開始位置
230
- const r2 = r1 + Math.min(p, s) * angleRange // activeLineの開始位置
231
- const r3 = r1 + Math.max(p, s) * angleRange // activeLineの終了位置
232
- const r4 = angleRange / 2 // ロータリー終了位置
233
- const x1 = center + center * Math.cos(radian(r1 - 90))
234
- const y1 = center + center * Math.sin(radian(r1 - 90))
235
- const x2 = center + center * Math.cos(radian(r2 - 90))
236
- const y2 = center + center * Math.sin(radian(r2 - 90))
237
- const x3 = center + center * Math.cos(radian(r3 - 90))
238
- const y3 = center + center * Math.sin(radian(r3 - 90))
239
- const x4 = center + center * Math.cos(radian(r4 - 90))
240
- const y4 = center + center * Math.sin(radian(r4 - 90))
241
-
242
218
  return (
243
- <svg
244
- ref={(div) => {
245
- elmRef.current = div
246
- wheelRefCallback(div)
247
- touchMoveRefCallback(div)
248
- }}
249
- className={clsx('tremolo-knob', className)}
250
- viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
251
- width={size}
252
- height={size}
253
- tabIndex={0}
254
- role="slider"
255
- aria-valuenow={value}
256
- aria-valuemin={min}
257
- aria-valuemax={max}
258
- aria-disabled={disabled}
259
- aria-readonly={readonly}
260
- onPointerDown={(event) => {
261
- pointerDownHandler(event)
262
- onPointerDown?.(event)
263
- }}
264
- onDoubleClick={(event) => {
265
- if (enableDoubleClickDefault && onChange) {
266
- onChange(defaultValue)
267
- }
268
- onDoubleClick?.(event)
269
- }}
270
- onKeyDown={(event) => {
271
- handleKeyDown(event)
272
- onKeyDown?.(event)
273
- }}
274
- {...props}
219
+ <KnobProvider
220
+ value={value}
221
+ min={min}
222
+ max={max}
223
+ step={step}
224
+ skew={skew}
225
+ startValue={startValue}
226
+ angleRange={angleRange}
275
227
  >
276
- {/* lines */}
277
- {startValue > min && (
278
- <path
279
- className={clsx(
280
- 'tremolo-knob-inactive-line',
281
- classes?.inactiveLine,
282
- )}
283
- d={`M ${x1} ${y1} A ${center} ${center} -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2}, ${y2}`}
284
- fill="none"
285
- stroke={inactiveLine || 'currentColor'}
286
- strokeWidth={lineWeight}
287
- />
288
- )}
289
- <path
290
- className={clsx('tremolo-knob-active-line', classes?.activeLine)}
291
- d={`M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`}
292
- fill="none"
293
- stroke={activeLine || 'currentColor'}
294
- strokeWidth={lineWeight}
295
- />
296
- {startValue < max && (
297
- <path
298
- className={clsx(
299
- 'tremolo-knob-inactive-line',
300
- classes?.inactiveLine,
301
- )}
302
- d={`M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`}
303
- fill="none"
304
- stroke={inactiveLine || 'currentColor'}
305
- strokeWidth={lineWeight}
306
- />
307
- )}
308
- {/* thumb */}
309
- <svg className={clsx('tremolo-knob-thumb', classes?.thumb)}>
310
- <circle
311
- cx="50%"
312
- cy="50%"
313
- r={`${thumbSize / 2}%`}
314
- fill={thumb || 'currentColor'}
315
- />
316
- <line
317
- className={clsx('tremolo-knob-thumb-line', classes?.thumbLine)}
318
- x1="50%"
319
- y1={`${padding}%`}
320
- x2="50%"
321
- y2={`${thumbLineLength}%`}
322
- stroke={thumbLine || 'currentColor'}
323
- strokeWidth={`${thumbLineWeight}%`}
324
- // NOTE
325
- // https://bugs.webkit.org/show_bug.cgi?id=201854
326
- // https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/transform-origin#browser_compatibility
327
- style={{
328
- transform: `rotate(${r1 + p * angleRange}deg)`,
329
- transformOrigin: '50% 50%',
330
- }}
331
- />
332
- </svg>
333
- </svg>
228
+ <div
229
+ ref={(elm) => {
230
+ elmRef.current = elm
231
+ wheelRefCallback(elm)
232
+ touchMoveRefCallback(elm)
233
+ }}
234
+ className={clsx('tremolo-knob', className)}
235
+ tabIndex={0}
236
+ role="slider"
237
+ aria-valuenow={value}
238
+ aria-valuemin={min}
239
+ aria-valuemax={max}
240
+ aria-disabled={disabled}
241
+ aria-readonly={readonly}
242
+ data-dragging={dragging}
243
+ style={{
244
+ width: size,
245
+ height: size,
246
+ ...style,
247
+ }}
248
+ onPointerDown={(event) => {
249
+ pointerDownHandler(event)
250
+ onPointerDown?.(event)
251
+ }}
252
+ onDoubleClick={(event) => {
253
+ if (enableDoubleClickDefault && onChange) {
254
+ onChange(defaultValue)
255
+ }
256
+ onDoubleClick?.(event)
257
+ }}
258
+ onKeyDown={(event) => {
259
+ handleKeyDown(event)
260
+ onKeyDown?.(event)
261
+ }}
262
+ {...props}
263
+ >
264
+ {children || <SVGRoot />}
265
+ </div>
266
+ </KnobProvider>
334
267
  )
335
268
  },
336
269
  )
270
+
271
+ /**
272
+ * Interactive rotary knob component implemented in SVG.
273
+ */
274
+ export const Knob = {
275
+ Root,
276
+ SVGRoot,
277
+ InactiveLine,
278
+ ActiveLine,
279
+ Thumb,
280
+ }
281
+
282
+ export { useKnobContext } from './context'
@@ -5,13 +5,11 @@ import { useLongPress } from '../../hooks/useLongPress'
5
5
 
6
6
  import { useNumberInputContext } from './context'
7
7
 
8
- /** @category NumberInput */
9
8
  export interface DecrementStepperProps {
10
9
  size?: number
11
10
  children?: ReactNode
12
11
  }
13
12
 
14
- /** @category NumberInput */
15
13
  export function DecrementStepper({
16
14
  size = 12,
17
15
  children,
@@ -5,13 +5,11 @@ import { useLongPress } from '../../hooks/useLongPress'
5
5
 
6
6
  import { useNumberInputContext } from './context'
7
7
 
8
- /** @category NumberInput */
9
8
  export interface IncrementStepperProps {
10
9
  size?: number
11
10
  children?: ReactNode
12
11
  }
13
12
 
14
- /** @category NumberInput */
15
13
  export function IncrementStepper({
16
14
  size = 12,
17
15
  children,
@@ -96,7 +96,9 @@ export const InternalInput = forwardRef<
96
96
  let newValue
97
97
  if (eventType == 'normalized') {
98
98
  if (!min || !max) {
99
- throw new Error('required parameter: min, max') // TODO
99
+ throw new Error(
100
+ '[NumberInput] "min" and "max" are required when InputEventOption[0] is set to "normalized".',
101
+ )
100
102
  }
101
103
  const n = normalizeValue(valueAsNumber, min, max)
102
104
  newValue = rawValue(n + x, min, max)
@@ -1,14 +1,12 @@
1
1
  import clsx from 'clsx'
2
2
  import { ComponentPropsWithoutRef, ReactNode } from 'react'
3
3
 
4
- /** @category NumberInput */
5
4
  export interface StepperProps {
6
5
  /** Display only when hovering. */
7
6
  dynamic?: boolean
8
7
  children?: ReactNode
9
8
  }
10
9
 
11
- /** @category NumberInput */
12
10
  export function Stepper({
13
11
  dynamic = true,
14
12
  children,
@@ -153,7 +153,6 @@ export function NumberInputProvider({
153
153
  )
154
154
  }
155
155
 
156
- /** @category NumberInput */
157
156
  export function useNumberInputContext<T>(
158
157
  selector: (state: State & Action) => T,
159
158
  ): T {
@@ -1,7 +1,8 @@
1
1
  .tremolo-number-input-wrapper {
2
2
  --border-radius: 4px;
3
3
  --stepper-width: 20px;
4
- --active-color: var(--tremolo-theme-color);
4
+ --active-color: oklch(59.5% 0.17548 266.373);
5
+ --border-color: oklch(81% 0 0);
5
6
 
6
7
  display: inline-flex;
7
8
  flex-direction: row;
@@ -9,13 +10,13 @@
9
10
  width: 140px;
10
11
  height: min-content;
11
12
  border-style: solid;
12
- border-color: #c1c1c1;
13
+ border-color: var(--border-color);
13
14
  border-width: 0px;
14
15
  outline: none;
15
16
  transition: all 0.1s;
16
17
 
17
18
  &:not(:focus-within):has(> .tremolo-number-input[data-error='true']) {
18
- border-color: #f34343;
19
+ border-color: oklch(64.245% 0.21266 25.499);
19
20
  }
20
21
 
21
22
  &[data-variant='outline'] {
@@ -23,29 +24,30 @@
23
24
  border-radius: var(--border-radius);
24
25
 
25
26
  &:hover {
26
- border-color: #909090;
27
+ --border-color: oklch(65.335% 0 0);
27
28
  }
28
29
 
29
30
  &:focus-within {
30
- border-color: var(--active-color);
31
+ --border-color: var(--active-color);
31
32
  box-shadow: 0px 0px 0px 2px
32
33
  color-mix(in srgb, var(--active-color) 10%, transparent);
33
34
  }
34
35
  }
35
36
 
36
37
  &[data-variant='filled'] {
38
+ --bg: oklch(95.88% 0 0);
37
39
  border-width: 1px;
38
40
  border-radius: var(--border-radius);
39
41
  border-color: transparent;
40
- background-color: #edf2f7;
42
+ background-color: var(--bg);
41
43
 
42
44
  &:hover {
43
- background-color: #e4e9ee;
45
+ --bg: oklch(93.166% 0 0);
44
46
  }
45
47
 
46
48
  &:focus-within {
47
49
  background-color: inherit;
48
- border-color: var(--active-color);
50
+ --border-color: var(--active-color);
49
51
  box-shadow: 0px 0px 0px 2px
50
52
  color-mix(in srgb, var(--active-color) 10%, transparent);
51
53
  }
@@ -55,11 +57,11 @@
55
57
  border-bottom-width: 1px;
56
58
 
57
59
  &:hover {
58
- border-color: #909090;
60
+ --border-color: oklch(65.335% 0 0);
59
61
  }
60
62
 
61
63
  &:focus-within {
62
- border-color: var(--active-color);
64
+ --border-color: var(--active-color);
63
65
  box-shadow: 0px 2px 0px 0px
64
66
  color-mix(in srgb, var(--active-color) 10%, transparent);
65
67
  }
@@ -116,7 +118,7 @@
116
118
  .tremolo-number-input-increment-stepper {
117
119
  border-inline-start-width: 1px;
118
120
  border-inline-start-style: solid;
119
- border-inline-start-color: #ccc;
121
+ border-inline-start-color: oklch(84.522% 0 0);
120
122
  font-size: calc(0.75rem);
121
123
  display: flex;
122
124
  justify-content: center;
@@ -128,7 +130,7 @@
128
130
  border-top-right-radius: var(--border-radius);
129
131
 
130
132
  &[aria-disabled='false']:active {
131
- background-color: #3443560e;
133
+ background-color: oklch(37.798% 0.03812 254.612 / 0.055);
132
134
  }
133
135
 
134
136
  &[aria-disabled='true'] {
@@ -143,8 +145,8 @@
143
145
  .tremolo-number-input-decrement-stepper {
144
146
  border-inline-start-width: 1px;
145
147
  border-inline-start-style: solid;
146
- border-inline-start-color: #ccc;
147
- border-top: 1px solid #ccc;
148
+ border-inline-start-color: oklch(84.522% 0 0);
149
+ border-top: 1px solid oklch(84.522% 0 0);
148
150
  font-size: calc(0.75rem);
149
151
  display: flex;
150
152
  justify-content: center;
@@ -156,7 +158,7 @@
156
158
  border-bottom-right-radius: var(--border-radius);
157
159
 
158
160
  &[aria-disabled='false']:active {
159
- background-color: #3443560e;
161
+ background-color: oklch(37.798% 0.03812 254.612 / 0.055);
160
162
  }
161
163
 
162
164
  &[aria-disabled='true'] {
@@ -167,3 +169,17 @@
167
169
  cursor: not-allowed;
168
170
  }
169
171
  }
172
+
173
+ :where(.dark, [data-theme='dark']) {
174
+ .tremolo-number-input-wrapper {
175
+ --border-color: oklch(52% 0 0);
176
+
177
+ &[data-variant='filled'] {
178
+ --bg: oklch(43.5% 0 0);
179
+
180
+ &:hover {
181
+ --bg: oklch(46.6% 0 0);
182
+ }
183
+ }
184
+ }
185
+ }
@@ -4,7 +4,10 @@ import { ComponentPropsWithoutRef, forwardRef, ReactNode } from 'react'
4
4
  import { InputEventOption } from '@tremolo-ui/functions'
5
5
 
6
6
  import { NumberInputProvider } from './context'
7
+ import { DecrementStepper } from './DecrementStepper'
8
+ import { IncrementStepper } from './IncrementStepper'
7
9
  import { InternalInput } from './InternalInput'
10
+ import { Stepper } from './Stepper'
8
11
  import { Units } from './type'
9
12
 
10
13
  /*
@@ -13,16 +16,15 @@ TODO
13
16
  - skew
14
17
  */
15
18
 
16
- /** @category NumberInput */
17
19
  export interface NumberInputProps {
18
20
  value: number | string
19
21
 
20
22
  /**
21
- * @default Number.MIN_SAFE_INTEGER
23
+ * Number.MIN_SAFE_INTEGER
22
24
  */
23
25
  min?: number
24
26
  /**
25
- * @default Number.MAX_SAFE_INTEGER
27
+ * Number.MAX_SAFE_INTEGER
26
28
  */
27
29
  max?: number
28
30
  step?: number
@@ -57,10 +59,12 @@ export interface NumberInputProps {
57
59
 
58
60
  /**
59
61
  * wheel control option
62
+ * If null, no event will be triggered
60
63
  */
61
64
  wheel?: InputEventOption | null
62
65
  /**
63
66
  * keyboard control option
67
+ * If null, no event will be triggered
64
68
  */
65
69
  keyboard?: InputEventOption | null
66
70
 
@@ -83,9 +87,6 @@ export interface NumberInputProps {
83
87
  children?: ReactNode
84
88
  }
85
89
 
86
- /**
87
- * @category NumberInput
88
- */
89
90
  export interface NumberInputMethods {
90
91
  focus: () => void
91
92
  blur: () => void
@@ -94,11 +95,7 @@ export interface NumberInputMethods {
94
95
  type Props = NumberInputProps &
95
96
  Omit<ComponentPropsWithoutRef<'input'>, keyof NumberInputProps | 'type'>
96
97
 
97
- /**
98
- * Input with some useful functions for entering numerical values.
99
- * @category NumberInput
100
- */
101
- export const NumberInput = forwardRef<NumberInputMethods, Props>(
98
+ const Root = forwardRef<NumberInputMethods, Props>(
102
99
  (
103
100
  {
104
101
  value,
@@ -174,7 +171,17 @@ export const NumberInput = forwardRef<NumberInputMethods, Props>(
174
171
  },
175
172
  )
176
173
 
177
- export * from './Stepper'
178
- export * from './IncrementStepper'
179
- export * from './DecrementStepper'
174
+ /**
175
+ * Input with some useful functions for entering numerical values.
176
+ */
177
+ export const NumberInput = {
178
+ Root,
179
+ Stepper,
180
+ IncrementStepper,
181
+ DecrementStepper,
182
+ }
183
+
184
+ export { type StepperProps } from './Stepper'
185
+ export { type IncrementStepperProps } from './IncrementStepper'
186
+ export { type DecrementStepperProps } from './DecrementStepper'
180
187
  export { type Units } from './type'
@@ -5,7 +5,6 @@ import { usePianoContext } from './context'
5
5
 
6
6
  import { getNoteRangeArray } from '.'
7
7
 
8
- /** @category Piano */
9
8
  export interface KeyLabelProps {
10
9
  /**
11
10
  * override Piano.label
@@ -21,7 +20,6 @@ export interface KeyLabelProps {
21
20
  __label?: (note: number, index: number) => ReactNode
22
21
  }
23
22
 
24
- /** @category Piano */
25
23
  export function KeyLabel({
26
24
  label,
27
25
  className,
@@ -1,7 +1,6 @@
1
1
  import { createContext, ReactNode, useContext, useEffect, useRef } from 'react'
2
2
  import { createStore, useStore } from 'zustand'
3
3
 
4
- /** @category Piano */
5
4
  export type NoteRange = {
6
5
  first: number
7
6
  last: number
@@ -67,7 +66,6 @@ export function PianoProvider({ children, ...props }: PianoProviderProps) {
67
66
  )
68
67
  }
69
68
 
70
- /** @category Piano */
71
69
  export function usePianoContext<T>(selector: (state: State & Action) => T): T {
72
70
  const store = useContext(PianoContext)
73
71
  if (!store) throw new Error('Missing PianoContext.Provider in the tree')