├── .nvmrc
├── .gitignore
├── src
├── index.js
├── components
│ └── Fretboard
│ │ ├── Skins
│ │ ├── BoardGraphicBoxes
│ │ │ ├── index.js
│ │ │ └── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ └── index.test.js.snap
│ │ │ │ └── index.test.js
│ │ ├── FretWrapper.js
│ │ ├── NutGraphicBoxes
│ │ │ ├── __tests__
│ │ │ │ ├── index.test.js
│ │ │ │ └── __snapshots__
│ │ │ │ │ └── index.test.js.snap
│ │ │ └── index.js
│ │ ├── NutGraphicStrings
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── index.test.js.snap
│ │ │ │ └── index.test.js
│ │ │ └── index.js
│ │ ├── BoardGraphicStrings
│ │ │ ├── __tests__
│ │ │ │ ├── index.test.js
│ │ │ │ └── __snapshots__
│ │ │ │ │ └── index.test.js.snap
│ │ │ └── index.js
│ │ ├── __tests__
│ │ │ ├── skins.test.js
│ │ │ ├── fretWrapperBoxes.test.js
│ │ │ ├── fretWrapperStrings.test.js
│ │ │ └── __snapshots__
│ │ │ │ ├── fretWrapperStrings.test.js.snap
│ │ │ │ └── fretWrapperBoxes.test.js.snap
│ │ ├── FretWrapperBoxes.js
│ │ ├── FretWrapperStrings.js
│ │ └── skins.js
│ │ ├── PositionLabels
│ │ ├── Wrapper.js
│ │ ├── FretLabels.js
│ │ ├── __tests__
│ │ │ ├── __snapshots__
│ │ │ │ ├── fretLabels.js.snap
│ │ │ │ ├── label.test.js.snap
│ │ │ │ └── index.test.js.snap
│ │ │ ├── fretLabels.js
│ │ │ ├── index.test.js
│ │ │ └── label.test.js
│ │ ├── Label.js
│ │ └── index.js
│ │ ├── Neck
│ │ ├── ViewPortMain
│ │ │ ├── Wrapper.js
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ ├── wrapper.test.js.snap
│ │ │ │ │ └── index.test.js.snap
│ │ │ │ ├── index.test.js
│ │ │ │ └── wrapper.test.js
│ │ │ └── index.js
│ │ ├── Board
│ │ │ ├── BoardPositions
│ │ │ │ ├── Wrapper.js
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── index.test.js
│ │ │ │ │ └── __snapshots__
│ │ │ │ │ │ └── index.test.js.snap
│ │ │ │ └── index.js
│ │ │ ├── __tests__
│ │ │ │ ├── index.test.js
│ │ │ │ └── __snapshots__
│ │ │ │ │ └── index.test.js.snap
│ │ │ └── index.js
│ │ ├── Position
│ │ │ ├── Wrapper.js
│ │ │ ├── Fret
│ │ │ │ ├── Content
│ │ │ │ │ ├── NoteContent
│ │ │ │ │ │ ├── AccWrapper.js
│ │ │ │ │ │ ├── __tests__
│ │ │ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ │ │ ├── accWrapper.test.js.snap
│ │ │ │ │ │ │ │ └── index.test.js.snap
│ │ │ │ │ │ │ ├── accWrapper.test.js
│ │ │ │ │ │ │ └── index.test.js
│ │ │ │ │ │ ├── Readme.md
│ │ │ │ │ │ └── index.js
│ │ │ │ │ ├── Wrapper.js
│ │ │ │ │ ├── __tests__
│ │ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ │ ├── wrapper.test.js.snap
│ │ │ │ │ │ │ └── index.test.js.snap
│ │ │ │ │ │ ├── wrapper.test.js
│ │ │ │ │ │ └── index.test.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── Wrapper.js
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── wrapper.test.js
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ ├── wrapper.test.js.snap
│ │ │ │ │ │ └── index.test.js.snap
│ │ │ │ │ └── index.test.js
│ │ │ │ ├── Readme.md
│ │ │ │ └── index.js
│ │ │ ├── __tests__
│ │ │ │ ├── wrapper.test.js
│ │ │ │ ├── index.test.js
│ │ │ │ └── __snapshots__
│ │ │ │ │ ├── wrapper.test.js.snap
│ │ │ │ │ └── index.test.js.snap
│ │ │ └── index.js
│ │ ├── ViewPort
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── index.test.js.snap
│ │ │ │ └── index.test.js
│ │ │ └── index.js
│ │ ├── Nut
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── index.test.js.snap
│ │ │ │ └── index.test.js
│ │ │ └── index.js
│ │ ├── OpenPosition
│ │ │ ├── __tests__
│ │ │ │ ├── index.test.js
│ │ │ │ └── __snapshots__
│ │ │ │ │ └── index.test.js.snap
│ │ │ └── index.js
│ │ ├── __tests__
│ │ │ ├── index.test.js
│ │ │ └── __snapshots__
│ │ │ │ └── index.test.js.snap
│ │ └── index.js
│ │ ├── Wrapper.js
│ │ ├── __tests__
│ │ ├── __snapshots__
│ │ │ ├── wrapper.test.js.snap
│ │ │ └── index.test.js.snap
│ │ ├── wrapper.test.js
│ │ └── index.test.js
│ │ ├── Readme.md
│ │ └── index.js
├── themes
│ ├── __tests__
│ │ ├── fretboard-theme.test.js
│ │ └── __snapshots__
│ │ │ └── fretboard-theme.test.js.snap
│ └── fretboard-theme.js
├── lib
│ ├── __tests__
│ │ ├── tonal-helpers.test.js
│ │ ├── fret.test.js
│ │ ├── __snapshots__
│ │ │ └── selection.test.js.snap
│ │ ├── fretboard.test.js
│ │ └── selection.test.js
│ ├── shapes.js
│ ├── tonal-helpers.js
│ ├── fretboard.js
│ ├── fret.js
│ └── selection.js
└── styleguide
│ ├── ThemeWrapper.js
│ ├── ContextProvider
│ └── index.js
│ └── fixtures.js
├── config
└── jestsetup.js
├── .babelrc
├── styleguide.config.js
├── .jestrc.json
├── webpack.config.js
├── .eslintrc
├── package.json
└── README.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | v8.6.0
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | coverage
5 | npm-debug.log
6 | yarn-error.log
7 | stats.json
8 |
9 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | intervalNotes,
3 | chordNotes,
4 | scaleNotes,
5 | } from 'lib/selection'
6 | export { default } from 'components/Fretboard'
7 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/BoardGraphicBoxes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | const BoardGraphicBoxes = () => ( )
5 |
6 |
7 | export default BoardGraphicBoxes
8 |
--------------------------------------------------------------------------------
/src/themes/__tests__/fretboard-theme.test.js:
--------------------------------------------------------------------------------
1 | import theme from '../fretboard-theme'
2 |
3 |
4 | it('should check fretboard-theme against snapshot', () => {
5 | expect(theme).toMatchSnapshot()
6 | })
7 |
--------------------------------------------------------------------------------
/src/components/Fretboard/PositionLabels/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const Wrapper = styled.div`
4 | display: flex;
5 | `
6 |
7 | /** @component */
8 | export default Wrapper
9 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/BoardGraphicBoxes/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`BoardGraphicBoxes component, snapshot 1`] = ` `;
4 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/ViewPortMain/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const Wrapper = styled.div`
4 | height: ${props => props.height}px;
5 | `
6 |
7 | /** @component */
8 | export default Wrapper
9 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const Wrapper = styled.div`
4 | width: 100%;
5 | background-color: ${props => props.theme.background};
6 | `
7 |
8 | /** @component */
9 | export default Wrapper
10 |
--------------------------------------------------------------------------------
/src/components/Fretboard/PositionLabels/FretLabels.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const FretLabels = styled.div`
4 | display: flex;
5 | width: ${props => props.width}%;
6 | `
7 |
8 | /** @component */
9 | export default FretLabels
10 |
--------------------------------------------------------------------------------
/src/components/Fretboard/__tests__/__snapshots__/wrapper.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return wrapper with correct width 1`] = `
4 | .c0 {
5 | width: 100%;
6 | }
7 |
8 |
11 | `;
12 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Board/BoardPositions/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const PositionWrapper = styled.div`
4 | display: flex;
5 | width: 100%;
6 | height: 100%;
7 | `
8 |
9 | /** @component */
10 | export default PositionWrapper
11 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/FretWrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 |
4 | const FretWrapper = styled.div`
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | `
9 |
10 | /** @component */
11 | export default FretWrapper
12 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const Wrapper = styled.div`
4 | display: flex;
5 | flex-flow: column;
6 | height: 100%;
7 | width: ${props => props.width}%;
8 | `
9 |
10 | /** @component */
11 | export default Wrapper
12 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/ViewPort/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ViewPort component, snapshot 1`] = `
4 |
10 |
11 |
12 | `;
13 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/ViewPortMain/__tests__/__snapshots__/wrapper.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return wrapper with correct width 1`] = `
4 | .c0 {
5 | height: 40px;
6 | }
7 |
8 |
12 | `;
13 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Nut/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Nut component, snapshot 1`] = `
4 |
8 |
11 |
12 | `;
13 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/ViewPortMain/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import ViewPort from '../index'
4 |
5 |
6 | it('ViewPort component, snapshot', () => {
7 | const wrapper = shallow( )
8 | expect(wrapper).toMatchSnapshot()
9 | })
10 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/NutGraphicBoxes/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import NutGraphicBoxes from '../index'
4 |
5 |
6 | it('NutGraphicBoxes component, snapshot', () => {
7 | const wrapper = shallow( )
8 | expect(wrapper).toMatchSnapshot()
9 | })
10 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/BoardGraphicBoxes/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import BoardGraphicBoxes from '../index'
4 |
5 |
6 | it('BoardGraphicBoxes component, snapshot', () => {
7 | const wrapper = shallow( )
8 | expect(wrapper).toMatchSnapshot()
9 | })
10 |
--------------------------------------------------------------------------------
/src/components/Fretboard/__tests__/wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import 'jest-styled-components'
4 |
5 | import Wrapper from '../Wrapper'
6 |
7 |
8 | it('should return wrapper with correct width', () => {
9 | const wrapper = shallow( )
10 | expect(wrapper).toMatchSnapshot()
11 | })
12 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/NoteContent/AccWrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const AccWrapper = styled.span`
4 | text-align: center;
5 | vertical-align: top;
6 | font-size: ${props => (props.theme.fontSize - 3)}px;
7 | overflow: hidden;
8 | `
9 |
10 | export default AccWrapper
11 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/NutGraphicStrings/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`NutGraphicStrings component, snapshot 1`] = `
4 |
11 | `;
12 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/NutGraphicBoxes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | const NutGraphicBoxes = () =>
5 |
15 |
16 |
17 | export default NutGraphicBoxes
18 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/ViewPort/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import ViewPort from '../index'
4 |
5 | const props = {
6 | width: 10,
7 | offset: 10,
8 | }
9 |
10 | it('ViewPort component, snapshot', () => {
11 | const wrapper = shallow( )
12 | expect(wrapper).toMatchSnapshot()
13 | })
14 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/NutGraphicBoxes/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`NutGraphicBoxes component, snapshot 1`] = `
4 |
14 | `;
15 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const Wrapper = styled.div`
4 | text-align: center;
5 | vertical-align: middle;
6 | font-size: ${props => props.theme.fontSize}px;
7 | overflow-x: hidden;
8 | text-overflow: clip;
9 | `
10 |
11 | /** @component */
12 | export default Wrapper
13 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const Wrapper = styled.div`
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | width: 100%;
8 | height: 100%;
9 | ${props => props.isClickable && 'cursor: pointer;'}
10 | `
11 |
12 | /** @component */
13 | export default Wrapper
14 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/NutGraphicStrings/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import NutGraphicStrings from '../index'
4 |
5 | const props = {
6 | nrOfStrings: 6,
7 | }
8 |
9 | it('NutGraphicStrings component, snapshot', () => {
10 | const wrapper = shallow( )
11 | expect(wrapper).toMatchSnapshot()
12 | })
13 |
--------------------------------------------------------------------------------
/config/jestsetup.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import 'babel-polyfill'
3 | import 'raf/polyfill'
4 |
5 | import { shallow, render, mount, configure } from 'enzyme'
6 | import Adapter from 'enzyme-adapter-react-16'
7 |
8 | configure({ adapter: new Adapter() })
9 |
10 | global.shallow = shallow
11 | global.render = render
12 | global.mount = mount
13 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-object-rest-spread"
4 | ],
5 | "env": {
6 | "production": {
7 | "presets": [
8 | ["env", { "modules": false }],
9 | "react"
10 | ]
11 | },
12 | "development": {
13 | "presets": ["env", "react"]
14 | },
15 | "test": {
16 | "presets": ["env", "react"]
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/__tests__/wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import 'jest-styled-components'
3 |
4 | import Wrapper from '../Wrapper'
5 |
6 | const props = {
7 | width: 10,
8 | }
9 |
10 | it('should return wrapper with correct width', () => {
11 | const wrapper = shallow( )
12 | expect(wrapper).toMatchSnapshot()
13 | })
14 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/NoteContent/__tests__/__snapshots__/accWrapper.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return wrapper with correct font size 1`] = `
4 | .c0 {
5 | text-align: center;
6 | vertical-align: top;
7 | font-size: 9px;
8 | overflow: hidden;
9 | }
10 |
11 |
14 | `;
15 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/ViewPortMain/__tests__/wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import 'jest-styled-components'
4 |
5 | import Wrapper from '../Wrapper'
6 |
7 | const props = {
8 | height: 40,
9 | }
10 |
11 | it('should return wrapper with correct width', () => {
12 | const wrapper = shallow( )
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 |
--------------------------------------------------------------------------------
/src/components/Fretboard/PositionLabels/__tests__/__snapshots__/fretLabels.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return FretLabels snapshot 1`] = `
4 | .c0 {
5 | display: -webkit-box;
6 | display: -webkit-flex;
7 | display: -ms-flexbox;
8 | display: flex;
9 | width: 10%;
10 | }
11 |
12 |
16 | `;
17 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Position from '../index'
4 |
5 | const props = {
6 | tuning: ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'],
7 | width: 12,
8 | pos: 5,
9 | }
10 |
11 | it('Position component, snapshot', () => {
12 | const wrapper = shallow( )
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/__tests__/__snapshots__/wrapper.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return wrapper with correct width 1`] = `
4 | .c0 {
5 | text-align: center;
6 | vertical-align: middle;
7 | font-size: 12px;
8 | overflow-x: hidden;
9 | text-overflow: clip;
10 | }
11 |
12 |
15 | `;
16 |
--------------------------------------------------------------------------------
/src/components/Fretboard/PositionLabels/__tests__/fretLabels.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import 'jest-styled-components'
3 |
4 | import FretLabels from '../FretLabels'
5 |
6 | const defaultProps = {
7 | width: 10,
8 | }
9 |
10 | it('should return FretLabels snapshot', () => {
11 | const wrapper = shallow( )
12 |
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/BoardGraphicStrings/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import BoardGraphicStrings from '../index'
4 |
5 | const props = {
6 | nrOfStrings: 6,
7 | nrOfFrets: 12,
8 | }
9 |
10 | it('BoardGraphicStrings component, snapshot', () => {
11 | const wrapper = shallow( )
12 | expect(wrapper).toMatchSnapshot()
13 | })
14 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/OpenPosition/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import OpenPosition from '../index'
4 |
5 | const props = {
6 | tuning: ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'],
7 | width: 10,
8 | }
9 |
10 | it('OpenPosition component, snapshot', () => {
11 | const wrapper = shallow( )
12 |
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 |
16 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Board/BoardPositions/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import BoardPositions from '../index'
4 |
5 | const props = {
6 | tuning: ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'],
7 | nrOfFrets: 12,
8 | }
9 |
10 | it('BoardPositions component, snapshot', () => {
11 | const wrapper = shallow( )
12 | expect(wrapper).toMatchSnapshot()
13 | })
14 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Nut/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Nut from '../index'
4 |
5 | const props = {
6 | tuning: ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'],
7 | skinType: 'boxes',
8 | width: 5,
9 | offset: 10,
10 | }
11 |
12 | it('Nut component, snapshot', () => {
13 | const wrapper = shallow( )
14 |
15 | expect(wrapper).toMatchSnapshot()
16 | })
17 |
18 |
--------------------------------------------------------------------------------
/src/components/Fretboard/PositionLabels/Label.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const Label = styled.div`
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | height: ${props => props.theme.dimensions.stringHeight}px;
8 | width: ${props => props.width}%;
9 | font-size: ${props => props.theme.fontSize}px;
10 | `
11 |
12 | /** @component */
13 | export default Label
14 |
--------------------------------------------------------------------------------
/src/components/Fretboard/PositionLabels/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import PositionLabels from '../index'
4 |
5 | const defaultProps = {
6 | nutWidth: 1,
7 | openWidth: 10,
8 | nrOfFrets: 12,
9 | }
10 |
11 | it('should return PositionLabels snapshot', () => {
12 | const wrapper = shallow( )
13 |
14 | expect(wrapper).toMatchSnapshot()
15 | })
16 |
--------------------------------------------------------------------------------
/src/lib/__tests__/tonal-helpers.test.js:
--------------------------------------------------------------------------------
1 | import { transpose, isEqual } from '../tonal-helpers'
2 |
3 | it('should transpose by semitones', () => {
4 | expect(transpose('E2')(5)).toBe('A2')
5 | })
6 |
7 | it('should match pitches and pcs', () => {
8 | const isEqualE2 = isEqual('E2')
9 | expect(isEqualE2('E2')).toBe(true)
10 | expect(isEqualE2('E3')).toBe(false)
11 | expect(isEqualE2('E')).toBe(true)
12 | })
13 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/__tests__/skins.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | boardGraphic,
3 | // nutGraphic,
4 | fretWrapper,
5 | } from '../skins'
6 |
7 | describe('skins module', () => {
8 | it('boardGraphic', () => {
9 | expect(boardGraphic('strings').name).toBe('BoardGraphicStrings')
10 | })
11 |
12 | it('fretWrapper', () => {
13 | expect(fretWrapper('strings').name).toBe('StyledComponent')
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/__tests__/wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import theme from 'themes/fretboard-theme'
3 | import 'jest-styled-components'
4 |
5 | import Wrapper from '../Wrapper'
6 |
7 | const props = {
8 | theme,
9 | }
10 |
11 | it('should return wrapper with correct width', () => {
12 | const wrapper = shallow( )
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 |
--------------------------------------------------------------------------------
/src/components/Fretboard/PositionLabels/__tests__/label.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import 'jest-styled-components'
3 | import theme from 'themes/fretboard-theme'
4 |
5 | import Label from '../Label'
6 |
7 | const defaultProps = {
8 | width: 10,
9 | theme,
10 | }
11 |
12 | it('should return Label snapshot', () => {
13 | const wrapper = shallow( )
14 |
15 | expect(wrapper).toMatchSnapshot()
16 | })
17 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Board/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Board from '../index'
4 |
5 | const props = {
6 | tuning: ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'],
7 | nrOfFrets: 12,
8 | skinType: 'strings',
9 | width: 90,
10 | offset: 10,
11 | }
12 |
13 | it('Board component, snapshot', () => {
14 | const wrapper = shallow( )
15 |
16 | expect(wrapper).toMatchSnapshot()
17 | })
18 |
19 |
--------------------------------------------------------------------------------
/src/lib/shapes.js:
--------------------------------------------------------------------------------
1 | import pt from 'prop-types'
2 |
3 | export const locShape = pt.shape({
4 | str: pt.number.isRequired,
5 | pos: pt.number.isRequired,
6 | })
7 |
8 | export const noteSelectionShape = pt.shape({
9 | note: pt.string.isRequired,
10 | status: pt.string,
11 | label: pt.string,
12 | })
13 |
14 | export const locSelectionShape = pt.shape({
15 | loc: locShape.isRequired,
16 | status: pt.string,
17 | label: pt.string,
18 | })
19 |
20 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/NoteContent/__tests__/accWrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import theme from 'themes/fretboard-theme'
3 | import 'jest-styled-components'
4 |
5 | import AccWrapper from '../AccWrapper'
6 |
7 | const props = {
8 | theme,
9 | }
10 |
11 | it('should return wrapper with correct font size', () => {
12 | const wrapper = shallow( )
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 |
--------------------------------------------------------------------------------
/src/styleguide/ThemeWrapper.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { ThemeProvider } from 'styled-components'
3 | import theme from 'themes/fretboard-theme'
4 |
5 | /* eslint-disable react/prefer-stateless-function, react/prop-types */
6 | export default class ThemeWrapper extends Component {
7 | render() {
8 | return (
9 |
10 | {this.props.children}
11 |
12 | )
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Neck from '../index'
4 |
5 | const props = {
6 | tuning: ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'],
7 | nrOfFrets: 12,
8 | dimensions: {
9 | openWidth: 10,
10 | nutWidth: 1,
11 | stringHeight: 30,
12 | },
13 | skinType: 'boxes',
14 | }
15 |
16 | it('ViewPort component, snapshot', () => {
17 | const wrapper = shallow( )
18 | expect(wrapper).toMatchSnapshot()
19 | })
20 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/__tests__/__snapshots__/wrapper.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return wrapper with correct width 1`] = `
4 | .c0 {
5 | display: -webkit-box;
6 | display: -webkit-flex;
7 | display: -ms-flexbox;
8 | display: flex;
9 | -webkit-flex-flow: column;
10 | -ms-flex-flow: column;
11 | flex-flow: column;
12 | height: 100%;
13 | width: 10%;
14 | }
15 |
16 |
20 | `;
21 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/FretWrapperBoxes.js:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components'
2 | import FretWrapper from './FretWrapper'
3 |
4 |
5 | const background = ({ color, isHighlighted }) =>
6 | isHighlighted && css`background-color: ${color};`
7 |
8 | const Boxes = FretWrapper.extend`
9 | height: 80%;
10 | width: 94%;
11 | margin: 10% 3%;
12 | border: 1px solid darkgray;
13 | border-radius: 3px;
14 | ${props => background(props)}
15 | `
16 |
17 | /** @component */
18 | export default Boxes
19 |
--------------------------------------------------------------------------------
/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | components: 'src/components/**/*.js',
5 | styleguideComponents: {
6 | Wrapper: path.join(__dirname, 'src/styleguide/ThemeWrapper'),
7 | },
8 | skipComponentsWithoutExample: true,
9 | getExampleFilename(componentPath) {
10 | const indexRegex = /index\.jsx?$/
11 | return componentPath.match(indexRegex)
12 | ? componentPath.replace(indexRegex, 'Readme.md')
13 | : componentPath.replace(/\.jsx?$/, '.md')
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Board/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Board component, snapshot 1`] = `
4 |
8 |
12 |
25 |
26 | `;
27 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/ViewPort/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 |
4 | const ViewPort = ({
5 | width,
6 | offset,
7 | children,
8 | }) =>
9 |
15 | {children}
16 |
17 |
18 | ViewPort.propTypes = {
19 | width: pt.number.isRequired,
20 | offset: pt.number.isRequired,
21 | children: pt.node,
22 | }
23 |
24 | ViewPort.defaultProps = {
25 | children: ,
26 | }
27 |
28 | export default ViewPort
29 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/ViewPortMain/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ViewPort component, snapshot 1`] = `
4 |
7 |
22 |
23 |
24 |
25 | `;
26 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/__tests__/wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import 'jest-styled-components'
3 |
4 | import Wrapper from '../Wrapper'
5 |
6 |
7 | it('should return an unclickable wrapper', () => {
8 | const props = {
9 | isClickable: false,
10 | }
11 | const wrapper = shallow( )
12 | expect(wrapper).toMatchSnapshot()
13 | })
14 |
15 | it('should return an unclickable wrapper', () => {
16 | const props = {
17 | isClickable: true,
18 | }
19 | const wrapper = shallow( )
20 | expect(wrapper).toMatchSnapshot()
21 | })
22 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/OpenPosition/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`OpenPosition component, snapshot 1`] = `
4 |
8 |
12 |
26 |
27 |
28 | `;
29 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/OpenPosition/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 |
4 | import Position from 'components/Fretboard/Neck/Position'
5 | import ViewPort from 'components/Fretboard/Neck/ViewPort'
6 |
7 | const OpenPosition = ({ width, tuning }) =>
8 |
9 |
10 |
11 |
12 |
13 |
14 | OpenPosition.propTypes = {
15 | tuning: pt.arrayOf(pt.string).isRequired,
16 | width: pt.number.isRequired,
17 | }
18 |
19 | export default OpenPosition
20 |
--------------------------------------------------------------------------------
/.jestrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "setupFiles": ["./config/jestsetup.js"],
3 | "snapshotSerializers": [
4 | "/node_modules/enzyme-to-json/serializer"
5 | ],
6 | "transform": {
7 | "^.+\\.js$": "babel-jest"
8 | },
9 | "collectCoverageFrom": [
10 | "src/{components,lib}/**/*.{js,jsx}",
11 | "!src/themes/**",
12 | "!src/styleguide/**"
13 | ],
14 | "moduleDirectories": [
15 | "node_modules",
16 | "src"
17 | ],
18 | "roots": ["/src"],
19 | "moduleNameMapper": {
20 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/config/__mocks__/fileMock.js"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/NutGraphicStrings/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 | import { stringCenter } from 'lib/fretboard'
4 |
5 |
6 | const NutGraphicStrings = ({ nrOfStrings }) => {
7 | const top = stringCenter(nrOfStrings)(0)
8 | const bottom = (stringCenter(nrOfStrings)(nrOfStrings - 1) - top)
9 |
10 | return (
11 |
18 | )
19 | }
20 |
21 | NutGraphicStrings.propTypes = {
22 | nrOfStrings: pt.number.isRequired,
23 | }
24 |
25 | export default NutGraphicStrings
26 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Content component, content not note 1`] = `
4 |
5 | foo
6 |
7 | `;
8 |
9 | exports[`Content component, showEnharmonics: true 1`] = `
10 |
11 |
16 |
17 | `;
18 |
19 | exports[`Content component, snapshot 1`] = `
20 |
21 |
26 |
27 | `;
28 |
--------------------------------------------------------------------------------
/src/components/Fretboard/PositionLabels/__tests__/__snapshots__/label.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return Label snapshot 1`] = `
4 | .c0 {
5 | display: -webkit-box;
6 | display: -webkit-flex;
7 | display: -ms-flexbox;
8 | display: flex;
9 | -webkit-box-pack: center;
10 | -webkit-justify-content: center;
11 | -ms-flex-pack: center;
12 | justify-content: center;
13 | -webkit-align-items: center;
14 | -webkit-box-align: center;
15 | -ms-flex-align: center;
16 | align-items: center;
17 | height: 30px;
18 | width: 10%;
19 | font-size: 12px;
20 | }
21 |
22 |
26 | `;
27 |
--------------------------------------------------------------------------------
/src/lib/tonal-helpers.js:
--------------------------------------------------------------------------------
1 | import { compose, flip } from 'ramda'
2 | import { Note } from 'tonal'
3 |
4 | export const transpose = (note, useSharps = true) => smtns =>
5 | compose(
6 | flip(Note.fromMidi)(useSharps),
7 | midi => midi + smtns,
8 | Note.midi,
9 | )(note)
10 |
11 | export const comparePitch = (note1, note2) =>
12 | Note.midi(note1) === Note.midi(note2)
13 |
14 | export const comparePc = (note1, note2) =>
15 | Note.chroma(note1) === Note.chroma(note2)
16 |
17 | export const isPc = note => Note.oct(note) === null
18 |
19 | export const isEqual = note1 => note2 => (
20 | isPc(note2)
21 | ? comparePc(note1, note2)
22 | : comparePitch(note1, note2)
23 | )
24 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 | import { reverse } from 'ramda'
4 | import { transpose } from 'lib/tonal-helpers'
5 | import Wrapper from './Wrapper'
6 | import Fret from './Fret'
7 |
8 | const Position = ({ tuning, width, pos }) =>
9 |
10 | {reverse(tuning).map((openNote, str) => {
11 | const note = transpose(openNote)(pos)
12 | const loc = { str, pos }
13 | return
14 | })}
15 |
16 |
17 |
18 | Position.propTypes = {
19 | tuning: pt.arrayOf(pt.string).isRequired,
20 | width: pt.number.isRequired,
21 | pos: pt.number.isRequired,
22 | }
23 |
24 | export default Position
25 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/ViewPortMain/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 |
4 | import Wrapper from './Wrapper'
5 |
6 | const ViewPortMain = ({
7 | height,
8 | children,
9 | }) =>
10 |
11 |
22 | {children}
23 |
24 |
25 |
26 | ViewPortMain.propTypes = {
27 | height: pt.number.isRequired,
28 | children: pt.node.isRequired,
29 | }
30 |
31 | export default ViewPortMain
32 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Nut/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 | import { nutGraphic } from 'components/Fretboard/Skins/skins'
4 | import ViewPort from 'components/Fretboard/Neck/ViewPort'
5 |
6 | const Nut = ({
7 | tuning,
8 | width,
9 | offset,
10 | skinType,
11 | }) => {
12 | const NutGraphic = nutGraphic(skinType)
13 | const nrOfStrings = tuning.length
14 | return (
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | Nut.propTypes = {
22 | tuning: pt.arrayOf(pt.string).isRequired,
23 | width: pt.number.isRequired,
24 | offset: pt.number.isRequired,
25 | skinType: pt.string.isRequired,
26 | }
27 |
28 | export default Nut
29 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/__tests__/fretWrapperBoxes.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import 'jest-styled-components'
3 |
4 | import theme from 'themes/fretboard-theme'
5 | import FretWrapperBoxes from '../FretWrapperBoxes'
6 |
7 | const defaultProps = {
8 | isHighlighted: true,
9 | theme,
10 | }
11 |
12 | describe('Boxes wrapper', () => {
13 | it('correct css, isHighlighted=true', () => {
14 | const wrapper = shallow( )
15 | expect(wrapper).toMatchSnapshot()
16 | })
17 |
18 | it('correct css, isHighlighted=false', () => {
19 | const props = { ...defaultProps, isHighlighted: false }
20 | const wrapper = shallow( )
21 | expect(wrapper).toMatchSnapshot()
22 | })
23 | })
24 |
25 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/__tests__/fretWrapperStrings.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import 'jest-styled-components'
3 |
4 | import theme from 'themes/fretboard-theme'
5 | import FretWrapperStrings from '../FretWrapperStrings'
6 |
7 | const defaultProps = {
8 | isHighlighted: true,
9 | theme,
10 | }
11 |
12 | describe('FretWrapperStrings wrapper', () => {
13 | it('correct css, isHighlighted=true', () => {
14 | const wrapper = shallow( )
15 | expect(wrapper).toMatchSnapshot()
16 | })
17 |
18 | it('correct css, isHighlighted=false', () => {
19 | const props = { ...defaultProps, isHighlighted: false }
20 | const wrapper = shallow( )
21 | expect(wrapper).toMatchSnapshot()
22 | })
23 | })
24 |
25 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/NoteContent/Readme.md:
--------------------------------------------------------------------------------
1 | ```js
2 |
7 | ```
8 |
9 | ```js
10 |
15 | ```
16 |
17 | ```js
18 |
23 | ```
24 |
25 | ```js
26 |
31 | ```
32 |
33 | ```js
34 |
39 | ```
40 |
41 | ```js
42 |
47 | ```
48 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Readme.md:
--------------------------------------------------------------------------------
1 | ### Generated examples
2 |
3 | ```js
4 | const ContextProvider = require('styleguide/ContextProvider').default
5 | const testSuites = require('styleguide/fixtures').default
6 |
7 | testSuites.map(suite =>
8 |
9 |
{suite.description}
10 | {suite.testCases.map((cs) => {
11 | const context = Object.assign({}, suite.context, cs.context)
12 |
13 | return (
14 |
15 | {cs.description}
16 |
17 |
18 |
19 |
20 | )
21 | })
22 | }
23 |
24 |
25 | )
26 | ```
27 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 | import { isNil } from 'ramda'
4 | import { Note } from 'tonal'
5 |
6 | import Wrapper from './Wrapper'
7 | import NoteContent from './NoteContent'
8 |
9 |
10 | const Content = ({ content, noteType, showEnharmonics }) => {
11 | const isNote = !isNil(Note.name(content))
12 |
13 | return (
14 |
15 | {isNote
16 | ?
20 | : content
21 | }
22 |
23 | )
24 | }
25 |
26 |
27 | Content.propTypes = {
28 | content: pt.string.isRequired,
29 | noteType: pt.string.isRequired,
30 | showEnharmonics: pt.bool.isRequired,
31 | }
32 |
33 | export default Content
34 |
--------------------------------------------------------------------------------
/src/themes/fretboard-theme.js:
--------------------------------------------------------------------------------
1 | export default {
2 | background: 'white',
3 | statusMap: {
4 | selected: 'rgb(255, 252, 127)',
5 | unselected: 'white',
6 | '1P': '#1a0500',
7 | '2m': '#4d0f00',
8 | '2M': '#801a00',
9 | '3m': '#cc2900',
10 | '3M': '#e62e00',
11 | '4P': '#ff3300',
12 | '4A': '#ff5c33',
13 | '5P': '#ff704d',
14 | '6m': '#ff9980',
15 | '6M': '#ffb199',
16 | '7m': '#ffd8cc',
17 | '7M': '#ffebe6',
18 | },
19 | octaveMap: {
20 | 2: 'rgb(95, 190, 244)',
21 | 3: 'rgb(135, 219, 244)',
22 | 4: 'rgb(205, 240, 247)',
23 | 5: 'rgb(242, 246, 247)',
24 | },
25 | fontSize: 12,
26 | dimensions: {
27 | openWidth: 10,
28 | nutWidth: 0.75,
29 | stringHeight: 30,
30 | },
31 | skins: {
32 | strings: {
33 | highlightSize: 85,
34 | highlightBorder: '1px solid gray',
35 | },
36 | },
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Board/BoardPositions/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 | import { times } from 'ramda'
4 | import { fretWidth } from 'lib/fretboard'
5 |
6 | import Position from 'components/Fretboard/Neck/Position'
7 | import Wrapper from './Wrapper'
8 |
9 | const positions = (tuning, nrOfFrets) => (n) => {
10 | const width = fretWidth(nrOfFrets)(n)
11 | const pos = n + 1 // set 1st position to 1, not 0
12 | return
13 | }
14 |
15 | const BoardPositions = ({ tuning, nrOfFrets }) =>
16 |
17 |
18 | {times(positions(tuning, nrOfFrets), nrOfFrets)}
19 |
20 |
21 |
22 | BoardPositions.propTypes = {
23 | tuning: pt.arrayOf(pt.string).isRequired,
24 | nrOfFrets: pt.number.isRequired,
25 | }
26 |
27 | export default BoardPositions
28 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Content from '../index'
4 |
5 | const defaultProps = {
6 | content: 'E2',
7 | noteType: 'pitch',
8 | showEnharmonics: false,
9 | }
10 |
11 | it('Content component, snapshot', () => {
12 | const wrapper = shallow( )
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 |
16 | it('Content component, showEnharmonics: true', () => {
17 | const props = {
18 | ...defaultProps,
19 | content: 'F#2',
20 | showEnharmonics: true,
21 | }
22 | const wrapper = shallow( )
23 | expect(wrapper).toMatchSnapshot()
24 | })
25 |
26 | it('Content component, content not note', () => {
27 | const props = {
28 | ...defaultProps,
29 | content: 'foo',
30 | }
31 | const wrapper = shallow( )
32 | expect(wrapper).toMatchSnapshot()
33 | })
34 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/FretWrapperStrings.js:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components'
2 | import FretWrapper from './FretWrapper'
3 |
4 | const highlightCSS = ({ theme, color }) => {
5 | const radiusPerc = theme.skins.strings.highlightSize
6 | const radius = Math.floor((theme.dimensions.stringHeight / 100) * radiusPerc)
7 |
8 | return css`
9 | width: ${radius}px;
10 | height: ${radius}px;
11 | border-radius: 50%;
12 | background-color: ${color};
13 | border: ${props => props.theme.skins.strings.highlightBorder}
14 | `
15 | }
16 |
17 | const defaultCSS = ({ theme }) =>
18 | css`
19 | padding: 0 5%;
20 | max-width: 60%;
21 | background-color: ${theme.background};
22 | `
23 |
24 | const FretWrapperStrings = FretWrapper.extend`
25 | ${props => (props.isHighlighted
26 | ? highlightCSS(props)
27 | : defaultCSS(props))}
28 | `
29 |
30 | /** @component */
31 | export default FretWrapperStrings
32 |
--------------------------------------------------------------------------------
/src/styleguide/ContextProvider/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import pt from 'prop-types'
3 | import { noteSelectionShape, locSelectionShape } from 'lib/shapes'
4 |
5 | /* eslint-disable react/prefer-stateless-function, react/prop-types */
6 | class ContextProvider extends Component {
7 | getChildContext() {
8 | return this.props.context
9 | }
10 | render() {
11 | return (
12 |
13 | {this.props.children}
14 |
15 | )
16 | }
17 | }
18 |
19 | ContextProvider.childContextTypes = {
20 | skinType: pt.string,
21 | noteType: pt.string,
22 | showNotes: pt.bool,
23 | showSelectionLabels: pt.bool,
24 | highlightOctaves: pt.bool,
25 | highlightSelections: pt.bool,
26 | showEnharmonics: pt.bool,
27 | selectedNotes: pt.arrayOf(noteSelectionShape),
28 | selectedLocations: pt.arrayOf(locSelectionShape),
29 | clickAction: pt.func,
30 | }
31 |
32 | export default ContextProvider
33 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Board/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 | import { boardGraphic } from 'components/Fretboard/Skins/skins'
4 |
5 | import ViewPort from 'components/Fretboard/Neck/ViewPort'
6 | import BoardPositions from './BoardPositions'
7 |
8 | const Board = ({
9 | tuning,
10 | nrOfFrets,
11 | skinType,
12 | width,
13 | offset,
14 | }) => {
15 | const BoardGraphic = boardGraphic(skinType)
16 | const nrOfStrings = tuning.length
17 | return (
18 |
19 |
20 |
21 |
22 | )
23 | }
24 |
25 | Board.propTypes = {
26 | tuning: pt.arrayOf(pt.string).isRequired,
27 | nrOfFrets: pt.number.isRequired,
28 | skinType: pt.string.isRequired,
29 | width: pt.number.isRequired,
30 | offset: pt.number.isRequired,
31 | }
32 |
33 | export default Board
34 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ViewPort component, snapshot 1`] = `
4 |
7 |
20 |
35 |
51 |
52 | `;
53 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/skins.js:
--------------------------------------------------------------------------------
1 | import BoardGraphicBoxes from 'components/Fretboard/Skins/BoardGraphicBoxes'
2 | import BoardGraphicStrings from 'components/Fretboard/Skins/BoardGraphicStrings'
3 | import NutGraphicBoxes from 'components/Fretboard/Skins/NutGraphicBoxes'
4 | import NutGraphicStrings from 'components/Fretboard/Skins/NutGraphicStrings'
5 | import FretWrapperBoxes from 'components/Fretboard/Skins/FretWrapperBoxes'
6 | import FretWrapperStrings from 'components/Fretboard/Skins/FretWrapperStrings'
7 |
8 | const skinsMap = {
9 | boxes: {
10 | board: BoardGraphicBoxes,
11 | nut: NutGraphicBoxes,
12 | fret: FretWrapperBoxes,
13 | },
14 | strings: {
15 | board: BoardGraphicStrings,
16 | nut: NutGraphicStrings,
17 | fret: FretWrapperStrings,
18 | },
19 | }
20 |
21 | export const boardGraphic = skinType => skinsMap[skinType].board
22 | export const nutGraphic = skinType => skinsMap[skinType].nut
23 | export const fretWrapper = skinType => skinsMap[skinType].fret
24 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 |
4 | module.exports = {
5 | mode: 'production',
6 | entry: './src/index.js',
7 | output: {
8 | path: path.resolve(__dirname, 'dist'),
9 | filename: 'react-fretboard.js',
10 | library: 'react-fretboard',
11 | libraryTarget: 'commonjs2',
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.js$/,
17 | include: path.resolve(__dirname, 'src'),
18 | exclude: /(node_modules|build)/,
19 | use: {
20 | loader: 'babel-loader',
21 | options: {
22 | presets: ['env'],
23 | },
24 | },
25 | },
26 | ],
27 | },
28 | plugins: [
29 | new webpack.NamedModulesPlugin(),
30 | ],
31 | resolve: {
32 | modules: ['src', 'node_modules'],
33 | extensions: [
34 | '.js',
35 | '.jsx',
36 | ],
37 | },
38 | externals: {
39 | react: true,
40 | 'styled-components': true,
41 | },
42 | }
43 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "sourceType": "module",
4 | "ecmaFeatures": {
5 | "jsx": true
6 | }
7 | },
8 | "extends": ["airbnb"],
9 | "env": {
10 | "browser": true,
11 | "node": true,
12 | "jest": true,
13 | "es6": true
14 | },
15 | "globals": {
16 | "shallow": false,
17 | "mount": false,
18 | "render": false
19 | },
20 | "plugins": ["import", "react"],
21 | "rules": {
22 | "no-console": 1,
23 | "semi": ["error", "never"],
24 | "max-len": ["error", { "ignoreComments": true }],
25 | "import/no-unresolved": 2,
26 | "import/no-named-as-default": 0,
27 | "import/extensions": ["error", "ignorePackages", {
28 | "js": "never",
29 | "mjs": "never",
30 | "jsx": "never"
31 | }],
32 | "react/jsx-uses-react": 2,
33 | "react/jsx-uses-vars": 2,
34 | "react/react-in-jsx-scope": 2,
35 | "react/jsx-filename-extension": 0,
36 | "react/jsx-wrap-multilines": 0
37 | },
38 | "settings": {
39 | "import/resolver": "webpack"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Fretboard/PositionLabels/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 | import { times } from 'ramda'
4 | import { fretWidth } from 'lib/fretboard'
5 |
6 | import Label from './Label'
7 | import Wrapper from './Wrapper'
8 | import FretLabels from './FretLabels'
9 |
10 | const labels = ['open', 'I', '', 'III', '', 'V', '', 'VII', '', '', 'X', '', 'XII']
11 |
12 | const fretLabels = nrOfFrets => n =>
13 |
17 | {labels[n + 1]}
18 |
19 |
20 | const PositionLabels = ({ openWidth, nutWidth, nrOfFrets }) =>
21 |
22 | {labels[0]}
23 |
24 |
25 | {times(fretLabels(nrOfFrets), nrOfFrets)}
26 |
27 |
28 |
29 |
30 | PositionLabels.propTypes = {
31 | nutWidth: pt.number.isRequired,
32 | openWidth: pt.number.isRequired,
33 | nrOfFrets: pt.number.isRequired,
34 | }
35 |
36 | export default PositionLabels
37 |
--------------------------------------------------------------------------------
/src/themes/__tests__/__snapshots__/fretboard-theme.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should check fretboard-theme against snapshot 1`] = `
4 | Object {
5 | "background": "white",
6 | "dimensions": Object {
7 | "nutWidth": 0.75,
8 | "openWidth": 10,
9 | "stringHeight": 30,
10 | },
11 | "fontSize": 12,
12 | "octaveMap": Object {
13 | "2": "rgb(95, 190, 244)",
14 | "3": "rgb(135, 219, 244)",
15 | "4": "rgb(205, 240, 247)",
16 | "5": "rgb(242, 246, 247)",
17 | },
18 | "skins": Object {
19 | "strings": Object {
20 | "highlightBorder": "1px solid gray",
21 | "highlightSize": 85,
22 | },
23 | },
24 | "statusMap": Object {
25 | "1P": "#1a0500",
26 | "2M": "#801a00",
27 | "2m": "#4d0f00",
28 | "3M": "#e62e00",
29 | "3m": "#cc2900",
30 | "4A": "#ff5c33",
31 | "4P": "#ff3300",
32 | "5P": "#ff704d",
33 | "6M": "#ffb199",
34 | "6m": "#ff9980",
35 | "7M": "#ffebe6",
36 | "7m": "#ffd8cc",
37 | "selected": "rgb(255, 252, 127)",
38 | "unselected": "white",
39 | },
40 | }
41 | `;
42 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/NoteContent/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import NoteContent from '../index'
4 |
5 | const defaultProps = {
6 | note: 'E2',
7 | noteType: 'pitch',
8 | showEnharmonics: false,
9 | }
10 |
11 | describe('NoteContent component', () => {
12 | it('E2, pitch, no enh ', () => {
13 | const wrapper = shallow( )
14 | expect(wrapper).toMatchSnapshot()
15 | })
16 |
17 | it('E2, pitch, enh, ', () => {
18 | const props = { ...defaultProps, showEnharmonics: true }
19 | const wrapper = shallow( )
20 | expect(wrapper).toMatchSnapshot()
21 | })
22 |
23 | it('F#2, pitch, no enh , ', () => {
24 | const props = { ...defaultProps, note: 'F#2' }
25 | const wrapper = shallow( )
26 | expect(wrapper).toMatchSnapshot()
27 | })
28 |
29 | it('F#2, pc, enh, ', () => {
30 | const props = {
31 | note: 'F#2',
32 | noteType: 'pc',
33 | showEnharmonics: true,
34 | }
35 | const wrapper = shallow( )
36 | expect(wrapper).toMatchSnapshot()
37 | })
38 | })
39 |
40 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/__tests__/__snapshots__/wrapper.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return an unclickable wrapper 1`] = `
4 | .c0 {
5 | display: -webkit-box;
6 | display: -webkit-flex;
7 | display: -ms-flexbox;
8 | display: flex;
9 | -webkit-box-pack: center;
10 | -webkit-justify-content: center;
11 | -ms-flex-pack: center;
12 | justify-content: center;
13 | -webkit-align-items: center;
14 | -webkit-box-align: center;
15 | -ms-flex-align: center;
16 | align-items: center;
17 | width: 100%;
18 | height: 100%;
19 | }
20 |
21 |
24 | `;
25 |
26 | exports[`should return an unclickable wrapper 2`] = `
27 | .c0 {
28 | display: -webkit-box;
29 | display: -webkit-flex;
30 | display: -ms-flexbox;
31 | display: flex;
32 | -webkit-box-pack: center;
33 | -webkit-justify-content: center;
34 | -ms-flex-pack: center;
35 | justify-content: center;
36 | -webkit-align-items: center;
37 | -webkit-box-align: center;
38 | -ms-flex-align: center;
39 | align-items: center;
40 | width: 100%;
41 | height: 100%;
42 | cursor: pointer;
43 | }
44 |
45 |
48 | `;
49 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Position component, snapshot 1`] = `
4 |
7 |
17 |
27 |
37 |
47 |
57 |
67 |
68 | `;
69 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 |
4 | import ViewPortMain from './ViewPortMain'
5 | import OpenPosition from './OpenPosition'
6 | import Nut from './Nut'
7 | import Board from './Board'
8 |
9 | const Neck = ({
10 | tuning,
11 | nrOfFrets,
12 | dimensions,
13 | skinType,
14 | }) => {
15 | const { openWidth, nutWidth, stringHeight } = dimensions
16 | const boardHeight = tuning.length * stringHeight
17 | const boardWidth = 100 - openWidth - nutWidth
18 |
19 | return (
20 |
21 |
25 |
31 |
38 |
39 | )
40 | }
41 |
42 |
43 | Neck.propTypes = {
44 | tuning: pt.arrayOf(pt.string).isRequired,
45 | nrOfFrets: pt.number.isRequired,
46 | dimensions: pt.shape({}).isRequired,
47 | skinType: pt.string.isRequired,
48 | }
49 |
50 | export default Neck
51 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/__tests__/__snapshots__/fretWrapperStrings.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`FretWrapperStrings wrapper correct css, isHighlighted=false 1`] = `
4 | .c0 {
5 | display: -webkit-box;
6 | display: -webkit-flex;
7 | display: -ms-flexbox;
8 | display: flex;
9 | -webkit-box-pack: center;
10 | -webkit-justify-content: center;
11 | -ms-flex-pack: center;
12 | justify-content: center;
13 | -webkit-align-items: center;
14 | -webkit-box-align: center;
15 | -ms-flex-align: center;
16 | align-items: center;
17 | padding: 0 5%;
18 | max-width: 60%;
19 | background-color: white;
20 | }
21 |
22 |
25 | `;
26 |
27 | exports[`FretWrapperStrings wrapper correct css, isHighlighted=true 1`] = `
28 | .c0 {
29 | display: -webkit-box;
30 | display: -webkit-flex;
31 | display: -ms-flexbox;
32 | display: flex;
33 | -webkit-box-pack: center;
34 | -webkit-justify-content: center;
35 | -ms-flex-pack: center;
36 | justify-content: center;
37 | -webkit-align-items: center;
38 | -webkit-box-align: center;
39 | -ms-flex-align: center;
40 | align-items: center;
41 | width: 25px;
42 | height: 25px;
43 | border-radius: 50%;
44 | border: 1px solid gray;
45 | }
46 |
47 |
50 | `;
51 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/__tests__/__snapshots__/fretWrapperBoxes.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Boxes wrapper correct css, isHighlighted=false 1`] = `
4 | .c0 {
5 | display: -webkit-box;
6 | display: -webkit-flex;
7 | display: -ms-flexbox;
8 | display: flex;
9 | -webkit-box-pack: center;
10 | -webkit-justify-content: center;
11 | -ms-flex-pack: center;
12 | justify-content: center;
13 | -webkit-align-items: center;
14 | -webkit-box-align: center;
15 | -ms-flex-align: center;
16 | align-items: center;
17 | height: 80%;
18 | width: 94%;
19 | margin: 10% 3%;
20 | border: 1px solid darkgray;
21 | border-radius: 3px;
22 | }
23 |
24 |
27 | `;
28 |
29 | exports[`Boxes wrapper correct css, isHighlighted=true 1`] = `
30 | .c0 {
31 | display: -webkit-box;
32 | display: -webkit-flex;
33 | display: -ms-flexbox;
34 | display: flex;
35 | -webkit-box-pack: center;
36 | -webkit-justify-content: center;
37 | -ms-flex-pack: center;
38 | justify-content: center;
39 | -webkit-align-items: center;
40 | -webkit-box-align: center;
41 | -ms-flex-align: center;
42 | align-items: center;
43 | height: 80%;
44 | width: 94%;
45 | margin: 10% 3%;
46 | border: 1px solid darkgray;
47 | border-radius: 3px;
48 | }
49 |
50 |
53 | `;
54 |
--------------------------------------------------------------------------------
/src/components/Fretboard/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Fretboard from '../index'
4 |
5 | const defaultProps = {
6 | tuning: ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'],
7 | nrOfFrets: 12,
8 | }
9 |
10 | it('Fretboard component, snapshot', () => {
11 | const wrapper = shallow( )
12 |
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 |
16 | it('Fretboard component with skinType strings, snapshot', () => {
17 | const props = { ...defaultProps, skinType: 'strings' }
18 | const wrapper = shallow( )
19 |
20 | expect(wrapper).toMatchSnapshot()
21 | })
22 |
23 | it('should return correct childContext', () => {
24 | const props = { ...defaultProps, skinType: 'strings' }
25 | const wrapper = shallow( )
26 |
27 | expect(wrapper.instance().getChildContext().skinType).toEqual('strings')
28 | })
29 |
30 | it('should return component w/out positionLabels', () => {
31 | const props = { ...defaultProps, showPositionLabels: false }
32 | const wrapper = shallow( )
33 |
34 | expect(wrapper).toMatchSnapshot()
35 | })
36 |
37 | it('should check the clickAction', () => {
38 | const wrapper = shallow( )
39 | const { clickAction } = wrapper.instance().getChildContext()
40 |
41 | expect(clickAction).toBeUndefined()
42 | })
43 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/BoardGraphicStrings/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 | import { times, range } from 'ramda'
4 | import { stringCenter, fretOffset } from 'lib/fretboard'
5 |
6 |
7 | const stringLine = nrOfStrings => (str) => {
8 | const y = stringCenter(nrOfStrings)(str)
9 | return (
10 | )
17 | }
18 |
19 | const fretLineBounds = nrOfStrings => (
20 | {
21 | top: stringCenter(nrOfStrings)(0),
22 | bottom: stringCenter(nrOfStrings)(nrOfStrings - 1),
23 | }
24 | )
25 | const fretLine = (nrOfFrets, nrOfStrings) => (frt) => {
26 | const { top, bottom } = fretLineBounds(nrOfStrings)
27 | const x = fretOffset(nrOfFrets)(frt)
28 |
29 | return (
30 | )
37 | }
38 |
39 | const BoardGraphicStrings = ({ nrOfStrings, nrOfFrets }) =>
40 |
41 | {times(stringLine(nrOfStrings), nrOfStrings)}
42 | {range(1, nrOfFrets).map(fretLine(nrOfFrets, nrOfStrings))}
43 |
44 |
45 |
46 | BoardGraphicStrings.propTypes = {
47 | nrOfStrings: pt.number.isRequired,
48 | nrOfFrets: pt.number.isRequired,
49 | }
50 |
51 | export default BoardGraphicStrings
52 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/NoteContent/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`NoteContent component E2, pitch, enh, 1`] = `
4 |
5 |
8 | E
9 |
10 |
13 | 2
14 |
15 |
16 | `;
17 |
18 | exports[`NoteContent component E2, pitch, no enh 1`] = `
19 |
20 |
23 | E
24 |
25 |
28 | 2
29 |
30 |
31 | `;
32 |
33 | exports[`NoteContent component F#2, pc, enh, 1`] = `
34 |
35 |
38 | F
39 |
40 |
43 | ♯
44 |
45 |
48 | /
49 |
50 |
53 | G
54 |
55 |
58 | ♭
59 |
60 |
61 | `;
62 |
63 | exports[`NoteContent component F#2, pitch, no enh , 1`] = `
64 |
65 |
68 | F
69 |
70 |
73 | ♯
74 |
75 |
78 | 2
79 |
80 |
81 | `;
82 |
--------------------------------------------------------------------------------
/src/lib/fretboard.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | import { times, sum, isNil } from 'ramda'
3 |
4 | export const fretWidth = nrFrets => pos =>
5 | (((2 ** (1 / nrFrets)) - 1) /
6 | (2 ** ((pos + 1) / nrFrets))) * 100 * 2
7 |
8 | export const fretOffset = nrFrets => pos =>
9 | // (1 - (1 / (2 ** (pos / nrFrets)))) * 100 * 2
10 | sum(times(fretWidth(nrFrets), pos))
11 |
12 | export const stringHeight = nrOfStrings => 100 / nrOfStrings
13 |
14 | export const stringOffset = nrOfStrings => str =>
15 | str * stringHeight(nrOfStrings)
16 |
17 | export const stringCenter = nrOfStrings => str =>
18 | stringOffset(nrOfStrings)(str) + (stringHeight(nrOfStrings) / 2)
19 |
20 |
21 | export const ensureNoteObjects = noteProps =>
22 | noteProps.map(noteProp => (
23 | typeof noteProp === 'string'
24 | ? ({
25 | note: noteProp,
26 | status: 'selected',
27 | label: noteProp,
28 | })
29 | : ({
30 | note: noteProp.note,
31 | status: noteProp.status || 'selected',
32 | label: noteProp.label || noteProp.note,
33 | })
34 | ))
35 |
36 | export const ensureLocObjects = locProps =>
37 | locProps.map(locProp => (
38 | !isNil(locProp.str)
39 | ? ({
40 | loc: locProp,
41 | status: 'selected',
42 | label: '',
43 | })
44 | : ({
45 | loc: locProp.loc,
46 | status: locProp.status || 'selected',
47 | label: locProp.label || '',
48 | })
49 | ))
50 |
--------------------------------------------------------------------------------
/src/lib/fret.js:
--------------------------------------------------------------------------------
1 | import { equals, compose } from 'ramda'
2 | import { Note } from 'tonal'
3 |
4 | import { isEqual } from 'lib/tonal-helpers'
5 |
6 | export const selectedNote = (note, selectedNotes) =>
7 | selectedNotes.reduce((acc, curr) => (
8 | isEqual(note)(curr.note)
9 | ? curr
10 | : acc
11 | ), undefined)
12 |
13 | export const selectedLoc = (loc, selectedLocations) =>
14 | selectedLocations.reduce((acc, curr) => (
15 | equals(loc)(curr.loc)
16 | ? curr
17 | : acc
18 | ), undefined)
19 |
20 | export const decideSelectionContent = (showSelectionLabels, selection) =>
21 | content =>
22 | (showSelectionLabels && selection ? selection.label : content)
23 |
24 | export const decideShowNotesContent = (showNotes, note) => content =>
25 | (showNotes ? note : content)
26 |
27 | export const decideContent = (
28 | showSelectionLabels,
29 | selection,
30 | showNotes,
31 | note,
32 | ) => compose(
33 | decideSelectionContent(showSelectionLabels, selection),
34 | decideShowNotesContent(showNotes, note),
35 | )(undefined)
36 |
37 | export const decideSelectionColor =
38 | (selection, highlightSelections, theme) => content =>
39 | (selection && highlightSelections
40 | ? theme.statusMap[selection.status]
41 | : content)
42 |
43 | export const decideOctaveColor = (highlightOctaves, note, theme) => content =>
44 | (highlightOctaves ? theme.octaveMap[Note.oct(note)] : content)
45 |
46 | export const decideColor = (
47 | selection,
48 | theme,
49 | highlightOctaves,
50 | highlightSelections,
51 | note,
52 | ) => compose(
53 | decideSelectionColor(selection, highlightSelections, theme),
54 | decideOctaveColor(highlightOctaves, note, theme),
55 | )(undefined)
56 |
57 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/Content/NoteContent/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 |
4 | import { Note } from 'tonal'
5 | import AccWrapper from './AccWrapper'
6 |
7 | const letterElement = (letter, note) =>
8 | {letter}
9 |
10 | const formatAcc = acc => acc.replace('#', '\u266F').replace('b', '\u266D')
11 | const accElement = (acc, note) =>
12 |
15 | {formatAcc(acc)}
16 |
17 |
18 | const octElement = (oct, note) =>
19 | {oct}
20 |
21 | const formatNote = (note, noteType) => {
22 | const tokens = Note.tokenize(note)
23 | return tokens.map((token, i) => {
24 | if (i === 0) return letterElement(token, note)
25 | if (i === 1 && token) return accElement(token, note)
26 | if (i === 2 && noteType === 'pitch') return octElement(token, note)
27 | return null
28 | })
29 | }
30 |
31 | const formatEnharmonics = (note, enharmonic, noteType) =>
32 | [
33 | ...formatNote(note, noteType),
34 | / ,
35 | ...formatNote(enharmonic, noteType),
36 | ]
37 |
38 | const NoteContent = ({ note, noteType, showEnharmonics }) => {
39 | const enharmonic = Note.enharmonic(note)
40 | const hasEnharmonic = note !== enharmonic
41 |
42 | const contentNote = hasEnharmonic && showEnharmonics
43 | ? formatEnharmonics(note, enharmonic, noteType)
44 | : formatNote(note, noteType)
45 |
46 | return (
47 | {contentNote}
48 | )
49 | }
50 |
51 |
52 | NoteContent.propTypes = {
53 | note: pt.string.isRequired,
54 | noteType: pt.string.isRequired,
55 | showEnharmonics: pt.bool.isRequired,
56 | }
57 |
58 | export default NoteContent
59 |
--------------------------------------------------------------------------------
/src/components/Fretboard/PositionLabels/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return PositionLabels snapshot 1`] = `
4 |
5 |
8 | open
9 |
10 |
13 |
14 |
15 |
18 |
22 | I
23 |
24 |
28 |
32 | III
33 |
34 |
38 |
42 | V
43 |
44 |
48 |
52 | VII
53 |
54 |
58 |
62 |
66 | X
67 |
68 |
72 |
76 | XII
77 |
78 |
79 |
80 | `;
81 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-fretboard",
3 | "version": "0.0.12",
4 | "description": "Reusable React Fretboard Component",
5 | "license": "MIT",
6 | "main": "dist/react-fretboard.js",
7 | "repository": "devboell/react-fretboard",
8 | "author": {
9 | "name": "Rob Boellaard",
10 | "email": "devboell@gmail.com"
11 | },
12 | "scripts": {
13 | "test": "jest --coverage -c .jestrc.json",
14 | "test:watch": "jest --watchAll -c .jestrc.json",
15 | "build": "webpack",
16 | "build:watch": "webpack --watch",
17 | "styleguide": "styleguidist server",
18 | "styleguide:build": "styleguidist build"
19 | },
20 | "dependencies": {
21 | "prop-types": "^15.6.1",
22 | "ramda": "^0.25.0",
23 | "tonal": "1.1.3"
24 | },
25 | "peerDependencies": {
26 | "react": "^16.2.0",
27 | "styled-components": "^3.2.3"
28 | },
29 | "devDependencies": {
30 | "babel-cli": "^6.24.1",
31 | "babel-core": "^6.24.1",
32 | "babel-eslint": "^8.2.2",
33 | "babel-loader": "^7.1.4",
34 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
35 | "babel-polyfill": "^6.26.0",
36 | "babel-preset-env": "^1.5.1",
37 | "babel-preset-react": "^6.24.1",
38 | "enzyme": "^3.3.0",
39 | "enzyme-adapter-react-16": "^1.1.1",
40 | "enzyme-to-json": "^3.3.3",
41 | "eslint": "^4.19.1",
42 | "eslint-config-airbnb": "^16.1.0",
43 | "eslint-import-resolver-webpack": "^0.9.0",
44 | "eslint-plugin-import": "^2.9.0",
45 | "eslint-plugin-jsx-a11y": "^6.0.3",
46 | "eslint-plugin-react": "^7.7.0",
47 | "jest": "22.4.3",
48 | "jest-styled-components": "^5.0.0",
49 | "raf": "^3.4.0",
50 | "react": "^16.2.0",
51 | "react-dom": "^16.2.0",
52 | "react-styleguidist": "^7.0.1",
53 | "webpack": "^4.3.0",
54 | "webpack-bundle-analyzer": "^2.11.1",
55 | "webpack-cli": "^2.0.13"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/lib/selection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * helper functions that return commonly used selection objects.
3 | */
4 |
5 | import { contains } from 'ramda'
6 | import { Distance, Interval, Chord, Scale } from 'tonal'
7 |
8 | // intervals
9 | const intervalNoteObject = (tonic, useIvlLabel, useIvlStatus) => note => ({
10 | note,
11 | label: useIvlLabel ? Distance.interval(tonic, note) : note,
12 | status: useIvlStatus ? Distance.interval(tonic, note) : 'selected',
13 | })
14 |
15 | const intervalNoteArray = (note, ivl) =>
16 | [
17 | note,
18 | Distance.transpose(note, Interval.fromSemitones(ivl)),
19 | ]
20 |
21 | export const intervalNotes = (
22 | note,
23 | ivl,
24 | useIvlLabel = false,
25 | useIvlStatus = false,
26 | ) =>
27 | intervalNoteArray(note, ivl)
28 | .map(intervalNoteObject(note, useIvlLabel, useIvlStatus))
29 |
30 |
31 | // chords
32 | const chordTonic = chord =>
33 | Chord.tokenize(chord)[0]
34 |
35 | export const chordNotes = (chord, useIvlLabel, useIvlStatus) =>
36 | Chord.notes(chord)
37 | .map(intervalNoteObject(chordTonic(chord), useIvlLabel, useIvlStatus))
38 |
39 |
40 | // scales
41 | export const scaleNotes = (tonic, scale, useIvlLabel, useIvlStatus) =>
42 | Scale.notes(tonic, scale)
43 | .map(intervalNoteObject(tonic, useIvlLabel, useIvlStatus))
44 |
45 |
46 | // locations
47 | const position = (openNote, width, note) => {
48 | const dist = Distance.semitones(openNote, note)
49 | return width > dist && dist >= 0
50 | ? dist
51 | : null
52 | }
53 |
54 | // experimental
55 | export const triadShape = (tuning, width, chord, str) => {
56 | const notes = Chord.notes(chord)
57 | const positions = notes.map((note, i) =>
58 | position(tuning[(tuning.length - (str + 1)) + i], width, note))
59 | const chordExists = !contains(null, positions)
60 |
61 | return chordExists
62 | ? positions.map((pos, i) => ({ str: str - i, pos }))
63 | : null
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Selections
4 | #### triadShape
5 | ```js
6 | const selection = require('lib/selection')
7 | const tuning = ['E2', 'A2', 'D3', 'G3', 'B3', 'E4']
8 | const width = 12
9 | ;
12 | ```
13 | #### chordNotes
14 | ```js
15 | const selection = require('lib/selection')
16 | ;
19 | ```
20 |
21 | #### scaleNotes, chromatic for checking colormap
22 | ```js
23 | const selection = require('lib/selection')
24 | ;
27 | ```
28 |
29 | #### single note selection
30 | ```js
31 |
34 | ```
35 | #### multiple pc selection
36 | ```js
37 |
42 | ```
43 | #### multiple noteObj selection
44 | ```js
45 |
49 | ```
50 | #### single locObj selection, with octaves
51 | ```js
52 |
58 | ```
59 |
60 |
61 | ### No Selections
62 | #### default fretboard
63 | ```js
64 |
66 | ```
67 | ```js
68 |
71 | ```
72 | #### showNotes
73 | ```js
74 |
77 | ```
78 | ```js
79 |
83 | ```
84 | #### highlightOctaves
85 | ```js
86 |
90 | ```
91 | ```js
92 |
97 | ```
98 |
99 |
--------------------------------------------------------------------------------
/src/lib/__tests__/fret.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | selectedNote,
3 | selectedLoc,
4 | } from '../fret'
5 |
6 |
7 | describe('lib/fret', () => {
8 | describe('selectedNote', () => {
9 | it('should return selected note', () => {
10 | const note = 'A2'
11 | const noteE2 = {
12 | note: 'E2',
13 | status: 'selected',
14 | label: 'E2',
15 | }
16 |
17 | const noteA2 = {
18 | note: 'A2',
19 | status: 'selected',
20 | label: 'A2',
21 | }
22 |
23 | const selectedNotes = [
24 | noteE2,
25 | noteA2,
26 | ]
27 | expect(selectedNote(note, selectedNotes)).toEqual(noteA2)
28 | })
29 |
30 | it('should return undefined', () => {
31 | const note = 'A2'
32 | const noteE2 = {
33 | note: 'E2',
34 | status: 'selected',
35 | label: 'E2',
36 | }
37 |
38 |
39 | const selectedNotes = [
40 | noteE2,
41 | ]
42 | expect(selectedNote(note, selectedNotes)).toBeUndefined()
43 | })
44 | })
45 |
46 | describe('selectedLoc', () => {
47 | it('should return selected loc', () => {
48 | const loc = { str: 4, pos: 0 }
49 | const locE2 = {
50 | loc: { str: 5, pos: 0 },
51 | status: 'selected',
52 | label: 'E2',
53 | }
54 |
55 | const locA2 = {
56 | loc: { str: 4, pos: 0 },
57 | status: 'selected',
58 | label: 'A2',
59 | }
60 |
61 | const selectedLocs = [
62 | locE2,
63 | locA2,
64 | ]
65 | expect(selectedLoc(loc, selectedLocs)).toEqual(locA2)
66 | })
67 |
68 | it('should return undefined', () => {
69 | const loc = { str: 4, pos: 0 }
70 | const locE2 = {
71 | loc: { str: 5, pos: 0 },
72 | status: 'selected',
73 | label: 'E2',
74 | }
75 |
76 |
77 | const selectedLocs = [
78 | locE2,
79 | ]
80 |
81 | expect(selectedLoc(loc, selectedLocs)).toBeUndefined()
82 | })
83 | })
84 | })
85 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 | import { isNil } from 'ramda'
4 | import { withTheme } from 'styled-components'
5 |
6 | import { locShape, noteSelectionShape, locSelectionShape } from 'lib/shapes'
7 | import {
8 | selectedNote,
9 | selectedLoc,
10 | decideContent,
11 | decideColor,
12 | } from 'lib/fret'
13 | import { fretWrapper } from 'components/Fretboard/Skins/skins'
14 |
15 | import Content from './Content'
16 | import Wrapper from './Wrapper'
17 |
18 |
19 | /* eslint-disable react/prefer-stateless-function */
20 | export class Fret extends React.Component {
21 | render() {
22 | const { note, loc, theme } = this.props
23 | const {
24 | skinType,
25 | noteType,
26 | showNotes,
27 | showSelectionLabels,
28 | highlightOctaves,
29 | highlightSelections,
30 | showEnharmonics,
31 | selectedNotes,
32 | selectedLocations,
33 | clickAction,
34 | } = this.context
35 |
36 | const noteSelection = selectedNote(note, selectedNotes)
37 | const locSelection = selectedLoc(loc, selectedLocations)
38 | const selection = noteSelection || locSelection
39 |
40 |
41 | const content = decideContent(
42 | showSelectionLabels,
43 | selection,
44 | showNotes,
45 | note,
46 | )
47 |
48 | const color = decideColor(
49 | selection,
50 | theme,
51 | highlightOctaves,
52 | highlightSelections,
53 | note,
54 | )
55 |
56 | const isHighlighted = !isNil(color)
57 |
58 | const SkinWrapper = fretWrapper(skinType)
59 | const isClickable = !isNil(clickAction)
60 | const onClickAction = isClickable
61 | ? (() => clickAction({ note, loc }))
62 | : undefined
63 |
64 | return (
65 |
66 |
67 | {content &&
68 |
69 | }
70 |
71 | )
72 | }
73 | }
74 |
75 |
76 | Fret.propTypes = {
77 | note: pt.string.isRequired,
78 | loc: locShape.isRequired,
79 | theme: pt.shape({}).isRequired,
80 | }
81 |
82 | Fret.contextTypes = {
83 | skinType: pt.string.isRequired,
84 | noteType: pt.string.isRequired,
85 | showNotes: pt.bool.isRequired,
86 | showSelectionLabels: pt.bool.isRequired,
87 | highlightOctaves: pt.bool.isRequired,
88 | highlightSelections: pt.bool.isRequired,
89 | showEnharmonics: pt.bool.isRequired,
90 | selectedNotes: pt.arrayOf(noteSelectionShape).isRequired,
91 | selectedLocations: pt.arrayOf(locSelectionShape).isRequired,
92 | clickAction: pt.func,
93 | }
94 |
95 | export default withTheme(Fret)
96 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Skins/BoardGraphicStrings/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`BoardGraphicStrings component, snapshot 1`] = `
4 |
5 |
12 |
19 |
26 |
33 |
40 |
47 |
54 |
61 |
68 |
75 |
82 |
89 |
96 |
103 |
110 |
117 |
124 |
125 | `;
126 |
--------------------------------------------------------------------------------
/src/styleguide/fixtures.js:
--------------------------------------------------------------------------------
1 | import { merge } from 'ramda'
2 |
3 | const noteE2 = 'E2'
4 | const noteA2 = 'A2'
5 | const noteC4 = 'C4'
6 | const locE2 = { str: 5, pos: 0 }
7 | const locA2 = { str: 4, pos: 0 }
8 | const selectedE2 = { note: noteE2, status: 'selected', label: 'root' }
9 | const selectedC4 = { note: noteC4, status: 'selected', label: 'root' }
10 | const selectedNotes = [selectedE2, selectedC4]
11 | const selectedLocations = []
12 |
13 | const testCases = isSelected => [
14 | {
15 | key: 'case1',
16 | description: isSelected
17 | ? 'no content, no highlight'
18 | : 'no content, no highlight',
19 | context: {
20 | showNotes: false,
21 | showSelectionLabels: false,
22 | highlightOctaves: false,
23 | highlightSelections: false,
24 | showEnharmonics: false,
25 | },
26 | },
27 | {
28 | key: 'case2',
29 | description: isSelected
30 | ? 'note content, selection highlight'
31 | : 'note content, no highlight',
32 | context: {
33 | showNotes: true,
34 | showSelectionLabels: false,
35 | highlightOctaves: false,
36 | highlightSelections: true,
37 | showEnharmonics: false,
38 | },
39 | },
40 | {
41 | key: 'case3',
42 | description: isSelected
43 | ? 'note content, selection highlight'
44 | : 'note content, octave highlight',
45 | context: {
46 | showNotes: true,
47 | showSelectionLabels: false,
48 | highlightOctaves: true,
49 | highlightSelections: true,
50 | showEnharmonics: false,
51 | },
52 | },
53 | {
54 | key: 'case4',
55 | description: isSelected
56 | ? 'selection content, selection highlight'
57 | : 'note content, octave highlight',
58 | context: {
59 | showNotes: true,
60 | showSelectionLabels: true,
61 | highlightOctaves: true,
62 | highlightSelections: true,
63 | showEnharmonics: false,
64 | },
65 | },
66 | ]
67 |
68 | const defaultContext = {
69 | noteType: 'pitch',
70 | clickAction: () => {},
71 | }
72 |
73 | export default [
74 | {
75 | key: 'suite1',
76 | description: 'E2, selected note, skinType=boxes',
77 | props: { note: noteE2, loc: locE2 },
78 | context: merge(defaultContext, {
79 | skinType: 'boxes',
80 | selectedNotes,
81 | selectedLocations,
82 | }),
83 | testCases: testCases(true),
84 | },
85 | {
86 | key: 'suite2',
87 | description: 'E2, selected note, skinType=strings',
88 | props: { note: noteE2, loc: locE2 },
89 | context: merge(defaultContext, {
90 | skinType: 'strings',
91 | selectedNotes,
92 | selectedLocations,
93 | }),
94 | testCases: testCases(true),
95 | },
96 | {
97 | key: 'suite3',
98 | description: 'A2, selected note, skinType=boxes',
99 | props: { note: noteA2, loc: locA2 },
100 | context: merge(defaultContext, {
101 | skinType: 'boxes',
102 | selectedNotes,
103 | selectedLocations,
104 | }),
105 | testCases: testCases(false),
106 | },
107 | {
108 | key: 'suite4',
109 | description: 'A2, unselected note, skinType=strings',
110 | props: { note: noteA2, loc: locA2 },
111 | context: merge(defaultContext, {
112 | skinType: 'strings',
113 | selectedNotes,
114 | selectedLocations,
115 | }),
116 | testCases: testCases(false),
117 | },
118 | ]
119 |
120 |
--------------------------------------------------------------------------------
/src/lib/__tests__/__snapshots__/selection.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`selection, chord shapes should return array locs 1`] = `
4 | Array [
5 | Object {
6 | "pos": 5,
7 | "str": 2,
8 | },
9 | Object {
10 | "pos": 4,
11 | "str": 1,
12 | },
13 | Object {
14 | "pos": 3,
15 | "str": 0,
16 | },
17 | ]
18 | `;
19 |
20 | exports[`selection, scales returns note objects with ivl label, and ivl status 1`] = `
21 | Array [
22 | Object {
23 | "label": "1P",
24 | "note": "C4",
25 | "status": "1P",
26 | },
27 | Object {
28 | "label": "2M",
29 | "note": "D4",
30 | "status": "2M",
31 | },
32 | Object {
33 | "label": "3M",
34 | "note": "E4",
35 | "status": "3M",
36 | },
37 | Object {
38 | "label": "4P",
39 | "note": "F4",
40 | "status": "4P",
41 | },
42 | Object {
43 | "label": "5P",
44 | "note": "G4",
45 | "status": "5P",
46 | },
47 | Object {
48 | "label": "6M",
49 | "note": "A4",
50 | "status": "6M",
51 | },
52 | Object {
53 | "label": "7M",
54 | "note": "B4",
55 | "status": "7M",
56 | },
57 | ]
58 | `;
59 |
60 | exports[`selection, scales returns note objects with ivl label, and status selected 1`] = `
61 | Array [
62 | Object {
63 | "label": "1P",
64 | "note": "C4",
65 | "status": "selected",
66 | },
67 | Object {
68 | "label": "2M",
69 | "note": "D4",
70 | "status": "selected",
71 | },
72 | Object {
73 | "label": "3M",
74 | "note": "E4",
75 | "status": "selected",
76 | },
77 | Object {
78 | "label": "4P",
79 | "note": "F4",
80 | "status": "selected",
81 | },
82 | Object {
83 | "label": "5P",
84 | "note": "G4",
85 | "status": "selected",
86 | },
87 | Object {
88 | "label": "6M",
89 | "note": "A4",
90 | "status": "selected",
91 | },
92 | Object {
93 | "label": "7M",
94 | "note": "B4",
95 | "status": "selected",
96 | },
97 | ]
98 | `;
99 |
100 | exports[`selection, scales returns note objects with pitch label, and status selected 1`] = `
101 | Array [
102 | Object {
103 | "label": "C4",
104 | "note": "C4",
105 | "status": "selected",
106 | },
107 | Object {
108 | "label": "D4",
109 | "note": "D4",
110 | "status": "selected",
111 | },
112 | Object {
113 | "label": "E4",
114 | "note": "E4",
115 | "status": "selected",
116 | },
117 | Object {
118 | "label": "F4",
119 | "note": "F4",
120 | "status": "selected",
121 | },
122 | Object {
123 | "label": "G4",
124 | "note": "G4",
125 | "status": "selected",
126 | },
127 | Object {
128 | "label": "A4",
129 | "note": "A4",
130 | "status": "selected",
131 | },
132 | Object {
133 | "label": "B4",
134 | "note": "B4",
135 | "status": "selected",
136 | },
137 | ]
138 | `;
139 |
140 | exports[`selection, scales returns note objs, with pc label, and status selected 1`] = `
141 | Array [
142 | Object {
143 | "label": "C",
144 | "note": "C",
145 | "status": "selected",
146 | },
147 | Object {
148 | "label": "D",
149 | "note": "D",
150 | "status": "selected",
151 | },
152 | Object {
153 | "label": "E",
154 | "note": "E",
155 | "status": "selected",
156 | },
157 | Object {
158 | "label": "F",
159 | "note": "F",
160 | "status": "selected",
161 | },
162 | Object {
163 | "label": "G",
164 | "note": "G",
165 | "status": "selected",
166 | },
167 | Object {
168 | "label": "A",
169 | "note": "A",
170 | "status": "selected",
171 | },
172 | Object {
173 | "label": "B",
174 | "note": "B",
175 | "status": "selected",
176 | },
177 | ]
178 | `;
179 |
--------------------------------------------------------------------------------
/src/lib/__tests__/fretboard.test.js:
--------------------------------------------------------------------------------
1 | import { times, sum } from 'ramda'
2 | import {
3 | fretWidth,
4 | fretOffset,
5 | stringCenter,
6 | ensureNoteObjects,
7 | ensureLocObjects,
8 | } from '../fretboard'
9 |
10 | describe('lib/fretboard', () => {
11 | describe('layout', () => {
12 | it('should check that all fret width percentages add up to 100', () => {
13 | const nrOfFrets = 12
14 | const totalWidth = 100
15 |
16 | const received = sum(times(fretWidth(nrOfFrets), nrOfFrets))
17 | expect(Math.round(received)).toBe(totalWidth)
18 | })
19 |
20 | it('should return correct fret offset', () => {
21 | const nrOfFrets = 12
22 | const position = 5
23 | const expected = 50
24 |
25 | const received = fretOffset(nrOfFrets)(position)
26 | expect(Math.round(received)).toBe(expected)
27 | })
28 |
29 | it('should return correct string center', () => {
30 | const nrOfStrings = 6
31 | const str = 5
32 | const expected = 92
33 |
34 | const received = stringCenter(nrOfStrings)(str)
35 | expect(Math.round(received)).toBe(expected)
36 | })
37 | })
38 |
39 | describe('selection conversions', () => {
40 | it('should return default noteObj for note string', () => {
41 | const selectedNotes = ['E2']
42 | const expected = [{ note: 'E2', status: 'selected', label: 'E2' }]
43 | const received = ensureNoteObjects(selectedNotes)
44 |
45 | expect(received).toEqual(expected)
46 | })
47 |
48 | it('should return same noteObj for complete noteObj', () => {
49 | const selectedNotes = [{ note: 'E2', status: 'foo', label: 'bar' }]
50 | const expected = [{ note: 'E2', status: 'foo', label: 'bar' }]
51 | const received = ensureNoteObjects(selectedNotes)
52 |
53 | expect(received).toEqual(expected)
54 | })
55 |
56 | it('should add missing status to noteObj', () => {
57 | const selectedNotes = [{ note: 'E2', label: 'bar' }]
58 | const expected = [{ note: 'E2', status: 'selected', label: 'bar' }]
59 | const received = ensureNoteObjects(selectedNotes)
60 |
61 | expect(received).toEqual(expected)
62 | })
63 |
64 | it('should add missing label to noteObj', () => {
65 | const selectedNotes = [{ note: 'E2', status: 'foo' }]
66 | const expected = [{ note: 'E2', status: 'foo', label: 'E2' }]
67 | const received = ensureNoteObjects(selectedNotes)
68 |
69 | expect(received).toEqual(expected)
70 | })
71 |
72 | it('should return default locObj for loc shape', () => {
73 | const loc = { str: 5, pos: 0 }
74 | const selectedLocs = [loc]
75 | const expected = [{ loc, status: 'selected', label: '' }]
76 | const received = ensureLocObjects(selectedLocs)
77 |
78 | expect(received).toEqual(expected)
79 | })
80 |
81 | it('should return same locObj for complete locObj', () => {
82 | const loc = { str: 5, pos: 0 }
83 | const selectedLocs = [{ loc, status: 'foo', label: 'bar' }]
84 | const expected = [{ loc, status: 'foo', label: 'bar' }]
85 | const received = ensureLocObjects(selectedLocs)
86 |
87 | expect(received).toEqual(expected)
88 | })
89 |
90 | it('should add missing status to locObj', () => {
91 | const loc = { str: 5, pos: 0 }
92 | const selectedLocs = [{ loc, label: 'bar' }]
93 | const expected = [{ loc, status: 'selected', label: 'bar' }]
94 | const received = ensureLocObjects(selectedLocs)
95 |
96 | expect(received).toEqual(expected)
97 | })
98 |
99 | it('should add missing label to locObj', () => {
100 | const loc = { str: 5, pos: 0 }
101 | const selectedLocs = [{ loc, status: 'foo' }]
102 | const expected = [{ loc, status: 'foo', label: '' }]
103 | const received = ensureLocObjects(selectedLocs)
104 |
105 | expect(received).toEqual(expected)
106 | })
107 | })
108 | })
109 |
110 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Board/BoardPositions/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`BoardPositions component, snapshot 1`] = `
4 |
8 |
9 |
24 |
39 |
54 |
69 |
84 |
99 |
114 |
129 |
144 |
159 |
174 |
189 |
190 |
191 | `;
192 |
--------------------------------------------------------------------------------
/src/components/Fretboard/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import pt from 'prop-types'
3 | import { mergeDeepRight } from 'ramda'
4 | import { ThemeProvider } from 'styled-components'
5 | import defaultTheme from 'themes/fretboard-theme'
6 |
7 | import { locShape, noteSelectionShape, locSelectionShape } from 'lib/shapes'
8 | import { ensureNoteObjects, ensureLocObjects } from 'lib/fretboard'
9 |
10 |
11 | import Neck from './Neck'
12 | import PositionLabels from './PositionLabels'
13 |
14 | import Wrapper from './Wrapper'
15 |
16 | /**
17 | * **This is the main api component**
18 | *
19 | * Here the props get split up in roughly two categories:
20 | * - props and theme props that determine the dimensions and overall look of the fretboard. They are passed directly to the children
21 | * - props that determine the appearance of the individual frets, ie the content and color of a Fret. They are passed through react context, and get picked up several levels down by the Fret component.
22 | */
23 |
24 | class Fretboard extends React.Component {
25 | getChildContext() {
26 | return {
27 | skinType: this.props.skinType,
28 | noteType: this.props.noteType,
29 | showNotes: this.props.showNotes,
30 | showSelectionLabels: this.props.showSelectionLabels,
31 | highlightOctaves: this.props.highlightOctaves,
32 | highlightSelections: this.props.highlightSelections,
33 | showEnharmonics: this.props.showEnharmonics,
34 | selectedNotes: ensureNoteObjects(this.props.selectedNotes),
35 | selectedLocations: ensureLocObjects(this.props.selectedLocations),
36 | clickAction: this.props.clickAction,
37 | }
38 | }
39 |
40 |
41 | render() {
42 | const {
43 | tuning,
44 | nrOfFrets,
45 | skinType,
46 | showPositionLabels,
47 | theme,
48 | } = this.props
49 | const mergedTheme = mergeDeepRight(defaultTheme, theme)
50 | const { dimensions } = mergedTheme
51 | const { openWidth, nutWidth } = dimensions
52 |
53 | return (
54 |
55 |
56 |
64 | {showPositionLabels &&
65 |
72 | }
73 |
74 |
75 | )
76 | }
77 | }
78 |
79 | Fretboard.propTypes = {
80 | tuning: pt.arrayOf(pt.string),
81 | nrOfFrets: pt.number,
82 | skinType: pt.oneOf(['boxes', 'strings']),
83 | noteType: pt.oneOf(['pc', 'pitch']),
84 | showNotes: pt.bool,
85 | showSelectionLabels: pt.bool,
86 | highlightOctaves: pt.bool,
87 | highlightSelections: pt.bool,
88 | showEnharmonics: pt.bool,
89 | showPositionLabels: pt.bool,
90 | selectedNotes: pt.arrayOf(pt.oneOfType([pt.string, noteSelectionShape])),
91 | selectedLocations: pt.arrayOf(pt.oneOfType([locShape, locSelectionShape])),
92 | theme: pt.shape({}),
93 | clickAction: pt.func,
94 | }
95 |
96 | Fretboard.defaultProps = {
97 | tuning: ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'],
98 | nrOfFrets: 12,
99 | skinType: 'boxes',
100 | noteType: 'pitch',
101 | showNotes: false,
102 | showSelectionLabels: true,
103 | highlightOctaves: false,
104 | highlightSelections: true,
105 | showEnharmonics: false,
106 | showPositionLabels: true,
107 | selectedNotes: [],
108 | selectedLocations: [],
109 | theme: {},
110 | clickAction: undefined,
111 | }
112 |
113 | Fretboard.childContextTypes = {
114 | skinType: pt.string,
115 | noteType: pt.string,
116 | showNotes: pt.bool,
117 | showSelectionLabels: pt.bool,
118 | highlightOctaves: pt.bool,
119 | highlightSelections: pt.bool,
120 | showEnharmonics: pt.bool,
121 | selectedNotes: pt.arrayOf(noteSelectionShape),
122 | selectedLocations: pt.arrayOf(locSelectionShape),
123 | clickAction: pt.func,
124 | }
125 |
126 | export default Fretboard
127 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Fret component -- other selection -- should not show selection if selectedLocation doesn't match 1`] = `
4 |
7 |
10 |
11 | `;
12 |
13 | exports[`Fret component -- other selection -- should not show selection if selectedNote doesn't match 1`] = `
14 |
17 |
20 |
21 | `;
22 |
23 | exports[`Fret component -- selectedLocations & highlights -- highlightOctaves=false, loc selected 1`] = `
24 |
27 |
30 |
31 | `;
32 |
33 | exports[`Fret component -- selectedLocations & highlights -- highlightOctaves=true, loc selected 1`] = `
34 |
37 |
41 |
42 | `;
43 |
44 | exports[`Fret component -- selectedLocations & labels -- showSelectionLabels=false, loc selected 1`] = `
45 |
48 |
51 |
52 | `;
53 |
54 | exports[`Fret component -- selectedLocations & labels -- showSelectionLabels=true, loc selected 1`] = `
55 |
58 |
61 |
66 |
67 |
68 | `;
69 |
70 | exports[`Fret component -- selectedNotes & highlights -- highlightOctaves=false, note selected 1`] = `
71 |
74 |
77 |
78 | `;
79 |
80 | exports[`Fret component -- selectedNotes & highlights -- highlightOctaves=true, highlightSelections=true, note selected 1`] = `
81 |
84 |
87 |
88 | `;
89 |
90 | exports[`Fret component -- selectedNotes & highlights -- highlightOctaves=true, no note selected 1`] = `
91 |
94 |
98 |
99 | `;
100 |
101 | exports[`Fret component -- selectedNotes & labels -- showSelectionLabels=false, showNotes=false, note selected 1`] = `
102 |
105 |
108 |
109 | `;
110 |
111 | exports[`Fret component -- selectedNotes & labels -- showSelectionLabels=false, showNotes=true, no note selected 1`] = `
112 |
115 |
118 |
123 |
124 |
125 | `;
126 |
127 | exports[`Fret component -- selectedNotes & labels -- showSelectionLabels=true, showNotes=false, note selected 1`] = `
128 |
131 |
134 |
139 |
140 |
141 | `;
142 |
143 | exports[`Fret component -- selectedNotes & labels -- showSelectionLabels=true, showNotes=true, note selected 1`] = `
144 |
147 |
150 |
155 |
156 |
157 | `;
158 |
159 | exports[`Fret component -- selectedNotes & labels -- showSelectionLabels=true, showNotes=true, note selected 2`] = `
160 |
163 |
166 |
171 |
172 |
173 | `;
174 |
175 | exports[`Fret component defaultContext, no selections 1`] = `
176 |
179 |
182 |
183 | `;
184 |
--------------------------------------------------------------------------------
/src/components/Fretboard/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Fretboard component with skinType strings, snapshot 1`] = `
4 |
45 |
46 |
67 |
72 |
73 |
74 | `;
75 |
76 | exports[`Fretboard component, snapshot 1`] = `
77 |
118 |
119 |
140 |
145 |
146 |
147 | `;
148 |
149 | exports[`should return component w/out positionLabels 1`] = `
150 |
191 |
192 |
213 |
214 |
215 | `;
216 |
--------------------------------------------------------------------------------
/src/lib/__tests__/selection.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | intervalNotes,
3 | chordNotes,
4 | scaleNotes,
5 | triadShape,
6 | } from '../selection'
7 |
8 | describe('selection, intervals', () => {
9 | it('returns note objs, with pc label, and status selected', () => {
10 | const note = 'C'
11 | const ivl = 4 // 3M
12 | const expected = [
13 | {
14 | note: 'C',
15 | label: 'C',
16 | status: 'selected',
17 | },
18 | {
19 | note: 'E',
20 | label: 'E',
21 | status: 'selected',
22 | },
23 | ]
24 | expect(intervalNotes(note, ivl)).toEqual(expected)
25 | })
26 |
27 | it('returns note objects with pc label, and status selected', () => {
28 | const note = 'C4'
29 | const ivl = 4 // 3M
30 | const expected = [
31 | {
32 | note: 'C4',
33 | label: 'C4',
34 | status: 'selected',
35 | },
36 | {
37 | note: 'E4',
38 | label: 'E4',
39 | status: 'selected',
40 | },
41 | ]
42 | expect(intervalNotes(note, ivl)).toEqual(expected)
43 | })
44 |
45 | it('returns note objs, with ivl label, and status selected', () => {
46 | const note = 'C'
47 | const ivl = 4
48 | const expected = [
49 | {
50 | note: 'C',
51 | label: '1P',
52 | status: 'selected',
53 | },
54 | {
55 | note: 'E',
56 | label: '3M',
57 | status: 'selected',
58 | },
59 | ]
60 | expect(intervalNotes(note, ivl, true)).toEqual(expected)
61 | })
62 |
63 | it('returns note objs, with ivl label, and ivl status', () => {
64 | const note = 'C'
65 | const ivl = 4
66 | const expected = [
67 | {
68 | note: 'C',
69 | label: '1P',
70 | status: '1P',
71 | },
72 | {
73 | note: 'E',
74 | label: '3M',
75 | status: '3M',
76 | },
77 | ]
78 | expect(intervalNotes(note, ivl, true, true)).toEqual(expected)
79 | })
80 |
81 | it('returns note objs, with pitch label, and ivl status', () => {
82 | const note = 'C4'
83 | const ivl = 4
84 | const expected = [
85 | {
86 | note: 'C4',
87 | label: 'C4',
88 | status: '1P',
89 | },
90 | {
91 | note: 'E4',
92 | label: 'E4',
93 | status: '3M',
94 | },
95 | ]
96 | expect(intervalNotes(note, ivl, false, true)).toEqual(expected)
97 | })
98 | })
99 |
100 | describe('selection, chords', () => {
101 | it('returns note objs, with pc label, and status selected', () => {
102 | const chord = 'CM'
103 | const expected = [
104 | {
105 | note: 'C',
106 | label: 'C',
107 | status: 'selected',
108 | },
109 | {
110 | note: 'E',
111 | label: 'E',
112 | status: 'selected',
113 | },
114 | {
115 | note: 'G',
116 | label: 'G',
117 | status: 'selected',
118 | },
119 | ]
120 | expect(chordNotes(chord)).toEqual(expected)
121 | })
122 |
123 | it('returns note objs, with pitch label, and status selected', () => {
124 | const chord = 'C4M'
125 | const expected = [
126 | {
127 | note: 'C4',
128 | label: 'C4',
129 | status: 'selected',
130 | },
131 | {
132 | note: 'E4',
133 | label: 'E4',
134 | status: 'selected',
135 | },
136 | {
137 | note: 'G4',
138 | label: 'G4',
139 | status: 'selected',
140 | },
141 | ]
142 | expect(chordNotes(chord)).toEqual(expected)
143 | })
144 |
145 | it('returns note objs, with ivl label, and status selected', () => {
146 | const chord = 'CM'
147 | const expected = [
148 | {
149 | note: 'C',
150 | label: '1P',
151 | status: 'selected',
152 | },
153 | {
154 | note: 'E',
155 | label: '3M',
156 | status: 'selected',
157 | },
158 | {
159 | note: 'G',
160 | label: '5P',
161 | status: 'selected',
162 | },
163 | ]
164 | expect(chordNotes(chord, true)).toEqual(expected)
165 | })
166 |
167 | it('returns note objs, with ivl label, and ivl status', () => {
168 | const chord = 'C4M'
169 | const expected = [
170 | {
171 | note: 'C4',
172 | label: '1P',
173 | status: '1P',
174 | },
175 | {
176 | note: 'E4',
177 | label: '3M',
178 | status: '3M',
179 | },
180 | {
181 | note: 'G4',
182 | label: '5P',
183 | status: '5P',
184 | },
185 | ]
186 | expect(chordNotes(chord, true, true)).toEqual(expected)
187 | })
188 | })
189 |
190 | describe('selection, scales', () => {
191 | it('returns note objs, with pc label, and status selected', () => {
192 | const tonic = 'C'
193 | const scale = 'major'
194 | expect(scaleNotes(tonic, scale)).toMatchSnapshot()
195 | })
196 |
197 |
198 | it('returns note objects with pitch label, and status selected', () => {
199 | const tonic = 'C4'
200 | const scale = 'major'
201 | expect(scaleNotes(tonic, scale)).toMatchSnapshot()
202 | })
203 |
204 | it('returns note objects with ivl label, and status selected', () => {
205 | const tonic = 'C4'
206 | const scale = 'major'
207 | expect(scaleNotes(tonic, scale, true)).toMatchSnapshot()
208 | })
209 |
210 | it('returns note objects with ivl label, and ivl status', () => {
211 | const tonic = 'C4'
212 | const scale = 'major'
213 | expect(scaleNotes(tonic, scale, true, true)).toMatchSnapshot()
214 | })
215 | })
216 |
217 | describe('selection, chord shapes', () => {
218 | it('should return array locs', () => {
219 | const chord = 'C4m'
220 | const tuning = ['E2', 'A2', 'D3', 'G3', 'B3', 'E4']
221 | const width = 12
222 | const str = 2
223 | expect(triadShape(tuning, width, chord, str)).toMatchSnapshot()
224 | })
225 |
226 | it('should return null for non existing shape', () => {
227 | const chord = 'G#3m'
228 | const tuning = ['E2', 'A2', 'D3', 'G3', 'B3', 'E4']
229 | const width = 12
230 | const str = 2
231 | expect(triadShape(tuning, width, chord, str)).toBe(null)
232 | })
233 | })
234 |
--------------------------------------------------------------------------------
/src/components/Fretboard/Neck/Position/Fret/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import theme from 'themes/fretboard-theme'
3 | import { Fret } from '../index'
4 |
5 |
6 | describe('Fret component', () => {
7 | const clickAction = jest.fn()
8 |
9 | const props = {
10 | note: 'E2',
11 | loc: { str: 5, pos: 0 },
12 | theme,
13 | }
14 | const defaultContext = {
15 | skinType: 'boxes',
16 | noteType: 'pitch',
17 | showNotes: false,
18 | showSelectionLabels: false,
19 | highlightOctaves: false,
20 | highlightSelections: false,
21 | showEnharmonics: false,
22 | selectedNotes: [],
23 | selectedLocations: [],
24 | clickAction: undefined,
25 | }
26 |
27 | it('defaultContext, no selections', () => {
28 | const wrapper = shallow( , { context: defaultContext })
29 | expect(wrapper).toMatchSnapshot()
30 | })
31 |
32 | describe('-- selectedNotes & labels --', () => {
33 | it('showSelectionLabels=false, showNotes=false, note selected', () => {
34 | const selectedNotes = [{ note: 'E2', status: 'selected', label: 'root' }]
35 | const context = { ...defaultContext, selectedNotes }
36 | const wrapper = shallow( , { context })
37 | expect(wrapper).toMatchSnapshot()
38 | })
39 |
40 | it('showSelectionLabels=true, showNotes=false, note selected', () => {
41 | const selectedNotes = [{ note: 'E2', status: 'selected', label: 'root' }]
42 | const context = {
43 | ...defaultContext,
44 | selectedNotes,
45 | showSelectionLabels: true,
46 | }
47 | const wrapper = shallow( , { context })
48 | expect(wrapper).toMatchSnapshot()
49 | })
50 |
51 | it('showSelectionLabels=true, showNotes=true, note selected', () => {
52 | const selectedNotes = [{ note: 'E2', status: 'selected', label: 'root' }]
53 | const context = {
54 | ...defaultContext,
55 | selectedNotes,
56 | showSelectionLabels: true,
57 | showNotes: true,
58 | }
59 | const wrapper = shallow( , { context })
60 | expect(wrapper).toMatchSnapshot()
61 | })
62 |
63 | it('showSelectionLabels=false, showNotes=true, no note selected', () => {
64 | const context = {
65 | ...defaultContext,
66 | showSelectionLabels: false,
67 | showNotes: true,
68 | }
69 | const wrapper = shallow( , { context })
70 | expect(wrapper).toMatchSnapshot()
71 | })
72 |
73 | it('showSelectionLabels=true, showNotes=true, note selected', () => {
74 | const selectedNotes = [{ note: 'E2', status: 'selected', label: 'root' }]
75 | const context = {
76 | ...defaultContext,
77 | selectedNotes,
78 | showSelectionLabels: false,
79 | showNotes: true,
80 | }
81 | const wrapper = shallow( , { context })
82 | expect(wrapper).toMatchSnapshot()
83 | })
84 | })
85 |
86 | describe('-- selectedNotes & highlights --', () => {
87 | it('highlightOctaves=false, note selected', () => {
88 | const selectedNotes = [{ note: 'E2', status: 'foo', label: 'root' }]
89 | const context = { ...defaultContext, selectedNotes }
90 | const wrapper = shallow( , { context })
91 | expect(wrapper).toMatchSnapshot()
92 | })
93 |
94 | it('highlightOctaves=true, highlightSelections=true, note selected', () => {
95 | const selectedNotes = [{ note: 'E2', status: 'foo', label: 'root' }]
96 | const context = {
97 | ...defaultContext,
98 | highlightOctaves: true,
99 | highlightSelections: true,
100 | selectedNotes,
101 | }
102 | const wrapper = shallow( , { context })
103 | expect(wrapper).toMatchSnapshot()
104 | })
105 |
106 | it('highlightOctaves=true, no note selected', () => {
107 | const context = {
108 | ...defaultContext,
109 | highlightOctaves: true,
110 | }
111 | const wrapper = shallow( , { context })
112 | expect(wrapper).toMatchSnapshot()
113 | })
114 | })
115 |
116 | describe('-- selectedLocations & labels --', () => {
117 | it('showSelectionLabels=false, loc selected', () => {
118 | const loc = { str: 5, pos: 0 }
119 | const selectedLocations = [{ loc, status: 'foo', label: 'root' }]
120 | const context = { ...defaultContext, selectedLocations }
121 | const wrapper = shallow( , { context })
122 | expect(wrapper).toMatchSnapshot()
123 | })
124 |
125 | it('showSelectionLabels=true, loc selected', () => {
126 | const loc = { str: 5, pos: 0 }
127 | const selectedLocations = [{ loc, status: 'foo', label: 'root' }]
128 | const context = {
129 | ...defaultContext,
130 | showSelectionLabels: true,
131 | selectedLocations,
132 | }
133 | const wrapper = shallow( , { context })
134 | expect(wrapper).toMatchSnapshot()
135 | })
136 | })
137 |
138 | describe('-- selectedLocations & highlights --', () => {
139 | it('highlightOctaves=false, loc selected', () => {
140 | const loc = { str: 5, pos: 0 }
141 | const selectedLocations = [{ loc, status: 'foo', label: 'root' }]
142 | const context = { ...defaultContext, selectedLocations }
143 | const wrapper = shallow( , { context })
144 | expect(wrapper).toMatchSnapshot()
145 | })
146 |
147 | it('highlightOctaves=true, loc selected', () => {
148 | const loc = { str: 5, pos: 0 }
149 | const selectedLocations = [{ loc, status: 'foo', label: 'root' }]
150 | const context = {
151 | ...defaultContext,
152 | highlightOctaves: true,
153 | selectedLocations,
154 | }
155 | const wrapper = shallow( , { context })
156 | expect(wrapper).toMatchSnapshot()
157 | })
158 | })
159 |
160 | describe('-- other selection --', () => {
161 | it('should not show selection if selectedNote doesn\'t match', () => {
162 | const selectedNotes = [{ note: 'A2', status: 'selected', label: 'root' }]
163 | const context = { ...defaultContext, selectedNotes }
164 | const wrapper = shallow( , { context })
165 | expect(wrapper).toMatchSnapshot()
166 | })
167 |
168 | it('should not show selection if selectedLocation doesn\'t match', () => {
169 | const loc = { str: 5, pos: 5 }
170 | const selectedLocations = [{ loc, status: 'foo', label: 'root' }]
171 | const context = { ...defaultContext, selectedLocations }
172 | const wrapper = shallow( , { context })
173 | expect(wrapper).toMatchSnapshot()
174 | })
175 | })
176 |
177 | it('clickAction is called with correct arg', () => {
178 | const context = {
179 | ...defaultContext,
180 | clickAction,
181 | }
182 | const wrapper = shallow( , { context })
183 | const expected = {
184 | note: 'E2',
185 | loc: { str: 5, pos: 0 },
186 | }
187 | wrapper.simulate('click')
188 | expect(clickAction).toBeCalledWith(expected)
189 | })
190 | })
191 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-fretboard
2 |
3 | A reusable react component that displays a fretboard, and lets you select notes, chords and scales.
4 |
5 | ## Installation
6 |
7 | `yarn add react-fretboard`
8 |
9 | or
10 |
11 | `npm install react-fretboard`
12 |
13 | ## Usage
14 |
15 | Besides the Fretboard component, this lib exports several functions (see [API](#functions)) that
16 | help you select notes, chords, etc. `react-fretboard` uses the [Tonal music theory library](https://github.com/danigb/tonal/) internally, and adopts it's naming conventions for notes, chords, etc. Check out Tonal, because you might want to use it together with `react-fretboard`
17 |
18 | ```js
19 | import React from 'react'
20 | import Fretboard, { chordNotes } from 'react-fretboard'
21 |
22 | const App = () =>
23 |
24 |
28 |
29 |
30 | export default App
31 | ```
32 |
33 | ## Examples
34 |
35 | There's a live demo with many examples here: https://react-fretboard-demo.herokuapp.com
36 | You can also clone the repo and run styleguidist (`yarn styleguide`), which will show interactive examples so you can experiment with the props.
37 |
38 | ## API
39 |
40 | ### props
41 | **tuning**
42 | *arrayOf(string)*
43 |
44 | Array of pitch strings, from which all the fret notes are calculated. You can provide any tuning you want. Additionally the number of notes in the tuning array determine how many strings the fretboard will display.
45 | The default is standard tuning for a 6 string guitar.
46 | Example: `tuning={['E2', 'A2', 'D3', 'G3', 'B3', 'E4']}`
47 |
48 | **nrOfFrets**
49 | *number*
50 |
51 | This sets the width of the fretboard. The number includes the open position, so 12 frets will need `nrOfFrets={13}`. 13 is also the default.
52 |
53 | **skinType**
54 | *oneOf(['boxes', 'strings'])*
55 |
56 | Determines the overall look of the fretboard. 'boxes' displays the frets as divs with a border, and 'strings' looks more like a standard fretboard diagram.
57 | Default is 'boxes'
58 |
59 | **noteType**
60 | *oneOf(['pc', 'pitch'])*
61 |
62 | Sets what type of note is displayed in the frets, f.i.: C (pc, short for pitch class), or C4 (pitch). This is purely a visual setting, you can still select pc's or pitches. (see selectedNotes below)
63 | Default is 'pitch'
64 |
65 | **showNotes**
66 | *bool*
67 |
68 | Determines whether to show the (unselected) note names in the fretboard, or not. (This is overridden by a label of a selected note if showSelectionLabels is true)
69 | Default is false
70 |
71 | **showSelectionLabels**
72 | *bool*
73 |
74 |
75 | Determines whether to show the selected note label. (This overrides the showNotes prop)
76 | Default is true
77 |
78 | **highlightOctaves**
79 | *bool*
80 |
81 | Determines whether to show a fret's octave background color. (Is overridden by highlightSelections). You can provide your own octave colors with the theme prop.
82 | Default is false
83 |
84 | **highlightSelections**
85 | *bool*
86 |
87 | Determines wether to show a selected fret's background color. (This overrides the octave color). (see selectedNotes below for more information)
88 | Default is true
89 |
90 | **showEnharmonics**
91 | *bool*
92 |
93 | If a note has a (simple) enharmonic, like C#, this prop lets you choose to display just 'C#' or 'C#/Db'. The note name may become too large for the smaller frets, in which case it is clipped. Check the theme's fontSize property for a bit more control of fitting a note name in a fret.
94 | Default is false
95 |
96 | **showPositionLabels**
97 | *bool*
98 |
99 | Shows the position markers underneath the fretboard (like: I, III, VII, X).
100 | Default is true
101 |
102 | **selectedNotes**
103 | *arrayOf(oneOfType([
104 | string,
105 | shape({
106 | note: string.isRequired,
107 | status: string, // optional, default 'selected'
108 | label: string, // optional, default note name
109 | })
110 | ]))*
111 |
112 | This can get complicated, but basic usage is very simple. There are two ways to select notes:
113 | 1. array of note names. Examples:
114 | `selectedNotes={['C']}`, selects all C notes.
115 | `selectedNotes={['A3', 'D4']}`, selects all A3 and D4 notes.
116 | If you have highlightSlections true, it will look in the theme.statusMap.selected for the specified color, and use it to highlight the frets. If you have showSelectionLabels true, it will display note names.
117 | 2. array of note objects. Examples of note objects:
118 | `{ note: 'C', label: 'root', status: 'root' }`, will select all C's, label them with 'root', and use theme.statusMap.root for the highlight color (which you'll need to specify)
119 | `{ note: 'C', status: 'root' }`, will select all C's, label them with 'C', and use the color under theme.statusMap.root.
120 | An example of a C3 triad could be:
121 | `selectedNotes={[{ note: 'C3', label: 'root', status: 'root' }, { note: 'E3', label: '3M', status: '3M' }, { note: 'G3', label: '5P', status: '5P' }]}`
122 | `react-fretboard` exports a couple of functions that help with generating these note objects.
123 |
124 |
125 | **selectedLocations**
126 | *arrayOf(oneOfType([
127 | shape({
128 | str: number.isRequired,
129 | pos: number.isRequired,
130 | }),
131 | shape({
132 | loc: shape({
133 | str: number.isRequired,
134 | pos: number.isRequired,
135 | }).isRequired,
136 | status: string,
137 | label: string,
138 | })
139 | ]))*
140 |
141 | Lets you select frets based on their location on the fretboard. The top (1st) string is `str: 0`, and bottom string is `str: tuning.length - 1`.
142 | This prop behaves almost identically to selectedNotes, except that instead of the note string, you'd use a loc object.
143 |
144 | **theme**
145 |
146 | Specify a theme to customize the style of the fretboard. (see [CSS](#CSS))
147 |
148 | **clickAction**
149 | *func*
150 |
151 | Provide a callback that will be called when a fret is clicked. The arg passed is an object with props: { note, loc }
152 |
153 |
154 | ### functions
155 | These are convenience functions, and you may not need to use them. F.i: these props have the equivalent result on the fretboard:
156 | `selectedNotes={['C', 'E']}`
157 | `selectedNotes={intervalNotes('C', 4)}`
158 |
159 | or these:
160 | `selectedNotes={['C', 'E', 'G']}`
161 | `selectedNotes={Tonal.Chord.notes('CM')}` using the [Tonal library](https://github.com/danigb/tonal/)
162 | `selectedNotes={chordNotes('CM')}`
163 |
164 | They become more useful when, instead of note names, you want to show the relative intervals of selected notes, by using the `useIvlLabel` and `useIvlStatus` params. With `useIvlLabel = true` the selection labels will be set to interval names. With `useIvlStatus = true` the highlight color will be theme.statusMap.[ivlName]. Check the examples below.
165 |
166 | **intervalNotes(note, ivl, useIvlLabel = false, useIvlStatus = false)**
167 |
168 | Examples:
169 | `intervalNotes('C', 4)` - returns array of note objects, C and E, with label set to note name and status 'selected'
170 | `intervalNotes('C', 4, true)` - returns array of note objects, C and E, with labels set to 1P and 3M, and status 'selected'.
171 | `intervalNotes('C', 4, true, true)` - returns array of note objects, C and E, with labels and status set to 1P and 3M.
172 |
173 |
174 |
175 | **chordNotes(chord, useIvlLabel = false, useIvlStatus = false)**
176 |
177 | Examples:
178 | `chordNotes('CM')` - returns array of note objects, C, E and G, with label set to note name and status 'selected'
179 | `chordNotes('CM', true)` - returns array of note objects, C, E and G, with labels set to 1P, 3M and 5P, and status 'selected'.
180 | `chordNotes('CM', true, true)` - returns array of note objects, C, E and G, with labels and status set to 1P, 3M and 5P.
181 |
182 |
183 | **scaleNotes(tonic, scale, useIvlLabel = false, useIvlStatus = false)**
184 |
185 | Examples:
186 | `scaleNotes('C', 'major')` - returns array of note objects of the C major scale, with label set to note name and status 'selected'
187 | `scaleNotes('C', 'major', true)` - returns array of note objects of the C major scale, with labels set to their respective interval names, and status 'selected'.
188 | `scaleNotes('C', 'major', true, true)` - returns array of note objects of the C major scale, with labels and status set to their respective interval names.
189 |
190 |
191 |
192 |
193 | ## CSS
194 |
195 | `react-fretboard` uses [`styled-components`](https://github.com/styled-components/styled-components) for it's styling. You can provide your own theme by passing it through the theme prop. This is the default theme:
196 | ```js
197 | {
198 | background: 'white',
199 | statusMap: {
200 | selected: 'rgb(255, 252, 127)',
201 | unselected: 'white',
202 | '1P': '#1a0500',
203 | '2m': '#4d0f00',
204 | '2M': '#801a00',
205 | '3m': '#cc2900',
206 | '3M': '#e62e00',
207 | '4P': '#ff3300',
208 | '4A': '#ff5c33',
209 | '5P': '#ff704d',
210 | '6m': '#ff9980',
211 | '6M': '#ffb199',
212 | '7m': '#ffd8cc',
213 | '7M': '#ffebe6',
214 | },
215 | octaveMap: {
216 | 2: 'rgb(95, 190, 244)',
217 | 3: 'rgb(135, 219, 244)',
218 | 4: 'rgb(205, 240, 247)',
219 | 5: 'rgb(242, 246, 247)',
220 | },
221 | fontSize: 12,
222 | dimensions: {
223 | openWidth: 10,
224 | nutWidth: 0.75,
225 | stringHeight: 30,
226 | },
227 | skins: {
228 | strings: {
229 | highlightSize: 85,
230 | highlightBorder: '1px solid gray',
231 | },
232 | },
233 | }
234 | ```
235 |
236 | **fontSize** - in px
237 | **dimensions.openWidth** - percentage width of the open position
238 | **dimensions.nut** - percentage width of the nut
239 | **dimensions.stringHeight** - height of strings in px
240 | **skins.strings.highlightSize** - percentage of stringHeight
241 |
242 |
243 | You can specify any theme attribute you want, as long as you keep the theme structure. It will be merged against the default theme.
244 |
--------------------------------------------------------------------------------