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';