Browse Source

Fix multiline input styling

Make styles similar to regular text input component.
refactor/pridepack-storybook
TheoryOfNekomata 3 years ago
parent
commit
394e50a4cc
3 changed files with 215 additions and 119 deletions
  1. +125
    -0
      packages/react-common/src/components/MultilineTextInput/MultilineTextInput.stories.tsx
  2. +84
    -113
      packages/react-common/src/components/MultilineTextInput/index.tsx
  3. +6
    -6
      packages/react-common/src/components/TextInput/index.tsx

+ 125
- 0
packages/react-common/src/components/MultilineTextInput/MultilineTextInput.stories.tsx View File

@@ -0,0 +1,125 @@
import * as React from 'react'
import { Props, MultilineTextInput, MultilineTextInputSize } from '.'
import { Meta, Story } from '@storybook/react'

export default {
title: 'components/MultilineTextInput',
component: MultilineTextInput,
argTypes: { onChange: { action: 'changed' } },
} as Meta

const Template: Story<Props> = ({ ref, ...args }) => (
<>
Text here
{' '}
<MultilineTextInput
{...args}
/>
{' '}
Other text here
</>
)

export const Default = Template.bind({})
Default.args = {
border: true,
label: '',
hint: '',
indicator: '',
size: MultilineTextInputSize.MEDIUM,
}

export const WithLabel = Template.bind({})
WithLabel.args = {
border: true,
label: 'Foo',
}

export const WithLabelAndHint = Template.bind({})
WithLabelAndHint.args = {
border: true,
label: 'Foo',
hint: '(example value)',
}

export const WithLabelAndHintAlternate = Template.bind({})
WithLabelAndHintAlternate.args = {
border: true,
alternate: true,
label: 'Foo',
hint: '(example value)',
}

export const WithLabelAndHintAlternateBorderless = Template.bind({})
WithLabelAndHintAlternateBorderless.args = {
alternate: true,
label: 'Foo',
hint: '(example value)',
}

export const WithLabelAndHintBorderless = Template.bind({})
WithLabelAndHintBorderless.args = {
label: 'Foo',
hint: '(example value)',
}

export const Block = Template.bind({})
Block.args = {
border: true,
block: true,
}

export const WithLabelHintAndIndicator = Template.bind({})
WithLabelHintAndIndicator.args = {
border: true,
label: 'Foo',
hint: '(example value)',
indicator: 'A',
}

export const WithLabelHintAndIndicatorAlternate = Template.bind({})
WithLabelHintAndIndicatorAlternate.args = {
alternate: true,
border: true,
label: 'Foo',
hint: '(example value)',
indicator: 'A',
}

export const WithLabelHintAndIndicatorAlternateSmall = Template.bind({})
WithLabelHintAndIndicatorAlternateSmall.args = {
alternate: true,
border: true,
label: 'Foo',
hint: '(example value)',
indicator: 'A',
size: MultilineTextInputSize.SMALL,
}

export const WithLabelHintAndIndicatorAlternateLarge = Template.bind({})
WithLabelHintAndIndicatorAlternateLarge.args = {
alternate: true,
border: true,
label: 'Foo',
hint: '(example value)',
indicator: 'A',
size: MultilineTextInputSize.LARGE,
}

export const WithLabelHintAndIndicatorSmall = Template.bind({})
WithLabelHintAndIndicatorSmall.args = {
border: true,
label: 'Foo',
hint: '(example value)',
indicator: 'A',
size: MultilineTextInputSize.SMALL,
}

export const WithLabelHintAndIndicatorLarge = Template.bind({})
WithLabelHintAndIndicatorLarge.args = {
border: true,
label: 'Foo',
hint: '(example value)',
indicator: 'A',
size: MultilineTextInputSize.LARGE,
}

+ 84
- 113
packages/react-common/src/components/MultilineTextInput/index.tsx View File

@@ -1,8 +1,5 @@
import * as React from 'react' import * as React from 'react'
import * as PropTypes from 'prop-types'
import styled from 'styled-components' import styled from 'styled-components'
import stringify from '../../utilities/stringify'
import { Size } from '../../utilities/utilities'


