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:
parent
70a1f5610a
commit
fa7dc0b247
|
|
@ -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';
|
||||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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});`;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
74
app/client/src/components/stories/Text.stories.tsx
Normal file
74
app/client/src/components/stories/Text.stories.tsx
Normal 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")}
|
||||
/>
|
||||
);
|
||||
50
app/client/src/components/stories/TextInput.stories.tsx
Normal file
50
app/client/src/components/stories/TextInput.stories.tsx
Normal 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>
|
||||
);
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user