Make component single channel only, in order to have a more concise API.master
@@ -43,6 +43,8 @@ window.document.body.appendChild(container) | |||
ReactDOM.render(<App />, container) | |||
``` | |||
### Interactivity | |||
The library also supports keyboard maps for handling mouse, touch, and keyboard events: | |||
```jsx harmony | |||
@@ -60,12 +62,8 @@ const App = () => { | |||
<Keyboard | |||
startKey={21} | |||
endKey={108} | |||
> | |||
<KeyboardMap | |||
channel={0} | |||
onChange={handleKeysChange} | |||
/> | |||
</Keyboard> | |||
onChange={handleKeysChange} | |||
/> | |||
</div> | |||
) | |||
} | |||
@@ -77,6 +75,10 @@ window.document.body.appendChild(container) | |||
ReactDOM.render(<App />, container) | |||
``` | |||
It is capable of server-side rendering support, falling back to making the keys behave like links, checkboxes or radio buttons. Simply supply the `behavior` prop. | |||
### Customization | |||
The component is stylable, just supply custom components for the keys: | |||
```jsx harmony | |||
@@ -108,7 +110,9 @@ window.document.body.appendChild(container) | |||
ReactDOM.render(<App />, container) | |||
``` | |||
Custom keys should accept a `keyChannels` prop for active keys. For instance, in the custom key components imported above: | |||
Components get their styles from CSS. The custom property `--opacity-highlight` is responsible for toggling the active, or "pressed" state of the key, simply assign it to the `opacity` style of the component you want to show for active keys. | |||
The library also exposes other custom properties: `--color-natural-key`, `--color-accidental-key`, and `--color-active-key` for basic coloring of the keys. You may expose your own properties for your custom key components. | |||
```jsx harmony | |||
// ./my-styled-keys/NaturalKey.js | |||
@@ -121,11 +125,9 @@ const NaturalKey = ({ | |||
keyChannels = [] | |||
}) => { | |||
return ( | |||
<div> | |||
<img src={NOT_PRESSED_KEY} alt="" /> | |||
{keyChannels.map(k => ( | |||
<img key={k.channel} src={PRESSED_KEY_OVERLAY} alt="" /> | |||
))} | |||
<div style={{ position: 'relative' }}> | |||
<img src={NOT_PRESSED_KEY} alt="" style={{ position: 'absolute', top: 0, left: 0, }} /> | |||
<img src={PRESSED_KEY_OVERLAY} style={{ position: 'absolute', top: 0, left: 0, opacity: 'var(--opacity-highlight)', }} alt="" /> | |||
</div> | |||
) | |||
} | |||
@@ -24,9 +24,10 @@ type KeyChannelCallback = (oldKeys: KeyChannel[]) => KeyChannel[] | |||
type HandleProps = { | |||
setKeyChannels(callback: KeyChannelCallback | KeyChannel[]): void, | |||
generator?: SoundGenerator, | |||
channel: number, | |||
} | |||
type Handle = (props: HandleProps) => (newKeys: KeyChannel[]) => void | |||
export const handle: Handle = ({ setKeyChannels, generator, }) => newKeys => { | |||
export const handle: Handle = ({ setKeyChannels, generator, channel, }) => newKeys => { | |||
setKeyChannels((oldKeys) => { | |||
if (generator! !== undefined) { | |||
const oldKeysKeys = oldKeys.map((k) => k.key) | |||
@@ -35,11 +36,11 @@ export const handle: Handle = ({ setKeyChannels, generator, }) => newKeys => { | |||
const keysOn = newKeys.filter((nk) => !oldKeysKeys.includes(nk.key)) | |||
keysOn.forEach((k) => { | |||
generator.noteOn(k.channel, k.key, Math.floor(k.velocity * 127)) | |||
generator.noteOn(channel, k.key, Math.floor(k.velocity * 127)) | |||
}) | |||
keysOff.forEach((k) => { | |||
generator.noteOff(k.channel, k.key, Math.floor(k.velocity * 127)) | |||
generator.noteOff(channel, k.key, Math.floor(k.velocity * 127)) | |||
}) | |||
} | |||
@@ -1,7 +1,7 @@ | |||
import * as React from 'react' | |||
import ReactDOM from 'react-dom' | |||
import Keyboard, { KeyboardMap } from '../src' | |||
import Keyboard from '../src' | |||
import * as Channel from './controllers/Channel' | |||
import * as Instrument from './controllers/Instrument' | |||
import * as Generator from './controllers/Generator' | |||
@@ -75,13 +75,9 @@ const App = () => { | |||
endKey={127} | |||
keyChannels={keyChannels} | |||
height="100%" | |||
> | |||
<KeyboardMap | |||
channel={channel} | |||
onChange={Channel.handle({ setKeyChannels, generator: generator.current!, })} | |||
keyboardMapping={keyboardMapping} | |||
/> | |||
</Keyboard> | |||
onChange={Channel.handle({ setKeyChannels, generator: generator.current!, channel, })} | |||
keyboardMapping={keyboardMapping} | |||
/> | |||
</div> | |||
</div> | |||
</React.Fragment> | |||
@@ -1,5 +1,5 @@ | |||
{ | |||
"version": "1.0.13", | |||
"version": "1.1.0", | |||
"license": "MIT", | |||
"main": "dist/index.js", | |||
"typings": "dist/index.d.ts", | |||
@@ -1,38 +1,28 @@ | |||
import * as React from 'react' | |||
import * as PropTypes from 'prop-types' | |||
import keyPropTypes from '../../services/keyPropTypes' | |||
type Props = PropTypes.InferProps<typeof keyPropTypes> | |||
const AccidentalKey: React.FC<Props> = ({ keyChannels }) => ( | |||
const AccidentalKey: React.FC = () => ( | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'var(--color-accidental-key, currentColor)', | |||
backgroundColor: 'var(--color-accidental-key, black)', | |||
border: '1px solid', | |||
boxSizing: 'border-box', | |||
position: 'relative', | |||
}} | |||
> | |||
{Array.isArray(keyChannels!) && | |||
keyChannels.map((c) => ( | |||
<div | |||
key={c!.channel} | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
opacity: 0.75, | |||
backgroundColor: `var(--color-channel-${c!.channel}, Highlight)`, | |||
}} | |||
/> | |||
))} | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
opacity: 'var(--opacity-highlight)', | |||
backgroundColor: `var(--color-active-key, Highlight)`, | |||
}} | |||
/> | |||
</div> | |||
) | |||
AccidentalKey.propTypes = keyPropTypes | |||
export default AccidentalKey |
@@ -3,7 +3,6 @@ import * as PropTypes from 'prop-types' | |||
import StyledAccidentalKey from '../StyledAccidentalKey/StyledAccidentalKey' | |||
import StyledNaturalKey from '../StyledNaturalKey/StyledNaturalKey' | |||
import Keyboard, { propTypes } from './Keyboard' | |||
import KeyboardMap from '../KeyboardMap/KeyboardMap' | |||
const Wrapper: React.FC = (props) => ( | |||
<div | |||
@@ -52,17 +51,14 @@ export const WithActiveKeys = (props?: Partial<Props>) => ( | |||
endKey={108} | |||
keyChannels={[ | |||
{ | |||
channel: 0, | |||
key: 60, | |||
velocity: 1, | |||
}, | |||
{ | |||
channel: 0, | |||
key: 64, | |||
velocity: 1, | |||
}, | |||
{ | |||
channel: 0, | |||
key: 67, | |||
velocity: 1, | |||
}, | |||
@@ -78,34 +74,39 @@ export const WithDifferentKeyRange = (props?: Partial<Props>) => ( | |||
) | |||
export const Styled = (props?: Partial<Props>) => ( | |||
<Wrapper> | |||
<Keyboard | |||
{...props} | |||
startKey={21} | |||
endKey={108} | |||
keyChannels={[ | |||
{ | |||
channel: 0, | |||
key: 60, | |||
velocity: 1, | |||
}, | |||
{ | |||
channel: 0, | |||
key: 63, | |||
velocity: 1, | |||
}, | |||
{ | |||
channel: 0, | |||
key: 67, | |||
velocity: 1, | |||
}, | |||
]} | |||
keyComponents={{ | |||
natural: StyledNaturalKey, | |||
accidental: StyledAccidentalKey, | |||
}} | |||
/> | |||
</Wrapper> | |||
<div | |||
style={{ | |||
// @ts-ignore | |||
'--color-accidental-key': '#35313b', | |||
'--color-natural-key': '#e3e3e5', | |||
}} | |||
> | |||
<Wrapper> | |||
<Keyboard | |||
{...props} | |||
startKey={21} | |||
endKey={108} | |||
keyChannels={[ | |||
{ | |||
key: 60, | |||
velocity: 1, | |||
}, | |||
{ | |||
key: 63, | |||
velocity: 1, | |||
}, | |||
{ | |||
key: 67, | |||
velocity: 1, | |||
}, | |||
]} | |||
keyComponents={{ | |||
natural: StyledNaturalKey, | |||
accidental: StyledAccidentalKey, | |||
}} | |||
/> | |||
</Wrapper> | |||
</div> | |||
) | |||
export const AnotherStyled = (props?: Partial<Props>) => ( | |||
@@ -113,6 +114,46 @@ export const AnotherStyled = (props?: Partial<Props>) => ( | |||
style={{ | |||
// @ts-ignore | |||
'--size-scale-factor': 2, | |||
'--color-accidental-key': '#35313b', | |||
'--color-natural-key': '#e3e3e5', | |||
}} | |||
> | |||
<Wrapper> | |||
<Keyboard | |||
{...props} | |||
startKey={21} | |||
endKey={108} | |||
keyChannels={[ | |||
{ | |||
key: 60, | |||
velocity: 1, | |||
}, | |||
{ | |||
key: 63, | |||
velocity: 1, | |||
}, | |||
{ | |||
key: 67, | |||
velocity: 1, | |||
}, | |||
]} | |||
keyComponents={{ | |||
natural: StyledNaturalKey, | |||
accidental: StyledAccidentalKey, | |||
}} | |||
/> | |||
</Wrapper> | |||
</div> | |||
) | |||
export const DarkStyled = (props?: Partial<Props>) => ( | |||
<div | |||
style={{ | |||
// @ts-ignore | |||
'--size-scale-factor': 2, | |||
'--color-accidental-key': '#666666', | |||
'--color-natural-key': '#35313b', | |||
'--color-active-key': 'red', | |||
}} | |||
> | |||
<Wrapper> | |||
@@ -122,17 +163,14 @@ export const AnotherStyled = (props?: Partial<Props>) => ( | |||
endKey={108} | |||
keyChannels={[ | |||
{ | |||
channel: 0, | |||
key: 60, | |||
velocity: 1, | |||
}, | |||
{ | |||
channel: 0, | |||
key: 63, | |||
velocity: 1, | |||
}, | |||
{ | |||
channel: 0, | |||
key: 67, | |||
velocity: 1, | |||
}, | |||
@@ -156,13 +194,14 @@ const HasMapComponent = () => { | |||
const newKeysKeys = newKeys.map((k) => k.key) | |||
const keysOff = oldKeys.filter((ok) => !newKeysKeys.includes(ok.key)) | |||
const keysOn = newKeys.filter((nk) => !oldKeysKeys.includes(nk.key)) | |||
const channel = 0 | |||
keysOn.forEach((k) => { | |||
midiAccess.current?.send([0b10010000 + k.channel, k.key, Math.floor(k.velocity * 127)]) | |||
midiAccess.current?.send([0b10010000 + channel, k.key, Math.floor(k.velocity * 127)]) | |||
}) | |||
keysOff.forEach((k) => { | |||
midiAccess.current?.send([0b10000000 + k.channel, k.key, Math.floor(k.velocity * 127)]) | |||
midiAccess.current?.send([0b10000000 + channel, k.key, Math.floor(k.velocity * 127)]) | |||
}) | |||
return newKeys | |||
@@ -183,54 +222,73 @@ const HasMapComponent = () => { | |||
return ( | |||
<Wrapper> | |||
<Keyboard hasMap startKey={21} endKey={108} keyChannels={keyChannels}> | |||
<KeyboardMap | |||
channel={0} | |||
onChange={handleKeyOn} | |||
keyboardMapping={{ | |||
KeyQ: 60, | |||
Digit2: 61, | |||
KeyW: 62, | |||
Digit3: 63, | |||
KeyE: 64, | |||
KeyR: 65, | |||
Digit5: 66, | |||
KeyT: 67, | |||
Digit6: 68, | |||
KeyY: 69, | |||
Digit7: 70, | |||
KeyU: 71, | |||
KeyI: 72, | |||
Digit9: 73, | |||
KeyO: 74, | |||
Digit0: 75, | |||
KeyP: 76, | |||
BracketLeft: 77, | |||
Equal: 78, | |||
BracketRight: 79, | |||
KeyZ: 48, | |||
KeyS: 49, | |||
KeyX: 50, | |||
KeyD: 51, | |||
KeyC: 52, | |||
KeyV: 53, | |||
KeyG: 54, | |||
KeyB: 55, | |||
KeyH: 56, | |||
KeyN: 57, | |||
KeyJ: 58, | |||
KeyM: 59, | |||
Comma: 60, | |||
KeyL: 61, | |||
Period: 62, | |||
Semicolon: 63, | |||
Slash: 64, | |||
}} | |||
/> | |||
</Keyboard> | |||
<Keyboard | |||
hasMap | |||
startKey={21} | |||
endKey={108} | |||
keyChannels={keyChannels} | |||
onChange={handleKeyOn} | |||
keyboardMapping={{ | |||
KeyQ: 60, | |||
Digit2: 61, | |||
KeyW: 62, | |||
Digit3: 63, | |||
KeyE: 64, | |||
KeyR: 65, | |||
Digit5: 66, | |||
KeyT: 67, | |||
Digit6: 68, | |||
KeyY: 69, | |||
Digit7: 70, | |||
KeyU: 71, | |||
KeyI: 72, | |||
Digit9: 73, | |||
KeyO: 74, | |||
Digit0: 75, | |||
KeyP: 76, | |||
BracketLeft: 77, | |||
Equal: 78, | |||
BracketRight: 79, | |||
KeyZ: 48, | |||
KeyS: 49, | |||
KeyX: 50, | |||
KeyD: 51, | |||
KeyC: 52, | |||
KeyV: 53, | |||
KeyG: 54, | |||
KeyB: 55, | |||
KeyH: 56, | |||
KeyN: 57, | |||
KeyJ: 58, | |||
KeyM: 59, | |||
Comma: 60, | |||
KeyL: 61, | |||
Period: 62, | |||
Semicolon: 63, | |||
Slash: 64, | |||
}} | |||
/> | |||
</Wrapper> | |||
) | |||
} | |||
export const HasMap = () => <HasMapComponent /> | |||
export const Checkbox = (props?: Partial<Props>) => ( | |||
<Wrapper> | |||
<Keyboard {...props} startKey={21} endKey={108} behavior="checkbox" name="checkbox" /> | |||
</Wrapper> | |||
) | |||
export const Radio = (props?: Partial<Props>) => ( | |||
<Wrapper> | |||
<Keyboard {...props} startKey={21} endKey={108} behavior="radio" name="radio" /> | |||
</Wrapper> | |||
) | |||
export const Link = (props?: Partial<Props>) => ( | |||
<Wrapper> | |||
<Keyboard {...props} startKey={21} endKey={108} behavior="link" href={(key) => `?key=${key}`} /> | |||
</Wrapper> | |||
) |
@@ -6,6 +6,10 @@ import getKeyLeftUnmemoized from '../../services/getKeyLeft' | |||
import generateKeys from '../../services/generateKeys' | |||
import DefaultAccidentalKey from '../AccidentalKey/AccidentalKey' | |||
import DefaultNaturalKey from '../NaturalKey/NaturalKey' | |||
import KeyboardMap from '../KeyboardMap/KeyboardMap' | |||
import getKeyBounds from '../../services/getKeyBounds' | |||
const BEHAVIOR = ['link', 'checkbox', 'radio'] as const | |||
export const propTypes = { | |||
/** | |||
@@ -35,7 +39,6 @@ export const propTypes = { | |||
*/ | |||
keyChannels: PropTypes.arrayOf( | |||
PropTypes.shape({ | |||
channel: PropTypes.number.isRequired, | |||
key: PropTypes.number.isRequired, | |||
velocity: PropTypes.number.isRequired, | |||
}), | |||
@@ -58,6 +61,26 @@ export const propTypes = { | |||
* Height of the component. | |||
*/ | |||
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||
/** | |||
* Event handler triggered upon change in activated keys in the component. | |||
*/ | |||
onChange: PropTypes.func, | |||
/** | |||
* Map from key code to key number. | |||
*/ | |||
keyboardMapping: PropTypes.object, | |||
/** | |||
* Behavior of the component when clicking. | |||
*/ | |||
behavior: PropTypes.oneOf(BEHAVIOR), | |||
/** | |||
* Name of the component used for forms. | |||
*/ | |||
name: PropTypes.string, | |||
/** | |||
* Destination of the component upon clicking a key, if behavior is set to 'link'. | |||
*/ | |||
href: PropTypes.func, | |||
} | |||
type Props = PropTypes.InferProps<typeof propTypes> | |||
@@ -72,6 +95,9 @@ type Props = PropTypes.InferProps<typeof propTypes> | |||
* @param width - Width of the component. | |||
* @param keyComponents - Components to use for each kind of key. | |||
* @param height - Height of the component. | |||
* @param name - Name of the component used for forms. | |||
* @param href - Destination of the component upon clicking a key, if behavior is set to 'link'. | |||
* @param behavior - Behavior of the component when clicking. | |||
*/ | |||
const Keyboard: React.FC<Props> = ({ | |||
startKey, | |||
@@ -82,7 +108,11 @@ const Keyboard: React.FC<Props> = ({ | |||
width = '100%', | |||
keyComponents = {}, | |||
height = 80, | |||
children, | |||
onChange, | |||
keyboardMapping, | |||
behavior, | |||
name, | |||
href, | |||
}) => { | |||
const [clientSide, setClientSide] = React.useState(false) | |||
const [clientSideKeys, setClientSideKeys] = React.useState<number[]>([]) | |||
@@ -105,89 +135,100 @@ const Keyboard: React.FC<Props> = ({ | |||
const keys = clientSide ? clientSideKeys : generateKeys(startKey, endKey) | |||
return ( | |||
<div | |||
style={{ | |||
width: width!, | |||
height: height!, | |||
position: 'relative', | |||
backgroundColor: 'currentColor', | |||
overflow: 'hidden', | |||
}} | |||
role="presentation" | |||
ref={baseRef} | |||
> | |||
{keys.map((key) => { | |||
const isNatural = isNaturalKey(key) | |||
const Component: any = isNatural ? NaturalKey! : AccidentalKey! | |||
const currentKeyChannels = Array.isArray(keyChannels!) ? keyChannels.filter((kc) => kc!.key === key) : null | |||
const width = getKeyWidth(key) | |||
const left = getKeyLeft(key) | |||
let leftBounds: number | |||
let rightBounds: number | |||
switch (key % 12) { | |||
case 0: | |||
case 5: | |||
leftBounds = left | |||
rightBounds = key + 1 > endKey! ? left + width : getKeyLeft(key + 1) | |||
break | |||
case 4: | |||
case 11: | |||
leftBounds = key - 1 < startKey! ? left : getKeyLeft(key - 1) + getKeyWidth(key - 1) | |||
rightBounds = left + width | |||
break | |||
case 2: | |||
case 7: | |||
case 9: | |||
leftBounds = key - 1 < startKey! ? left : getKeyLeft(key - 1) + getKeyWidth(key - 1) | |||
rightBounds = key + 1 > endKey! ? left + width : getKeyLeft(key + 1) | |||
break | |||
default: | |||
leftBounds = left | |||
rightBounds = left + width | |||
break | |||
<React.Fragment> | |||
<style>{` | |||
.ReactMusicalKeyboard-checkbox:checked + * { | |||
--opacity-highlight: 1, | |||
} | |||
const octaveStart = Math.floor(key / 12) * 12 | |||
const octaveEnd = octaveStart + 11 | |||
const octaveLeftBounds = getKeyLeft(octaveStart) | |||
const octaveRightBounds = getKeyLeft(octaveEnd) + getKeyWidth(octaveEnd) | |||
return ( | |||
<div | |||
key={key} | |||
data-key={key} | |||
data-octave-left-bounds={octaveLeftBounds} | |||
data-octave-right-bounds={octaveRightBounds} | |||
data-left-bounds={leftBounds} | |||
data-right-bounds={rightBounds} | |||
data-left-full-bounds={isNatural ? left : undefined} | |||
data-right-full-bounds={isNatural ? left + width : undefined} | |||
style={{ | |||
zIndex: isNatural ? 0 : 2, | |||
width: width + '%', | |||
height: (isNatural ? 100 : 100 * accidentalKeyLengthRatio!) + '%', | |||
left: left + '%', | |||
position: 'absolute', | |||
top: 0, | |||
}} | |||
> | |||
<Component keyChannels={currentKeyChannels} /> | |||
</div> | |||
) | |||
})} | |||
{children! && | |||
React.Children.map(children, (unknownChild) => { | |||
const child = unknownChild as React.ReactElement | |||
const { props = {} } = child | |||
return React.cloneElement(child, { | |||
...props, | |||
accidentalKeyLengthRatio, | |||
}) | |||
`}</style> | |||
<div | |||
style={{ | |||
width: width!, | |||
height: height!, | |||
position: 'relative', | |||
backgroundColor: 'currentColor', | |||
overflow: 'hidden', | |||
}} | |||
role="presentation" | |||
ref={baseRef} | |||
> | |||
{keys.map((key) => { | |||
const isNatural = isNaturalKey(key) | |||
const Component: any = isNatural ? NaturalKey! : AccidentalKey! | |||
const [currentKey = null] = Array.isArray(keyChannels!) ? keyChannels.filter((kc) => kc!.key === key) : [] | |||
const width = getKeyWidth(key) | |||
const left = getKeyLeft(key) | |||
const { left: leftBounds, right: rightBounds } = getKeyBounds( | |||
startKey, | |||
endKey, | |||
getKeyLeft, | |||
getKeyWidth, | |||
)(key, left, width) | |||
const octaveStart = Math.floor(key / 12) * 12 | |||
const octaveEnd = octaveStart + 11 | |||
const octaveLeftBounds = getKeyLeft(octaveStart) | |||
const octaveRightBounds = getKeyLeft(octaveEnd) + getKeyWidth(octaveEnd) | |||
const components: Record<string, string> = { | |||
link: 'a', | |||
checkbox: 'label', | |||
radio: 'label', | |||
} | |||
const { [behavior!]: component = 'div' } = components | |||
const KeyComponent = component as React.ElementType | |||
return ( | |||
<KeyComponent | |||
key={key} | |||
href={behavior === 'link' ? href!(key) : undefined} | |||
data-key={key} | |||
data-octave-left-bounds={octaveLeftBounds} | |||
data-octave-right-bounds={octaveRightBounds} | |||
data-left-bounds={leftBounds} | |||
data-right-bounds={rightBounds} | |||
data-left-full-bounds={isNatural ? left : undefined} | |||
data-right-full-bounds={isNatural ? left + width : undefined} | |||
style={{ | |||
zIndex: isNatural ? 0 : 2, | |||
width: width + '%', | |||
height: (isNatural ? 100 : 100 * accidentalKeyLengthRatio!) + '%', | |||
left: left + '%', | |||
position: 'absolute', | |||
top: 0, | |||
cursor: onChange || behavior ? 'pointer' : undefined, | |||
color: 'inherit', | |||
'--opacity-highlight': currentKey !== null ? 1 : 0, | |||
}} | |||
> | |||
{(behavior! === 'checkbox' || behavior === 'radio') && ( | |||
<input | |||
type={behavior} | |||
className="ReactMusicalKeyboard-checkbox" | |||
name={name!} | |||
value={key} | |||
defaultChecked={currentKey !== null} | |||
style={{ | |||
position: 'absolute', | |||
left: -999999, | |||
width: 1, | |||
height: 1, | |||
}} | |||
/> | |||
)} | |||
<Component /> | |||
</KeyComponent> | |||
) | |||
})} | |||
</div> | |||
{clientSide && ( | |||
<KeyboardMap | |||
accidentalKeyLengthRatio={accidentalKeyLengthRatio} | |||
onChange={onChange} | |||
keyboardMapping={keyboardMapping} | |||
/> | |||
)} | |||
</div> | |||
</React.Fragment> | |||
) | |||
} | |||
@@ -3,6 +3,10 @@ import * as PropTypes from 'prop-types' | |||
import reverseGetKeyFromPoint from '../../services/reverseGetKeyFromPoint' | |||
const propTypes = { | |||
/** | |||
* Ratio of the length of the accidental keys to the natural keys. | |||
*/ | |||
accidentalKeyLengthRatio: PropTypes.number, | |||
/** | |||
* Event handler triggered upon change in activated keys in the component. | |||
*/ | |||
@@ -11,22 +15,17 @@ const propTypes = { | |||
* Map from key code to key number. | |||
*/ | |||
keyboardMapping: PropTypes.object, | |||
/** | |||
* Active MIDI channel for registering keys. | |||
*/ | |||
channel: PropTypes.number.isRequired, | |||
} | |||
type Props = PropTypes.InferProps<typeof propTypes> & { accidentalKeyLengthRatio?: number } | |||
type Props = PropTypes.InferProps<typeof propTypes> | |||
/** | |||
* Keyboard map for allowing interactivity with the keyboard. | |||
* @param channel - Active MIDI channel for registering keys. | |||
* @param accidentalKeyLengthRatio - Ratio of the length of the accidental keys to the natural keys. This is set by the Keyboard component. | |||
* @param accidentalKeyLengthRatio - Ratio of the length of the accidental keys to the natural keys. | |||
* @param onChange - Event handler triggered upon change in activated keys in the component. | |||
* @param keyboardMapping - Map from key code to key number. | |||
*/ | |||
const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onChange, keyboardMapping = {} }) => { | |||
const KeyboardMap: React.FC<Props> = ({ accidentalKeyLengthRatio, onChange, keyboardMapping = {} }) => { | |||
const baseRef = React.useRef<HTMLDivElement>(null) | |||
const keysOnRef = React.useRef<any[]>([]) | |||
const lastVelocity = React.useRef<number | undefined>(undefined) | |||
@@ -62,7 +61,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||
if (lastVelocity.current === undefined) { | |||
lastVelocity.current = keyData.velocity > 1 ? 1 : keyData.velocity < 0 ? 0 : keyData.velocity | |||
} | |||
keysOnRef.current = [...keysOnRef.current, { ...keyData, velocity: lastVelocity.current, channel, id: -1 }] | |||
keysOnRef.current = [...keysOnRef.current, { ...keyData, velocity: lastVelocity.current, id: -1 }] | |||
if (typeof onChange! === 'function') { | |||
onChange(keysOnRef.current) | |||
} | |||
@@ -89,10 +88,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||
if (lastVelocity.current === undefined) { | |||
lastVelocity.current = keyData.velocity > 1 ? 1 : keyData.velocity < 0 ? 0 : keyData.velocity | |||
} | |||
keysOnRef.current = [ | |||
...keysOnRef.current, | |||
{ ...keyData, velocity: lastVelocity.current, channel, id: t.identifier }, | |||
] | |||
keysOnRef.current = [...keysOnRef.current, { ...keyData, velocity: lastVelocity.current, id: t.identifier }] | |||
if (typeof onChange! === 'function') { | |||
onChange(keysOnRef.current) | |||
} | |||
@@ -136,7 +132,6 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||
...keysOnRef.current.filter((k) => k.id !== t.identifier), | |||
{ | |||
...keyData, | |||
channel, | |||
velocity: lastVelocity.current, | |||
id: t.identifier, | |||
}, | |||
@@ -152,7 +147,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||
return () => { | |||
window.removeEventListener('touchmove', handleTouchMove) | |||
} | |||
}, [accidentalKeyLengthRatio, channel, onChange]) | |||
}, [accidentalKeyLengthRatio, onChange]) | |||
React.useEffect(() => { | |||
const handleMouseMove = (e: MouseEvent) => { | |||
@@ -188,7 +183,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||
if (mouseKey.key !== keyData.key) { | |||
keysOnRef.current = [ | |||
...keysOnRef.current.filter((k) => k.id !== -1), | |||
{ ...keyData, velocity: lastVelocity.current, channel, id: -1 }, | |||
{ ...keyData, velocity: lastVelocity.current, id: -1 }, | |||
] | |||
if (typeof onChange! === 'function') { | |||
onChange(keysOnRef.current) | |||
@@ -201,7 +196,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||
return () => { | |||
window.removeEventListener('mousemove', handleMouseMove) | |||
} | |||
}, [accidentalKeyLengthRatio, channel, onChange]) | |||
}, [accidentalKeyLengthRatio, onChange]) | |||
React.useEffect(() => { | |||
const handleTouchEnd = (e: TouchEvent) => { | |||
@@ -245,7 +240,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||
return () => { | |||
window.removeEventListener('mouseup', handleMouseUp) | |||
} | |||
}, [accidentalKeyLengthRatio, channel, onChange]) | |||
}, [accidentalKeyLengthRatio, onChange]) | |||
React.useEffect(() => { | |||
const baseRefComponent = baseRef.current | |||
@@ -267,7 +262,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||
if (keysOnRef.current.some((k) => k.key === key && k.id === -2)) { | |||
return | |||
} | |||
keysOnRef.current = [...keysOnRef.current, { key, velocity: 0.75, channel, id: -2 }] | |||
keysOnRef.current = [...keysOnRef.current, { key, velocity: 0.75, id: -2 }] | |||
if (typeof onChange! === 'function') { | |||
onChange(keysOnRef.current) | |||
} | |||
@@ -322,6 +317,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||
height: '100%', | |||
zIndex: 4, | |||
outline: 0, | |||
cursor: 'pointer', | |||
}} | |||
onContextMenu={handleContextMenu} | |||
onDragStart={handleDragStart} | |||
@@ -1,10 +1,6 @@ | |||
import * as React from 'react' | |||
import * as PropTypes from 'prop-types' | |||
import keyPropTypes from '../../services/keyPropTypes' | |||
type Props = PropTypes.InferProps<typeof keyPropTypes> | |||
const NaturalKey: React.FC<Props> = ({ keyChannels }) => ( | |||
const NaturalKey: React.FC = () => ( | |||
<div | |||
style={{ | |||
width: '100%', | |||
@@ -15,24 +11,18 @@ const NaturalKey: React.FC<Props> = ({ keyChannels }) => ( | |||
position: 'relative', | |||
}} | |||
> | |||
{Array.isArray(keyChannels!) && | |||
keyChannels.map((c) => ( | |||
<div | |||
key={c!.channel} | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
opacity: 0.75, | |||
backgroundColor: `var(--color-channel-${c!.channel}, Highlight)`, | |||
}} | |||
/> | |||
))} | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
opacity: 'var(--opacity-highlight)', | |||
backgroundColor: `var(--color-active-key, Highlight)`, | |||
}} | |||
/> | |||
</div> | |||
) | |||
NaturalKey.propTypes = keyPropTypes | |||
export default NaturalKey |
@@ -1,230 +1,452 @@ | |||
import * as React from 'react' | |||
import * as PropTypes from 'prop-types' | |||
import keyPropTypes from '../../services/keyPropTypes' | |||
const DEFAULT_COLOR = '#35313b' | |||
const LIGHT_COLOR = 'white' | |||
type Props = PropTypes.InferProps<typeof keyPropTypes> | |||
const StyledAccidentalKey: React.FC<Props> = ({ keyChannels }) => { | |||
const hasKeyChannels = Array.isArray(keyChannels!) && keyChannels.length > 0 | |||
const StyledAccidentalKey: React.FC = () => { | |||
return ( | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
position: 'relative', | |||
// @ts-ignore | |||
'--color-accidental-key': hasKeyChannels ? `var(--color-channel-${keyChannels![0]!.channel})` : undefined, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
borderRadius: 'calc(1px * var(--size-scale-factor, 1))', | |||
boxShadow: '0 0 0 calc(1px * var(--size-scale-factor, 1)) rgba(0, 0, 0, 0.25)', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(6 / 50 * 100%)', | |||
padding: '0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
backgroundColor: `var(--color-accidental-key, ${DEFAULT_COLOR})`, | |||
maskImage: 'linear-gradient(to bottom, white, rgba(0, 0, 0, 0.9))', | |||
WebkitMaskImage: 'linear-gradient(to bottom, white, rgba(0, 0, 0, 0.9))', | |||
opacity: hasKeyChannels ? 0.75 : '1', | |||
position: 'relative', | |||
}} | |||
/> | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
borderRadius: 'calc(1px * var(--size-scale-factor, 1))', | |||
boxShadow: '0 0 0 calc(1px * var(--size-scale-factor, 1)) rgba(0, 0, 0, 0.25)', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(6 / 50 * 100%)', | |||
padding: '0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
backgroundColor: `var(--color-accidental-key, #35313b)`, | |||
maskImage: 'linear-gradient(to bottom, white, rgba(0, 0, 0, 0.9))', | |||
WebkitMaskImage: 'linear-gradient(to bottom, white, rgba(0, 0, 0, 0.9))', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(44 / 50 * 100%)', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1)) 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: `var(--color-accidental-key, #35313b)`, | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(4px * var(--size-scale-factor, 1))', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 0 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(2px * var(--size-scale-factor, 1))', | |||
height: 'calc(10 / 52 * 100%)', | |||
padding: '0 calc(1px * var(--size-scale-factor, 1)) 0 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 0, | |||
right: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
opacity: '0.4', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(2px * var(--size-scale-factor, 1))', | |||
height: 'calc(34 / 52 * 100%)', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 'calc(8 / 52 * 100%)', | |||
right: 0, | |||
paddingRight: 'calc(1px * var(--size-scale-factor, 1))', | |||
paddingLeft: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
opacity: '0.4', | |||
borderBottomRightRadius: 'calc(1px * var(--size-scale-factor, 1))', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(6 / 52 * 100%)', | |||
padding: | |||
'0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
borderRadius: | |||
'calc(4px * var(--size-scale-factor, 1)) calc(4px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(38 / 52 * 100%)', | |||
padding: '0 calc(3px * var(--size-scale-factor, 1)) 0 calc(3px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 'calc(3px * var(--size-scale-factor, 1))', | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
borderRadius: 99999, | |||
opacity: 0.12, | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
paddingTop: 0, | |||
paddingRight: 'calc(1px * var(--size-scale-factor, 1))', | |||
paddingLeft: 'calc(2px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 'calc(8 / 52 * 100%)', | |||
left: 0, | |||
height: 'calc(1px * var(--size-scale-factor, 1))', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.4', | |||
}} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(44 / 50 * 100%)', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1)) 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: `var(--color-accidental-key, ${DEFAULT_COLOR})`, | |||
opacity: hasKeyChannels ? 0.75 : '1', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(4px * var(--size-scale-factor, 1))', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 0 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(2px * var(--size-scale-factor, 1))', | |||
height: 'calc(10 / 52 * 100%)', | |||
padding: '0 calc(1px * var(--size-scale-factor, 1)) 0 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 0, | |||
right: 0, | |||
opacity: hasKeyChannels ? 0.5 : '1', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
opacity: '0.4', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(2px * var(--size-scale-factor, 1))', | |||
height: hasKeyChannels ? 'calc(38 / 52 * 100%)' : 'calc(34 / 52 * 100%)', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: hasKeyChannels ? 'calc(4 / 52 * 100%)' : 'calc(8 / 52 * 100%)', | |||
right: 0, | |||
paddingRight: 'calc(1px * var(--size-scale-factor, 1))', | |||
paddingLeft: 0, | |||
opacity: hasKeyChannels ? 0.5 : '1', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
opacity: '0.4', | |||
borderBottomRightRadius: 'calc(1px * var(--size-scale-factor, 1))', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: hasKeyChannels ? 'calc(2 / 52 * 100%)' : 'calc(6 / 52 * 100%)', | |||
padding: | |||
'0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 0, | |||
left: 0, | |||
opacity: hasKeyChannels ? 3 : '4', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
borderRadius: | |||
'calc(4px * var(--size-scale-factor, 1)) calc(4px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: hasKeyChannels ? 'calc(42 / 52 * 100%)' : 'calc(38 / 52 * 100%)', | |||
padding: '0 calc(3px * var(--size-scale-factor, 1)) 0 calc(3px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 'calc(3px * var(--size-scale-factor, 1))', | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
borderRadius: 99999, | |||
opacity: hasKeyChannels ? 0.06 : '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
paddingTop: 0, | |||
paddingRight: 'calc(1px * var(--size-scale-factor, 1))', | |||
paddingLeft: 'calc(2px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: hasKeyChannels ? 'calc(4 / 52 * 100%)' : 'calc(8 / 52 * 100%)', | |||
left: 0, | |||
height: 'calc(1px * var(--size-scale-factor, 1))', | |||
opacity: hasKeyChannels ? 0.5 : '1', | |||
height: '100%', | |||
opacity: 'var(--opacity-highlight)', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.4', | |||
position: 'relative', | |||
}} | |||
/> | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
borderRadius: 'calc(1px * var(--size-scale-factor, 1))', | |||
boxShadow: '0 0 0 calc(1px * var(--size-scale-factor, 1)) rgba(0, 0, 0, 0.25)', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(6 / 50 * 100%)', | |||
padding: '0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
backgroundColor: 'var(--color-active-key, Highlight)', | |||
maskImage: 'linear-gradient(to bottom, white, rgba(0, 0, 0, 0.9))', | |||
WebkitMaskImage: 'linear-gradient(to bottom, white, rgba(0, 0, 0, 0.9))', | |||
opacity: 0.75, | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(44 / 50 * 100%)', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1)) 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'var(--color-active-key, Highlight)', | |||
opacity: 0.75, | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(4px * var(--size-scale-factor, 1))', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 0 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(2px * var(--size-scale-factor, 1))', | |||
height: 'calc(10 / 52 * 100%)', | |||
padding: '0 calc(1px * var(--size-scale-factor, 1)) 0 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 0, | |||
right: 0, | |||
opacity: 0.5, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
opacity: '0.4', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(2px * var(--size-scale-factor, 1))', | |||
height: 'calc(38 / 52 * 100%)', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 'calc(4 / 52 * 100%)', | |||
right: 0, | |||
paddingRight: 'calc(1px * var(--size-scale-factor, 1))', | |||
paddingLeft: 0, | |||
opacity: 0.5, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
opacity: '0.4', | |||
borderBottomRightRadius: 'calc(1px * var(--size-scale-factor, 1))', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(2 / 52 * 100%)', | |||
padding: | |||
'0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 0, | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
borderRadius: | |||
'calc(4px * var(--size-scale-factor, 1)) calc(4px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(42 / 52 * 100%)', | |||
padding: '0 calc(3px * var(--size-scale-factor, 1)) 0 calc(3px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: 'calc(3px * var(--size-scale-factor, 1))', | |||
left: 0, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
borderRadius: 99999, | |||
opacity: 0.06, | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
paddingTop: 0, | |||
paddingRight: 'calc(1px * var(--size-scale-factor, 1))', | |||
paddingLeft: 'calc(2px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: 'calc(4 / 52 * 100%)', | |||
left: 0, | |||
height: 'calc(1px * var(--size-scale-factor, 1))', | |||
opacity: 0.5, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.4', | |||
}} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
) | |||
} | |||
StyledAccidentalKey.propTypes = keyPropTypes | |||
export default StyledAccidentalKey |
@@ -1,202 +1,399 @@ | |||
import * as React from 'react' | |||
import * as PropTypes from 'prop-types' | |||
import keyPropTypes from '../../services/keyPropTypes' | |||
const DEFAULT_COLOR = '#e3e3e5' | |||
const LIGHT_COLOR = 'white' | |||
type Props = PropTypes.InferProps<typeof keyPropTypes> | |||
const StyledNaturalKey: React.FC<Props> = ({ keyChannels }) => { | |||
const hasKeyChannels = Array.isArray(keyChannels!) && keyChannels.length > 0 | |||
const StyledNaturalKey: React.FC = () => { | |||
return ( | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
position: 'relative', | |||
// @ts-ignore | |||
'--color-natural-key': hasKeyChannels ? `var(--color-channel-${keyChannels![0]!.channel})` : undefined, | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
top: 0, | |||
left: 0, | |||
width: '100%', | |||
height: '100%', | |||
padding: | |||
'calc(1px * var(--size-scale-factor, 1)) 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: `var(--color-natural-key, ${DEFAULT_COLOR})`, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: hasKeyChannels ? 0.75 : 1, | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(33 / 80 * 100%)', | |||
padding: | |||
'0 calc(1px * var(--size-scale-factor, 1)) calc(2px * var(--size-scale-factor, 1)) calc(2px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
backgroundClip: 'content-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
backgroundColor: LIGHT_COLOR, | |||
opacity: hasKeyChannels ? 0.12 : 0.25, | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
padding: | |||
'calc(1px * var(--size-scale-factor, 1)) calc(2px * var(--size-scale-factor, 1)) calc(3px * var(--size-scale-factor, 1)) calc(3px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
backgroundClip: 'content-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
opacity: '0.08', | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(2px * var(--size-scale-factor, 1))', | |||
padding: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.25', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(2px * var(--size-scale-factor, 1))', | |||
height: '100%', | |||
padding: | |||
'calc(1px * var(--size-scale-factor, 1)) 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
borderRadius: '0 0 0 calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.07', | |||
position: 'relative', | |||
}} | |||
/> | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
padding: | |||
'calc(1px * var(--size-scale-factor, 1)) 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: `var(--color-natural-key, #e3e3e5)`, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: 1, | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(33 / 80 * 100%)', | |||
padding: | |||
'0 calc(1px * var(--size-scale-factor, 1)) calc(2px * var(--size-scale-factor, 1)) calc(2px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
backgroundClip: 'content-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
backgroundColor: LIGHT_COLOR, | |||
opacity: 0.25, | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
padding: | |||
'calc(1px * var(--size-scale-factor, 1)) calc(2px * var(--size-scale-factor, 1)) calc(3px * var(--size-scale-factor, 1)) calc(3px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
backgroundClip: 'content-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
opacity: '0.08', | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(2px * var(--size-scale-factor, 1))', | |||
padding: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.25', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(2px * var(--size-scale-factor, 1))', | |||
height: '100%', | |||
padding: | |||
'calc(1px * var(--size-scale-factor, 1)) 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
borderRadius: '0 0 0 calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.07', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(6px * var(--size-scale-factor, 1))', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 0 calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
maskImage: 'linear-gradient(to bottom, white, transparent)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, white, transparent)', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 0 calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
height: 'calc(3px * var(--size-scale-factor, 1))', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(1px * var(--size-scale-factor, 1))', | |||
height: '100%', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 calc(1px * var(--size-scale-factor, 1)) 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
right: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) 0', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(6px * var(--size-scale-factor, 1))', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 0 calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
maskImage: 'linear-gradient(to bottom, white, transparent)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, white, transparent)', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
top: 0, | |||
left: 0, | |||
width: '100%', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 0 calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
height: hasKeyChannels | |||
? 'calc(4px * var(--size-scale-factor, 1))' | |||
: 'calc(3px * var(--size-scale-factor, 1))', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(1px * var(--size-scale-factor, 1))', | |||
height: '100%', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 calc(1px * var(--size-scale-factor, 1)) 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
right: '0', | |||
opacity: 'var(--opacity-highlight)', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) 0', | |||
opacity: '0.12', | |||
position: 'relative', | |||
}} | |||
/> | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
padding: | |||
'calc(1px * var(--size-scale-factor, 1)) 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: `var(--color-active-key, Highlight)`, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: 0.75, | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(33 / 80 * 100%)', | |||
padding: | |||
'0 calc(1px * var(--size-scale-factor, 1)) calc(2px * var(--size-scale-factor, 1)) calc(2px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
backgroundClip: 'content-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
backgroundColor: LIGHT_COLOR, | |||
opacity: 0.12, | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
padding: | |||
'calc(1px * var(--size-scale-factor, 1)) calc(2px * var(--size-scale-factor, 1)) calc(3px * var(--size-scale-factor, 1)) calc(3px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
backgroundClip: 'content-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
opacity: '0.08', | |||
maskImage: 'linear-gradient(to bottom, transparent, white)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, transparent, white)', | |||
}} | |||
/> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(2px * var(--size-scale-factor, 1))', | |||
padding: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.25', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(2px * var(--size-scale-factor, 1))', | |||
height: '100%', | |||
padding: | |||
'calc(1px * var(--size-scale-factor, 1)) 0 calc(1px * var(--size-scale-factor, 1)) calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
borderRadius: '0 0 0 calc(1px * var(--size-scale-factor, 1))', | |||
opacity: '0.07', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: 'calc(6px * var(--size-scale-factor, 1))', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 0 calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
maskImage: 'linear-gradient(to bottom, white, transparent)', | |||
WebkitMaskImage: 'linear-gradient(to bottom, white, transparent)', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: '100%', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 0 calc(1px * var(--size-scale-factor, 1))', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
top: '0', | |||
left: '0', | |||
height: 'calc(4px * var(--size-scale-factor, 1))', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'black', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
<div | |||
style={{ | |||
width: 'calc(1px * var(--size-scale-factor, 1))', | |||
height: '100%', | |||
padding: 'calc(1px * var(--size-scale-factor, 1)) 0 calc(1px * var(--size-scale-factor, 1)) 0', | |||
boxSizing: 'border-box', | |||
position: 'absolute', | |||
bottom: '0', | |||
right: '0', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: LIGHT_COLOR, | |||
borderRadius: '0 0 calc(1px * var(--size-scale-factor, 1)) 0', | |||
opacity: '0.12', | |||
}} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
) | |||
} | |||
StyledNaturalKey.propTypes = keyPropTypes | |||
export default StyledNaturalKey |
@@ -1,8 +1,7 @@ | |||
import Keyboard from './components/Keyboard/Keyboard' | |||
import KeyboardMap from './components/KeyboardMap/KeyboardMap' | |||
import StyledNaturalKey from './components/StyledNaturalKey/StyledNaturalKey' | |||
import StyledAccidentalKey from './components/StyledAccidentalKey/StyledAccidentalKey' | |||
export default Keyboard | |||
export { StyledNaturalKey, StyledAccidentalKey, KeyboardMap } | |||
export { StyledNaturalKey, StyledAccidentalKey } |
@@ -0,0 +1,43 @@ | |||
type Bounds = { | |||
left: number | |||
right: number | |||
} | |||
type GetKeyBounds = ( | |||
startKey: number, | |||
endKey: number, | |||
getKeyLeft: (key: number) => number, | |||
getKeyWidth: (key: number) => number, | |||
) => (key: number, left: number, width: number) => Bounds | |||
const getKeyBounds: GetKeyBounds = (startKey, endKey, getKeyLeft, getKeyWidth) => (key, left, width) => { | |||
switch (key % 12) { | |||
case 0: | |||
case 5: | |||
return { | |||
left, | |||
right: key + 1 > endKey! ? left + width : getKeyLeft(key + 1), | |||
} | |||
case 4: | |||
case 11: | |||
return { | |||
left: key - 1 < startKey! ? left : getKeyLeft(key - 1) + getKeyWidth(key - 1), | |||
right: left + width, | |||
} | |||
case 2: | |||
case 7: | |||
case 9: | |||
return { | |||
left: key - 1 < startKey! ? left : getKeyLeft(key - 1) + getKeyWidth(key - 1), | |||
right: key + 1 > endKey! ? left + width : getKeyLeft(key + 1), | |||
} | |||
default: | |||
break | |||
} | |||
return { | |||
left, | |||
right: left + width, | |||
} | |||
} | |||
export default getKeyBounds |
@@ -1,11 +0,0 @@ | |||
import * as PropTypes from 'prop-types' | |||
export default { | |||
keyChannels: PropTypes.arrayOf( | |||
PropTypes.shape({ | |||
channel: PropTypes.number.isRequired, | |||
key: PropTypes.number.isRequired, | |||
velocity: PropTypes.number.isRequired, | |||
}), | |||
), | |||
} |