@@ -0,0 +1,11 @@ | |||
root = true | |||
[*] | |||
charset = utf-8 | |||
end_of_line = lf | |||
indent_size = 2 | |||
indent_style = space | |||
insert_final_newline = true | |||
max_line_length = 120 | |||
tab_width = 8 | |||
trim_trailing_whitespace = true |
@@ -0,0 +1,7 @@ | |||
*.log | |||
.DS_Store | |||
node_modules | |||
.cache | |||
dist | |||
.idea/ | |||
coverage/ |
@@ -0,0 +1,6 @@ | |||
{ | |||
"jsxSingleQuote": false, | |||
"singleQuote": true, | |||
"semi": false, | |||
"trailingComma": "es5" | |||
} |
@@ -0,0 +1,24 @@ | |||
module.exports = { | |||
stories: ['../src/**/*.stories.(ts|tsx)'], | |||
addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-docs'], | |||
webpackFinal: async (config) => { | |||
config.module.rules.push({ | |||
test: /\.(ts|tsx)$/, | |||
use: [ | |||
{ | |||
loader: require.resolve('ts-loader'), | |||
options: { | |||
transpileOnly: true, | |||
}, | |||
}, | |||
{ | |||
loader: require.resolve('react-docgen-typescript-loader'), | |||
}, | |||
], | |||
}); | |||
config.resolve.extensions.push('.ts', '.tsx'); | |||
return config; | |||
}, | |||
}; |
@@ -0,0 +1,21 @@ | |||
MIT License | |||
Copyright (c) 2020 TheoryOfNekomata | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,7 @@ | |||
# React Fretboard | |||
Fretboard for any stringed instrument, made with React. | |||
## License | |||
[MIT](./LICENSE) |
@@ -0,0 +1,3 @@ | |||
node_modules | |||
.cache | |||
dist |
@@ -0,0 +1,14 @@ | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="UTF-8" /> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> | |||
<title>Playground</title> | |||
</head> | |||
<body> | |||
<div id="root"></div> | |||
<script src="./index.tsx"></script> | |||
</body> | |||
</html> |
@@ -0,0 +1,14 @@ | |||
import 'react-app-polyfill/ie11'; | |||
import * as React from 'react'; | |||
import * as ReactDOM from 'react-dom'; | |||
import Fretboard from '../.'; | |||
const App = () => { | |||
return ( | |||
<div> | |||
<Fretboard /> | |||
</div> | |||
); | |||
}; | |||
ReactDOM.render(<App />, document.getElementById('root')); |
@@ -0,0 +1,24 @@ | |||
{ | |||
"name": "example", | |||
"version": "1.0.0", | |||
"main": "index.js", | |||
"license": "MIT", | |||
"scripts": { | |||
"start": "parcel index.html", | |||
"build": "parcel build index.html" | |||
}, | |||
"dependencies": { | |||
"react-app-polyfill": "^1.0.0" | |||
}, | |||
"alias": { | |||
"react": "../node_modules/react", | |||
"react-dom": "../node_modules/react-dom/profiling", | |||
"scheduler/tracing": "../node_modules/scheduler/tracing-profiling" | |||
}, | |||
"devDependencies": { | |||
"@types/react": "^16.9.11", | |||
"@types/react-dom": "^16.8.4", | |||
"parcel": "^1.12.3", | |||
"typescript": "^3.4.5" | |||
} | |||
} |
@@ -0,0 +1,18 @@ | |||
{ | |||
"compilerOptions": { | |||
"allowSyntheticDefaultImports": false, | |||
"target": "es5", | |||
"module": "commonjs", | |||
"jsx": "react", | |||
"moduleResolution": "node", | |||
"noImplicitAny": false, | |||
"noUnusedLocals": false, | |||
"noUnusedParameters": false, | |||
"removeComments": true, | |||
"strictNullChecks": true, | |||
"preserveConstEnums": true, | |||
"sourceMap": true, | |||
"lib": ["es2015", "es2016", "dom"], | |||
"types": ["node"] | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
{ | |||
"version": "0.1.0", | |||
"license": "MIT", | |||
"main": "dist/index.js", | |||
"typings": "dist/index.d.ts", | |||
"files": [ | |||
"dist", | |||
"src" | |||
], | |||
"engines": { | |||
"node": ">=10" | |||
}, | |||
"scripts": { | |||
"start": "tsdx watch", | |||
"build": "tsdx build", | |||
"test": "tsdx test --passWithNoTests", | |||
"lint": "tsdx lint", | |||
"prepare": "tsdx build", | |||
"storybook": "start-storybook -p 6006", | |||
"build-storybook": "build-storybook" | |||
}, | |||
"peerDependencies": { | |||
"react": ">=16" | |||
}, | |||
"husky": { | |||
"hooks": { | |||
"pre-commit": "tsdx lint" | |||
} | |||
}, | |||
"name": "@theoryofnekomata/react-fretboard", | |||
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>", | |||
"module": "dist/react-fretboard.esm.js", | |||
"devDependencies": { | |||
"@babel/core": "^7.11.6", | |||
"@storybook/addon-actions": "^6.0.22", | |||
"@storybook/addon-docs": "^6.0.22", | |||
"@storybook/addon-info": "^5.3.21", | |||
"@storybook/addon-links": "^6.0.22", | |||
"@storybook/addons": "^6.0.22", | |||
"@storybook/react": "^6.0.22", | |||
"@types/prop-types": "^15.7.3", | |||
"@types/react": "^16.9.49", | |||
"@types/react-dom": "^16.9.8", | |||
"babel-loader": "^8.1.0", | |||
"husky": "^4.3.0", | |||
"prop-types": "^15.7.2", | |||
"react": "^16.13.1", | |||
"react-docgen-typescript-loader": "^3.7.2", | |||
"react-dom": "^16.13.1", | |||
"react-is": "^16.13.1", | |||
"ts-loader": "^8.0.4", | |||
"tsdx": "^0.14.0", | |||
"tslib": "^2.0.1", | |||
"typescript": "^4.0.3" | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
import React from 'react' | |||
import Fretboard, { Props } from './Fretboard' | |||
export default { | |||
title: 'Welcome', | |||
} | |||
// By passing optional props to this story, you can control the props of the component when | |||
// you consume the story in a test. | |||
export const Default = (props?: Partial<Props>) => <Fretboard {...props} /> |
@@ -0,0 +1,185 @@ | |||
import React, { FC } from 'react' | |||
import * as PropTypes from 'prop-types' | |||
const propTypes = { | |||
tunings: PropTypes.arrayOf(PropTypes.number), | |||
frets: PropTypes.number, | |||
leftHanded: PropTypes.bool, | |||
} | |||
export type Props = PropTypes.InferProps<typeof propTypes> | |||
const PITCHES = 'C C# D D# E F F# G G# A A# B'.split(' ') | |||
const Fretboard: FC<Props> = ({ tunings = [64, 59, 55, 50, 45, 40], frets = 24, leftHanded = false }) => { | |||
if (!Array.isArray(tunings!)) { | |||
return null | |||
} | |||
const strings = tunings!.map(t => new Array(frets).fill(0).map((_, i) => t! + i + 1)) | |||
return ( | |||
<div | |||
style={{ | |||
border: '0.0625rem solid', | |||
boxSizing: 'border-box', | |||
transform: leftHanded | |||
? 'perspective(6rem) rotateY(2deg) scaleX(1.375)' | |||
: 'perspective(6rem) rotateY(-2deg) scaleX(1.375)', | |||
transformOrigin: leftHanded ? 'left' : 'right', | |||
}} | |||
> | |||
{strings!.map((pitches, stringNumber) => ( | |||
<div | |||
style={{ | |||
display: 'flex', | |||
flexDirection: leftHanded ? 'row-reverse' : 'row', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
display: 'flex', | |||
flexDirection: leftHanded ? 'row-reverse' : 'row', | |||
width: '5rem', | |||
height: '1rem', | |||
padding: 0, | |||
boxSizing: 'border-box', | |||
}} | |||
> | |||
<input | |||
type="number" | |||
style={{ | |||
display: 'block', | |||
width: '3rem', | |||
height: '1rem', | |||
padding: 0, | |||
boxSizing: 'border-box', | |||
}} | |||
defaultValue={tunings[stringNumber]!} | |||
min={0} | |||
max={127} | |||
/> | |||
<span | |||
style={{ | |||
display: 'block', | |||
width: '2rem', | |||
height: '1rem', | |||
textAlign: leftHanded ? 'left' : 'right', | |||
}} | |||
> | |||
{PITCHES[tunings[stringNumber]! % 12]} | |||
{Math.floor(tunings[stringNumber]! / 12)} | |||
</span> | |||
</div> | |||
<div | |||
style={{ | |||
display: 'block', | |||
position: 'relative', | |||
width: '1rem', | |||
height: '1rem', | |||
flexShrink: 0, | |||
padding: 0, | |||
borderWidth: '0 0.0625rem', | |||
borderStyle: 'none solid', | |||
}} | |||
> | |||
<button | |||
type="button" | |||
style={{ | |||
position: 'relative', | |||
display: 'block', | |||
width: '1rem', | |||
height: '1rem', | |||
flexShrink: 0, | |||
padding: 0, | |||
border: 0, | |||
backgroundColor: 'transparent', | |||
}} | |||
> | |||
<span | |||
style={{ | |||
display: 'block', | |||
width: '100%', | |||
height: `${0.03125 + stringNumber * 0.03125}rem`, | |||
backgroundColor: 'currentColor', | |||
}} | |||
/> | |||
</button> | |||
</div> | |||
{pitches.map((_, i) => ( | |||
<div | |||
style={{ | |||
width: `${(frets! - i + frets! / 2) * 100}%`, | |||
height: '1rem', | |||
position: 'relative', | |||
}} | |||
> | |||
{(i % 12 === 2 || i % 12 === 4 || i % 12 === 6 || i % 12 === 8 || i % 12 === 11) && stringNumber === 0 && ( | |||
<div | |||
style={{ | |||
position: 'absolute', | |||
top: 0, | |||
height: `${tunings.length * 100}%`, | |||
width: '100%', | |||
display: 'flex', | |||
alignItems: 'center', | |||
justifyContent: 'space-around', | |||
flexDirection: 'column', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
width: `${0.75 - i * (1 / 80)}rem`, | |||
height: `${0.75 - i * (1 / 256)}rem`, | |||
backgroundColor: 'currentColor', | |||
opacity: 0.5, | |||
borderRadius: '50%', | |||
}} | |||
/> | |||
{i % 12 === 11 && ( | |||
<div | |||
style={{ | |||
width: `${0.75 - i * (1 / 80)}rem`, | |||
height: `${0.75 - i * (1 / 256)}rem`, | |||
backgroundColor: 'currentColor', | |||
opacity: 0.5, | |||
borderRadius: '50%', | |||
}} | |||
/> | |||
)} | |||
</div> | |||
)} | |||
<button | |||
key={i} | |||
type="button" | |||
style={{ | |||
position: 'relative', | |||
display: 'block', | |||
width: '100%', | |||
height: '1rem', | |||
padding: 0, | |||
borderWidth: '0 0.0625rem', | |||
borderStyle: 'none solid', | |||
backgroundColor: 'transparent', | |||
}} | |||
> | |||
<span | |||
style={{ | |||
display: 'block', | |||
width: '100%', | |||
height: `${0.03125 + stringNumber * 0.03125}rem`, | |||
backgroundColor: 'currentColor', | |||
}} | |||
/> | |||
</button> | |||
</div> | |||
))} | |||
</div> | |||
))} | |||
</div> | |||
) | |||
} | |||
Fretboard.propTypes = propTypes | |||
export default Fretboard |
@@ -0,0 +1,11 @@ | |||
import React, { FC, HTMLAttributes, ReactChild } from 'react' | |||
export interface Props extends HTMLAttributes<HTMLDivElement> { | |||
children?: ReactChild | |||
} | |||
// Please do not use types off of a default export module or else Storybook Docs will suffer. | |||
// see: https://github.com/storybookjs/storybook/issues/9556 | |||
export const Thing: FC<Props> = ({ children }) => { | |||
return <div>{children || `the snozzberries taste like snozzberries`}</div> | |||
} |
@@ -0,0 +1,19 @@ | |||
{ | |||
"include": ["src", "types"], | |||
"compilerOptions": { | |||
"module": "esnext", | |||
"lib": ["dom", "esnext"], | |||
"importHelpers": true, | |||
"declaration": true, | |||
"sourceMap": true, | |||
"rootDir": "./src", | |||
"strict": true, | |||
"noUnusedLocals": true, | |||
"noUnusedParameters": true, | |||
"noImplicitReturns": true, | |||
"noFallthroughCasesInSwitch": true, | |||
"moduleResolution": "node", | |||
"jsx": "react", | |||
"esModuleInterop": true | |||
} | |||
} |