ソースを参照

Remove channels, overhaul styling of components

Make component single channel only, in order to have a more concise API.
master
コミット
6168c18c3c
14個のファイルの変更1134行の追加610行の削除
  1. +14
    -12
      README.md
  2. +4
    -3
      example/controllers/Channel.ts
  3. +4
    -8
      example/index.tsx
  4. +1
    -1
      package.json
  5. +13
    -23
      src/components/AccidentalKey/AccidentalKey.tsx
  6. +141
    -83
      src/components/Keyboard/Keyboard.stories.tsx
  7. +124
    -83
      src/components/Keyboard/Keyboard.tsx
  8. +15
    -19
      src/components/KeyboardMap/KeyboardMap.tsx
  9. +12
    -22
      src/components/NaturalKey/NaturalKey.tsx
  10. +406
    -184
      src/components/StyledAccidentalKey/StyledAccidentalKey.tsx
  11. +356
    -159
      src/components/StyledNaturalKey/StyledNaturalKey.tsx
  12. +1
    -2
      src/index.ts
  13. +43
    -0
      src/services/getKeyBounds.ts
  14. +0
    -11
      src/services/keyPropTypes.ts

+ 14
- 12
README.md ファイルの表示

@@ -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>
)
}


+ 4
- 3
example/controllers/Channel.ts ファイルの表示

@@ -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))
})
}



+ 4
- 8
example/index.tsx ファイルの表示

@@ -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
- 1
package.json ファイルの表示

@@ -1,5 +1,5 @@
{
"version": "1.0.13",
"version": "1.1.0",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",


+ 13
- 23
src/components/AccidentalKey/AccidentalKey.tsx ファイルの表示

@@ -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

+ 141
- 83
src/components/Keyboard/Keyboard.stories.tsx ファイルの表示

@@ -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>
)

+ 124
- 83
src/components/Keyboard/Keyboard.tsx ファイルの表示

@@ -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>
)
}



+ 15
- 19
src/components/KeyboardMap/KeyboardMap.tsx ファイルの表示

@@ -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}


+ 12
- 22
src/components/NaturalKey/NaturalKey.tsx ファイルの表示

@@ -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

+ 406
- 184
src/components/StyledAccidentalKey/StyledAccidentalKey.tsx ファイルの表示

@@ -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

+ 356
- 159
src/components/StyledNaturalKey/StyledNaturalKey.tsx ファイルの表示

@@ -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
- 2
src/index.ts ファイルの表示

@@ -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 }

+ 43
- 0
src/services/getKeyBounds.ts ファイルの表示

@@ -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

+ 0
- 11
src/services/keyPropTypes.ts ファイルの表示

@@ -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,
}),
),
}

読み込み中…
キャンセル
保存