@tremolo-ui/react 0.0.4 → 0.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/LICENSE +21 -0
- package/dist/cjs/index.js +196 -93
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/AnimationCanvas/index.d.ts +3 -4
- package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +1 -1
- package/dist/cjs/types/components/Knob/index.d.ts +6 -7
- package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
- package/dist/cjs/types/components/NumberInput/index.d.ts +10 -7
- package/dist/cjs/types/components/NumberInput/index.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/BlackKey.d.ts +4 -0
- package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +1 -0
- package/dist/cjs/types/components/Piano/KeyLabel.d.ts +20 -0
- package/dist/cjs/types/components/Piano/KeyLabel.d.ts.map +1 -0
- package/dist/cjs/types/components/Piano/WhiteKey.d.ts +4 -0
- package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +1 -0
- package/dist/cjs/types/components/Piano/index.d.ts +35 -0
- package/dist/cjs/types/components/Piano/index.d.ts.map +1 -0
- package/dist/cjs/types/components/Piano/key.d.ts +39 -0
- package/dist/cjs/types/components/Piano/key.d.ts.map +1 -0
- package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +12 -0
- package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts.map +1 -0
- package/dist/cjs/types/components/Slider/Thumb.d.ts +17 -2
- package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/Track.d.ts +21 -2
- package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/index.d.ts +18 -10
- package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
- package/dist/cjs/types/components/_util/index.d.ts +4 -0
- package/dist/cjs/types/components/_util/index.d.ts.map +1 -0
- package/dist/esm/index.js +197 -94
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/AnimationCanvas/index.d.ts +3 -4
- package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
- package/dist/esm/types/components/Knob/index.d.ts +6 -7
- package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
- package/dist/esm/types/components/NumberInput/index.d.ts +10 -7
- package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/BlackKey.d.ts +4 -0
- package/dist/esm/types/components/Piano/BlackKey.d.ts.map +1 -0
- package/dist/esm/types/components/Piano/KeyLabel.d.ts +20 -0
- package/dist/esm/types/components/Piano/KeyLabel.d.ts.map +1 -0
- package/dist/esm/types/components/Piano/WhiteKey.d.ts +4 -0
- package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +1 -0
- package/dist/esm/types/components/Piano/index.d.ts +35 -0
- package/dist/esm/types/components/Piano/index.d.ts.map +1 -0
- package/dist/esm/types/components/Piano/key.d.ts +39 -0
- package/dist/esm/types/components/Piano/key.d.ts.map +1 -0
- package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +12 -0
- package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts.map +1 -0
- package/dist/esm/types/components/Slider/Thumb.d.ts +17 -2
- package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/Track.d.ts +21 -2
- package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/index.d.ts +18 -10
- package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
- package/dist/esm/types/components/_util/index.d.ts +4 -0
- package/dist/esm/types/components/_util/index.d.ts.map +1 -0
- package/dist/styles/{variables.css → index.css} +4 -0
- package/package.json +12 -16
- package/src/components/AnimationCanvas/index.tsx +3 -7
- package/src/components/Knob/index.tsx +16 -20
- package/src/components/NumberInput/index.tsx +19 -12
- package/src/components/Piano/BlackKey.tsx +108 -0
- package/src/components/Piano/KeyLabel.tsx +69 -0
- package/src/components/Piano/WhiteKey.tsx +109 -0
- package/src/components/Piano/index.tsx +222 -0
- package/src/components/Piano/key.ts +43 -0
- package/src/components/Piano/keyboardShortcuts.ts +12 -0
- package/src/components/Slider/Thumb.tsx +56 -15
- package/src/components/Slider/Track.tsx +62 -3
- package/src/components/Slider/index.tsx +125 -89
- package/src/components/_util/index.ts +11 -0
- package/src/styles/{variables.css → index.css} +4 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { css } from "@emotion/react"
|
|
2
|
+
import React, { forwardRef, useImperativeHandle } from "react"
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
import { KeyLabel } from "./KeyLabel"
|
|
5
|
+
import { KeyMethods, KeyProps } from "./key"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export const WhiteKey = forwardRef<KeyMethods, KeyProps>(({
|
|
9
|
+
width = 40,
|
|
10
|
+
height = '100%',
|
|
11
|
+
bg = 'white',
|
|
12
|
+
activeBg = '#ccc',
|
|
13
|
+
style,
|
|
14
|
+
children,
|
|
15
|
+
__glissando,
|
|
16
|
+
__position,
|
|
17
|
+
__note,
|
|
18
|
+
__disabled,
|
|
19
|
+
__index,
|
|
20
|
+
__fill,
|
|
21
|
+
__width,
|
|
22
|
+
__playNote,
|
|
23
|
+
__stopNote,
|
|
24
|
+
__label,
|
|
25
|
+
}: KeyProps, ref) => {
|
|
26
|
+
const [played, setPlayed] = useState(false)
|
|
27
|
+
|
|
28
|
+
useImperativeHandle(ref, () => {
|
|
29
|
+
return {
|
|
30
|
+
play() {
|
|
31
|
+
if (__disabled) return
|
|
32
|
+
setPlayed(true)
|
|
33
|
+
if (__playNote) __playNote(__note!)
|
|
34
|
+
},
|
|
35
|
+
stop() {
|
|
36
|
+
setPlayed(false)
|
|
37
|
+
if (__stopNote) __stopNote(__note!)
|
|
38
|
+
},
|
|
39
|
+
played() {
|
|
40
|
+
return played
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}, [])
|
|
44
|
+
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
let keyLabelProps: any
|
|
47
|
+
if (children != undefined) {
|
|
48
|
+
React.Children.forEach(children, (child) => {
|
|
49
|
+
if (React.isValidElement(child)) {
|
|
50
|
+
if (child.type == KeyLabel) {
|
|
51
|
+
keyLabelProps = child.props
|
|
52
|
+
} else {
|
|
53
|
+
throw new Error('only <KeyLabel>')
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
throw new Error('children is an invalid element.')
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
className={`tremolo-piano-white-key`}
|
|
64
|
+
aria-disabled={__disabled}
|
|
65
|
+
css={css({
|
|
66
|
+
position: 'absolute',
|
|
67
|
+
backgroundColor: played ? activeBg : bg,
|
|
68
|
+
color: 'black',
|
|
69
|
+
left: __position,
|
|
70
|
+
width: __fill ? __width : (width ?? __width),
|
|
71
|
+
height: height,
|
|
72
|
+
border: '1px solid #555',
|
|
73
|
+
borderRadius: '0 0 8px 8px',
|
|
74
|
+
cursor: __disabled ? 'not-allowed' : 'pointer',
|
|
75
|
+
zIndex: 1,
|
|
76
|
+
...style,
|
|
77
|
+
})}
|
|
78
|
+
onPointerDown={() => {
|
|
79
|
+
if (__disabled) return
|
|
80
|
+
setPlayed(true)
|
|
81
|
+
if (__playNote) __playNote(__note!)
|
|
82
|
+
}}
|
|
83
|
+
onPointerEnter={() => {
|
|
84
|
+
if (__disabled) return
|
|
85
|
+
if (__glissando) {
|
|
86
|
+
setPlayed(true)
|
|
87
|
+
if (__playNote) __playNote(__note!)
|
|
88
|
+
}
|
|
89
|
+
}}
|
|
90
|
+
onPointerUp={() => {
|
|
91
|
+
if (__disabled) return
|
|
92
|
+
setPlayed(false)
|
|
93
|
+
if (__stopNote) __stopNote(__note!)
|
|
94
|
+
}}
|
|
95
|
+
onPointerLeave={() => {
|
|
96
|
+
if (__disabled) return
|
|
97
|
+
setPlayed(false)
|
|
98
|
+
if (__stopNote) __stopNote(__note!)
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<KeyLabel
|
|
102
|
+
__note={__note}
|
|
103
|
+
__label={__label}
|
|
104
|
+
__index={__index}
|
|
105
|
+
{...keyLabelProps}
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
})
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import React, { createRef, ReactElement, ReactNode, RefObject, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
import { css, CSSObject } from '@emotion/react'
|
|
3
|
+
import { isWhiteKey, NoteName, noteName, noteNames, parseNoteName } from '@tremolo-ui/functions'
|
|
4
|
+
|
|
5
|
+
import { useEventListener } from '../../hooks/useEventListener'
|
|
6
|
+
|
|
7
|
+
import { KeyMethods } from './key'
|
|
8
|
+
import { WhiteKey } from './WhiteKey'
|
|
9
|
+
import { BlackKey } from './BlackKey'
|
|
10
|
+
import { KeyBoardShortcuts } from './keyboardShortcuts'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Piano component
|
|
14
|
+
*
|
|
15
|
+
* TODO:
|
|
16
|
+
* - add scale highlight
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface PianoProps {
|
|
20
|
+
// required
|
|
21
|
+
noteRange: { first: number, last: number }
|
|
22
|
+
|
|
23
|
+
// optional
|
|
24
|
+
height?: number | string
|
|
25
|
+
|
|
26
|
+
glissando?: boolean
|
|
27
|
+
midiMax?: number
|
|
28
|
+
keyboardShortcuts?: KeyBoardShortcuts
|
|
29
|
+
fill?: boolean
|
|
30
|
+
|
|
31
|
+
style?: CSSObject
|
|
32
|
+
|
|
33
|
+
playNote?: (noteNumber: number) => void
|
|
34
|
+
stopNote?: (noteNumber: number) => void
|
|
35
|
+
|
|
36
|
+
label?: (note: number, index: number) => ReactNode
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* \<WhiteKey /> | \<BlackKey />
|
|
40
|
+
*/
|
|
41
|
+
children?: ReactElement | ReactElement[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function Piano({
|
|
45
|
+
noteRange,
|
|
46
|
+
glissando = true,
|
|
47
|
+
midiMax = 127,
|
|
48
|
+
keyboardShortcuts,
|
|
49
|
+
fill = false,
|
|
50
|
+
height = fill ? '100%' : 160,
|
|
51
|
+
style,
|
|
52
|
+
playNote,
|
|
53
|
+
stopNote,
|
|
54
|
+
label,
|
|
55
|
+
children,
|
|
56
|
+
}: PianoProps) {
|
|
57
|
+
// -- state and ref ---
|
|
58
|
+
const [pressed, setPressed] = useState(false)
|
|
59
|
+
const [whiteNoteWidth, setWhiteNoteWidth] = useState(-1)
|
|
60
|
+
const keyRefs = useRef<RefObject<KeyMethods>[]>([])
|
|
61
|
+
for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) {
|
|
62
|
+
keyRefs.current[i] = createRef<KeyMethods>()
|
|
63
|
+
}
|
|
64
|
+
const pianoRef = useRef<HTMLDivElement>(null)
|
|
65
|
+
|
|
66
|
+
// --- interpret props ---
|
|
67
|
+
const noteRangeArray = Array.from(
|
|
68
|
+
{ length: noteRange.last - noteRange.first + 1 },
|
|
69
|
+
(_, i) => i + noteRange.first
|
|
70
|
+
)
|
|
71
|
+
const whiteNoteCount = noteRangeArray.filter((v) => isWhiteKey(v)).length
|
|
72
|
+
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
+
let whiteKeyProps: any, blackKeyProps: any
|
|
75
|
+
if (children != undefined) {
|
|
76
|
+
React.Children.forEach(children, (child) => {
|
|
77
|
+
if (React.isValidElement(child)) {
|
|
78
|
+
if (child.type == WhiteKey) {
|
|
79
|
+
whiteKeyProps = child.props
|
|
80
|
+
} else if (child.type == BlackKey) {
|
|
81
|
+
blackKeyProps = child.props
|
|
82
|
+
} else {
|
|
83
|
+
throw new Error('only <WhiteKey> or <BlackKey>')
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
throw new Error('children is an invalid element.')
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// --- internal functions ---
|
|
92
|
+
function notePosition(note: number) {
|
|
93
|
+
const pitchPositions: Record<NoteName, number> = {
|
|
94
|
+
C: 0,
|
|
95
|
+
'C#': 0.55,
|
|
96
|
+
D: 1,
|
|
97
|
+
'D#': 1.8,
|
|
98
|
+
E: 2,
|
|
99
|
+
F: 3,
|
|
100
|
+
'F#': 3.5,
|
|
101
|
+
G: 4,
|
|
102
|
+
'G#': 4.7,
|
|
103
|
+
A: 5,
|
|
104
|
+
'A#': 5.85,
|
|
105
|
+
B: 6,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const toNoteName = (n: number) => {
|
|
109
|
+
const { letter, accidental } = parseNoteName(noteName(n))
|
|
110
|
+
return (letter + accidental) as NoteName
|
|
111
|
+
}
|
|
112
|
+
const _noteName = toNoteName(note)
|
|
113
|
+
const firstNoteName = toNoteName(noteRange.first)
|
|
114
|
+
const pos = pitchPositions[_noteName] - pitchPositions[firstNoteName]
|
|
115
|
+
const octave = Math.floor((note - noteRange.first) / 12)
|
|
116
|
+
const octaveOffset = noteNames.indexOf(firstNoteName) > noteNames.indexOf(_noteName) ? 1 : 0
|
|
117
|
+
const length = whiteNoteWidth + 3
|
|
118
|
+
return pos * length + (octave + octaveOffset) * 7 * length
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- hooks ---
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (fill && pianoRef.current) {
|
|
124
|
+
const parent = pianoRef.current.parentElement
|
|
125
|
+
if (!parent) throw new Error("doesn't have a parent element.")
|
|
126
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
127
|
+
const w = pianoRef.current!.clientWidth
|
|
128
|
+
setWhiteNoteWidth(w / whiteNoteCount - 3)
|
|
129
|
+
})
|
|
130
|
+
resizeObserver.observe(parent)
|
|
131
|
+
} else {
|
|
132
|
+
setWhiteNoteWidth(whiteKeyProps?.width || 40)
|
|
133
|
+
}
|
|
134
|
+
}, [])
|
|
135
|
+
|
|
136
|
+
useEventListener(window, 'keydown', (e) => {
|
|
137
|
+
if (e.repeat) return
|
|
138
|
+
if (!keyboardShortcuts) return
|
|
139
|
+
const index = keyboardShortcuts.keys.indexOf(e.key)
|
|
140
|
+
if (index != -1) {
|
|
141
|
+
if (!keyRefs.current[index]?.current?.played()) {
|
|
142
|
+
keyRefs.current[index]?.current?.play()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
useEventListener(window, 'keyup', (e) => {
|
|
148
|
+
if (e.repeat) return
|
|
149
|
+
if (!keyboardShortcuts) return
|
|
150
|
+
const index = keyboardShortcuts.keys.indexOf(e.key)
|
|
151
|
+
if (index != -1) {
|
|
152
|
+
keyRefs.current[index]?.current?.stop()
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<div
|
|
158
|
+
ref={pianoRef}
|
|
159
|
+
className="tremolo-piano"
|
|
160
|
+
css={css({
|
|
161
|
+
display: 'inline-block',
|
|
162
|
+
boxSizing: 'border-box',
|
|
163
|
+
userSelect: 'none',
|
|
164
|
+
width: fill ? '100%' : undefined,
|
|
165
|
+
height: height,
|
|
166
|
+
position: 'relative',
|
|
167
|
+
...style
|
|
168
|
+
})}
|
|
169
|
+
onPointerDown={() => {
|
|
170
|
+
setPressed(true)
|
|
171
|
+
}}
|
|
172
|
+
onPointerUp={() => {
|
|
173
|
+
setPressed(false)
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
{noteRangeArray.map((note, index) => {
|
|
177
|
+
return (
|
|
178
|
+
isWhiteKey(note) ? (
|
|
179
|
+
<WhiteKey
|
|
180
|
+
key={note}
|
|
181
|
+
ref={keyRefs.current[index]}
|
|
182
|
+
__glissando={glissando && pressed}
|
|
183
|
+
__position={notePosition(note)}
|
|
184
|
+
__note={note}
|
|
185
|
+
__disabled={note > midiMax}
|
|
186
|
+
__index={index}
|
|
187
|
+
__fill={fill}
|
|
188
|
+
__width={whiteNoteWidth}
|
|
189
|
+
__playNote={playNote}
|
|
190
|
+
__stopNote={stopNote}
|
|
191
|
+
__label={label}
|
|
192
|
+
{...whiteKeyProps}
|
|
193
|
+
/>
|
|
194
|
+
) : (
|
|
195
|
+
<BlackKey
|
|
196
|
+
key={note}
|
|
197
|
+
ref={keyRefs.current[index]}
|
|
198
|
+
__glissando={glissando && pressed}
|
|
199
|
+
__position={notePosition(note)}
|
|
200
|
+
__note={note}
|
|
201
|
+
__disabled={note > midiMax}
|
|
202
|
+
__index={index}
|
|
203
|
+
__fill={fill}
|
|
204
|
+
__width={whiteNoteWidth * 0.65}
|
|
205
|
+
__playNote={playNote}
|
|
206
|
+
__stopNote={stopNote}
|
|
207
|
+
__label={label}
|
|
208
|
+
{...blackKeyProps}
|
|
209
|
+
/>
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
})}
|
|
213
|
+
</div>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export * from './key'
|
|
218
|
+
export { WhiteKey } from './WhiteKey'
|
|
219
|
+
export { BlackKey } from './BlackKey'
|
|
220
|
+
export { KeyLabel } from './KeyLabel'
|
|
221
|
+
|
|
222
|
+
export * from './keyboardShortcuts'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { CSSObject } from "@emotion/react"
|
|
2
|
+
import { ReactElement, ReactNode } from "react"
|
|
3
|
+
|
|
4
|
+
export interface KeyProps {
|
|
5
|
+
width?: number
|
|
6
|
+
height?: number | string
|
|
7
|
+
|
|
8
|
+
bg?: string
|
|
9
|
+
activeBg?: string
|
|
10
|
+
style?: CSSObject
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* \<KeyLabel />
|
|
14
|
+
*/
|
|
15
|
+
children?: ReactElement
|
|
16
|
+
|
|
17
|
+
/** @internal */
|
|
18
|
+
__glissando?: boolean
|
|
19
|
+
/** @internal */
|
|
20
|
+
__position?: number
|
|
21
|
+
/** @internal */
|
|
22
|
+
__note?: number
|
|
23
|
+
/** @internal */
|
|
24
|
+
__disabled?: boolean
|
|
25
|
+
/** @internal */
|
|
26
|
+
__index?: number
|
|
27
|
+
/** @internal */
|
|
28
|
+
__fill?: boolean
|
|
29
|
+
/** @internal */
|
|
30
|
+
__width?: number
|
|
31
|
+
/** @internal */
|
|
32
|
+
__playNote?: (noteNumber: number) => void
|
|
33
|
+
/** @internal */
|
|
34
|
+
__stopNote?: (noteNumber: number) => void
|
|
35
|
+
/** @internal */
|
|
36
|
+
__label?: (note: number) => ReactNode
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface KeyMethods {
|
|
40
|
+
play: () => void
|
|
41
|
+
stop: () => void
|
|
42
|
+
played: () => boolean
|
|
43
|
+
}
|
|
@@ -1,28 +1,67 @@
|
|
|
1
1
|
import { CSSObject, css } from '@emotion/react'
|
|
2
|
-
import { ReactNode } from 'react'
|
|
2
|
+
import { forwardRef, ReactNode, useImperativeHandle, useRef } from 'react'
|
|
3
|
+
import clsx from 'clsx'
|
|
3
4
|
|
|
4
5
|
interface SliderThumbProps {
|
|
6
|
+
size?: number | string
|
|
7
|
+
width?: number | string
|
|
8
|
+
height?: number | string
|
|
5
9
|
color?: string
|
|
10
|
+
hoverColor?: string
|
|
11
|
+
disabledColor?: string
|
|
12
|
+
disabledHoverColor?: string
|
|
13
|
+
zIndex?: number
|
|
14
|
+
|
|
15
|
+
className?: string
|
|
6
16
|
style?: CSSObject
|
|
7
|
-
__css?: CSSObject
|
|
8
17
|
children?: ReactNode
|
|
18
|
+
|
|
19
|
+
/** @internal */
|
|
20
|
+
__disabled?: boolean
|
|
21
|
+
/** @internal */
|
|
22
|
+
__css?: CSSObject
|
|
9
23
|
}
|
|
10
24
|
|
|
11
|
-
export
|
|
12
|
-
|
|
25
|
+
export interface SliderThumbMethods {
|
|
26
|
+
focus: () => void
|
|
27
|
+
blur: () => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const SliderThumb = forwardRef<SliderThumbMethods, SliderThumbProps>(({
|
|
31
|
+
size,
|
|
32
|
+
width = 22,
|
|
33
|
+
height = 22,
|
|
34
|
+
color = '#4e76e6',
|
|
35
|
+
hoverColor = color,
|
|
36
|
+
disabledColor = '#5d6478',
|
|
37
|
+
disabledHoverColor = disabledColor,
|
|
38
|
+
zIndex = 100,
|
|
13
39
|
children,
|
|
40
|
+
className,
|
|
14
41
|
style,
|
|
42
|
+
__disabled,
|
|
15
43
|
__css,
|
|
16
|
-
}: SliderThumbProps) {
|
|
44
|
+
}: SliderThumbProps, ref) => {
|
|
45
|
+
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
46
|
+
|
|
47
|
+
useImperativeHandle(ref, () => {
|
|
48
|
+
return {
|
|
49
|
+
focus() {
|
|
50
|
+
wrapperRef.current?.focus()
|
|
51
|
+
},
|
|
52
|
+
blur() {
|
|
53
|
+
wrapperRef.current?.blur()
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}, [])
|
|
57
|
+
|
|
17
58
|
return (
|
|
18
59
|
<div
|
|
19
60
|
className="tremolo-slider-thumb-wrapper"
|
|
20
61
|
css={css({
|
|
21
|
-
width: 'fit-content',
|
|
22
|
-
height: 'fit-content',
|
|
23
62
|
position: 'absolute',
|
|
24
63
|
translate: '-50% -50%',
|
|
25
|
-
zIndex:
|
|
64
|
+
zIndex: zIndex,
|
|
26
65
|
...__css,
|
|
27
66
|
})}
|
|
28
67
|
>
|
|
@@ -31,18 +70,20 @@ export function SliderThumb({
|
|
|
31
70
|
) : (
|
|
32
71
|
// default slider thumb
|
|
33
72
|
<div
|
|
34
|
-
|
|
73
|
+
ref={wrapperRef}
|
|
74
|
+
className={clsx('tremolo-slider-thumb', className)}
|
|
35
75
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
36
76
|
tabIndex={0}
|
|
37
77
|
css={css({
|
|
38
|
-
background: color,
|
|
39
|
-
width:
|
|
40
|
-
height:
|
|
78
|
+
background: __disabled ? disabledColor : color,
|
|
79
|
+
width: size ?? width,
|
|
80
|
+
height: size ?? height,
|
|
41
81
|
borderRadius: '50%',
|
|
82
|
+
outline: 'none',
|
|
42
83
|
'&:hover': {
|
|
43
|
-
background:
|
|
84
|
+
background: __disabled ? disabledHoverColor : hoverColor
|
|
44
85
|
},
|
|
45
|
-
'&:
|
|
86
|
+
'&:focus': !__disabled && {
|
|
46
87
|
boxShadow: '0px 0px 0px 3px rgba(var(--tremolo-theme-color-rgb), 0.2)',
|
|
47
88
|
},
|
|
48
89
|
...style,
|
|
@@ -51,4 +92,4 @@ export function SliderThumb({
|
|
|
51
92
|
)}
|
|
52
93
|
</div>
|
|
53
94
|
)
|
|
54
|
-
}
|
|
95
|
+
})
|
|
@@ -1,20 +1,79 @@
|
|
|
1
1
|
import { css, CSSObject } from '@emotion/react'
|
|
2
2
|
import { ReactElement } from 'react'
|
|
3
|
+
import clsx from 'clsx'
|
|
4
|
+
import { styleHelper } from '@tremolo-ui/functions'
|
|
5
|
+
import { isHorizontal } from '@tremolo-ui/functions/Slider'
|
|
6
|
+
|
|
7
|
+
import { Direction, gradientDirection } from './type'
|
|
3
8
|
|
|
4
9
|
interface SliderTrackProps {
|
|
10
|
+
length?: number | string
|
|
11
|
+
thickness?: number | string
|
|
12
|
+
|
|
13
|
+
activeBg?: string
|
|
14
|
+
inactiveBg?: string
|
|
15
|
+
activeHoverBg?: string
|
|
16
|
+
inactiveHoverBg?: string
|
|
17
|
+
|
|
18
|
+
disabledActiveBg?: string
|
|
19
|
+
disabledInactiveBg?: string
|
|
20
|
+
disabledActiveHoverBg?: string
|
|
21
|
+
disabledInactiveHoverBg?: string
|
|
22
|
+
|
|
23
|
+
className?: string
|
|
5
24
|
style?: CSSObject
|
|
6
25
|
children?: ReactElement
|
|
7
|
-
|
|
26
|
+
|
|
27
|
+
/** inherit */
|
|
28
|
+
__thumb?: ReactElement
|
|
29
|
+
/** inherit */
|
|
30
|
+
__direction?: Direction
|
|
31
|
+
/** inherit */
|
|
32
|
+
__disabled?: boolean
|
|
33
|
+
/** internal */
|
|
34
|
+
__percent?: number
|
|
8
35
|
}
|
|
9
36
|
|
|
10
37
|
export function SliderTrack({
|
|
38
|
+
length = 140,
|
|
39
|
+
thickness = 10,
|
|
40
|
+
activeBg = '#7998ec',
|
|
41
|
+
inactiveBg = '#eee',
|
|
42
|
+
activeHoverBg = '#6387e9',
|
|
43
|
+
inactiveHoverBg = '#e0e0e0',
|
|
44
|
+
disabledActiveBg = '#858890',
|
|
45
|
+
disabledInactiveBg = '#ddd',
|
|
46
|
+
disabledActiveHoverBg = disabledActiveBg,
|
|
47
|
+
disabledInactiveHoverBg = disabledInactiveBg,
|
|
11
48
|
children,
|
|
49
|
+
className,
|
|
12
50
|
style,
|
|
13
|
-
|
|
51
|
+
__thumb,
|
|
52
|
+
__direction,
|
|
53
|
+
__disabled,
|
|
54
|
+
__percent,
|
|
14
55
|
}: SliderTrackProps) {
|
|
56
|
+
const active = __disabled ? disabledActiveBg : activeBg
|
|
57
|
+
const inactive = __disabled ? disabledInactiveBg : inactiveBg
|
|
58
|
+
const activeHover = __disabled ? disabledActiveHoverBg : activeHoverBg
|
|
59
|
+
const inactiveHover = __disabled ? disabledInactiveHoverBg : inactiveHoverBg
|
|
15
60
|
return (
|
|
16
|
-
<div
|
|
61
|
+
<div
|
|
62
|
+
className={clsx('tremolo-slider-track', className)}
|
|
63
|
+
css={css({
|
|
64
|
+
position: 'relative',
|
|
65
|
+
background: `linear-gradient(to ${gradientDirection(__direction!)}, ${active} ${__percent}%, ${inactive} ${__percent}%)`,
|
|
66
|
+
borderRadius: styleHelper(thickness!, '/', 2),
|
|
67
|
+
width: isHorizontal(__direction!) ? length : thickness,
|
|
68
|
+
height: isHorizontal(__direction!) ? thickness : length,
|
|
69
|
+
':hover': {
|
|
70
|
+
background: `linear-gradient(to ${gradientDirection(__direction!)}, ${activeHover} ${__percent}%, ${inactiveHover} ${__percent}%)`
|
|
71
|
+
},
|
|
72
|
+
...style
|
|
73
|
+
})}
|
|
74
|
+
>
|
|
17
75
|
{children}
|
|
76
|
+
{__thumb}
|
|
18
77
|
</div>
|
|
19
78
|
)
|
|
20
79
|
}
|