Text and TextInput component ads (#249)

* Adding blank components.

* Adding Todo note for tree

* Adding todo note for LighteningMenu

* ads button component

* Adding storybook support.

* storybook integrated with button component

* ads button component props completed

* button component icon and loading logic implemented

* button component completed

* Added a text knob.

* Adding default text for button.

* Merging theme and other fixes.

* Fixed info button.

* Better types.

* Adding background param to components.

* Re-using vsariant for callount.

* Added props for Text input.

* Adding text component.

* feedback changes added in button and icon component

* type any removed in button component

* Text component created with typography and story

* Fixing spinner issues

* Textinput component ads (#252)

* text input component with story is implemented

* Update README.md

* Fixing spinner issues

* Fixing hexToRGBA import

* story error fixed and text component commented for now

* props condition fixed and added error input story

* unused import removed

Co-authored-by: Rohit Kumawat <rohit.kumawat@primathon.in>
Co-authored-by: Nikhil Nandagopal <nikhil.nandagopal@gmail.com>

* story spacing added and component name fixed

* feedback changes implemented

* forward ref added

* feedback changes implemented

* text and textinput stories updated

* pr comments resolved

Co-authored-by: Rohit Kumawat <rohit.kumawat@primathon.in>
Co-authored-by: Nikhil Nandagopal <nikhil.nandagopal@gmail.com>
This commit is contained in:
satbir121 2020-08-14 10:28:03 +05:30 committed by GitHub
parent 70a1f5610a
commit fa7dc0b247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 363 additions and 67 deletions

View File

@ -2,4 +2,5 @@ import "@storybook/addon-knobs/register";
import "@storybook/addon-notes/register";
import "@storybook/addon-contexts/register";
import "storybook-addon-designs/register";
import '@storybook/addon-backgrounds/register';
import '@storybook/addon-backgrounds/register';
import '@storybook/addon-actions/register';

View File

@ -1,5 +1,5 @@
import React from "react";
import { CommonComponentProps } from "./common";
import { CommonComponentProps, hexToRgba, ThemeProp } from "./common";
import styled from "styled-components";
import { IconName, Icon } from "./Icon";
import Spinner from "./Spinner";
@ -7,13 +7,8 @@ import {
mediumButton,
smallButton,
largeButton,
Theme,
} from "../../constants/DefaultTheme";
export type ThemeProp = {
theme: Theme;
};
export enum Category {
primary = "primary",
secondary = "secondary",
@ -65,40 +60,6 @@ type ButtonProps = CommonComponentProps & {
size?: Size;
};
function hexToRgb(
hex: string,
): {
r: number;
g: number;
b: number;
} {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: {
r: -1,
g: -1,
b: -1,
};
}
// const darken = (color: Color, intensity: number) => {
// return new tinycolor(color).darken(intensity).toString();
// };
// const lighten = (color: Color, intensity: number) => {
// return new tinycolor(color).lighten(intensity).toString();
// };
const hexToRgba = (color: string, alpha: number) => {
const value = hexToRgb(color);
return `rgba(${value.r}, ${value.g}, ${value.b}, ${alpha});`;
};
const stateStyles = (
props: ThemeProp & ButtonProps,
state: string,
@ -113,7 +74,7 @@ const stateStyles = (
borderColorTertiary,
txtColorTertiary;
if (props.isLoading || props.isDisabled) {
if (props.isLoading || props.disabled) {
switch (props.category) {
case Category.primary:
if (props.variant) {
@ -302,7 +263,7 @@ const StyledButton = styled("button")<ThemeProp & ButtonProps>`
color: ${props => btnColorStyles(props, "hover").txtColor};
border: ${props => btnColorStyles(props, "hover").border};
cursor: ${props =>
props.isLoading || props.isDisabled ? `not-allowed` : `pointer`};
props.isLoading || props.disabled ? `not-allowed` : `pointer`};
.ads-icon {
margin-right: ${props =>
props.text && props.icon ? `${props.theme.spaces[4]}px` : `0`}
@ -317,7 +278,7 @@ const StyledButton = styled("button")<ThemeProp & ButtonProps>`
color: ${props => btnColorStyles(props, "active").txtColor};
border: ${props => btnColorStyles(props, "active").border};
cursor: ${props =>
props.isLoading || props.isDisabled ? `not-allowed` : `pointer`};
props.isLoading || props.disabled ? `not-allowed` : `pointer`};
.ads-icon {
path {
fill: ${props => btnColorStyles(props, "active").txtColor};
@ -340,7 +301,7 @@ Button.defaultProps = {
variant: Variant.success,
size: Size.small,
isLoading: false,
isDisabled: false,
disabled: false,
};
export const VisibilityWrapper = styled.div`

View File

@ -1,6 +1,7 @@
import React from "react";
import styled, { keyframes } from "styled-components";
import { Size, ThemeProp } from "./Button";
import { ThemeProp } from "./common";
import { Size } from "./Button";
export const sizeHandler = (props: ThemeProp & SpinnerProp) => {
let iconSize = 0;

View File

@ -1,11 +1,53 @@
import React from "react";
import styled from "styled-components";
import { ThemeProp } from "./common";
export enum TextType {
P1 = "p1",
P2 = "p2",
P3 = "p3",
H1 = "h1",
H2 = "h2",
H3 = "h3",
H4 = "h4",
H5 = "h5",
H6 = "h6",
}
export type TextProps = {
type: "p1" | "p2" | "p3" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
underline: boolean;
italic: boolean;
type: TextType;
underline?: boolean;
italic?: boolean;
};
export default function Text(props: TextProps) {
return <span></span>;
}
const typeSelector = (props: TextProps & ThemeProp): string => {
let color = "";
switch (props.type) {
case TextType.P1:
color = props.theme.colors.blackShades[6];
break;
case TextType.P2:
color = props.theme.colors.blackShades[6];
break;
case TextType.P3:
color = props.theme.colors.blackShades[6];
break;
default:
color = props.theme.colors.blackShades[7];
break;
}
return color;
};
const Text = styled.span<TextProps>`
text-decoration: ${props => (props.underline ? "underline" : "unset")};
font-style: ${props => (props.italic ? "italic" : "normal")};
font-family: ${props => props.theme.fonts[2]};
font-weight: ${props => props.theme.typography[props.type].fontWeight};
font-size: ${props => props.theme.typography[props.type].fontSize}px;
line-height: ${props => props.theme.typography[props.type].lineHeight}px;
letter-spacing: ${props =>
props.theme.typography[props.type].letterSpacing}px;
color: ${props => typeSelector(props)};
`;
export default Text;

View File

@ -1,15 +1,134 @@
import { CommonComponentProps } from "./common";
import React, { forwardRef, Ref, useCallback, useMemo, useState } from "react";
import { CommonComponentProps, hexToRgba } from "./common";
import styled from "styled-components";
import { theme } from "../../constants/DefaultTheme";
import Text, { TextType } from "./Text";
type TextProps = CommonComponentProps & {
export type TextInputProps = CommonComponentProps & {
placeholder?: string;
value: string;
hasError: boolean;
disabled: boolean;
validator: (value: string) => { isValid: boolean; message: string };
onChange: (value: string) => void;
cypressSelector?: string;
fill?: boolean;
defaultValue?: string;
validator?: (value: string) => { isValid: boolean; message: string };
onChange?: (value: string) => void;
};
export default function Text(props: TextProps) {
return null;
}
type boxReturnType = {
bgColor: string;
color: string;
borderColor: string;
};
const boxStyles = (props: TextInputProps, isValid: boolean): boxReturnType => {
let bgColor = theme.colors.blackShades[0];
let color = theme.colors.blackShades[9];
let borderColor = theme.colors.blackShades[0];
if (props.disabled) {
bgColor = theme.colors.blackShades[2];
color = theme.colors.blackShades[6];
borderColor = theme.colors.blackShades[2];
}
if (!isValid) {
bgColor = hexToRgba(theme.colors.danger.main, 0.1);
color = theme.colors.danger.main;
borderColor = theme.colors.danger.main;
}
return { bgColor, color, borderColor };
};
const StyledInput = styled.input<
TextInputProps & { inputStyle: boxReturnType; isValid: boolean }
>`
width: ${props => (props.fill ? "100%" : "260px")};
border-radius: 0;
outline: 0;
box-shadow: none;
margin-bottom: ${props => props.theme.spaces[1]}px;
border: 1px solid ${props => props.inputStyle.borderColor};
font-family: ${props => props.theme.fonts[3]};
padding: ${props => props.theme.spaces[4]}px
${props => props.theme.spaces[6]}px;
background-color: ${props => props.inputStyle.bgColor};
color: ${props => props.inputStyle.color};
&::placeholder {
color: ${props => props.theme.colors.blackShades[5]};
}
&:focus {
border: 1px solid
${props =>
props.isValid
? props.theme.colors.info.main
: props.theme.colors.danger.main};
box-shadow: 0px 0px 0px 4px rgba(203, 72, 16, 0.18);
}
&:disabled {
cursor: not-allowed;
}
`;
const InputWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
span {
color: ${props => props.theme.colors.danger.main};
}
`;
const TextInput = forwardRef(
(props: TextInputProps, ref: Ref<HTMLInputElement>) => {
const initialValidation = () => {
let validationObj = { isValid: true, message: "" };
if (props.defaultValue && props.validator) {
validationObj = props.validator(props.defaultValue);
}
return validationObj;
};
const [validation, setValidation] = useState<{
isValid: boolean;
message: string;
}>(initialValidation());
const inputStyle = useMemo(() => boxStyles(props, validation.isValid), [
props.disabled,
validation,
]);
const memoizedChangeHandler = useCallback(
el => {
props.validator && setValidation(props.validator(el.target.value));
return props.onChange && props.onChange(el.target.value);
},
[props],
);
const ErrorMessage = <Text type={TextType.P3}>{validation.message}</Text>;
return (
<InputWrapper>
<StyledInput
type="text"
ref={ref}
inputStyle={inputStyle}
isValid={validation.isValid}
defaultValue={props.defaultValue}
{...props}
placeholder={props.placeholder ? props.placeholder : ""}
onChange={memoizedChangeHandler}
/>
{validation.isValid ? null : ErrorMessage}
</InputWrapper>
);
},
);
TextInput.defaultProps = {
fill: false,
};
TextInput.displayName = "TextInput";
export default TextInput;

View File

@ -1,5 +1,37 @@
import { Theme } from "../../constants/DefaultTheme";
export interface CommonComponentProps {
isLoading?: boolean; //default false
cypressSelector?: string;
isDisabled?: boolean; //default false
disabled?: boolean; //default false
}
export type ThemeProp = {
theme: Theme;
};
export const hexToRgb = (
hex: string,
): {
r: number;
g: number;
b: number;
} => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: {
r: -1,
g: -1,
b: -1,
};
};
export const hexToRgba = (color: string, alpha: number) => {
const value = hexToRgb(color);
return `rgba(${value.r}, ${value.g}, ${value.b}, ${alpha});`;
};

View File

@ -24,7 +24,7 @@ export const withDynamicProps = () => (
)}
icon={select("iconName", ["delete", "user"], undefined)}
isLoading={boolean("Loading", false)}
isDisabled={boolean("Disabled", false)}
disabled={boolean("Disabled", false)}
text={text("text", "Get")}
></Button>
);

View File

@ -0,0 +1,74 @@
import React from "react";
import { boolean, select, text, withKnobs } from "@storybook/addon-knobs";
import Text, { TextType } from "../ads/Text";
import styled from "styled-components";
export default {
title: "Text",
component: Text,
decorators: [withKnobs],
};
const StyledDiv = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
margin-right: 50px;
span {
align-self: left;
margin-bottom: 10px;
}
`;
export const Typography = () => (
<div style={{ padding: "20px", display: "flex" }}>
<StyledDiv>
<Text type={TextType.H1}>Hi there, I am h1 element.</Text>
<Text type={TextType.H2}>Hi there, I am h2 element.</Text>
<Text type={TextType.H3}>Hi there, I am h3 element.</Text>
<Text type={TextType.H4}>Hi there, I am h4 element.</Text>
<Text type={TextType.H5}>Hi there, I am h5 element.</Text>
<Text type={TextType.H6}>Hi there, I am h6 element.</Text>
</StyledDiv>
<br />
<StyledDiv>
<Text type={TextType.P1}>Hi there, I am p1 element.</Text>
<Text type={TextType.P2}>Hi there, I am p2 element.</Text>
<Text type={TextType.P3}>Hi there, I am p3 element.</Text>
</StyledDiv>
</div>
);
const ValueWrapper = (props: { type: TextType; value: string }) => (
<Text
type={props.type}
underline={boolean("underline", false)}
italic={boolean("italic", false)}
>
{props.value}
</Text>
);
export const SingleText = () => (
<ValueWrapper
type={select(
"type",
[
TextType.H1,
TextType.H2,
TextType.H3,
TextType.H4,
TextType.H5,
TextType.H6,
TextType.P1,
TextType.P2,
TextType.P3,
],
TextType.H1,
)}
value={text("text", "Hi There I am Earth")}
/>
);

View File

@ -0,0 +1,50 @@
import React from "react";
import { withKnobs, boolean, text } from "@storybook/addon-knobs";
import TextInput from "../ads/TextInput";
import { action } from "@storybook/addon-actions";
export default {
title: "Text Input",
component: TextInput,
decorators: [withKnobs],
};
const callValidator1 = () => {
return {
isValid: true,
message: "This is a warning text for the above field.",
};
};
export const TextInputStory = () => (
<div style={{ background: "#302D2D", height: "500px", padding: "100px" }}>
<TextInput
placeholder={text("placeholder", "Your name")}
disabled={boolean("disabled", false)}
fill={boolean("fill", true)}
defaultValue={text("defaultValue", "This is valid")}
onChange={action("input value changed")}
validator={() => callValidator1()}
></TextInput>
</div>
);
const callValidator2 = () => {
return {
isValid: false,
message: "This is a warning text for the above field.",
};
};
export const ErrorTextInputStory = () => (
<div style={{ background: "#302D2D", height: "500px", padding: "100px" }}>
<TextInput
placeholder={text("placeholder", "Your name")}
disabled={boolean("disabled", false)}
fill={boolean("fill", true)}
onChange={value => console.log(value)}
defaultValue={text("defaultValue", "This is wrong")}
validator={() => callValidator2()}
></TextInput>
</div>
);

View File

@ -431,44 +431,56 @@ export const theme: Theme = {
h1: {
fontSize: 20,
lineHeight: 27,
letterSpacing: "normal",
fontWeight: 500,
},
h2: {
fontSize: 18,
lineHeight: 25,
letterSpacing: "normal",
fontWeight: 500,
},
h3: {
fontSize: 17,
lineHeight: 22,
letterSpacing: "normal",
fontWeight: 500,
},
h4: {
fontSize: 16,
lineHeight: 21,
letterSpacing: -0.24,
fontWeight: 500,
},
h5: {
fontSize: 14,
lineHeight: 19,
letterSpacing: -0.24,
fontWeight: 500,
},
h6: {
fontSize: 12,
lineHeight: 14,
letterSpacing: 0.8,
fontWeight: 500,
},
p1: {
fontSize: 14,
lineHeight: 19,
letterSpacing: -0.24,
fontWeight: "normal",
},
p2: {
fontSize: 13,
lineHeight: 17,
letterSpacing: -0.24,
fontWeight: "normal",
},
p3: {
fontSize: 12,
lineHeight: 16,
letterSpacing: -0.221538,
fontWeight: "normal",
},
btnLarge: {
fontSize: 13,
@ -591,7 +603,11 @@ export const theme: Theme = {
lightningborder: Colors.ALABASTER,
},
lineHeights: [0, 14, 16, 18, 22, 24, 28, 36, 48, 64, 80],
fonts: [FontFamilies.DMSans, FontFamilies.FiraCode],
fonts: [
FontFamilies.DMSans,
FontFamilies.FiraCode,
FontFamilies.HomePageRedesign,
],
borders: [
{
thickness: 1,