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) | ReactDOM.render(<App />, container) | ||||
``` | ``` | ||||
### Interactivity | |||||
The library also supports keyboard maps for handling mouse, touch, and keyboard events: | The library also supports keyboard maps for handling mouse, touch, and keyboard events: | ||||
```jsx harmony | ```jsx harmony | ||||
@@ -60,12 +62,8 @@ const App = () => { | |||||
<Keyboard | <Keyboard | ||||
startKey={21} | startKey={21} | ||||
endKey={108} | endKey={108} | ||||
> | |||||
<KeyboardMap | |||||
channel={0} | |||||
onChange={handleKeysChange} | |||||
/> | |||||
</Keyboard> | |||||
onChange={handleKeysChange} | |||||
/> | |||||
</div> | </div> | ||||
) | ) | ||||
} | } | ||||
@@ -77,6 +75,10 @@ window.document.body.appendChild(container) | |||||
ReactDOM.render(<App />, 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: | The component is stylable, just supply custom components for the keys: | ||||
```jsx harmony | ```jsx harmony | ||||
@@ -108,7 +110,9 @@ window.document.body.appendChild(container) | |||||
ReactDOM.render(<App />, 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 | ```jsx harmony | ||||
// ./my-styled-keys/NaturalKey.js | // ./my-styled-keys/NaturalKey.js | ||||
@@ -121,11 +125,9 @@ const NaturalKey = ({ | |||||
keyChannels = [] | keyChannels = [] | ||||
}) => { | }) => { | ||||
return ( | 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> | </div> | ||||
) | ) | ||||
} | } | ||||
@@ -24,9 +24,10 @@ type KeyChannelCallback = (oldKeys: KeyChannel[]) => KeyChannel[] | |||||
type HandleProps = { | type HandleProps = { | ||||
setKeyChannels(callback: KeyChannelCallback | KeyChannel[]): void, | setKeyChannels(callback: KeyChannelCallback | KeyChannel[]): void, | ||||
generator?: SoundGenerator, | generator?: SoundGenerator, | ||||
channel: number, | |||||
} | } | ||||
type Handle = (props: HandleProps) => (newKeys: KeyChannel[]) => void | type Handle = (props: HandleProps) => (newKeys: KeyChannel[]) => void | ||||
export const handle: Handle = ({ setKeyChannels, generator, }) => newKeys => { | |||||
export const handle: Handle = ({ setKeyChannels, generator, channel, }) => newKeys => { | |||||
setKeyChannels((oldKeys) => { | setKeyChannels((oldKeys) => { | ||||
if (generator! !== undefined) { | if (generator! !== undefined) { | ||||
const oldKeysKeys = oldKeys.map((k) => k.key) | 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)) | const keysOn = newKeys.filter((nk) => !oldKeysKeys.includes(nk.key)) | ||||
keysOn.forEach((k) => { | 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) => { | 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 * as React from 'react' | ||||
import ReactDOM from 'react-dom' | import ReactDOM from 'react-dom' | ||||
import Keyboard, { KeyboardMap } from '../src' | |||||
import Keyboard from '../src' | |||||
import * as Channel from './controllers/Channel' | import * as Channel from './controllers/Channel' | ||||
import * as Instrument from './controllers/Instrument' | import * as Instrument from './controllers/Instrument' | ||||
import * as Generator from './controllers/Generator' | import * as Generator from './controllers/Generator' | ||||
@@ -75,13 +75,9 @@ const App = () => { | |||||
endKey={127} | endKey={127} | ||||
keyChannels={keyChannels} | keyChannels={keyChannels} | ||||
height="100%" | 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> | ||||
</div> | </div> | ||||
</React.Fragment> | </React.Fragment> | ||||
@@ -1,5 +1,5 @@ | |||||
{ | { | ||||
"version": "1.0.13", | |||||
"version": "1.1.0", | |||||
"license": "MIT", | "license": "MIT", | ||||
"main": "dist/index.js", | "main": "dist/index.js", | ||||
"typings": "dist/index.d.ts", | "typings": "dist/index.d.ts", | ||||
@@ -1,38 +1,28 @@ | |||||
import * as React from 'react' | 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 | <div | ||||
style={{ | style={{ | ||||
width: '100%', | width: '100%', | ||||
height: '100%', | height: '100%', | ||||
backgroundColor: 'var(--color-accidental-key, currentColor)', | |||||
backgroundColor: 'var(--color-accidental-key, black)', | |||||
border: '1px solid', | border: '1px solid', | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
position: 'relative', | 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> | </div> | ||||
) | ) | ||||
AccidentalKey.propTypes = keyPropTypes | |||||
export default AccidentalKey | export default AccidentalKey |
@@ -3,7 +3,6 @@ import * as PropTypes from 'prop-types' | |||||
import StyledAccidentalKey from '../StyledAccidentalKey/StyledAccidentalKey' | import StyledAccidentalKey from '../StyledAccidentalKey/StyledAccidentalKey' | ||||
import StyledNaturalKey from '../StyledNaturalKey/StyledNaturalKey' | import StyledNaturalKey from '../StyledNaturalKey/StyledNaturalKey' | ||||
import Keyboard, { propTypes } from './Keyboard' | import Keyboard, { propTypes } from './Keyboard' | ||||
import KeyboardMap from '../KeyboardMap/KeyboardMap' | |||||
const Wrapper: React.FC = (props) => ( | const Wrapper: React.FC = (props) => ( | ||||
<div | <div | ||||
@@ -52,17 +51,14 @@ export const WithActiveKeys = (props?: Partial<Props>) => ( | |||||
endKey={108} | endKey={108} | ||||
keyChannels={[ | keyChannels={[ | ||||
{ | { | ||||
channel: 0, | |||||
key: 60, | key: 60, | ||||
velocity: 1, | velocity: 1, | ||||
}, | }, | ||||
{ | { | ||||
channel: 0, | |||||
key: 64, | key: 64, | ||||
velocity: 1, | velocity: 1, | ||||
}, | }, | ||||
{ | { | ||||
channel: 0, | |||||
key: 67, | key: 67, | ||||
velocity: 1, | velocity: 1, | ||||
}, | }, | ||||
@@ -78,34 +74,39 @@ export const WithDifferentKeyRange = (props?: Partial<Props>) => ( | |||||
) | ) | ||||
export const Styled = (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>) => ( | export const AnotherStyled = (props?: Partial<Props>) => ( | ||||
@@ -113,6 +114,46 @@ export const AnotherStyled = (props?: Partial<Props>) => ( | |||||
style={{ | style={{ | ||||
// @ts-ignore | // @ts-ignore | ||||
'--size-scale-factor': 2, | '--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> | <Wrapper> | ||||
@@ -122,17 +163,14 @@ export const AnotherStyled = (props?: Partial<Props>) => ( | |||||
endKey={108} | endKey={108} | ||||
keyChannels={[ | keyChannels={[ | ||||
{ | { | ||||
channel: 0, | |||||
key: 60, | key: 60, | ||||
velocity: 1, | velocity: 1, | ||||
}, | }, | ||||
{ | { | ||||
channel: 0, | |||||
key: 63, | key: 63, | ||||
velocity: 1, | velocity: 1, | ||||
}, | }, | ||||
{ | { | ||||
channel: 0, | |||||
key: 67, | key: 67, | ||||
velocity: 1, | velocity: 1, | ||||
}, | }, | ||||
@@ -156,13 +194,14 @@ const HasMapComponent = () => { | |||||
const newKeysKeys = newKeys.map((k) => k.key) | const newKeysKeys = newKeys.map((k) => k.key) | ||||
const keysOff = oldKeys.filter((ok) => !newKeysKeys.includes(ok.key)) | const keysOff = oldKeys.filter((ok) => !newKeysKeys.includes(ok.key)) | ||||
const keysOn = newKeys.filter((nk) => !oldKeysKeys.includes(nk.key)) | const keysOn = newKeys.filter((nk) => !oldKeysKeys.includes(nk.key)) | ||||
const channel = 0 | |||||
keysOn.forEach((k) => { | 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) => { | 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 | return newKeys | ||||
@@ -183,54 +222,73 @@ const HasMapComponent = () => { | |||||
return ( | return ( | ||||
<Wrapper> | <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> | </Wrapper> | ||||
) | ) | ||||
} | } | ||||
export const HasMap = () => <HasMapComponent /> | 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 generateKeys from '../../services/generateKeys' | ||||
import DefaultAccidentalKey from '../AccidentalKey/AccidentalKey' | import DefaultAccidentalKey from '../AccidentalKey/AccidentalKey' | ||||
import DefaultNaturalKey from '../NaturalKey/NaturalKey' | 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 = { | export const propTypes = { | ||||
/** | /** | ||||
@@ -35,7 +39,6 @@ export const propTypes = { | |||||
*/ | */ | ||||
keyChannels: PropTypes.arrayOf( | keyChannels: PropTypes.arrayOf( | ||||
PropTypes.shape({ | PropTypes.shape({ | ||||
channel: PropTypes.number.isRequired, | |||||
key: PropTypes.number.isRequired, | key: PropTypes.number.isRequired, | ||||
velocity: PropTypes.number.isRequired, | velocity: PropTypes.number.isRequired, | ||||
}), | }), | ||||
@@ -58,6 +61,26 @@ export const propTypes = { | |||||
* Height of the component. | * Height of the component. | ||||
*/ | */ | ||||
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | 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> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
@@ -72,6 +95,9 @@ type Props = PropTypes.InferProps<typeof propTypes> | |||||
* @param width - Width of the component. | * @param width - Width of the component. | ||||
* @param keyComponents - Components to use for each kind of key. | * @param keyComponents - Components to use for each kind of key. | ||||
* @param height - Height of the component. | * @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> = ({ | const Keyboard: React.FC<Props> = ({ | ||||
startKey, | startKey, | ||||
@@ -82,7 +108,11 @@ const Keyboard: React.FC<Props> = ({ | |||||
width = '100%', | width = '100%', | ||||
keyComponents = {}, | keyComponents = {}, | ||||
height = 80, | height = 80, | ||||
children, | |||||
onChange, | |||||
keyboardMapping, | |||||
behavior, | |||||
name, | |||||
href, | |||||
}) => { | }) => { | ||||
const [clientSide, setClientSide] = React.useState(false) | const [clientSide, setClientSide] = React.useState(false) | ||||
const [clientSideKeys, setClientSideKeys] = React.useState<number[]>([]) | const [clientSideKeys, setClientSideKeys] = React.useState<number[]>([]) | ||||
@@ -105,89 +135,100 @@ const Keyboard: React.FC<Props> = ({ | |||||
const keys = clientSide ? clientSideKeys : generateKeys(startKey, endKey) | const keys = clientSide ? clientSideKeys : generateKeys(startKey, endKey) | ||||
return ( | 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' | import reverseGetKeyFromPoint from '../../services/reverseGetKeyFromPoint' | ||||
const propTypes = { | 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. | * Event handler triggered upon change in activated keys in the component. | ||||
*/ | */ | ||||
@@ -11,22 +15,17 @@ const propTypes = { | |||||
* Map from key code to key number. | * Map from key code to key number. | ||||
*/ | */ | ||||
keyboardMapping: PropTypes.object, | 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. | * 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 onChange - Event handler triggered upon change in activated keys in the component. | ||||
* @param keyboardMapping - Map from key code to key number. | * @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 baseRef = React.useRef<HTMLDivElement>(null) | ||||
const keysOnRef = React.useRef<any[]>([]) | const keysOnRef = React.useRef<any[]>([]) | ||||
const lastVelocity = React.useRef<number | undefined>(undefined) | const lastVelocity = React.useRef<number | undefined>(undefined) | ||||
@@ -62,7 +61,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||||
if (lastVelocity.current === undefined) { | if (lastVelocity.current === undefined) { | ||||
lastVelocity.current = keyData.velocity > 1 ? 1 : keyData.velocity < 0 ? 0 : keyData.velocity | 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') { | if (typeof onChange! === 'function') { | ||||
onChange(keysOnRef.current) | onChange(keysOnRef.current) | ||||
} | } | ||||
@@ -89,10 +88,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||||
if (lastVelocity.current === undefined) { | if (lastVelocity.current === undefined) { | ||||
lastVelocity.current = keyData.velocity > 1 ? 1 : keyData.velocity < 0 ? 0 : keyData.velocity | 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') { | if (typeof onChange! === 'function') { | ||||
onChange(keysOnRef.current) | onChange(keysOnRef.current) | ||||
} | } | ||||
@@ -136,7 +132,6 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||||
...keysOnRef.current.filter((k) => k.id !== t.identifier), | ...keysOnRef.current.filter((k) => k.id !== t.identifier), | ||||
{ | { | ||||
...keyData, | ...keyData, | ||||
channel, | |||||
velocity: lastVelocity.current, | velocity: lastVelocity.current, | ||||
id: t.identifier, | id: t.identifier, | ||||
}, | }, | ||||
@@ -152,7 +147,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||||
return () => { | return () => { | ||||
window.removeEventListener('touchmove', handleTouchMove) | window.removeEventListener('touchmove', handleTouchMove) | ||||
} | } | ||||
}, [accidentalKeyLengthRatio, channel, onChange]) | |||||
}, [accidentalKeyLengthRatio, onChange]) | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const handleMouseMove = (e: MouseEvent) => { | const handleMouseMove = (e: MouseEvent) => { | ||||
@@ -188,7 +183,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||||
if (mouseKey.key !== keyData.key) { | if (mouseKey.key !== keyData.key) { | ||||
keysOnRef.current = [ | keysOnRef.current = [ | ||||
...keysOnRef.current.filter((k) => k.id !== -1), | ...keysOnRef.current.filter((k) => k.id !== -1), | ||||
{ ...keyData, velocity: lastVelocity.current, channel, id: -1 }, | |||||
{ ...keyData, velocity: lastVelocity.current, id: -1 }, | |||||
] | ] | ||||
if (typeof onChange! === 'function') { | if (typeof onChange! === 'function') { | ||||
onChange(keysOnRef.current) | onChange(keysOnRef.current) | ||||
@@ -201,7 +196,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||||
return () => { | return () => { | ||||
window.removeEventListener('mousemove', handleMouseMove) | window.removeEventListener('mousemove', handleMouseMove) | ||||
} | } | ||||
}, [accidentalKeyLengthRatio, channel, onChange]) | |||||
}, [accidentalKeyLengthRatio, onChange]) | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const handleTouchEnd = (e: TouchEvent) => { | const handleTouchEnd = (e: TouchEvent) => { | ||||
@@ -245,7 +240,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||||
return () => { | return () => { | ||||
window.removeEventListener('mouseup', handleMouseUp) | window.removeEventListener('mouseup', handleMouseUp) | ||||
} | } | ||||
}, [accidentalKeyLengthRatio, channel, onChange]) | |||||
}, [accidentalKeyLengthRatio, onChange]) | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const baseRefComponent = baseRef.current | 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)) { | if (keysOnRef.current.some((k) => k.key === key && k.id === -2)) { | ||||
return | 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') { | if (typeof onChange! === 'function') { | ||||
onChange(keysOnRef.current) | onChange(keysOnRef.current) | ||||
} | } | ||||
@@ -322,6 +317,7 @@ const KeyboardMap: React.FC<Props> = ({ channel, accidentalKeyLengthRatio, onCha | |||||
height: '100%', | height: '100%', | ||||
zIndex: 4, | zIndex: 4, | ||||
outline: 0, | outline: 0, | ||||
cursor: 'pointer', | |||||
}} | }} | ||||
onContextMenu={handleContextMenu} | onContextMenu={handleContextMenu} | ||||
onDragStart={handleDragStart} | onDragStart={handleDragStart} | ||||
@@ -1,10 +1,6 @@ | |||||
import * as React from 'react' | 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 | <div | ||||
style={{ | style={{ | ||||
width: '100%', | width: '100%', | ||||
@@ -15,24 +11,18 @@ const NaturalKey: React.FC<Props> = ({ keyChannels }) => ( | |||||
position: 'relative', | 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> | </div> | ||||
) | ) | ||||
NaturalKey.propTypes = keyPropTypes | |||||
export default NaturalKey | export default NaturalKey |
@@ -1,230 +1,452 @@ | |||||
import * as React from 'react' | import * as React from 'react' | ||||
import * as PropTypes from 'prop-types' | |||||
import keyPropTypes from '../../services/keyPropTypes' | |||||
const DEFAULT_COLOR = '#35313b' | |||||
const LIGHT_COLOR = 'white' | 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 ( | return ( | ||||
<div | <div | ||||
style={{ | style={{ | ||||
width: '100%', | width: '100%', | ||||
height: '100%', | height: '100%', | ||||
position: 'relative', | position: 'relative', | ||||
// @ts-ignore | |||||
'--color-accidental-key': hasKeyChannels ? `var(--color-channel-${keyChannels![0]!.channel})` : undefined, | |||||
}} | }} | ||||
> | > | ||||
<div | <div | ||||
style={{ | style={{ | ||||
width: '100%', | |||||
height: '100%', | |||||
position: 'absolute', | position: 'absolute', | ||||
top: 0, | top: 0, | ||||
left: 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%', | width: '100%', | ||||
height: '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 | <div | ||||
style={{ | style={{ | ||||
width: '100%', | width: '100%', | ||||
height: '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> | ||||
<div | <div | ||||
style={{ | 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', | position: 'absolute', | ||||
top: 0, | top: 0, | ||||
left: 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%', | 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 | <div | ||||
style={{ | style={{ | ||||
width: '100%', | width: '100%', | ||||
height: '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> | ||||
</div> | </div> | ||||
) | ) | ||||
} | } | ||||
StyledAccidentalKey.propTypes = keyPropTypes | |||||
export default StyledAccidentalKey | export default StyledAccidentalKey |
@@ -1,202 +1,399 @@ | |||||
import * as React from 'react' | import * as React from 'react' | ||||
import * as PropTypes from 'prop-types' | |||||
import keyPropTypes from '../../services/keyPropTypes' | |||||
const DEFAULT_COLOR = '#e3e3e5' | |||||
const LIGHT_COLOR = 'white' | 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 ( | return ( | ||||
<div | <div | ||||
style={{ | style={{ | ||||
width: '100%', | width: '100%', | ||||
height: '100%', | height: '100%', | ||||
position: 'relative', | position: 'relative', | ||||
// @ts-ignore | |||||
'--color-natural-key': hasKeyChannels ? `var(--color-channel-${keyChannels![0]!.channel})` : undefined, | |||||
}} | }} | ||||
> | > | ||||
<div | <div | ||||
style={{ | style={{ | ||||
width: '100%', | |||||
height: '100%', | |||||
backgroundColor: 'black', | |||||
position: 'absolute', | position: 'absolute', | ||||
top: '0', | |||||
left: '0', | |||||
}} | |||||
/> | |||||
<div | |||||
style={{ | |||||
top: 0, | |||||
left: 0, | |||||
width: '100%', | width: '100%', | ||||
height: '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 | <div | ||||
style={{ | style={{ | ||||
width: '100%', | width: '100%', | ||||
height: '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> | ||||
<div | <div | ||||
style={{ | 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', | 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%', | 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%', | 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 | <div | ||||
style={{ | style={{ | ||||
width: '100%', | width: '100%', | ||||
height: '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> | ||||
</div> | </div> | ||||
) | ) | ||||
} | } | ||||
StyledNaturalKey.propTypes = keyPropTypes | |||||
export default StyledNaturalKey | export default StyledNaturalKey |
@@ -1,8 +1,7 @@ | |||||
import Keyboard from './components/Keyboard/Keyboard' | import Keyboard from './components/Keyboard/Keyboard' | ||||
import KeyboardMap from './components/KeyboardMap/KeyboardMap' | |||||
import StyledNaturalKey from './components/StyledNaturalKey/StyledNaturalKey' | import StyledNaturalKey from './components/StyledNaturalKey/StyledNaturalKey' | ||||
import StyledAccidentalKey from './components/StyledAccidentalKey/StyledAccidentalKey' | import StyledAccidentalKey from './components/StyledAccidentalKey/StyledAccidentalKey' | ||||
export default Keyboard | 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, | |||||
}), | |||||
), | |||||
} |