diff --git a/.npmignore b/.npmignore
index 54bdf08..30ba8a2 100644
--- a/.npmignore
+++ b/.npmignore
@@ -8,7 +8,6 @@ utilities/
.editorconfig
.prettierrc
doczrc.js
-global.d.ts
jest.config.js
jest.setup.ts
plopfile.js
diff --git a/.prettierrc b/.prettierrc
index 75c93d5..3380468 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -4,6 +4,6 @@
"printWidth": 120,
"semi": false,
"trailingComma": "all",
- "quoteProps": "consistent",
+ "quoteProps": "as-needed",
"arrowParens": "always"
}
diff --git a/lib/components/Button/Button.test.tsx b/lib/components/Button/Button.test.tsx
index e2c1b70..c2f286c 100644
--- a/lib/components/Button/Button.test.tsx
+++ b/lib/components/Button/Button.test.tsx
@@ -1,16 +1,17 @@
+///
+///
+
import * as fc from 'fast-check'
import * as Enzyme from 'enzyme'
import * as Axe from 'jest-axe'
import * as React from 'react'
-import Button, { Variant } from './Button'
+import Button, { Variant, ButtonElement } from './Button'
import stringify from '../../services/stringify'
const CUSTOM_VARIANTS: string[] = ['primary']
const AVAILABLE_VARIANTS: string[] = ['outline', 'primary']
-const BLOCK_DISPLAYS = ['block', 'grid', 'flex', 'table']
-
it('should exist', () => {
expect(Button).toBeDefined()
})
@@ -23,21 +24,46 @@ it('should render without crashing given minimum required props', () => {
expect(() => ).not.toThrow()
})
-it('should render a border', () => {
- const wrapper = Enzyme.shallow()
- expect(wrapper.find('button').find('span')).toHaveLength(1)
+it('should render a button element for button kind', () => {
+ const wrapper = Enzyme.shallow()
+ expect(wrapper.name()).toBe('button')
+})
+
+it('should render an a element for anchor kind', () => {
+ const wrapper = Enzyme.shallow()
+ expect(wrapper.name()).toBe('a')
})
-it('should render fullwidth when declared as block', () => {
- const wrapper = Enzyme.shallow()
+describe('on unknown kinds', () => {
+ let originalConsoleError: typeof console.error
- expect(wrapper.find('button')).toHaveStyle('width', '100%')
+ beforeEach(() => {
+ originalConsoleError = console.error
+ console.error = () => {}
+ })
+
+ afterEach(() => {
+ console.error = originalConsoleError
+ })
+
+ it('should render null', () => {
+ fc.assert(
+ fc.property(fc.string().filter(s => !['button', 'a'].includes(s)), (element) => {
+ const wrapper = Enzyme.shallow()
+
+ expect(wrapper.isEmptyRender()).toBe(true)
+ }),
+ {
+ numRuns: 300,
+ },
+ )
+ })
})
-it('should render as block element when declared as block', () => {
- const wrapper = Enzyme.shallow()
- expect(BLOCK_DISPLAYS).toContain(wrapper.find('button').prop('style')!.display)
+it('should render a border', () => {
+ const wrapper = Enzyme.shallow()
+ expect(wrapper.find('button').find('span')).toHaveLength(1)
})
it('should render a label', () => {
@@ -53,6 +79,18 @@ it('should render a label', () => {
)
})
+describe('on disabled', () => {
+ it('should render a disabled button element for button kind', () => {
+ const wrapper = Enzyme.shallow()
+ expect(wrapper.find('button')).toHaveProp('disabled', true)
+ })
+
+ it('should render a span element for anchor kind', () => {
+ const wrapper = Enzyme.shallow()
+ expect(wrapper.name()).toBe('span')
+ })
+})
+
describe.each(AVAILABLE_VARIANTS)('with %p variant', (rawVariant) => {
const variant = rawVariant as Variant
@@ -77,7 +115,7 @@ describe.each(AVAILABLE_VARIANTS)('with %p variant', (rawVariant) => {
})
describe('with unknown variants', () => {
- let originalConsoleError: any
+ let originalConsoleError: typeof console.error
beforeEach(() => {
// silence console.error() from prop type validation since
diff --git a/lib/components/Button/Button.tsx b/lib/components/Button/Button.tsx
index adec171..410aba4 100644
--- a/lib/components/Button/Button.tsx
+++ b/lib/components/Button/Button.tsx
@@ -1,64 +1,86 @@
import * as React from 'react'
import * as PropTypes from 'prop-types'
-import styled from 'styled-components'
+import styled, { CSSObject } from 'styled-components'
import { Size, SizeMap } from '../../services/utilities'
import stringify from '../../services/stringify'
export type Variant = 'outline' | 'primary'
+export type ButtonElement = 'a' | 'button'
+
+type ButtonType = 'submit' | 'reset' | 'button'
+
const MIN_HEIGHTS: SizeMap = {
small: '2.5rem',
medium: '3rem',
large: '4rem',
}
-const Base = styled('button')({
- 'appearance': 'none',
- 'padding': '0 1rem',
- 'font': 'inherit',
- 'fontFamily': 'var(--font-family-base)',
- 'textTransform': 'uppercase',
- 'fontWeight': 'bolder',
- 'borderRadius': '0.25rem',
- 'placeContent': 'center',
- 'position': 'relative',
- 'cursor': 'pointer',
- 'border': 0,
- 'userSelect': 'none',
- 'textDecoration': 'none',
- 'transitionProperty': 'background-color, color',
- 'whiteSpace': 'nowrap',
- 'lineHeight': 1,
+const disabledButtonStyles: CSSObject = {
+ opacity: 0.5,
+ cursor: 'not-allowed',
+}
+
+const buttonStyles: CSSObject = {
+ display: 'grid',
+ appearance: 'none',
+ padding: '0 1rem',
+ font: 'inherit',
+ fontFamily: 'var(--font-family-base)',
+ textTransform: 'uppercase',
+ fontWeight: 'bolder',
+ borderRadius: '0.25rem',
+ placeContent: 'center',
+ position: 'relative',
+ cursor: 'pointer',
+ border: 0,
+ userSelect: 'none',
+ textDecoration: 'none',
+ transitionProperty: 'background-color, color',
+ whiteSpace: 'nowrap',
+ lineHeight: 1,
':focus': {
'--color-accent': 'var(--color-active, Highlight)',
- 'outline': 0,
- },
- ':disabled': {
- opacity: 0.5,
- cursor: 'not-allowed',
+ outline: 0,
},
+ ':disabled': disabledButtonStyles,
'::-moz-focus-inner': {
outline: 0,
border: 0,
},
-})
+}
+
+const disabledLinkButtonStyles: CSSObject = {
+ ...buttonStyles,
+ ...disabledButtonStyles,
+}
+
+const Base = styled('button')(buttonStyles)
Base.displayName = 'button'
+const LinkBase = styled('a')(buttonStyles)
+
+LinkBase.displayName = 'a'
+
+const DisabledLinkBase = styled('span')(disabledLinkButtonStyles)
+
+DisabledLinkBase.displayName = 'span'
+
const Border = styled('span')({
- 'borderColor': 'var(--color-accent, blue)',
- 'boxSizing': 'border-box',
- 'display': 'inline-block',
- 'borderWidth': '0.125rem',
- 'borderStyle': 'solid',
- 'position': 'absolute',
- 'top': 0,
- 'left': 0,
- 'width': '100%',
- 'height': '100%',
- 'borderRadius': 'inherit',
- 'pointerEvents': 'none',
- 'transitionProperty': 'border-color',
+ borderColor: 'var(--color-accent, blue)',
+ boxSizing: 'border-box',
+ display: 'inline-block',
+ borderWidth: '0.125rem',
+ borderStyle: 'solid',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ borderRadius: 'inherit',
+ pointerEvents: 'none',
+ transitionProperty: 'border-color',
'::before': {
position: 'absolute',
top: 0,
@@ -77,6 +99,19 @@ const Border = styled('span')({
Border.displayName = 'span'
+const defaultVariantStyleSet: React.CSSProperties = {
+ backgroundColor: 'transparent',
+ color: 'var(--color-accent, blue)',
+}
+
+const variantStyleSets: Record = {
+ outline: defaultVariantStyleSet,
+ primary: {
+ backgroundColor: 'var(--color-accent, blue)',
+ color: 'var(--color-bg, white)',
+ },
+}
+
const propTypes = {
/**
* Size of the component.
@@ -85,11 +120,7 @@ const propTypes = {
/**
* Variant of the component.
*/
- variant: PropTypes.oneOf>(['outline', 'primary']),
- /**
- * Should the component take up the remaining space parallel to the content flow?
- */
- block: PropTypes.bool,
+ variant: PropTypes.oneOf(['outline', 'primary']),
/**
* Text to identify the action associated upon activation of the component.
*/
@@ -98,43 +129,88 @@ const propTypes = {
* Can the component be activated?
*/
disabled: PropTypes.bool,
+ /**
+ * The corresponding HTML element of the component.
+ */
+ element: PropTypes.oneOf(['a', 'button']),
+ /**
+ * The URL of the page to navigate to, if element is set to "a".
+ */
+ href: PropTypes.string,
+ /**
+ * The target on where to display the page navigated to, if element is set to "a".
+ */
+ target: PropTypes.string,
+ /**
+ * The relationship of the current page to the referred page in "href", if element is set to "a".
+ */
+ rel: PropTypes.string,
+ /**
+ * The type of the button, if element is set to "button".
+ */
+ type: PropTypes.oneOf(['submit', 'reset', 'button']),
}
type Props = PropTypes.InferProps
-const defaultVariantStyleSet: React.CSSProperties = {
- backgroundColor: 'transparent',
- color: 'var(--color-accent, blue)',
-}
-
-const variantStyleSets: Record = {
- outline: defaultVariantStyleSet,
- primary: {
- backgroundColor: 'var(--color-accent, blue)',
- color: 'var(--color-bg, white)',
- },
-}
-
-const Button = React.forwardRef(
- ({ size = 'medium', variant = 'outline', block = false, disabled = false, children, ...etcProps }, ref) => {
+const Button = React.forwardRef(
+ (
+ {
+ size = 'medium',
+ variant = 'outline',
+ disabled = false,
+ children,
+ element = 'button',
+ href,
+ target,
+ rel,
+ type = 'button',
+ },
+ ref,
+ ) => {
const { [variant as Variant]: theVariantStyleSet = defaultVariantStyleSet } = variantStyleSets
-
- return (
-
+ const commonButtonStyles: React.CSSProperties = {
+ ...theVariantStyleSet,
+ minHeight: MIN_HEIGHTS[size!],
+ }
+ const buttonContent = (
+
{stringify(children)}
-
+
)
+
+ switch (element) {
+ case 'button':
+ return (
+ } disabled={disabled!} style={commonButtonStyles}>
+ {buttonContent}
+
+ )
+ case 'a':
+ if (disabled) {
+ return (
+ } style={commonButtonStyles}>
+ {buttonContent}
+
+ )
+ }
+ return (
+ }
+ style={commonButtonStyles}
+ >
+ {buttonContent}
+
+ )
+ default:
+ break
+ }
+
+ return null
},
)
diff --git a/lib/components/Checkbox/Checkbox.test.tsx b/lib/components/Checkbox/Checkbox.test.tsx
index 602782f..2701bc2 100644
--- a/lib/components/Checkbox/Checkbox.test.tsx
+++ b/lib/components/Checkbox/Checkbox.test.tsx
@@ -1,3 +1,6 @@
+///
+///
+
import * as fc from 'fast-check'
import * as Enzyme from 'enzyme'
import * as Axe from 'jest-axe'
diff --git a/lib/components/Checkbox/Checkbox.tsx b/lib/components/Checkbox/Checkbox.tsx
index ae8104b..d458d4a 100644
--- a/lib/components/Checkbox/Checkbox.tsx
+++ b/lib/components/Checkbox/Checkbox.tsx
@@ -9,7 +9,7 @@ const Base = styled('div')({
})
const CaptureArea = styled('label')({
- 'marginTop': '0.25rem',
+ marginTop: '0.25rem',
'::after': {
content: '""',
},
@@ -52,18 +52,18 @@ const IndicatorWrapper = styled('span')({
})
const Border = styled('span')({
- 'borderColor': 'var(--color-accent, blue)',
- 'boxSizing': 'border-box',
- 'display': 'inline-block',
- 'borderWidth': '0.125rem',
- 'borderStyle': 'solid',
- 'position': 'absolute',
- 'top': 0,
- 'left': 0,
- 'width': '100%',
- 'height': '100%',
- 'borderRadius': 'inherit',
- 'transitionProperty': 'border-color',
+ borderColor: 'var(--color-accent, blue)',
+ boxSizing: 'border-box',
+ display: 'inline-block',
+ borderWidth: '0.125rem',
+ borderStyle: 'solid',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ borderRadius: 'inherit',
+ transitionProperty: 'border-color',
'::before': {
position: 'absolute',
top: 0,
diff --git a/lib/components/Icon/Icon.test.tsx b/lib/components/Icon/Icon.test.tsx
index 8d18042..becab96 100644
--- a/lib/components/Icon/Icon.test.tsx
+++ b/lib/components/Icon/Icon.test.tsx
@@ -1,3 +1,6 @@
+///
+///
+
import * as fc from 'fast-check'
import * as Enzyme from 'enzyme'
import * as React from 'react'
diff --git a/lib/components/Icon/Icon.tsx b/lib/components/Icon/Icon.tsx
index 4f1f01d..1dd47f9 100644
--- a/lib/components/Icon/Icon.tsx
+++ b/lib/components/Icon/Icon.tsx
@@ -6,10 +6,10 @@ import { pascalCase, pascalCaseTransformMerge } from 'pascal-case'
import splitValueAndUnit from '../../services/splitValueAndUnit'
const Label = styled('span')({
- 'position': 'absolute',
- 'left': -999999,
- 'width': 1,
- 'height': 1,
+ position: 'absolute',
+ left: -999999,
+ width: 1,
+ height: 1,
':empty': {
display: 'none',
},
@@ -51,19 +51,6 @@ const propTypes = {
type Props = PropTypes.InferProps
-/**
- * Component for displaying graphical icons.
- *
- * @see {@link //feathericons.com|Feather Icons} for a complete list of icons.
- * @param {string} name - Name of the icon to display.
- * @param {string} weight - Width of the icon's strokes.
- * @param {string | number} [size] - Size of the icon. This controls both the width and the height.
- * @param {CSSProperties} [style] - CSS style of the icon. For icon dimensions, use `size` instead.
- * @param {string} [label] - Describe of what the component represents.
- * @param {string} [className] - Class name used for styling.
- * @param {object} etcProps - The rest of the props.
- * @returns {React.ReactElement | null} - The component elements.
- */
const Icon: React.FC = ({
name,
weight = '0.125rem',
diff --git a/lib/components/RadioButton/RadioButton.test.tsx b/lib/components/RadioButton/RadioButton.test.tsx
index 958b13e..59ff9b3 100644
--- a/lib/components/RadioButton/RadioButton.test.tsx
+++ b/lib/components/RadioButton/RadioButton.test.tsx
@@ -1,3 +1,6 @@
+///
+///
+
import * as fc from 'fast-check'
import * as Enzyme from 'enzyme'
import * as Axe from 'jest-axe'
diff --git a/lib/components/RadioButton/RadioButton.tsx b/lib/components/RadioButton/RadioButton.tsx
index b217bc6..59240b3 100644
--- a/lib/components/RadioButton/RadioButton.tsx
+++ b/lib/components/RadioButton/RadioButton.tsx
@@ -8,7 +8,7 @@ const Base = styled('div')({
})
const CaptureArea = styled('label')({
- 'marginTop': '0.25rem',
+ marginTop: '0.25rem',
'::after': {
content: '""',
clear: 'both',
@@ -52,18 +52,18 @@ const IndicatorWrapper = styled('span')({
})
const Border = styled('span')({
- 'borderColor': 'var(--color-accent, blue)',
- 'boxSizing': 'border-box',
- 'display': 'inline-block',
- 'borderWidth': '0.125rem',
- 'borderStyle': 'solid',
- 'position': 'absolute',
- 'top': 0,
- 'left': 0,
- 'width': '100%',
- 'height': '100%',
- 'borderRadius': 'inherit',
- 'transitionProperty': 'border-color',
+ borderColor: 'var(--color-accent, blue)',
+ boxSizing: 'border-box',
+ display: 'inline-block',
+ borderWidth: '0.125rem',
+ borderStyle: 'solid',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ borderRadius: 'inherit',
+ transitionProperty: 'border-color',
'::before': {
position: 'absolute',
top: 0,
@@ -131,21 +131,26 @@ type Props = PropTypes.InferProps
* @see {@link Select} for a similar component suitable for selecting more values.
* @type {React.ComponentType<{readonly label?: string, readonly name?: string} & React.ClassAttributes>}
*/
-const RadioButton = React.forwardRef(({ label = '', name, ...etcProps }, ref) => (
-
-
-
-
-
-
-
- {typeof label! !== 'undefined' && label !== null && ' '}
-
-
-
-))
+const RadioButton = React.forwardRef(
+ (
+ { label = '', name, ...etcProps },
+ ref
+ ) => (
+
+
+
+
+
+
+
+ {typeof label! !== 'undefined' && label !== null && ' '}
+
+
+
+ )
+)
RadioButton.propTypes = propTypes
diff --git a/lib/components/Select/Select.test.tsx b/lib/components/Select/Select.test.tsx
index 9caf497..8e61a09 100644
--- a/lib/components/Select/Select.test.tsx
+++ b/lib/components/Select/Select.test.tsx
@@ -1,3 +1,6 @@
+///
+///
+
import * as fc from 'fast-check'
import * as Enzyme from 'enzyme'
import * as Axe from 'jest-axe'
@@ -68,33 +71,6 @@ it('should render a hint for describing valid input values', () => {
)
})
-describe('on being declared a block component', () => {
- const BLOCK_DISPLAYS = ['block', 'grid', 'flex', 'table']
-
- it('should render the base element fullwidth', () => {
- const wrapper = Enzyme.shallow()
-
- expect(BLOCK_DISPLAYS).toContain(wrapper.find('div').prop('style')!.display)
- })
-
- it('should render the input fullwidth', () => {
- const wrapper = Enzyme.shallow()
-
- expect(wrapper.find('label').find('select')).toHaveStyle('width', '100%')
- })
-
- it('should render the input as block element', () => {
- const wrapper = Enzyme.shallow()
-
- expect(BLOCK_DISPLAYS).toContain(
- wrapper
- .find('label')
- .find('select')
- .prop('style')!.display,
- )
- })
-})
-
it('should render as half-opaque when disabled', () => {
const wrapper = Enzyme.shallow()
diff --git a/lib/components/Select/Select.tsx b/lib/components/Select/Select.tsx
index 5b03510..cfd36c6 100644
--- a/lib/components/Select/Select.tsx
+++ b/lib/components/Select/Select.tsx
@@ -36,10 +36,10 @@ const SECONDARY_TEXT_SIZES: SizeMap = {
}
const ComponentBase = styled('div')({
- 'position': 'relative',
- 'borderRadius': '0.25rem',
- 'fontFamily': 'var(--font-family-base)',
- 'maxWidth': '100%',
+ position: 'relative',
+ borderRadius: '0.25rem',
+ fontFamily: 'var(--font-family-base)',
+ maxWidth: '100%',
':focus-within': {
'--color-accent': 'var(--color-active, Highlight)',
},
@@ -76,21 +76,22 @@ const LabelWrapper = styled('span')({
LabelWrapper.displayName = 'span'
const Input = styled('select')({
- 'backgroundColor': 'var(--color-bg, white)',
- 'color': 'var(--color-fg, black)',
- 'appearance': 'none',
- 'boxSizing': 'border-box',
- 'position': 'relative',
- 'border': 0,
- 'paddingLeft': '1rem',
- 'margin': 0,
- 'font': 'inherit',
- 'minHeight': '4rem',
- 'minWidth': '16rem',
- 'maxWidth': '100%',
- 'zIndex': 1,
- 'cursor': 'pointer',
- 'transitionProperty': 'background-color, color',
+ display: 'block',
+ backgroundColor: 'var(--color-bg, white)',
+ color: 'var(--color-fg, black)',
+ appearance: 'none',
+ boxSizing: 'border-box',
+ position: 'relative',
+ border: 0,
+ paddingLeft: '1rem',
+ margin: 0,
+ font: 'inherit',
+ minHeight: '4rem',
+ minWidth: '8rem',
+ maxWidth: '100%',
+ zIndex: 1,
+ cursor: 'pointer',
+ transitionProperty: 'background-color, color',
':focus': {
outline: 0,
},
@@ -106,20 +107,20 @@ const Input = styled('select')({
Input.displayName = 'select'
const Border = styled('span')({
- 'borderColor': 'var(--color-accent, blue)',
- 'boxSizing': 'border-box',
- 'display': 'inline-block',
- 'borderWidth': '0.125rem',
- 'borderStyle': 'solid',
- 'position': 'absolute',
- 'top': 0,
- 'left': 0,
- 'width': '100%',
- 'height': '100%',
- 'borderRadius': 'inherit',
- 'zIndex': 2,
- 'pointerEvents': 'none',
- 'transitionProperty': 'border-color',
+ borderColor: 'var(--color-accent, blue)',
+ boxSizing: 'border-box',
+ display: 'inline-block',
+ borderWidth: '0.125rem',
+ borderStyle: 'solid',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ borderRadius: 'inherit',
+ zIndex: 2,
+ pointerEvents: 'none',
+ transitionProperty: 'border-color',
'::before': {
position: 'absolute',
top: 0,
@@ -188,10 +189,6 @@ const propTypes = {
* Size of the component.
*/
size: PropTypes.oneOf(['small', 'medium', 'large']),
- /**
- * Should the component take up the remaining space parallel to the content flow?
- */
- block: PropTypes.bool,
/**
* Can multiple values be selected?
*/
@@ -208,22 +205,13 @@ const propTypes = {
type Props = PropTypes.InferProps
-/**
- * Component for selecting values from a larger number of options.
- * @see {@link Checkbox} for a similar component on selecting values among very few choices.
- * @see {@link RadioButton} for a similar component on selecting a single value among very few choices.
- * @type {React.ComponentType<{readonly label?: string, readonly hint?: string, readonly className?: string, readonly
- * size?: 'small' | 'medium' | 'large', readonly multiple?: boolean, readonly block?: boolean} &
- * React.ClassAttributes>}
- */
const Select = React.forwardRef(
(
{
label = '',
className = '',
hint = '',
- size: sizeProp = 'medium',
- block = false,
+ size = 'medium',
multiple = false,
disabled = false,
style = {},
@@ -231,11 +219,9 @@ const Select = React.forwardRef(
},
ref,
) => {
- const size = sizeProp as Size
return (
@@ -259,10 +245,8 @@ const Select = React.forwardRef(
multiple={multiple!}
style={{
...style,
- display: block ? 'block' : 'inline-block',
verticalAlign: 'top',
fontSize: INPUT_FONT_SIZES[size!],
- width: block || multiple ? '100%' : undefined,
height: multiple ? undefined : MIN_HEIGHTS[size!],
minHeight: MIN_HEIGHTS[size!],
resize: multiple ? 'vertical' : undefined,
diff --git a/lib/components/Slider/Slider.test.tsx b/lib/components/Slider/Slider.test.tsx
index 9bb2c30..a710259 100644
--- a/lib/components/Slider/Slider.test.tsx
+++ b/lib/components/Slider/Slider.test.tsx
@@ -1,3 +1,6 @@
+///
+///
+
import * as fc from 'fast-check'
import * as Enzyme from 'enzyme'
import * as React from 'react'
diff --git a/lib/components/Slider/Slider.tsx b/lib/components/Slider/Slider.tsx
index e2bc111..8990f58 100644
--- a/lib/components/Slider/Slider.tsx
+++ b/lib/components/Slider/Slider.tsx
@@ -5,9 +5,9 @@ import styled from 'styled-components'
import stringify from '../../services/stringify'
const Wrapper = styled('div')({
- 'position': 'relative',
- 'display': 'inline-block',
- 'verticalAlign': 'top',
+ position: 'relative',
+ display: 'inline-block',
+ verticalAlign: 'top',
':focus-within': {
'--color-accent': 'var(--color-active, Highlight)',
},
@@ -16,10 +16,10 @@ const Wrapper = styled('div')({
Wrapper.displayName = 'div'
const Base = styled(ReachSlider.SliderInput)({
- 'boxSizing': 'border-box',
- 'position': 'absolute',
- 'bottom': 0,
- 'right': 0,
+ boxSizing: 'border-box',
+ position: 'absolute',
+ bottom: 0,
+ right: 0,
':active': {
cursor: 'grabbing',
},
@@ -28,10 +28,10 @@ const Base = styled(ReachSlider.SliderInput)({
Base.displayName = 'input'
const Track = styled(ReachSlider.SliderTrack)({
- 'borderRadius': '0.125rem',
- 'position': 'relative',
- 'width': '100%',
- 'height': '100%',
+ borderRadius: '0.125rem',
+ position: 'relative',
+ width: '100%',
+ height: '100%',
'::before': {
display: 'block',
position: 'absolute',
@@ -45,15 +45,15 @@ const Track = styled(ReachSlider.SliderTrack)({
})
const Handle = styled(ReachSlider.SliderHandle)({
- 'cursor': 'grab',
- 'width': '1.25rem',
- 'height': '1.25rem',
- 'backgroundColor': 'var(--color-accent, blue)',
- 'borderRadius': '50%',
- 'zIndex': 1,
- 'transformOrigin': 'center',
- 'outline': 0,
- 'position': 'relative',
+ cursor: 'grab',
+ width: '1.25rem',
+ height: '1.25rem',
+ backgroundColor: 'var(--color-accent, blue)',
+ borderRadius: '50%',
+ zIndex: 1,
+ transformOrigin: 'center',
+ outline: 0,
+ position: 'relative',
'::before': {
position: 'absolute',
top: 0,
@@ -113,12 +113,12 @@ const LabelWrapper = styled('span')({
LabelWrapper.displayName = 'span'
const FallbackTrack = styled('span')({
- 'padding': '1.875rem 0.75rem 0.875rem',
- 'boxSizing': 'border-box',
- 'position': 'absolute',
- 'top': 0,
- 'left': 0,
- 'pointerEvents': 'none',
+ padding: '1.875rem 0.75rem 0.875rem',
+ boxSizing: 'border-box',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ pointerEvents: 'none',
'::before': {
display: 'block',
content: "''",
@@ -131,17 +131,17 @@ const FallbackTrack = styled('span')({
})
const FallbackSlider = styled('input')({
- 'boxSizing': 'border-box',
- 'backgroundColor': 'transparent',
- 'verticalAlign': 'top',
- 'margin': 0,
- 'width': '100%',
- 'height': '2rem',
- 'marginTop': '1rem',
- 'appearance': 'none',
- 'outline': 0,
- 'position': 'absolute',
- 'left': 0,
+ boxSizing: 'border-box',
+ backgroundColor: 'transparent',
+ verticalAlign: 'top',
+ margin: 0,
+ width: '100%',
+ height: '2rem',
+ marginTop: '1rem',
+ appearance: 'none',
+ outline: 0,
+ position: 'absolute',
+ left: 0,
'::-moz-focus-inner': {
outline: 0,
border: 0,
diff --git a/lib/components/TextInput/TextInput.test.tsx b/lib/components/TextInput/TextInput.test.tsx
index 06a48a9..554361d 100644
--- a/lib/components/TextInput/TextInput.test.tsx
+++ b/lib/components/TextInput/TextInput.test.tsx
@@ -1,3 +1,6 @@
+///
+///
+
import * as fc from 'fast-check'
import * as Enzyme from 'enzyme'
import * as Axe from 'jest-axe'
@@ -64,13 +67,12 @@ it('should render as half-opaque when disabled', () => {
})
describe.each`
- multiline | inputWidth | tag
- ${false} | ${'100%'} | ${'input'}
- ${true} | ${undefined} | ${'textarea'}
-`('on multiline (multiline=$multiline)', ({ tag: rawTag, multiline: rawMultiline, inputWidth: rawInputWidth }) => {
+ multiline | tag
+ ${false} | ${'input'}
+ ${true} | ${'textarea'}
+`('on multiline (multiline=$multiline)', ({ tag: rawTag, multiline: rawMultiline, }) => {
const tag = rawTag as string
const multiline = rawMultiline as boolean
- const inputWidth = rawInputWidth as string
it('should render an element to input text on', () => {
const wrapper = Enzyme.shallow()
@@ -99,39 +101,6 @@ describe.each`
.prop('style')!.paddingRight,
).not.toBe('1rem')
})
-
- describe('on being declared a block component', () => {
- const BLOCK_DISPLAYS = ['block', 'grid', 'flex', 'table']
-
- it('should render the base element fullwidth', () => {
- const wrapper = Enzyme.shallow()
-
- const { display } = wrapper.find('div').prop('style')!
-
- expect(BLOCK_DISPLAYS).toContain(display)
- })
-
- it('should render the input fullwidth', () => {
- const wrapper = Enzyme.shallow()
-
- if (tag === 'textarea') {
- expect(wrapper.find('label').find(tag)).not.toHaveStyle('width')
- } else {
- expect(wrapper.find('label').find(tag)).toHaveStyle('width', inputWidth)
- }
- })
-
- it('should render the input as block element', () => {
- const wrapper = Enzyme.shallow()
-
- const { display } = wrapper
- .find('label')
- .find(tag)
- .prop('style')!
-
- expect(BLOCK_DISPLAYS).toContain(display)
- })
- })
})
describe('on aiding user input', () => {
diff --git a/lib/components/TextInput/TextInput.tsx b/lib/components/TextInput/TextInput.tsx
index 6a63d37..3f829a8 100644
--- a/lib/components/TextInput/TextInput.tsx
+++ b/lib/components/TextInput/TextInput.tsx
@@ -3,7 +3,6 @@ import * as PropTypes from 'prop-types'
import styled from 'styled-components'
import stringify from '../../services/stringify'
import { Size, SizeMap } from '../../services/utilities'
-import { ChangeEvent, InputHTMLAttributes, TextareaHTMLAttributes } from 'react'
const MIN_HEIGHTS: SizeMap = {
small: '2.5rem',
@@ -36,10 +35,10 @@ const SECONDARY_TEXT_SIZES: SizeMap = {
}
const ComponentBase = styled('div')({
- 'position': 'relative',
- 'borderRadius': '0.25rem',
- 'fontFamily': 'var(--font-family-base)',
- 'maxWidth': '100%',
+ position: 'relative',
+ borderRadius: '0.25rem',
+ fontFamily: 'var(--font-family-base)',
+ maxWidth: '100%',
':focus-within': {
'--color-accent': 'var(--color-active, Highlight)',
},
@@ -78,20 +77,20 @@ const LabelWrapper = styled('span')({
LabelWrapper.displayName = 'span'
const Border = styled('span')({
- 'borderColor': 'var(--color-accent, blue)',
- 'boxSizing': 'border-box',
- 'display': 'inline-block',
- 'borderWidth': '0.125rem',
- 'borderStyle': 'solid',
- 'position': 'absolute',
- 'top': 0,
- 'left': 0,
- 'width': '100%',
- 'height': '100%',
- 'borderRadius': 'inherit',
- 'zIndex': 2,
- 'pointerEvents': 'none',
- 'transitionProperty': 'border-color',
+ borderColor: 'var(--color-accent, blue)',
+ boxSizing: 'border-box',
+ display: 'inline-block',
+ borderWidth: '0.125rem',
+ borderStyle: 'solid',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ borderRadius: 'inherit',
+ zIndex: 2,
+ pointerEvents: 'none',
+ transitionProperty: 'border-color',
'::before': {
position: 'absolute',
top: 0,
@@ -109,23 +108,24 @@ const Border = styled('span')({
})
const Input = styled('input')({
- 'backgroundColor': 'var(--color-bg, white)',
- 'color': 'var(--color-fg, black)',
- 'verticalAlign': 'top',
- 'paddingTop': 0,
- 'paddingBottom': 0,
- 'boxSizing': 'border-box',
- 'position': 'relative',
- 'border': 0,
- 'borderRadius': 'inherit',
- 'paddingLeft': '1rem',
- 'margin': 0,
- 'font': 'inherit',
- 'minHeight': '4rem',
- 'minWidth': '16rem',
- 'maxWidth': '100%',
- 'zIndex': 1,
- 'transitionProperty': 'background-color, color',
+ display: 'block',
+ backgroundColor: 'var(--color-bg, white)',
+ color: 'var(--color-fg, black)',
+ verticalAlign: 'top',
+ paddingTop: 0,
+ paddingBottom: 0,
+ boxSizing: 'border-box',
+ position: 'relative',
+ border: 0,
+ borderRadius: 'inherit',
+ paddingLeft: '1rem',
+ margin: 0,
+ font: 'inherit',
+ minHeight: '4rem',
+ minWidth: '8rem',
+ maxWidth: '100%',
+ zIndex: 1,
+ transitionProperty: 'background-color, color',
':focus': {
outline: 0,
color: 'var(--color-fg, black)',
@@ -138,22 +138,23 @@ const Input = styled('input')({
Input.displayName = 'input'
const TextArea = styled('textarea')({
- 'backgroundColor': 'var(--color-bg, white)',
- 'color': 'var(--color-fg, black)',
- 'verticalAlign': 'top',
- 'width': '100%',
- 'boxSizing': 'border-box',
- 'position': 'relative',
- 'border': 0,
- 'borderRadius': 'inherit',
- 'paddingLeft': '1rem',
- 'margin': 0,
- 'font': 'inherit',
- 'minHeight': '4rem',
- 'minWidth': '16rem',
- 'maxWidth': '100%',
- 'zIndex': 1,
- 'transitionProperty': 'background-color, color',
+ display: 'block',
+ backgroundColor: 'var(--color-bg, white)',
+ color: 'var(--color-fg, black)',
+ verticalAlign: 'top',
+ width: '100%',
+ boxSizing: 'border-box',
+ position: 'relative',
+ border: 0,
+ borderRadius: 'inherit',
+ paddingLeft: '1rem',
+ margin: 0,
+ font: 'inherit',
+ minHeight: '4rem',
+ minWidth: '16rem',
+ maxWidth: '100%',
+ zIndex: 1,
+ transitionProperty: 'background-color, color',
':focus': {
outline: 0,
},
@@ -216,10 +217,6 @@ const propTypes = {
* Additional description, usually graphical, indicating the nature of the component's value.
*/
indicator: PropTypes.node,
- /**
- * Should the component take up the remaining space parallel to the content flow?
- */
- block: PropTypes.bool,
/**
* Should the component accept multiple lines of input?
*/
@@ -240,12 +237,6 @@ const propTypes = {
type Props = PropTypes.InferProps
-/**
- * Component for inputting textual values.
- * @type {React.ComponentType<{readonly label?: string, readonly hint?: string, readonly multiline?: boolean, readonly
- * className?: string, readonly indicator?: *, readonly size?: 'small' | 'medium' | 'large', readonly block?:
- * boolean} & React.ClassAttributes>}
- */
const TextInput = React.forwardRef(
(
{
@@ -253,92 +244,84 @@ const TextInput = React.forwardRef {
- const size: Size = sizeProp as Size
- return (
-
-
-
-
- {stringify(label)}
-
- {stringify(label).length > 0 && ' '}
- {multiline && (
-
- {stringify(hint).length > 0 && ' '}
- {stringify(hint).length > 0 && (
- (
+
+
+
+
+ {stringify(label)}
+
+ {stringify(label).length > 0 && ' '}
+ {multiline && (
+
+ />
)}
- {(indicator as PropTypes.ReactComponentLike) && (
- }
+ disabled={disabled!}
style={{
- width: MIN_HEIGHTS[size!],
- height: MIN_HEIGHTS[size!],
+ fontSize: INPUT_FONT_SIZES[size!],
+ minHeight: MIN_HEIGHTS[size!],
+ paddingRight: indicator ? MIN_HEIGHTS[size!] : '1rem',
}}
- >
- {indicator}
-
+ />
)}
-
- )
- },
+
+ {stringify(hint).length > 0 && ' '}
+ {stringify(hint).length > 0 && (
+
+ ({stringify(hint)})
+
+ )}
+ {(indicator as PropTypes.ReactComponentLike) && (
+
+ {indicator}
+
+ )}
+
+ ),
)
TextInput.propTypes = propTypes
diff --git a/lib/index.test.ts b/lib/index.test.ts
new file mode 100644
index 0000000..7d5d7ce
--- /dev/null
+++ b/lib/index.test.ts
@@ -0,0 +1,26 @@
+///
+///
+
+import * as T from './index'
+
+const Tesseract = T as Record
+
+describe.each`
+ name | componentId
+ ${'button'} | ${'Button'}
+ ${'checkbox'} | ${'Checkbox'}
+ ${'icon'} | ${'Icon'}
+ ${'radio button'} | ${'RadioButton'}
+ ${'select'} | ${'Select'}
+ ${'slider'} | ${'Slider'}
+ ${'text input'} | ${'TextInput'}
+`('on $name component', ({ componentId, }) => {
+ const theComponentId = componentId as string
+ it('should exist', () => {
+ expect(Tesseract[theComponentId]).toBeDefined()
+ })
+
+ it('should be a component', () => {
+ expect(Tesseract[theComponentId]).toBeComponent()
+ })
+})
diff --git a/lib/services/isEmpty.test.ts b/lib/services/isEmpty.test.ts
index 88b656d..4837494 100644
--- a/lib/services/isEmpty.test.ts
+++ b/lib/services/isEmpty.test.ts
@@ -16,12 +16,9 @@ describe('lib/services/isEmpty', () => {
it('should return a boolean value', () => {
fc.assert(
- fc.property(
- fc.anything(),
- v => {
- expect(typeof isEmpty(v)).toBe('boolean')
- }
- )
+ fc.property(fc.anything(), (v) => {
+ expect(typeof isEmpty(v)).toBe('boolean')
+ }),
)
})
@@ -36,12 +33,9 @@ describe('lib/services/isEmpty', () => {
it('should return `false` on an argument with value that is neither `undefined` nor `null`', () => {
fc.assert(
- fc.property(
- fc.anything().filter(v => typeof v !== 'undefined' && v !== null),
- v => {
- expect(isEmpty(v)).toBe(false)
- }
- )
+ fc.property(fc.anything().filter((v) => typeof v !== 'undefined' && v !== null), (v) => {
+ expect(isEmpty(v)).toBe(false)
+ }),
)
})
})
diff --git a/lib/services/isEmpty.ts b/lib/services/isEmpty.ts
index b0b72f9..60ba2e2 100644
--- a/lib/services/isEmpty.ts
+++ b/lib/services/isEmpty.ts
@@ -2,9 +2,6 @@ interface IsEmpty {
(v: any): boolean
}
-const isEmpty: IsEmpty = v => (
- typeof v === 'undefined'
- || v === null
-)
+const isEmpty: IsEmpty = (v) => typeof v === 'undefined' || v === null
export default isEmpty
diff --git a/lib/services/splitValueAndUnit.test.ts b/lib/services/splitValueAndUnit.test.ts
index 8ca9483..5e0027e 100644
--- a/lib/services/splitValueAndUnit.test.ts
+++ b/lib/services/splitValueAndUnit.test.ts
@@ -1,5 +1,5 @@
import * as fc from 'fast-check'
-import splitValueAndUnit, { Unit, } from './splitValueAndUnit'
+import splitValueAndUnit, { Unit } from './splitValueAndUnit'
it('should exist', () => {
expect(splitValueAndUnit).toBeDefined()
@@ -15,46 +15,33 @@ it('should accept 1 argument', () => {
it('should throw a TypeError when invalid values are supplied', () => {
fc.assert(
- fc.property(
- fc.anything().filter(s => !['string', 'number'].includes(typeof s)),
- s => {
- expect(() => splitValueAndUnit(s)).toThrowError(TypeError)
- }
- )
+ fc.property(fc.anything().filter((s) => !['string', 'number'].includes(typeof s)), (s) => {
+ expect(() => splitValueAndUnit(s)).toThrowError(TypeError)
+ }),
)
})
it('should parse valid CSS numbers', () => {
fc.assert(
fc.property(
- fc.tuple(
- fc.float(),
- fc.oneof(
- fc.constant('px'),
- fc.constant('rem'),
- fc.constant('%'),
- )
- ),
- ([magnitude, unit,]) => {
+ fc.tuple(fc.float(), fc.oneof(fc.constant('px'), fc.constant('rem'), fc.constant('%'))),
+ ([magnitude, unit]) => {
expect(splitValueAndUnit(`${magnitude}${unit}`)).toEqual({
magnitude,
unit,
})
- }
- )
+ },
+ ),
)
})
it('should parse numbers as CSS numbers with implicit pixel units', () => {
fc.assert(
- fc.property(
- fc.float(),
- magnitude => {
- expect(splitValueAndUnit(magnitude)).toEqual({
- magnitude,
- unit: 'px',
- })
- }
- )
+ fc.property(fc.float(), (magnitude) => {
+ expect(splitValueAndUnit(magnitude)).toEqual({
+ magnitude,
+ unit: 'px',
+ })
+ }),
)
})
diff --git a/lib/services/splitValueAndUnit.ts b/lib/services/splitValueAndUnit.ts
index 07723c5..812b3c9 100644
--- a/lib/services/splitValueAndUnit.ts
+++ b/lib/services/splitValueAndUnit.ts
@@ -1,15 +1,15 @@
export type Unit = 'px' | '%' | 'rem'
export interface ValueAndUnit {
- magnitude: number,
- unit: Unit,
+ magnitude: number
+ unit: Unit
}
interface SplitValueAndUnit {
(value: any): ValueAndUnit
}
-const splitValueAndUnit: SplitValueAndUnit = value => {
+const splitValueAndUnit: SplitValueAndUnit = (value) => {
if (!['string', 'number'].includes(typeof value)) {
throw TypeError('Argument must be a valid CSS number')
}
diff --git a/lib/services/stringify.test.ts b/lib/services/stringify.test.ts
index 180b62b..7af7924 100644
--- a/lib/services/stringify.test.ts
+++ b/lib/services/stringify.test.ts
@@ -16,12 +16,9 @@ it('should accept 1 argument', () => {
it('should return a string value', () => {
fc.assert(
- fc.property(
- fc.anything(),
- v => {
- expect(stringify(v)).toBeString()
- }
- )
+ fc.property(fc.anything(), (v) => {
+ expect(stringify(v)).toBeString()
+ }),
)
})
@@ -36,58 +33,42 @@ describe('on arguments', () => {
it('should stringify non-objects', () => {
fc.assert(
- fc.property(
- fcArb.nonObject(),
- fc.string(),
- v => {
- expect(stringify(v)).toBe(String(v))
- }
- )
+ fc.property(fcArb.nonObject(), fc.string(), (v) => {
+ expect(stringify(v)).toBe(String(v))
+ }),
)
})
it('should stringify objects', () => {
fc.assert(
- fc.property(
- fc.object(),
- v => {
- expect(stringify(v)).toBe(JSON.stringify(v))
- }
- )
+ fc.property(fc.object(), (v) => {
+ expect(stringify(v)).toBe(JSON.stringify(v))
+ }),
)
})
describe('on arrays', () => {
it('should stringify empty arrays', () => {
fc.assert(
- fc.property(
- fc.array(fcArb.nonObject(), 0),
- v => {
- expect(stringify(v)).toBe('')
- }
- )
+ fc.property(fc.array(fcArb.nonObject(), 0), (v) => {
+ expect(stringify(v)).toBe('')
+ }),
)
})
it('should stringify arrays with single values', () => {
fc.assert(
- fc.property(
- fc.array(fcArb.nonObject(), 1, 1),
- v => {
- expect(stringify(v)).toBe(String(v[0]))
- }
- )
+ fc.property(fc.array(fcArb.nonObject(), 1, 1), (v) => {
+ expect(stringify(v)).toBe(String(v[0]))
+ }),
)
})
it('should stringify arrays with 2 or more values', () => {
fc.assert(
- fc.property(
- fc.array(fcArb.nonObject(), 2, 20),
- v => {
- expect(stringify(v)).toContain(',')
- }
- )
+ fc.property(fc.array(fcArb.nonObject(), 2, 20), (v) => {
+ expect(stringify(v)).toContain(',')
+ }),
)
})
})
diff --git a/lib/services/stringify.ts b/lib/services/stringify.ts
index e0b7833..e34bbc6 100644
--- a/lib/services/stringify.ts
+++ b/lib/services/stringify.ts
@@ -1,18 +1,18 @@
import isEmpty from './isEmpty'
interface Stringify {
- (v: any): string,
+ (v: any): string
}
-const stringify: Stringify = v => {
+const stringify: Stringify = (v) => {
if (isEmpty(v)) {
return ''
}
if (Array.isArray(v)) {
return v
- .filter(v => !isEmpty(v))
- .map(v => stringify(v))
+ .filter((v) => !isEmpty(v))
+ .map((v) => stringify(v))
.join(',')
}
diff --git a/package.json b/package.json
index c8ccf74..abd94f2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@tesseract-design/react-common",
- "version": "0.0.0",
+ "version": "0.0.1",
"description": "Common front-end components for Web using the Tesseract design system, written in React.",
"directories": {
"lib": "dist"