// TODO implement web-client text inputs! // TODO implement web-client text inputs!


@@ -21,7 +18,7 @@ const MIN_HEIGHTS: Record<MultilineTextInputSize, string | number> = {
const LABEL_VERTICAL_PADDING_SIZES: Record<MultilineTextInputSize, string | number> = { const LABEL_VERTICAL_PADDING_SIZES: Record<MultilineTextInputSize, string | number> = {
[MultilineTextInputSize.SMALL]: '0.125rem', [MultilineTextInputSize.SMALL]: '0.125rem',
[MultilineTextInputSize.MEDIUM]: '0.25rem', [MultilineTextInputSize.MEDIUM]: '0.25rem',
[MultilineTextInputSize.LARGE]: '0.5rem',
[MultilineTextInputSize.LARGE]: '0.375rem',
} }


const VERTICAL_PADDING_SIZES: Record<MultilineTextInputSize, string | number> = { const VERTICAL_PADDING_SIZES: Record<MultilineTextInputSize, string | number> = {
@@ -43,6 +40,7 @@ const SECONDARY_TEXT_SIZES: Record<MultilineTextInputSize, string | number> = {
} }


const ComponentBase = styled('div')({ const ComponentBase = styled('div')({
verticalAlign: 'middle',
position: 'relative', position: 'relative',
borderRadius: '0.25rem', borderRadius: '0.25rem',
fontFamily: 'var(--font-family-base, sans-serif)', fontFamily: 'var(--font-family-base, sans-serif)',
@@ -58,6 +56,7 @@ const CaptureArea = styled('label')({
display: 'block', display: 'block',
borderRadius: 'inherit', borderRadius: 'inherit',
overflow: 'hidden', overflow: 'hidden',
boxSizing: 'border-box',
}) })


CaptureArea.displayName = 'label' CaptureArea.displayName = 'label'
@@ -70,6 +69,7 @@ const LabelWrapper = styled('span')({
left: 0, left: 0,
fontSize: '0.85em', fontSize: '0.85em',
maxWidth: '100%', maxWidth: '100%',
width: '100%',
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
@@ -95,7 +95,7 @@ const Border = styled('span')({
width: '100%', width: '100%',
height: '100%', height: '100%',
borderRadius: 'inherit', borderRadius: 'inherit',
zIndex: 2,
zIndex: 3,
pointerEvents: 'none', pointerEvents: 'none',
transitionProperty: 'border-color', transitionProperty: 'border-color',
'::before': { '::before': {
@@ -150,22 +150,24 @@ const TextArea = styled('textarea')({
display: 'block', display: 'block',
verticalAlign: 'top', verticalAlign: 'top',
width: '100%', width: '100%',
height: '100%',
boxSizing: 'border-box', boxSizing: 'border-box',
position: 'relative', position: 'relative',
border: 0, border: 0,
borderRadius: 'inherit', borderRadius: 'inherit',
paddingTop: 0,
paddingBottom: 0,
margin: 0, margin: 0,
font: 'inherit', font: 'inherit',
minHeight: '4rem',
minWidth: '3rem', minWidth: '3rem',
maxWidth: '100%', maxWidth: '100%',
zIndex: 1, zIndex: 1,
transitionProperty: 'background-color, color', transitionProperty: 'background-color, color',
resize: 'none',
':focus': { ':focus': {
outline: 0, outline: 0,
}, },
'@media only screen': { '@media only screen': {
backgroundColor: 'var(--color-bg, white)',
color: 'var(--color-fg, black)', color: 'var(--color-fg, black)',
}, },
}) })
@@ -177,15 +179,19 @@ const HintWrapper = styled('span')({
position: 'absolute', position: 'absolute',
left: 0, left: 0,
fontSize: '0.85em', fontSize: '0.85em',
opacity: 0.5,
maxWidth: '100%', maxWidth: '100%',
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
zIndex: 2, zIndex: 2,
pointerEvents: 'none', pointerEvents: 'none',
lineHeight: 1,
userSelect: 'none', userSelect: 'none',
width: '100%',
})

const HintContent = styled('span')({
display: 'block',
opacity: 0.5,
}) })


const IndicatorWrapper = styled('span')({ const IndicatorWrapper = styled('span')({
@@ -204,84 +210,43 @@ const IndicatorWrapper = styled('span')({
userSelect: 'none', userSelect: 'none',
}) })


const propTypes = {
export type Props = Omit<React.HTMLProps<HTMLTextAreaElement>, 'as' | 'className' | 'style' | 'placeholder' | 'size'> & {
/** /**
* 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?: React.ReactNode,
/** /**
* Short textual description as guidelines for valid input values. * Short textual description as guidelines for valid input values.
*/ */
hint: PropTypes.any,
hint?: React.ReactNode,
/** /**
* Size of the component. * Size of the component.
*/ */
size: PropTypes.oneOf<Size>(['small', 'medium', 'large']),
size?: MultilineTextInputSize,
/** /**
* Additional description, usually graphical, indicating the nature of the component's value. * Additional description, usually graphical, indicating the nature of the component's value.
*/ */
indicator: PropTypes.node,
/**
* Should the component accept multiple lines of input?
*/
multiline: PropTypes.bool,
/**
* Is the component active?
*/
disabled: PropTypes.bool,
/**
* Should the component resize itself to show all its value?
*/
autoResize: PropTypes.bool,
/**
* Placeholder of the component when there is no value.
*/
placeholder: PropTypes.string,
/**
* How many rows should the component display if it accepts multiline input?
*/
rows: PropTypes.number,
/**
* Does the button display a border?
*/
border: PropTypes.bool,
/**
* Event handler triggered when the component changes value.
*/
onChange: PropTypes.func,
/**
* Event handler triggered when the component receives focus.
*/
onFocus: PropTypes.func,
indicator?: React.ReactNode,
/** /**
* Event handler triggered when the component loses focus.
* Should the component display a border?
*/ */
onBlur: PropTypes.func,
border?: boolean,
/** /**
* Should the component be displayed with an alternate appearance? * Should the component be displayed with an alternate appearance?
*/ */
alternate: PropTypes.bool,
alternate?: boolean,
/** /**
* Default value of the component.
* Should the component occupy the whole width of its parent?
*/ */
defaultValue: PropTypes.any,
block?: boolean,
/** /**
* Value of the component.
* How many rows should the component display if it accepts multiline input?
*/ */
value: PropTypes.any,
rows?: number,
/** /**
* Name of the form field associated with this component.
* Should the component resize itself to show all its value?
*/ */
name: PropTypes.string,
}

type Props = {
label?: React.ReactNode,
hint?: React.ReactNode,
size?: MultilineTextInputSize,
indicator?: React.ReactNode,
border?: boolean,
alternate?: boolean,
autoResize?: boolean,
} }


/** /**
@@ -289,7 +254,7 @@ type Props = {
* *
* This component supports multiline input and adjusts its layout accordingly. * This component supports multiline input and adjusts its layout accordingly.
*/ */
const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Props>(
export const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Props>(
( (
{ {
label = '', label = '',
@@ -297,8 +262,6 @@ const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Props>(
indicator = null, indicator = null,
size = MultilineTextInputSize.MEDIUM, size = MultilineTextInputSize.MEDIUM,
disabled = false, disabled = false,
autoResize = false,
placeholder = '',
rows = 3, rows = 3,
border = false, border = false,
onChange, onChange,
@@ -308,67 +271,79 @@ const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Props>(
defaultValue, defaultValue,
value, value,
name, name,
block = false,
...etcProps
}, },
ref, ref,
) => ( ) => (
<ComponentBase <ComponentBase
style={{ style={{
display: block ? 'block' : 'inline-block',
opacity: disabled ? 0.5 : undefined, opacity: disabled ? 0.5 : undefined,
}} }}
> >
{border && <Border />} {border && <Border />}
<CaptureArea>
<LabelWrapper
style={{
paddingLeft: alternate ? '0.5rem' : undefined,
paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!],
paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!],
paddingRight: indicator ? MIN_HEIGHTS[size!] : '0.5rem',
fontSize: SECONDARY_TEXT_SIZES[size!],
}}
>
{stringify(label)}
</LabelWrapper>
{stringify(label).length > 0 && ' '}
<CaptureArea
style={{
paddingTop: alternate ? `calc(${SECONDARY_TEXT_SIZES[size!]} * 2)` : `calc(${SECONDARY_TEXT_SIZES[size!]} * 1.25)`,
paddingBottom: alternate ? '0.375rem' : VERTICAL_PADDING_SIZES[size!],
height: `calc(${MIN_HEIGHTS[size!]} * ${rows})`,
minHeight: MIN_HEIGHTS[size!],
resize: block ? 'vertical' : 'both',
}}
>
{label && (
<>
<LabelWrapper
style={{
paddingLeft: alternate ? (border ? '0.5rem' : undefined) : '0.5rem',
paddingTop: alternate ? `calc(${LABEL_VERTICAL_PADDING_SIZES[size!]} * 0.25)` : LABEL_VERTICAL_PADDING_SIZES[size!],
paddingBottom: alternate ? LABEL_VERTICAL_PADDING_SIZES[size!] : undefined,
paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? (border ? '0.5rem' : undefined) : '0.5rem'),
fontSize: SECONDARY_TEXT_SIZES[size!],
}}
>
{label}
</LabelWrapper>
{' '}
</>
)}
<TextArea <TextArea
onChange={onChange as React.ChangeEventHandler}
onFocus={onFocus as React.FocusEventHandler}
onBlur={onBlur as React.FocusEventHandler}
placeholder={placeholder!}
{...etcProps}
ref={ref as React.Ref<HTMLTextAreaElement>} ref={ref as React.Ref<HTMLTextAreaElement>}
disabled={disabled!}
rows={rows!}
disabled={disabled}
style={{ style={{
height: `calc(${MIN_HEIGHTS[size!]} * ${rows})`,
paddingLeft: alternate ? (border ? '0.5rem' : undefined) : '1rem',
fontSize: INPUT_FONT_SIZES[size!], fontSize: INPUT_FONT_SIZES[size!],
minHeight: MIN_HEIGHTS[size!],
paddingTop: alternate ? VERTICAL_PADDING_SIZES[size!] : `calc(${SECONDARY_TEXT_SIZES[size!]} * 2)`,
paddingBottom: VERTICAL_PADDING_SIZES[size!],
paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? '1rem' : undefined),
paddingLeft: alternate ? '1rem' : undefined,
paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? (border ? '0.5rem' : undefined) : '1rem'),
}} }}
defaultValue={defaultValue} defaultValue={defaultValue}
value={value} value={value}
name={name} name={name}
/> />
</CaptureArea> </CaptureArea>
{stringify(hint).length > 0 && ' '}
{stringify(hint).length > 0 && (
<HintWrapper
style={{
top: alternate ? undefined : '0.75rem',
bottom: alternate ? 0 : undefined,
paddingLeft: alternate ? '1rem' : undefined,
paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!],
paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!],
paddingRight: indicator ? MIN_HEIGHTS[size!] : '1rem',
fontSize: SECONDARY_TEXT_SIZES[size!],
}}
>
{stringify(hint)}
</HintWrapper>
{hint && (
<>
{' '}
<HintWrapper
style={{
top: alternate ? `calc(${SECONDARY_TEXT_SIZES[size!]} * 0.75)` : undefined,
bottom: alternate ? undefined : 0,
paddingLeft: alternate ? (border ? '0.5rem' : undefined) : '1rem',
paddingTop: alternate ? undefined : LABEL_VERTICAL_PADDING_SIZES[size!],
paddingBottom:alternate ? undefined : LABEL_VERTICAL_PADDING_SIZES[size!],
paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? (border ? '0.5rem' : undefined) : '1rem'),
fontSize: SECONDARY_TEXT_SIZES[size!],
lineHeight: alternate ? 1.5 : 1,
}}
>
<HintContent>
{hint}
</HintContent>
</HintWrapper>
</>
)} )}
{(indicator as PropTypes.ReactComponentLike) && (
{indicator && (
<IndicatorWrapper <IndicatorWrapper
style={{ style={{
width: MIN_HEIGHTS[size!], width: MIN_HEIGHTS[size!],
@@ -382,8 +357,4 @@ const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Props>(
), ),
) )


MultilineTextInput.propTypes = propTypes

MultilineTextInput.displayName = 'TextInput'

export default MultilineTextInput
MultilineTextInput.displayName = 'MultilineTextInput'

+ 6
- 6
packages/react-common/src/components/TextInput/index.tsx View File

@@ -18,7 +18,7 @@ const MIN_HEIGHTS: Record<TextInputSize, string | number> = {
const LABEL_VERTICAL_PADDING_SIZES: Record<TextInputSize, string | number> = { const LABEL_VERTICAL_PADDING_SIZES: Record<TextInputSize, string | number> = {
[TextInputSize.SMALL]: '0.125rem', [TextInputSize.SMALL]: '0.125rem',
[TextInputSize.MEDIUM]: '0.25rem', [TextInputSize.MEDIUM]: '0.25rem',
[TextInputSize.LARGE]: '0.5rem',
[TextInputSize.LARGE]: '0.375rem',
} }


const INPUT_FONT_SIZES: Record<TextInputSize, string | number> = { const INPUT_FONT_SIZES: Record<TextInputSize, string | number> = {
@@ -178,7 +178,6 @@ const HintWrapper = styled('span')({
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
zIndex: 2, zIndex: 2,
pointerEvents: 'none', pointerEvents: 'none',
lineHeight: 1,
userSelect: 'none', userSelect: 'none',
}) })


@@ -262,7 +261,7 @@ export const TextInput = React.forwardRef<HTMLInputElement, Props>(
<LabelWrapper <LabelWrapper
style={{ style={{
paddingLeft: alternate ? (border ? '0.5rem' : undefined) : '0.5rem', paddingLeft: alternate ? (border ? '0.5rem' : undefined) : '0.5rem',
paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!],
paddingTop: alternate ? `calc(${LABEL_VERTICAL_PADDING_SIZES[size!]} * 0.25)` : LABEL_VERTICAL_PADDING_SIZES[size!],
paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!], paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!],
paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? (border ? '0.5rem' : undefined) : '0.5rem'), paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? (border ? '0.5rem' : undefined) : '0.5rem'),
fontSize: SECONDARY_TEXT_SIZES[size!], fontSize: SECONDARY_TEXT_SIZES[size!],
@@ -292,13 +291,14 @@ export const TextInput = React.forwardRef<HTMLInputElement, Props>(
{' '} {' '}
<HintWrapper <HintWrapper
style={{ style={{
top: alternate ? '0.75rem' : undefined,
top: alternate ? SECONDARY_TEXT_SIZES[size!] : undefined,
bottom: alternate ? undefined : 0, bottom: alternate ? undefined : 0,
paddingLeft: alternate ? (border ? '0.5rem' : undefined) : '1rem', paddingLeft: alternate ? (border ? '0.5rem' : undefined) : '1rem',
paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!],
paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!],
paddingTop: alternate ? undefined : LABEL_VERTICAL_PADDING_SIZES[size!],
paddingBottom: alternate ? undefined : LABEL_VERTICAL_PADDING_SIZES[size!],
paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? (border ? '0.5rem' : undefined) : '1rem'), paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? (border ? '0.5rem' : undefined) : '1rem'),
fontSize: SECONDARY_TEXT_SIZES[size!], fontSize: SECONDARY_TEXT_SIZES[size!],
lineHeight: alternate ? 1.5 : 1,
}} }}
> >
{hint} {hint}


Loading…
Cancel
Save