akveo / react-native-ui-kitten

:boom: React Native UI Library based on Eva Design System :new_moon_with_face::sparkles:Dark Mode
https://akveo.github.io/react-native-ui-kitten/
MIT License
10.35k stars 957 forks source link

Add captionMarginTop back, but only apply if caption set #1434

Open sschottler opened 3 years ago

sschottler commented 3 years ago

🚀 Feature Proposal

captionMarginTop and the captionContainer View it was applied to were removed completely in this PR: https://github.com/akveo/react-native-ui-kitten/pull/1349

It makes sense to remove the caption margin when no caption is set so we don't have extra margin applied to Inputs. However, I agree with this comment that captionMarginTop should still be handled for the user when caption IS set:

"Since we don't use this default spacing between caption text and Input field, a user will more likely have to apply it on his own. Which makes Eva not Eva."

Could we add it back but only apply when caption is set? It's a bit clunky to have to write:

<Input label="whatever" caption={(props) => <View style={{ marginTop: 8 }}><Text {...props}>Caption</Text></View>} />

Currently, captionMarginTop is still getting destructured out in Input.component but not being used: https://github.com/akveo/react-native-ui-kitten/blob/1403fdb993f2b6f3fb46fcddfddb185d5652883a/src/components/ui/input/input.component.tsx#L203

Maybe the fix could be as simple as adding it back to Input.component.tsx but only applying if FalsyText renders something?

// add captionContainer style back to getComponentStyle:
captionContainer: {
  marginTop: captionMarginTop,
},
...

// add to style array for FalsyText so it gets forwarded to underlying Text component/RenderProp?
<FalsyText 
  style={[evaStyle.captionLabel, styles.captionLabel, styles.captionContainer]} 
  component={caption} />

If we don't want to apply margins to Text directly, we could have an intermediate captionContainer View with captionMarginTop that Falsy wraps the Text with.

Motivation

Make UI Kitten easier to use and api consistent with other components. I only discovered this issue when I realized the captionMarginTop override in my customMapping was no longer being applied correctly after updating @ui-kitten/components package.

Example

<Input label="no caption" /> // no captionContainer view rendered and no extra margin
<Input label="has caption" caption="caption" /> // automatically gets marginTop set without user having to set render prop 
sschottler commented 3 years ago

I ended up adding most of the old caption mapping parameters back to my customMapping, but with a conditional check before wrapping captionIcon and caption in the View with captionMarginTop. I think the Input component should handle this automatically so we can get the default top margin as well as the caption icon colors and sizes from the mapping.

import React, { FC } from 'react';
import { StyleSheet, View, ImageProps } from 'react-native';
import {
  Input as DefaultInput,
  InputProps as DefaultInputProps,
  styled,
  TextProps,
  StyleType,
} from '@ui-kitten/components';
import { FalsyText, FalsyFC, RenderProp } from '@ui-kitten/components/devsupport';

const getComponentStyle = ({
  captionColor,
  captionMarginTop,
  captionIconWidth,
  captionIconHeight,
  captionIconMarginRight,
}: StyleType) => ({
  captionContainer: {
    marginTop: captionMarginTop,
  },
  captionIcon: {
    width: captionIconWidth,
    height: captionIconHeight,
    tintColor: captionColor,
    marginRight: captionIconMarginRight,
  },
});

export interface InputProps extends DefaultInputProps {
  captionIcon?: RenderProp<Partial<ImageProps>>;
}

const Input: FC<InputProps> = ({ eva, style, captionIcon, caption: originalCaption, ...props }) => {
  // @ts-ignore
  const evaStyle = getComponentStyle(eva.style);

  let caption;
  // This addresses a default margin issue with captions:
  // https://github.com/akveo/react-native-ui-kitten/issues/1434
  if (originalCaption || captionIcon) {
    caption = (textProps?: TextProps) => (
      <View style={[evaStyle.captionContainer, styles.captionContainer]}>
        <FalsyFC style={evaStyle.captionIcon} component={captionIcon} />
        <FalsyText {...textProps} component={originalCaption} />
      </View>
    );
  }

  return <DefaultInput {...props} caption={caption} style={[style, styles.centerShadow]} />;
};

const styles = StyleSheet.create({
  // UI Kitten mapping does not support object styles so we set here:
  centerShadow: {
    shadowOffset: {
      height: 0,
      width: 0,
    },
  },
  captionContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
});

const StyledInput: FC<InputProps> = styled('Input')(Input);
export { StyledInput as Input };