Skip to content

Commit 8decd17

Browse files
authored
Merge pull request #20 from dotsui-design/feature/textfieldv1
Feature/textfieldv1
2 parents 14d62c0 + 14d3cc8 commit 8decd17

File tree

4 files changed

+202
-2
lines changed

4 files changed

+202
-2
lines changed

src/components/TextField.tsx

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,170 @@
11
import React from "react";
2+
import "./css/main.css";
3+
import styled from "styled-components";
4+
import { getThemeValue } from "./ThemeProvider";
25

3-
export const TextField = () => <input type="text" />;
6+
export type TextFieldProps = React.HTMLAttributes<HTMLInputElement> & {
7+
children: React.ReactNode;
8+
type?: "text" | "password" | "email" | "number";
9+
disabled?: boolean;
10+
placeholder?: string;
11+
regex?: RegExp;
12+
value?: string;
13+
setValue?: (value: string) => void;
14+
error?: string;
15+
success?: string;
16+
info?: string;
17+
divProps?: React.HTMLAttributes<HTMLDivElement>;
18+
variant?: "underlined" | "outlined" | "none";
19+
};
20+
21+
export const TextField = (props: TextFieldProps) => {
22+
const { regex, error, success, info } = props;
23+
const [regexPassed, setRegexPassed] = React.useState(true);
24+
const ref = React.useRef<HTMLInputElement>(null);
25+
26+
React.useEffect(() => {
27+
ref.current?.addEventListener("input", (event: any) => {
28+
const e = event as React.ChangeEvent<HTMLInputElement>;
29+
if (regex && e.target.value) {
30+
setRegexPassed(regex.test(e.target.value ?? ""));
31+
} else {
32+
setRegexPassed(true);
33+
}
34+
});
35+
}, [regex, ref]);
36+
37+
React.useEffect(() => {
38+
if (regex && props.value) {
39+
setRegexPassed(regex.test(props.value ?? ""));
40+
} else {
41+
setRegexPassed(true);
42+
}
43+
}, [regex, props.value]);
44+
45+
return (
46+
<TextFieldContainer {...props.divProps}>
47+
<TextFieldElement
48+
{...props}
49+
error={error ?? !regexPassed ? "" : undefined}
50+
ref={ref}
51+
/>
52+
<HelperText
53+
shown={
54+
(!!error && typeof error === "string" && error.length > 0) ||
55+
(!!success && typeof success === "string" && success.length > 0) ||
56+
(!!info && typeof info === "string" && info.length > 0)
57+
}
58+
error={!!error}
59+
success={!!success}
60+
info={!!info}
61+
>
62+
{error ?? success ?? info ?? ""}
63+
</HelperText>
64+
</TextFieldContainer>
65+
);
66+
};
67+
68+
const TextFieldElement = styled.input<TextFieldProps>`
69+
font-family: "Poppins", sans-serif;
70+
font-size: 1em;
71+
color: ${(props) => props.theme.text ?? getThemeValue("text")};
72+
border: 2px solid ${(props) => props.theme.text ?? getThemeValue("text")};
73+
background-color: ${(props) =>
74+
props.theme.background ?? getThemeValue("background")};
75+
border-radius: 0.375em;
76+
padding: 0.5em 1em;
77+
outline: none;
78+
transition: all 0.2s ease-in-out;
79+
80+
:disabled {
81+
border-color: ${(props) =>
82+
props.theme.disabled ?? getThemeValue("disabled")};
83+
color: ${(props) => props.theme.disabled ?? getThemeValue("disabled")};
84+
cursor: not-allowed;
85+
}
86+
87+
&:hover:not(:disabled) {
88+
border-color: ${(props) =>
89+
props.theme.secondary ?? getThemeValue("secondary")};
90+
}
91+
92+
&:active:not(:disabled),
93+
&:focus:not(:disabled) {
94+
border-color: ${(props) => props.theme.primary ?? getThemeValue("primary")};
95+
}
96+
97+
&:not(:focus):not(:disabled) {
98+
${(props) =>
99+
props.info &&
100+
`
101+
border-color: ${props.theme.info ?? getThemeValue("info")};
102+
color: ${props.theme.info ?? getThemeValue("info")};
103+
::placeholder {
104+
color: ${props.theme.info ?? getThemeValue("info")};
105+
}
106+
`}
107+
${(props) =>
108+
props.success &&
109+
`
110+
border-color: ${props.theme.success ?? getThemeValue("success")};
111+
color: ${props.theme.success ?? getThemeValue("success")};
112+
::placeholder {
113+
color: ${props.theme.success ?? getThemeValue("success")};
114+
}
115+
`}
116+
${(props) =>
117+
props.error &&
118+
`
119+
border-color: ${props.theme.error ?? getThemeValue("error")};
120+
color: ${props.theme.error ?? getThemeValue("error")};
121+
::placeholder {
122+
color: ${props.theme.error ?? getThemeValue("error")};
123+
}
124+
`}
125+
}
126+
127+
${(props) =>
128+
props.variant === "underlined" &&
129+
`
130+
border: none;
131+
border-bottom: 2px solid ${props.theme.text ?? getThemeValue("text")};
132+
border-radius: 0;
133+
padding: 0.25em;
134+
`}
135+
136+
${(props) =>
137+
props.variant === "none" &&
138+
`
139+
border: none;
140+
padding: 0.25em;
141+
`}
142+
`;
143+
144+
const HelperText = styled.p<{
145+
shown: boolean;
146+
error?: boolean;
147+
success?: boolean;
148+
info?: boolean;
149+
}>`
150+
opacity: ${(props) => (props.shown ? 1 : 0)};
151+
font-family: "Poppins", sans-serif;
152+
font-size: 0.75em;
153+
color: ${(props) =>
154+
props.error
155+
? props.theme.error ?? getThemeValue("error")
156+
: props.success
157+
? props.theme.success ?? getThemeValue("success")
158+
: props.info
159+
? props.theme.info ?? getThemeValue("info")
160+
: props.theme.text ?? getThemeValue("text")};
161+
margin: 0.25em 0 0 0.5em;
162+
position: absolute;
163+
bottom: ${(props) => (props.shown ? "-1.5em" : "-0.5em")};
164+
transition: all 0.2s ease-in-out;
165+
`;
166+
167+
const TextFieldContainer = styled.div`
168+
display: flex;
169+
position: relative;
170+
`;

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// MARK: Component imports
22

33
export { Button, type ButtonProps } from './components/Button';
4-
export { TextField } from './components/TextField';
4+
export { TextField, type TextFieldProps } from './components/TextField';
55
export { Text, type TextProps } from './components/Text';
66
export { ThemeProvider } from './components/ThemeProvider';
File renamed without changes.

src/stories/TextField.stories.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
3+
import { TextField } from "../components/TextField";
4+
5+
const meta: Meta<typeof TextField> = {
6+
title: 'Example/TextField',
7+
component: TextField,
8+
tags: ['autodocs'],
9+
parameters: {
10+
controls: { hideNoControlsWarning: true },
11+
},
12+
argTypes: {
13+
placeholder: {
14+
defaultValue: "lorem ipsum",
15+
control: {
16+
type: "text",
17+
},
18+
},
19+
},
20+
21+
};
22+
23+
export default meta;
24+
type Story = StoryObj<typeof meta>;
25+
26+
export const TextStory: Story = {
27+
args: {
28+
type: 'text',
29+
disabled: false,
30+
placeholder: "lorem ipsum",
31+
regex: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g,
32+
},
33+
};

0 commit comments

Comments
 (0)