Add value, defaultValue, and checked props to appropriate elements. Add link to Feather Icons for the Icon component. The styling of TextInput docs has been fixed.tags/0.3.0
@@ -1 +0,0 @@ | |||||
packages/react-common-docs/src/pages/index.md |
@@ -0,0 +1,94 @@ | |||||
# Tesseract Design - React Common | |||||
Common front-end components for Web using the [Tesseract design system](https://make.modal.sh/tesseract/design), written for [React](https://reactjs.org). | |||||
Package: | |||||
[![@tesseract-design/react-common](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=package&color=C78AB3&kind=name)](https://js.pack.modal.sh/-/web/detail/@tesseract-design/react-common) | |||||
[![Version List](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=package&color=C78AB3&kind=version)](https://js.pack.modal.sh/-/web/detail/@tesseract-design/react-common#versions) | |||||
Dependencies: | |||||
[![React](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=react&kind=peerDependencies)](https://github.com/facebook/react) | |||||
[![Styled Components](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=styled-components&kind=peerDependencies)](https://github.com/styled-components/styled-components) | |||||
[![TypeScript](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=typescript&kind=devDependencies)](https://github.com/microsoft/typescript) | |||||
[![Jest](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=jest&kind=devDependencies)](https://github.com/facebook/jest) | |||||
[![Rollup](https://code.modal.sh/badge?repo=tesseract-design/react-common&type=dependency&dependency=rollup&kind=devDependencies)](https://github.com/rollup/rollup) | |||||
Check the [documentation](https://make.modal.sh/tesseract/web/react/common) for more details. | |||||
## Installation | |||||
This package resides primarily in the [Modal.sh JavaScript Package Registry](https://js.pack.modal.sh/). You will need to | |||||
adjust configuration in your chosen package manager. | |||||
With [Yarn v.1.x](https://classic.arnpkg.com), add this to your `.yarnrc` file: | |||||
```yarnrc | |||||
"@tesseract-design:registry" "https://js.pack.modal.sh/" | |||||
``` | |||||
Then, install the package by running the following command: | |||||
```shell script | |||||
yarn add @tesseract-design/react-common | |||||
``` | |||||
## Usage | |||||
The package includes components as named exports. Simply import the components you need individually or use a namespace | |||||
import, like so: | |||||
```jsx harmony | |||||
import * as React from 'react' | |||||
import ReactDOM from 'react-dom' | |||||
import * as T from '@tesseract-design/react-common' | |||||
const LoginForm = etcProps => ( | |||||
<form | |||||
{...etcProps} | |||||
> | |||||
<fieldset> | |||||
<legend> | |||||
Log In | |||||
</legend> | |||||
<div> | |||||
<T.TextInput | |||||
label="Username" | |||||
/> | |||||
</div> | |||||
<div> | |||||
<T.TextInput | |||||
type="password" | |||||
label="Password" | |||||
/> | |||||
</div> | |||||
<div> | |||||
<T.Button> | |||||
Log In | |||||
</T.Button> | |||||
</div> | |||||
</fieldset> | |||||
</form> | |||||
) | |||||
const mountNode = window.document.createElement('div') | |||||
ReactDOM.render( | |||||
<LoginForm />, | |||||
mountNode, | |||||
) | |||||
window.document.body.appendChild(mountNode) | |||||
``` | |||||
Detailed usage guides can be found in the documentation linked above. | |||||
## TypeScript | |||||
The package is written and tested using TypeScript. Thus, typings for consumption in TypeScript are bundled with the | |||||
compiled source. | |||||
## License | |||||
MIT. See the LICENSE file on the package repository for the full text. |
@@ -2,7 +2,7 @@ | |||||
"name": "@tesseract-design/react-common", | "name": "@tesseract-design/react-common", | ||||
"title": "React Common", | "title": "React Common", | ||||
"org": "Tesseract Design", | "org": "Tesseract Design", | ||||
"version": "0.2.4", | |||||
"version": "0.2.5", | |||||
"description": "Common front-end components for Web using the Tesseract design system, written in React.", | "description": "Common front-end components for Web using the Tesseract design system, written in React.", | ||||
"directories": { | "directories": { | ||||
"lib": "dist" | "lib": "dist" | ||||
@@ -52,8 +52,7 @@ | |||||
"prepublishOnly": "NODE_ENV=production rm -rf dist/ && rollup -c", | "prepublishOnly": "NODE_ENV=production rm -rf dist/ && rollup -c", | ||||
"test": "jest", | "test": "jest", | ||||
"build": "rm -rf dist/ && rollup -c", | "build": "rm -rf dist/ && rollup -c", | ||||
"generate": "plop", | |||||
"docs": "docz" | |||||
"generate": "plop" | |||||
}, | }, | ||||
"peerDependencies": { | "peerDependencies": { | ||||
"@reach/slider": "^0.10.5", | "@reach/slider": "^0.10.5", | ||||
@@ -19,7 +19,7 @@ const Title = styled('strong')({ | |||||
const Org = styled('small')({ | const Org = styled('small')({ | ||||
position: 'absolute', | position: 'absolute', | ||||
top: '-0.25em', | |||||
top: '-0.375em', | |||||
right: 0, | right: 0, | ||||
fontWeight: 600, | fontWeight: 600, | ||||
textTransform: 'lowercase', | textTransform: 'lowercase', | ||||
@@ -46,7 +46,7 @@ | |||||
--font-family-base: 'Encode Sans', system-ui; | --font-family-base: 'Encode Sans', system-ui; | ||||
--font-stretch-base: semi-expanded; | --font-stretch-base: semi-expanded; | ||||
--font-weight-base: 400; | --font-weight-base: 400; | ||||
--line-height-base: 2; | |||||
--line-height-base: 2em; | |||||
--font-family-headings: 'Encode Sans', system-ui; | --font-family-headings: 'Encode Sans', system-ui; | ||||
--font-stretch-headings: condensed; | --font-stretch-headings: condensed; | ||||
--font-weight-headings: 100; | --font-weight-headings: 100; | ||||
@@ -228,6 +228,24 @@ | |||||
"type": { | "type": { | ||||
"name": "(...args: any[]) => any" | "name": "(...args: any[]) => any" | ||||
} | } | ||||
}, | |||||
"value": { | |||||
"defaultValue": null, | |||||
"description": "Value of the component.", | |||||
"name": "value", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"checked": { | |||||
"defaultValue": null, | |||||
"description": "Checked state of the component.", | |||||
"name": "checked", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
} | } | ||||
} | } | ||||
}, | }, | ||||
@@ -331,6 +349,24 @@ | |||||
"type": { | "type": { | ||||
"name": "(...args: any[]) => any" | "name": "(...args: any[]) => any" | ||||
} | } | ||||
}, | |||||
"value": { | |||||
"defaultValue": null, | |||||
"description": "Value of the component.", | |||||
"name": "value", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"checked": { | |||||
"defaultValue": null, | |||||
"description": "Checked state of the component.", | |||||
"name": "checked", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
} | } | ||||
} | } | ||||
}, | }, | ||||
@@ -431,6 +467,15 @@ | |||||
"name": "(...args: any[]) => any" | "name": "(...args: any[]) => any" | ||||
} | } | ||||
}, | }, | ||||
"value": { | |||||
"defaultValue": null, | |||||
"description": "Value of the component.", | |||||
"name": "value", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"hint": { | "hint": { | ||||
"defaultValue": { | "defaultValue": { | ||||
"value": "" | "value": "" | ||||
@@ -452,6 +497,15 @@ | |||||
"type": { | "type": { | ||||
"name": "boolean" | "name": "boolean" | ||||
} | } | ||||
}, | |||||
"defaultValue": { | |||||
"defaultValue": null, | |||||
"description": "Default value of the component.", | |||||
"name": "defaultValue", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
} | } | ||||
} | } | ||||
}, | }, | ||||
@@ -491,6 +545,24 @@ | |||||
"name": "(...args: any[]) => any" | "name": "(...args: any[]) => any" | ||||
} | } | ||||
}, | }, | ||||
"value": { | |||||
"defaultValue": null, | |||||
"description": "Value of the component.", | |||||
"name": "value", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"defaultValue": { | |||||
"defaultValue": null, | |||||
"description": "Default value of the component.", | |||||
"name": "defaultValue", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"orientation": { | "orientation": { | ||||
"defaultValue": { | "defaultValue": { | ||||
"value": "horizontal" | "value": "horizontal" | ||||
@@ -521,6 +593,17 @@ | |||||
"type": { | "type": { | ||||
"name": "ReactText" | "name": "ReactText" | ||||
} | } | ||||
}, | |||||
"fallback": { | |||||
"defaultValue": { | |||||
"value": false | |||||
}, | |||||
"description": "Use the sSR-friendly rendering of the component.", | |||||
"name": "fallback", | |||||
"required": false, | |||||
"type": { | |||||
"name": "boolean" | |||||
} | |||||
} | } | ||||
} | } | ||||
}, | }, | ||||
@@ -612,6 +695,15 @@ | |||||
"name": "(...args: any[]) => any" | "name": "(...args: any[]) => any" | ||||
} | } | ||||
}, | }, | ||||
"value": { | |||||
"defaultValue": null, | |||||
"description": "Value of the component.", | |||||
"name": "value", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"hint": { | "hint": { | ||||
"defaultValue": { | "defaultValue": { | ||||
"value": "" | "value": "" | ||||
@@ -623,6 +715,15 @@ | |||||
"name": "any" | "name": "any" | ||||
} | } | ||||
}, | }, | ||||
"defaultValue": { | |||||
"defaultValue": null, | |||||
"description": "Default value of the component.", | |||||
"name": "defaultValue", | |||||
"required": false, | |||||
"type": { | |||||
"name": "any" | |||||
} | |||||
}, | |||||
"indicator": { | "indicator": { | ||||
"defaultValue": { | "defaultValue": { | ||||
"value": null | "value": null | ||||
@@ -19,3 +19,7 @@ import Header from '../../components/Header/Header' | |||||
## Props | ## Props | ||||
<Props of="Icon" /> | <Props of="Icon" /> | ||||
## See Also | |||||
- [Feather Icons](https://feathericons.com) for a list of icon names. |
@@ -70,7 +70,13 @@ some content that is best displayed outside the component instead of putting in | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<TextInput alternate border size="large" label="Country" hint="abbreviations are accepted" /> | <TextInput alternate border size="large" label="Country" hint="abbreviations are accepted" /> | ||||
<small> | |||||
<small | |||||
style={{ | |||||
display: 'block', | |||||
marginTop: '0.5em', | |||||
lineHeight: '1.5em', | |||||
}} | |||||
> | |||||
Consult the <a href="#">fees table</a> for shipping fee details. | Consult the <a href="#">fees table</a> for shipping fee details. | ||||
</small> | </small> | ||||
</div> | </div> | ||||
@@ -6,7 +6,7 @@ import * as Enzyme from 'enzyme' | |||||
import * as Axe from 'jest-axe' | import * as Axe from 'jest-axe' | ||||
import * as React from 'react' | import * as React from 'react' | ||||
import Button, { Variant, ButtonElement } from './Button' | import Button, { Variant, ButtonElement } from './Button' | ||||
import stringify from '../../services/stringify' | |||||
import stringify from '../../utilities/stringify' | |||||
const CUSTOM_VARIANTS: string[] = ['primary'] | const CUSTOM_VARIANTS: string[] = ['primary'] | ||||
@@ -1,8 +1,8 @@ | |||||
import * as React from 'react' | import * as React from 'react' | ||||
import * as PropTypes from 'prop-types' | import * as PropTypes from 'prop-types' | ||||
import styled, { CSSObject } from 'styled-components' | import styled, { CSSObject } from 'styled-components' | ||||
import { Size, SizeMap } from '../../services/utilities' | |||||
import stringify from '../../services/stringify' | |||||
import { Size, SizeMap } from '../../utilities/utilities' | |||||
import stringify from '../../utilities/stringify' | |||||
export type Variant = 'outline' | 'primary' | export type Variant = 'outline' | 'primary' | ||||
@@ -6,7 +6,7 @@ import * as Enzyme from 'enzyme' | |||||
import * as Axe from 'jest-axe' | import * as Axe from 'jest-axe' | ||||
import * as React from 'react' | import * as React from 'react' | ||||
import Checkbox from './Checkbox' | import Checkbox from './Checkbox' | ||||
import stringify from '../../services/stringify' | |||||
import stringify from '../../utilities/stringify' | |||||
it('should exist', () => { | it('should exist', () => { | ||||
expect(Checkbox).toBeDefined() | expect(Checkbox).toBeDefined() | ||||
@@ -1,7 +1,7 @@ | |||||
import * as React from 'react' | import * as React from 'react' | ||||
import * as PropTypes from 'prop-types' | import * as PropTypes from 'prop-types' | ||||
import styled from 'styled-components' | import styled from 'styled-components' | ||||
import stringify from '../../services/stringify' | |||||
import stringify from '../../utilities/stringify' | |||||
import Icon from '../Icon/Icon' | import Icon from '../Icon/Icon' | ||||
const Base = styled('div')({ | const Base = styled('div')({ | ||||
@@ -107,6 +107,7 @@ const Label = styled('span')({ | |||||
width: 'calc(100% - 2.5rem)', | width: 'calc(100% - 2.5rem)', | ||||
fontFamily: 'var(--font-family-base, sans-serif)', | fontFamily: 'var(--font-family-base, sans-serif)', | ||||
pointerEvents: 'none', | pointerEvents: 'none', | ||||
marginTop: '-0.25em', | |||||
}) | }) | ||||
const LabelContent = styled('span')({ | const LabelContent = styled('span')({ | ||||
@@ -135,6 +136,14 @@ const propTypes = { | |||||
* Event handler triggered when the component loses focus. | * Event handler triggered when the component loses focus. | ||||
*/ | */ | ||||
onBlur: PropTypes.func, | onBlur: PropTypes.func, | ||||
/** | |||||
* Value of the component. | |||||
*/ | |||||
value: PropTypes.any, | |||||
/** | |||||
* Checked state of the component. | |||||
*/ | |||||
checked: PropTypes.bool, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
@@ -143,9 +152,8 @@ type Props = PropTypes.InferProps<typeof propTypes> | |||||
* Component for values that have an on/off state. | * Component for values that have an on/off state. | ||||
* @see {@link Select} for a similar component suitable for selecting more values. | * @see {@link Select} for a similar component suitable for selecting more values. | ||||
* @see {@link RadioButton} for a similar component on selecting a single value 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} & React.ClassAttributes<unknown>>} | |||||
*/ | */ | ||||
const Checkbox = React.forwardRef<HTMLInputElement, Props>(({ label = '', name, onChange, onFocus, onBlur }, ref) => ( | |||||
const Checkbox = React.forwardRef<HTMLInputElement, Props>(({ label = '', name, onChange, onFocus, onBlur, value, checked, }, ref) => ( | |||||
<Base> | <Base> | ||||
<CaptureArea> | <CaptureArea> | ||||
<Input | <Input | ||||
@@ -155,6 +163,8 @@ const Checkbox = React.forwardRef<HTMLInputElement, Props>(({ label = '', name, | |||||
onChange={onChange as React.ChangeEventHandler} | onChange={onChange as React.ChangeEventHandler} | ||||
onFocus={onFocus as React.FocusEventHandler} | onFocus={onFocus as React.FocusEventHandler} | ||||
onBlur={onBlur as React.FocusEventHandler} | onBlur={onBlur as React.FocusEventHandler} | ||||
value={value} | |||||
checked={checked} | |||||
/> | /> | ||||
<IndicatorWrapper> | <IndicatorWrapper> | ||||
<Border /> | <Border /> | ||||
@@ -3,7 +3,7 @@ import * as PropTypes from 'prop-types' | |||||
import * as FeatherIcon from 'react-feather' | import * as FeatherIcon from 'react-feather' | ||||
import styled from 'styled-components' | import styled from 'styled-components' | ||||
import { pascalCase, pascalCaseTransformMerge } from 'pascal-case' | import { pascalCase, pascalCaseTransformMerge } from 'pascal-case' | ||||
import splitValueAndUnit from '../../services/splitValueAndUnit' | |||||
import splitValueAndUnit from '../../utilities/splitValueAndUnit' | |||||
const Label = styled('span')({ | const Label = styled('span')({ | ||||
position: 'absolute', | position: 'absolute', | ||||
@@ -45,11 +45,11 @@ type Props = PropTypes.InferProps<typeof propTypes> | |||||
/** | /** | ||||
* Component for displaying graphics. | * Component for displaying graphics. | ||||
* @param name | |||||
* @param weight | |||||
* @param size | |||||
* @param label | |||||
* @constructor | |||||
* @param name - Name of the icon to display. | |||||
* @param weight - Width of the icon's strokes. | |||||
* @param size - Size of the icon. This controls both the width and the height. | |||||
* @param label - Description of what the component represents. | |||||
* @see {@link https://feathericons.com|Feather Icons} for a list of icon names. | |||||
*/ | */ | ||||
const Icon: React.FC<Props> = ({ name, weight = '0.125rem', size = '1.5rem', label = name }) => { | const Icon: React.FC<Props> = ({ name, weight = '0.125rem', size = '1.5rem', label = name }) => { | ||||
const iconName = pascalCase(name, { transform: pascalCaseTransformMerge }) | const iconName = pascalCase(name, { transform: pascalCaseTransformMerge }) | ||||
@@ -6,7 +6,7 @@ import * as Enzyme from 'enzyme' | |||||
import * as Axe from 'jest-axe' | import * as Axe from 'jest-axe' | ||||
import * as React from 'react' | import * as React from 'react' | ||||
import RadioButton from './RadioButton' | import RadioButton from './RadioButton' | ||||
import stringify from '../../services/stringify' | |||||
import stringify from '../../utilities/stringify' | |||||
it('should exist', () => { | it('should exist', () => { | ||||
expect(RadioButton).toBeDefined() | expect(RadioButton).toBeDefined() | ||||
@@ -1,7 +1,7 @@ | |||||
import * as React from 'react' | import * as React from 'react' | ||||
import * as PropTypes from 'prop-types' | import * as PropTypes from 'prop-types' | ||||
import styled from 'styled-components' | import styled from 'styled-components' | ||||
import stringify from '../../services/stringify' | |||||
import stringify from '../../utilities/stringify' | |||||
const Base = styled('div')({ | const Base = styled('div')({ | ||||
display: 'block', | display: 'block', | ||||
@@ -105,6 +105,7 @@ const Label = styled('span')({ | |||||
width: 'calc(100% - 2.5rem)', | width: 'calc(100% - 2.5rem)', | ||||
fontFamily: 'var(--font-family-base, sans-serif)', | fontFamily: 'var(--font-family-base, sans-serif)', | ||||
pointerEvents: 'none', | pointerEvents: 'none', | ||||
marginTop: '-0.25em', | |||||
}) | }) | ||||
const LabelContent = styled('span')({ | const LabelContent = styled('span')({ | ||||
@@ -133,6 +134,14 @@ const propTypes = { | |||||
* Event handler triggered when the component loses focus. | * Event handler triggered when the component loses focus. | ||||
*/ | */ | ||||
onBlur: PropTypes.func, | onBlur: PropTypes.func, | ||||
/** | |||||
* Value of the component. | |||||
*/ | |||||
value: PropTypes.any, | |||||
/** | |||||
* Checked state of the component. | |||||
*/ | |||||
checked: PropTypes.bool, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
@@ -144,7 +153,7 @@ type Props = PropTypes.InferProps<typeof propTypes> | |||||
* @type {React.ComponentType<{readonly label?: string, readonly name?: string} & React.ClassAttributes<unknown>>} | * @type {React.ComponentType<{readonly label?: string, readonly name?: string} & React.ClassAttributes<unknown>>} | ||||
*/ | */ | ||||
const RadioButton = React.forwardRef<HTMLInputElement, Props>( | const RadioButton = React.forwardRef<HTMLInputElement, Props>( | ||||
({ label = '', name, onChange, onFocus, onBlur }, ref) => ( | |||||
({ label = '', name, onChange, onFocus, onBlur, value, checked, }, ref) => ( | |||||
<Base> | <Base> | ||||
<CaptureArea> | <CaptureArea> | ||||
<Input | <Input | ||||
@@ -154,6 +163,8 @@ const RadioButton = React.forwardRef<HTMLInputElement, Props>( | |||||
onChange={onChange as React.ChangeEventHandler} | onChange={onChange as React.ChangeEventHandler} | ||||
onFocus={onFocus as React.FocusEventHandler} | onFocus={onFocus as React.FocusEventHandler} | ||||
onBlur={onBlur as React.FocusEventHandler} | onBlur={onBlur as React.FocusEventHandler} | ||||
value={value} | |||||
checked={checked} | |||||
/> | /> | ||||
<IndicatorWrapper> | <IndicatorWrapper> | ||||
<Border /> | <Border /> | ||||
@@ -6,7 +6,7 @@ import * as Enzyme from 'enzyme' | |||||
import * as Axe from 'jest-axe' | import * as Axe from 'jest-axe' | ||||
import * as React from 'react' | import * as React from 'react' | ||||
import Select from './Select' | import Select from './Select' | ||||
import stringify from '../../services/stringify' | |||||
import stringify from '../../utilities/stringify' | |||||
it('should exist', () => { | it('should exist', () => { | ||||
expect(Select).toBeDefined() | expect(Select).toBeDefined() | ||||
@@ -1,8 +1,8 @@ | |||||
import * as React from 'react' | import * as React from 'react' | ||||
import * as PropTypes from 'prop-types' | import * as PropTypes from 'prop-types' | ||||
import styled from 'styled-components' | import styled from 'styled-components' | ||||
import stringify from '../../services/stringify' | |||||
import { Size, SizeMap } from '../../services/utilities' | |||||
import stringify from '../../utilities/stringify' | |||||
import { Size, SizeMap } from '../../utilities/utilities' | |||||
import Icon from '../Icon/Icon' | import Icon from '../Icon/Icon' | ||||
const MIN_HEIGHTS: SizeMap<string | number> = { | const MIN_HEIGHTS: SizeMap<string | number> = { | ||||
@@ -216,6 +216,14 @@ const propTypes = { | |||||
* Event handler triggered when the component loses focus. | * Event handler triggered when the component loses focus. | ||||
*/ | */ | ||||
onBlur: PropTypes.func, | onBlur: PropTypes.func, | ||||
/** | |||||
* Default value of the component. | |||||
*/ | |||||
defaultValue: PropTypes.any, | |||||
/** | |||||
* Value of the component. | |||||
*/ | |||||
value: PropTypes.any, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
@@ -237,7 +245,8 @@ const Select = React.forwardRef<HTMLSelectElement, Props>( | |||||
onChange, | onChange, | ||||
onFocus, | onFocus, | ||||
onBlur, | onBlur, | ||||
...etcProps | |||||
defaultValue, | |||||
value, | |||||
}, | }, | ||||
ref, | ref, | ||||
) => { | ) => { | ||||
@@ -261,7 +270,8 @@ const Select = React.forwardRef<HTMLSelectElement, Props>( | |||||
</LabelWrapper> | </LabelWrapper> | ||||
{stringify(label).length > 0 && ' '} | {stringify(label).length > 0 && ' '} | ||||
<Input | <Input | ||||
{...etcProps} | |||||
defaultValue={defaultValue} | |||||
value={value} | |||||
onChange={onChange as React.ChangeEventHandler} | onChange={onChange as React.ChangeEventHandler} | ||||
onFocus={onFocus as React.FocusEventHandler} | onFocus={onFocus as React.FocusEventHandler} | ||||
onBlur={onBlur as React.FocusEventHandler} | onBlur={onBlur as React.FocusEventHandler} | ||||
@@ -2,7 +2,8 @@ import * as React from 'react' | |||||
import * as PropTypes from 'prop-types' | import * as PropTypes from 'prop-types' | ||||
import * as ReachSlider from '@reach/slider' | import * as ReachSlider from '@reach/slider' | ||||
import styled from 'styled-components' | import styled from 'styled-components' | ||||
import stringify from '../../services/stringify' | |||||
import stringify from '../../utilities/stringify' | |||||
import useHydration from '../../utilities/hooks/useHydration' | |||||
const Wrapper = styled('div')({ | const Wrapper = styled('div')({ | ||||
position: 'relative', | position: 'relative', | ||||
@@ -208,6 +209,18 @@ const propTypes = { | |||||
* Event handler triggered when the component changes value. | * Event handler triggered when the component changes value. | ||||
*/ | */ | ||||
onChange: PropTypes.func, | onChange: PropTypes.func, | ||||
/** | |||||
* Use the sSR-friendly rendering of the component. | |||||
*/ | |||||
fallback: PropTypes.bool, | |||||
/** | |||||
* Default value of the component. | |||||
*/ | |||||
defaultValue: PropTypes.any, | |||||
/** | |||||
* Value of the component. | |||||
*/ | |||||
value: PropTypes.any, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
@@ -223,20 +236,20 @@ type Props = PropTypes.InferProps<typeof propTypes> | |||||
* @param {*} [label] - Short textual description indicating the nature of the component's value. | * @param {*} [label] - Short textual description indicating the nature of the component's value. | ||||
* @param {string | number} [length] - CSS size for the component length. | * @param {string | number} [length] - CSS size for the component length. | ||||
* @param {boolean} [disabled] - Is the component active? | * @param {boolean} [disabled] - Is the component active? | ||||
* @returns {React.ReactElement} The component elements. | |||||
* @param [onChange] - Event handler triggered when the component changes value. | |||||
*/ | */ | ||||
const Slider: React.FC<Props> = ({ | |||||
const Slider = React.forwardRef<unknown, Props>(({ | |||||
orientation = 'horizontal', | orientation = 'horizontal', | ||||
label = '', | label = '', | ||||
length = '16rem', | length = '16rem', | ||||
disabled = false, | disabled = false, | ||||
onChange, | onChange, | ||||
}) => { | |||||
const [isClient, setIsClient] = React.useState(false) | |||||
fallback = false, | |||||
}, ref) => { | |||||
const isClient = useHydration() | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
window.document.body.style.setProperty('--reach-slider', '1') | window.document.body.style.setProperty('--reach-slider', '1') | ||||
setIsClient(true) | |||||
}, []) | }, []) | ||||
const parallelDimension: Dimension = orientation === 'horizontal' ? 'width' : 'height' | const parallelDimension: Dimension = orientation === 'horizontal' ? 'width' : 'height' | ||||
@@ -255,7 +268,7 @@ const Slider: React.FC<Props> = ({ | |||||
orient: orientation, | orient: orientation, | ||||
} | } | ||||
if (isClient) { | |||||
if (!fallback && isClient) { | |||||
return ( | return ( | ||||
<Wrapper | <Wrapper | ||||
style={{ | style={{ | ||||
@@ -301,6 +314,7 @@ const Slider: React.FC<Props> = ({ | |||||
cursor: disabled ? 'not-allowed' : undefined, | cursor: disabled ? 'not-allowed' : undefined, | ||||
}} | }} | ||||
onChange={onChange!} | onChange={onChange!} | ||||
ref={ref} | |||||
> | > | ||||
<Track> | <Track> | ||||
<Highlight | <Highlight | ||||
@@ -369,13 +383,14 @@ const Slider: React.FC<Props> = ({ | |||||
disabled={disabled!} | disabled={disabled!} | ||||
onChange={onChange!} | onChange={onChange!} | ||||
type="range" | type="range" | ||||
ref={ref} | |||||
/> | /> | ||||
</ClickArea> | </ClickArea> | ||||
</TransformWrapper> | </TransformWrapper> | ||||
</SizingWrapper> | </SizingWrapper> | ||||
</Wrapper> | </Wrapper> | ||||
) | ) | ||||
} | |||||
}) | |||||
Slider.propTypes = propTypes | Slider.propTypes = propTypes | ||||
@@ -6,7 +6,7 @@ import * as Enzyme from 'enzyme' | |||||
import * as Axe from 'jest-axe' | import * as Axe from 'jest-axe' | ||||
import * as React from 'react' | import * as React from 'react' | ||||
import TextInput from './TextInput' | import TextInput from './TextInput' | ||||
import stringify from '../../services/stringify' | |||||
import stringify from '../../utilities/stringify' | |||||
it('should exist', () => { | it('should exist', () => { | ||||
expect(TextInput).toBeDefined() | expect(TextInput).toBeDefined() | ||||
@@ -1,8 +1,8 @@ | |||||
import * as React from 'react' | import * as React from 'react' | ||||
import * as PropTypes from 'prop-types' | import * as PropTypes from 'prop-types' | ||||
import styled from 'styled-components' | import styled from 'styled-components' | ||||
import stringify from '../../services/stringify' | |||||
import { Size, SizeMap } from '../../services/utilities' | |||||
import stringify from '../../utilities/stringify' | |||||
import { Size, SizeMap } from '../../utilities/utilities' | |||||
// TODO implement web-client text inputs! | // TODO implement web-client text inputs! | ||||
@@ -255,6 +255,14 @@ const propTypes = { | |||||
* Should the component be displayed with an alternate appearance? | * Should the component be displayed with an alternate appearance? | ||||
*/ | */ | ||||
alternate: PropTypes.bool, | alternate: PropTypes.bool, | ||||
/** | |||||
* Default value of the component. | |||||
*/ | |||||
defaultValue: PropTypes.any, | |||||
/** | |||||
* Value of the component. | |||||
*/ | |||||
value: PropTypes.any, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
@@ -281,7 +289,8 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||||
onFocus, | onFocus, | ||||
onBlur, | onBlur, | ||||
alternate = false, | alternate = false, | ||||
...etcProps | |||||
defaultValue, | |||||
value, | |||||
}, | }, | ||||
ref, | ref, | ||||
) => ( | ) => ( | ||||
@@ -306,7 +315,6 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||||
{stringify(label).length > 0 && ' '} | {stringify(label).length > 0 && ' '} | ||||
{multiline && ( | {multiline && ( | ||||
<TextArea | <TextArea | ||||
{...etcProps} | |||||
onChange={onChange as React.ChangeEventHandler} | onChange={onChange as React.ChangeEventHandler} | ||||
onFocus={onFocus as React.FocusEventHandler} | onFocus={onFocus as React.FocusEventHandler} | ||||
onBlur={onBlur as React.FocusEventHandler} | onBlur={onBlur as React.FocusEventHandler} | ||||
@@ -323,11 +331,12 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||||
paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? '1rem' : undefined), | paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? '1rem' : undefined), | ||||
paddingLeft: alternate ? '1rem' : undefined, | paddingLeft: alternate ? '1rem' : undefined, | ||||
}} | }} | ||||
defaultValue={defaultValue} | |||||
value={value} | |||||
/> | /> | ||||
)} | )} | ||||
{!multiline && ( | {!multiline && ( | ||||
<Input | <Input | ||||
{...etcProps} | |||||
onChange={onChange as React.ChangeEventHandler} | onChange={onChange as React.ChangeEventHandler} | ||||
onFocus={onFocus as React.FocusEventHandler} | onFocus={onFocus as React.FocusEventHandler} | ||||
onBlur={onBlur as React.FocusEventHandler} | onBlur={onBlur as React.FocusEventHandler} | ||||
@@ -341,6 +350,8 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||||
paddingTop: alternate ? undefined : `calc(${SECONDARY_TEXT_SIZES[size!]} * 2)`, | paddingTop: alternate ? undefined : `calc(${SECONDARY_TEXT_SIZES[size!]} * 2)`, | ||||
paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? '1rem' : undefined), | paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? '1rem' : undefined), | ||||
}} | }} | ||||
defaultValue={defaultValue} | |||||
value={value} | |||||
/> | /> | ||||
)} | )} | ||||
</CaptureArea> | </CaptureArea> | ||||
@@ -0,0 +1,13 @@ | |||||
import { useEffect, useState } from 'react' | |||||
const useHydration = () => { | |||||
const [hydrated, setHydrated] = useState(false) | |||||
useEffect(() => { | |||||
setHydrated(true) | |||||
}, []) | |||||
return hydrated | |||||
} | |||||
export default useHydration |