Add event handler props for components.tags/0.3.0
@@ -48,7 +48,7 @@ describe('on unknown kinds', () => { | |||||
it('should render null', () => { | it('should render null', () => { | ||||
fc.assert( | fc.assert( | ||||
fc.property(fc.string().filter(s => !['button', 'a'].includes(s)), (element) => { | |||||
fc.property(fc.string().filter((s) => !['button', 'a'].includes(s)), (element) => { | |||||
const wrapper = Enzyme.shallow(<Button element={element as ButtonElement} />) | const wrapper = Enzyme.shallow(<Button element={element as ButtonElement} />) | ||||
expect(wrapper.isEmptyRender()).toBe(true) | expect(wrapper.isEmptyRender()).toBe(true) | ||||
@@ -156,6 +156,18 @@ const propTypes = { | |||||
* Does the button display a border? | * Does the button display a border? | ||||
*/ | */ | ||||
border: PropTypes.bool, | border: PropTypes.bool, | ||||
/** | |||||
* Event handler triggered when the component is clicked. | |||||
*/ | |||||
onClick: PropTypes.func, | |||||
/** | |||||
* Event handler triggered when the component receives focus. | |||||
*/ | |||||
onFocus: PropTypes.func, | |||||
/** | |||||
* Event handler triggered when the component loses focus. | |||||
*/ | |||||
onBlur: PropTypes.func, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
@@ -173,7 +185,9 @@ const Button = React.forwardRef<HTMLAnchorElement | HTMLButtonElement | HTMLSpan | |||||
rel, | rel, | ||||
type = 'button', | type = 'button', | ||||
border = false, | border = false, | ||||
...etcProps | |||||
onClick, | |||||
onFocus, | |||||
onBlur, | |||||
}, | }, | ||||
ref, | ref, | ||||
) => { | ) => { | ||||
@@ -184,12 +198,7 @@ const Button = React.forwardRef<HTMLAnchorElement | HTMLButtonElement | HTMLSpan | |||||
} | } | ||||
const buttonContent = ( | const buttonContent = ( | ||||
<React.Fragment> | <React.Fragment> | ||||
{ | |||||
border | |||||
&& ( | |||||
<Border /> | |||||
) | |||||
} | |||||
{border && <Border />} | |||||
{stringify(children)} | {stringify(children)} | ||||
</React.Fragment> | </React.Fragment> | ||||
) | ) | ||||
@@ -197,26 +206,36 @@ const Button = React.forwardRef<HTMLAnchorElement | HTMLButtonElement | HTMLSpan | |||||
switch (element) { | switch (element) { | ||||
case 'button': | case 'button': | ||||
return ( | return ( | ||||
<Base {...etcProps} type={type!} ref={ref as React.Ref<HTMLButtonElement>} disabled={disabled!} style={commonButtonStyles}> | |||||
<Base | |||||
onClick={onClick as React.EventHandler<React.SyntheticEvent>} | |||||
type={type!} | |||||
ref={ref as React.Ref<HTMLButtonElement>} | |||||
disabled={disabled!} | |||||
style={commonButtonStyles} | |||||
onFocus={onFocus as React.FocusEventHandler} | |||||
onBlur={onBlur as React.FocusEventHandler} | |||||
> | |||||
{buttonContent} | {buttonContent} | ||||
</Base> | </Base> | ||||
) | ) | ||||
case 'a': | case 'a': | ||||
if (disabled) { | if (disabled) { | ||||
return ( | return ( | ||||
<DisabledLinkBase {...etcProps} ref={ref as React.Ref<HTMLSpanElement>} style={commonButtonStyles}> | |||||
<DisabledLinkBase ref={ref as React.Ref<HTMLSpanElement>} style={commonButtonStyles}> | |||||
{buttonContent} | {buttonContent} | ||||
</DisabledLinkBase> | </DisabledLinkBase> | ||||
) | ) | ||||
} | } | ||||
return ( | return ( | ||||
<LinkBase | <LinkBase | ||||
{...etcProps} | |||||
onClick={onClick as React.EventHandler<React.SyntheticEvent>} | |||||
href={href!} | href={href!} | ||||
target={target!} | target={target!} | ||||
rel={rel!} | rel={rel!} | ||||
ref={ref as React.Ref<HTMLAnchorElement>} | ref={ref as React.Ref<HTMLAnchorElement>} | ||||
style={commonButtonStyles} | style={commonButtonStyles} | ||||
onFocus={onFocus as React.FocusEventHandler} | |||||
onBlur={onBlur as React.FocusEventHandler} | |||||
> | > | ||||
{buttonContent} | {buttonContent} | ||||
</LinkBase> | </LinkBase> | ||||
@@ -123,6 +123,18 @@ const propTypes = { | |||||
* Name of the form field associated with this component. | * Name of the form field associated with this component. | ||||
*/ | */ | ||||
name: PropTypes.string, | name: PropTypes.string, | ||||
/** | |||||
* Event handler triggered when the component is toggled. | |||||
*/ | |||||
onChange: PropTypes.func, | |||||
/** | |||||
* Event handler triggered when the component receives focus. | |||||
*/ | |||||
onFocus: PropTypes.func, | |||||
/** | |||||
* Event handler triggered when the component loses focus. | |||||
*/ | |||||
onBlur: PropTypes.func, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
@@ -133,10 +145,17 @@ type Props = PropTypes.InferProps<typeof propTypes> | |||||
* @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>>} | * @type {React.ComponentType<{readonly label?: string} & React.ClassAttributes<unknown>>} | ||||
*/ | */ | ||||
const Checkbox = React.forwardRef<HTMLInputElement, Props>(({ label = '', name, }, ref) => ( | |||||
const Checkbox = React.forwardRef<HTMLInputElement, Props>(({ label = '', name, onChange, onFocus, onBlur }, ref) => ( | |||||
<Base> | <Base> | ||||
<CaptureArea> | <CaptureArea> | ||||
<Input ref={ref} type="checkbox" name={name!} /> | |||||
<Input | |||||
ref={ref} | |||||
type="checkbox" | |||||
name={name!} | |||||
onChange={onChange as React.ChangeEventHandler} | |||||
onFocus={onFocus as React.FocusEventHandler} | |||||
onBlur={onBlur as React.FocusEventHandler} | |||||
/> | |||||
<IndicatorWrapper> | <IndicatorWrapper> | ||||
<Border /> | <Border /> | ||||
<Indicator> | <Indicator> | ||||
@@ -43,12 +43,7 @@ const propTypes = { | |||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
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 }) | ||||
const { [iconName as keyof typeof FeatherIcon]: TheIcon = null } = FeatherIcon | const { [iconName as keyof typeof FeatherIcon]: TheIcon = null } = FeatherIcon | ||||
const { magnitude: sizeValue, unit: sizeUnit } = splitValueAndUnit(size) | const { magnitude: sizeValue, unit: sizeUnit } = splitValueAndUnit(size) | ||||
@@ -121,6 +121,18 @@ const propTypes = { | |||||
* Short textual description indicating the nature of the component's value. | * Short textual description indicating the nature of the component's value. | ||||
*/ | */ | ||||
label: PropTypes.any, | label: PropTypes.any, | ||||
/** | |||||
* Event handler triggered when the component is selected. | |||||
*/ | |||||
onChange: PropTypes.func, | |||||
/** | |||||
* Event handler triggered when the component receives focus. | |||||
*/ | |||||
onFocus: PropTypes.func, | |||||
/** | |||||
* Event handler triggered when the component loses focus. | |||||
*/ | |||||
onBlur: PropTypes.func, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
@@ -132,13 +144,17 @@ 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, }, | |||||
ref | |||||
) => ( | |||||
({ label = '', name, onChange, onFocus, onBlur }, ref) => ( | |||||
<Base> | <Base> | ||||
<CaptureArea> | <CaptureArea> | ||||
<Input ref={ref} name={name} type="radio" /> | |||||
<Input | |||||
ref={ref} | |||||
name={name} | |||||
type="radio" | |||||
onChange={onChange as React.ChangeEventHandler} | |||||
onFocus={onFocus as React.FocusEventHandler} | |||||
onBlur={onBlur as React.FocusEventHandler} | |||||
/> | |||||
<IndicatorWrapper> | <IndicatorWrapper> | ||||
<Border /> | <Border /> | ||||
<Indicator /> | <Indicator /> | ||||
@@ -149,7 +165,7 @@ const RadioButton = React.forwardRef<HTMLInputElement, Props>( | |||||
</Label> | </Label> | ||||
</CaptureArea> | </CaptureArea> | ||||
</Base> | </Base> | ||||
) | |||||
), | |||||
) | ) | ||||
RadioButton.propTypes = propTypes | RadioButton.propTypes = propTypes | ||||
@@ -242,12 +242,7 @@ const Select = React.forwardRef<HTMLSelectElement, Props>( | |||||
opacity: disabled ? 0.5 : undefined, | opacity: disabled ? 0.5 : undefined, | ||||
}} | }} | ||||
> | > | ||||
{ | |||||
border | |||||
&& ( | |||||
<Border /> | |||||
) | |||||
} | |||||
{border && <Border />} | |||||
<CaptureArea> | <CaptureArea> | ||||
<LabelWrapper | <LabelWrapper | ||||
style={{ | style={{ | ||||
@@ -204,6 +204,10 @@ const propTypes = { | |||||
* Is the component active? | * Is the component active? | ||||
*/ | */ | ||||
disabled: PropTypes.bool, | disabled: PropTypes.bool, | ||||
/** | |||||
* Event handler triggered when the component changes value. | |||||
*/ | |||||
onChange: PropTypes.func, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
@@ -226,6 +230,7 @@ const Slider: React.FC<Props> = ({ | |||||
label = '', | label = '', | ||||
length = '16rem', | length = '16rem', | ||||
disabled = false, | disabled = false, | ||||
onChange, | |||||
}) => { | }) => { | ||||
const [isClient, setIsClient] = React.useState(false) | const [isClient, setIsClient] = React.useState(false) | ||||
@@ -295,6 +300,7 @@ const Slider: React.FC<Props> = ({ | |||||
padding: orientation === 'horizontal' ? '0.875rem 0.75rem' : '0.75rem 0.875rem', | padding: orientation === 'horizontal' ? '0.875rem 0.75rem' : '0.75rem 0.875rem', | ||||
cursor: disabled ? 'not-allowed' : undefined, | cursor: disabled ? 'not-allowed' : undefined, | ||||
}} | }} | ||||
onChange={onChange!} | |||||
> | > | ||||
<Track> | <Track> | ||||
<Highlight | <Highlight | ||||
@@ -361,6 +367,7 @@ const Slider: React.FC<Props> = ({ | |||||
width: length!, | width: length!, | ||||
}} | }} | ||||
disabled={disabled!} | disabled={disabled!} | ||||
onChange={onChange!} | |||||
type="range" | type="range" | ||||
/> | /> | ||||
</ClickArea> | </ClickArea> | ||||
@@ -70,7 +70,7 @@ describe.each` | |||||
multiline | tag | multiline | tag | ||||
${false} | ${'input'} | ${false} | ${'input'} | ||||
${true} | ${'textarea'} | ${true} | ${'textarea'} | ||||
`('on multiline (multiline=$multiline)', ({ tag: rawTag, multiline: rawMultiline, }) => { | |||||
`('on multiline (multiline=$multiline)', ({ tag: rawTag, multiline: rawMultiline }) => { | |||||
const tag = rawTag as string | const tag = rawTag as string | ||||
const multiline = rawMultiline as boolean | const multiline = rawMultiline as boolean | ||||
@@ -279,12 +279,7 @@ const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props | |||||
opacity: disabled ? 0.5 : undefined, | opacity: disabled ? 0.5 : undefined, | ||||
}} | }} | ||||
> | > | ||||
{ | |||||
border | |||||
&& ( | |||||
<Border /> | |||||
) | |||||
} | |||||
{border && <Border />} | |||||
<CaptureArea> | <CaptureArea> | ||||
<LabelWrapper | <LabelWrapper | ||||
style={{ | style={{ | ||||
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"name": "@tesseract-design/react-common", | "name": "@tesseract-design/react-common", | ||||
"version": "0.1.1", | |||||
"version": "0.2.0", | |||||
"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" | ||||
@@ -37,7 +37,15 @@ | |||||
"rollup-plugin-terser": "5.3.0", | "rollup-plugin-terser": "5.3.0", | ||||
"ts-jest": "^26.1.3", | "ts-jest": "^26.1.3", | ||||
"tslib": "^2.0.0", | "tslib": "^2.0.0", | ||||
"typescript": "^3.9.7" | |||||
"typescript": "^3.9.7", | |||||
"@reach/slider": "^0.10.5", | |||||
"docz": "^2.3.1", | |||||
"pascal-case": "3.1.1", | |||||
"prop-types": "15.7.2", | |||||
"react": "16.13.1", | |||||
"react-dom": "16.13.1", | |||||
"react-feather": "2.0.3", | |||||
"styled-components": "5.1.0" | |||||
}, | }, | ||||
"scripts": { | "scripts": { | ||||
"prepublishOnly": "NODE_ENV=production rm -rf dist/ && rollup -c", | "prepublishOnly": "NODE_ENV=production rm -rf dist/ && rollup -c", | ||||
@@ -46,7 +54,7 @@ | |||||
"generate": "plop", | "generate": "plop", | ||||
"docs": "docz" | "docs": "docz" | ||||
}, | }, | ||||
"dependencies": { | |||||
"peerDependencies": { | |||||
"@reach/slider": "^0.10.5", | "@reach/slider": "^0.10.5", | ||||
"docz": "^2.3.1", | "docz": "^2.3.1", | ||||
"pascal-case": "3.1.1", | "pascal-case": "3.1.1", | ||||