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 PropTypes from 'prop-types'
import styled from 'styled-components'
import stringify from '../../utilities/stringify'
import { Size } from '../../utilities/utilities'

// 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> = {
[MultilineTextInputSize.SMALL]: '0.125rem',
[MultilineTextInputSize.MEDIUM]: '0.25rem',
[MultilineTextInputSize.LARGE]: '0.5rem',
[MultilineTextInputSize.LARGE]: '0.375rem',
}

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

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

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

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

const IndicatorWrapper = styled('span')({
@@ -204,84 +210,43 @@ const IndicatorWrapper = styled('span')({
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.
*/
label: PropTypes.any,
label?: React.ReactNode,
/**
* Short textual description as guidelines for valid input values.
*/
hint: PropTypes.any,
hint?: React.ReactNode,
/**
* 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.
*/
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?
*/
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.
*/
const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Props>(
export const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Props>(
(
{
label = '',
@@ -297,8 +262,6 @@ const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Props>(
indicator = null,
size = MultilineTextInputSize.MEDIUM,
disabled = false,
autoResize = false,
placeholder = '',
rows = 3,
border = false,
onChange,
@@ -308,67 +271,79 @@ const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Props>(
defaultValue,
value,
name,
block = false,
...etcProps
},
ref,
) => (
<ComponentBase
style={{
display: block ? 'block' : 'inline-block',
opacity: disabled ? 0.5 : undefined,
}}
>
{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
onChange={onChange as React.ChangeEventHandler}
onFocus={onFocus as React.FocusEventHandler}
onBlur={onBlur as React.FocusEventHandler}
placeholder={placeholder!}
{...etcProps}
ref={ref as React.Ref<HTMLTextAreaElement>}
disabled={disabled!}
rows={rows!}
disabled={disabled}
style={{
height: `calc(${MIN_HEIGHTS[size!]} * ${rows})`,
paddingLeft: alternate ? (border ? '0.5rem' : undefined) : '1rem',
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}
value={value}
name={name}
/>
</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
style={{
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> = {
[TextInputSize.SMALL]: '0.125rem',
[TextInputSize.MEDIUM]: '0.25rem',
[TextInputSize.LARGE]: '0.5rem',
[TextInputSize.LARGE]: '0.375rem',
}

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

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


Loading…
Cancel
Save