diff --git a/src/Counter/Counter.js b/src/Counter/Counter.js new file mode 100644 index 00000000..cb7f0e6c --- /dev/null +++ b/src/Counter/Counter.js @@ -0,0 +1,51 @@ +import React from 'react'; +import propTypes from 'prop-types'; +import styled from 'styled-components'; + +import { createWellBorderStyles } from '../common'; +import Digit from './Digit'; + +const CounterWrapper = styled.div` + ${createWellBorderStyles(true)} + display: inline-flex; + background: #000000; +`; + +const pixelSizes = { + sm: 1, + md: 2, + lg: 3, + xl: 4 +}; + +const Counter = React.forwardRef(function Counter(props, ref) { + const { value, minLength, size, ...otherProps } = props; + let stringValue = value.toString(); + if (minLength && minLength > stringValue.length) { + stringValue = + Array(minLength - stringValue.length) + .fill('0') + .join('') + stringValue; + } + return ( + + {stringValue.split('').map((digit, i) => ( + + ))} + + ); +}); + +Counter.defaultProps = { + minLength: 3, + size: 'md', + value: 0 +}; + +Counter.propTypes = { + minLength: propTypes.number, + size: propTypes.oneOf(['sm', 'md', 'lg']), + value: propTypes.number +}; + +export default Counter; diff --git a/src/Counter/Counter.mdx b/src/Counter/Counter.mdx new file mode 100644 index 00000000..10c0f2d8 --- /dev/null +++ b/src/Counter/Counter.mdx @@ -0,0 +1,24 @@ +--- +name: Bar +menu: Components +--- + +import { Playground, Props } from 'docz'; + +import Counter from '../Counter/Counter' + +# Counter + +## Usage + + + + + +## API + +### Import + +### Props + + diff --git a/src/Counter/Counter.spec.js b/src/Counter/Counter.spec.js new file mode 100644 index 00000000..fa69b1d4 --- /dev/null +++ b/src/Counter/Counter.spec.js @@ -0,0 +1,50 @@ +import React from 'react'; +import { renderWithTheme } from '../../test/utils'; + +import Counter from './Counter'; + +describe('', () => { + it('should render', () => { + const { container } = renderWithTheme(); + const counter = container.firstChild; + + expect(counter).toBeInTheDocument(); + }); + + it('should handle custom style', () => { + const { container } = renderWithTheme( + + ); + const counter = container.firstChild; + + expect(counter).toHaveAttribute('style', 'background-color: papayawhip;'); + }); + + it('should handle custom props', () => { + const customProps = { title: 'potatoe' }; + const { container } = renderWithTheme(); + const counter = container.firstChild; + + expect(counter).toHaveAttribute('title', 'potatoe'); + }); + + describe('prop: minLength', () => { + it('renders correct number of digits', () => { + const { container } = renderWithTheme( + + ); + const counter = container.firstChild; + + expect(counter.childElementCount).toBe(7); + }); + + it('value length takes priority if bigger than minLength', () => { + const { container } = renderWithTheme( + + ); + const counter = container.firstChild; + + expect(counter.childElementCount).toBe(4); + }); + }); +}); diff --git a/src/Counter/Counter.stories.js b/src/Counter/Counter.stories.js new file mode 100644 index 00000000..9b758abf --- /dev/null +++ b/src/Counter/Counter.stories.js @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; + +import { Counter, Panel, Button } from 'react95'; + +const Wrapper = styled.div` + padding: 5rem; + background: teal; + .counter-wrapper { + display: flex; + margin-top: 1rem; + } + .counter-wrapper button { + margin-left: 0.5rem; + height: 51px; + } + .wrapper { + padding: 1rem; + } +`; + +export default { + title: 'Counter', + component: Counter, + decorators: [story => {story()}] +}; + +export const Default = () => { + const [count, setCount] = useState(13); + const handleClick = () => setCount(count + 1); + return ( + + + +
+ + +
+
+ ); +}; + +Default.story = { + name: 'default' +}; diff --git a/src/Counter/Digit.js b/src/Counter/Digit.js new file mode 100644 index 00000000..fe816c7f --- /dev/null +++ b/src/Counter/Digit.js @@ -0,0 +1,193 @@ +import React from 'react'; +import propTypes from 'prop-types'; +import styled, { css } from 'styled-components'; + +import { createHatchedBackground } from '../common'; + +const DigitWrapper = styled.div` + position: relative; + --react95-digit-primary-color: #ff0102; + --react95-digit-secondary-color: #740201; + --react95-digit-bg-color: #000000; + + ${({ pixelSize }) => css` + width: ${11 * pixelSize}px; + height: ${21 * pixelSize}px; + margin: ${pixelSize}px; + + span, + span:before, + span:after { + box-sizing: border-box; + display: inline-block; + position: absolute; + } + span.active, + span.active:before, + span.active:after { + background: var(--react95-digit-primary-color); + } + span:not(.active), + span:not(.active):before, + span:not(.active):after { + ${createHatchedBackground({ + mainColor: 'var(--react95-digit-bg-color)', + secondaryColor: 'var(--react95-digit-secondary-color)', + pixelSize + })} + } + + span.horizontal, + span.horizontal:before, + span.horizontal:after { + height: ${pixelSize}px; + border-left: ${pixelSize}px solid var(--react95-digit-bg-color); + border-right: ${pixelSize}px solid var(--react95-digit-bg-color); + } + span.horizontal.active, + span.horizontal.active:before, + span.horizontal.active:after { + height: ${pixelSize}px; + border-left: ${pixelSize}px solid var(--react95-digit-primary-color); + border-right: ${pixelSize}px solid var(--react95-digit-primary-color); + } + span.horizontal { + left: ${pixelSize}px; + width: ${9 * pixelSize}px; + } + span.horizontal:before { + content: ''; + width: 100%; + top: ${pixelSize}px; + left: ${0}px; + } + span.horizontal:after { + content: ''; + width: calc(100% - ${pixelSize * 2}px); + top: ${2 * pixelSize}px; + left: ${pixelSize}px; + } + span.horizontal.top { + top: 0; + } + span.horizontal.bottom { + bottom: 0; + transform: rotateX(180deg); + } + + span.center, + span.center:before, + span.center:after { + height: ${pixelSize}px; + border-left: ${pixelSize}px solid var(--react95-digit-bg-color); + border-right: ${pixelSize}px solid var(--react95-digit-bg-color); + } + span.center.active, + span.center.active:before, + span.center.active:after { + border-left: ${pixelSize}px solid var(--react95-digit-primary-color); + border-right: ${pixelSize}px solid var(--react95-digit-primary-color); + } + span.center { + top: 50%; + transform: translateY(-50%); + left: ${pixelSize}px; + width: ${9 * pixelSize}px; + } + span.center:before, + span.center:after { + content: ''; + width: 100%; + } + span.center:before { + top: ${pixelSize}px; + } + span.center:after { + bottom: ${pixelSize}px; + } + + span.vertical, + span.vertical:before, + span.vertical:after { + width: ${pixelSize}px; + border-top: ${pixelSize}px solid var(--react95-digit-bg-color); + border-bottom: ${pixelSize}px solid var(--react95-digit-bg-color); + } + span.vertical { + height: ${11 * pixelSize}px; + } + span.vertical.left { + left: 0; + } + span.vertical.right { + right: 0; + transform: rotateY(180deg); + } + span.vertical.top { + top: 0px; + } + span.vertical.bottom { + bottom: 0px; + } + span.vertical:before { + content: ''; + height: 100%; + top: ${0}px; + left: ${pixelSize}px; + } + span.vertical:after { + content: ''; + height: calc(100% - ${pixelSize * 2}px); + top: ${pixelSize}px; + left: ${pixelSize * 2}px; + } + `} +`; + +const segments = [ + 'horizontal top', + 'center', + 'horizontal bottom', + 'vertical top left', + 'vertical top right', + 'vertical bottom left', + 'vertical bottom right' +]; + +const digitActiveSegments = [ + [1, 0, 1, 1, 1, 1, 1], // 0 + [0, 0, 0, 0, 1, 0, 1], // 1 + [1, 1, 1, 0, 1, 1, 0], // 2 + [1, 1, 1, 0, 1, 0, 1], // 3 + [0, 1, 0, 1, 1, 0, 1], // 4 + [1, 1, 1, 1, 0, 0, 1], // 5 + [1, 1, 1, 1, 0, 1, 1], // 6 + [1, 0, 0, 0, 1, 0, 1], // 7 + [1, 1, 1, 1, 1, 1, 1], // 8 + [1, 1, 1, 1, 1, 0, 1] // 9 +]; + +const Digit = ({ digit, pixelSize, ...otherProps }) => { + const segmentClasses = digitActiveSegments[digit].map((isActive, i) => + isActive ? `${segments[i]} active` : segments[i] + ); + return ( + + {segmentClasses.map((className, i) => ( + + ))} + + ); +}; + +Digit.defaultProps = { + pixelSize: 2, + digit: 0 +}; + +Digit.propTypes = { + pixelSize: propTypes.number, + digit: propTypes.oneOfType(propTypes.number, propTypes.string) +}; + +export default Digit; diff --git a/src/common/index.js b/src/common/index.js index 7946a122..134d267c 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -18,25 +18,28 @@ export const createBoxStyles = () => css` // TODO for flat box styles add checkered background when disabled (not solid color) export const createHatchedBackground = ({ mainColor = 'black', - secondaryColor = 'transparent' + secondaryColor = 'transparent', + pixelSize = 2 }) => css` - background-image: linear-gradient( + background-image: ${[ + `linear-gradient( 45deg, ${mainColor} 25%, transparent 25%, transparent 75%, ${mainColor} 75% - ), - linear-gradient( + )`, + `linear-gradient( 45deg, ${mainColor} 25%, transparent 25%, transparent 75%, ${mainColor} 75% - ); + )` + ].join(',')}; background-color: ${secondaryColor}; - background-size: 4px 4px; - background-position: 0 0, 2px 2px; + background-size: ${`${pixelSize * 2}px ${pixelSize * 2}px`}; + background-position: 0 0, ${`${pixelSize}px ${pixelSize}px`}; `; export const createFlatBoxStyles = () => css` position: relative; diff --git a/src/index.js b/src/index.js index ddbe457a..11081477 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ export { default as Bar } from './Bar/Bar'; export { default as Button } from './Button/Button'; export { default as Checkbox } from './Checkbox/Checkbox'; export { default as ColorInput } from './ColorInput/ColorInput'; +export { default as Counter } from './Counter/Counter'; export { default as Cutout } from './Cutout/Cutout'; export { default as Desktop } from './Desktop/Desktop'; export { default as Divider } from './Divider/Divider';