Skip to content

Commit fad5295

Browse files
committed
feat: custom-counter-arrows-in-number-field
1 parent 2fb36bb commit fad5295

File tree

4 files changed

+157
-26
lines changed

4 files changed

+157
-26
lines changed
Lines changed: 25 additions & 0 deletions
Loading
Lines changed: 24 additions & 0 deletions
Loading

src/examples/form.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Searchbar from "../lib/form/searchbar";
55
import Textarea from "../lib/form/textarea";
66
import Slider from "../lib/form/slider";
77
import Datepicker from "../lib/form/datepicker";
8+
import Telegram from "../assets/svgs/telegram.svg";
89

910
const Form = () => {
1011
const [value, setValue] = useState(1);
@@ -26,6 +27,8 @@ const Form = () => {
2627
variant="success"
2728
message="success msg"
2829
/>
30+
<Field placeholder={"Number"} type="number" Icon={Telegram} />
31+
<Field placeholder={"Number"} type="number" />
2932
<Searchbar />
3033
<Textarea
3134
placeholder={"eg. longer text"}

src/lib/form/field.tsx

Lines changed: 105 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import React, { InputHTMLAttributes } from "react";
1+
import React, { InputHTMLAttributes, useRef } from "react";
22
import styled, { css } from "styled-components";
33
import SuccessIcon from "../../assets/svgs/status-icons/success.svg";
44
import WarningIcon from "../../assets/svgs/status-icons/warning.svg";
55
import ErrorIcon from "../../assets/svgs/status-icons/error.svg";
66
import InfoIcon from "../../assets/svgs/status-icons/info.svg";
7+
import UpArrowIcon from "../../assets/svgs/arrows/field-arrow-up.svg";
8+
import DownArrowIcon from "../../assets/svgs/arrows/field-arrow-down.svg";
9+
710
import { borderBox, small, svg } from "../../styles/common-style";
11+
import { useHover } from "usehooks-ts";
812

913
export type VariantProp = {
1014
variant?: "success" | "warning" | "error" | string;
@@ -80,14 +84,67 @@ const StyledInput = styled.input<{
8084
padding-top: 14px;
8185
padding-bottom: 14px;
8286
padding-left: 16px;
83-
padding-right: ${({ Icon, variant }) => {
84-
if (Icon) return "56px";
85-
if (variant) return "44px";
86-
return "16px";
87+
padding-right: ${({ Icon, variant, type }) => {
88+
if (Icon) return type === "number" ? "64px" : "56px";
89+
if (variant) return type === "number" ? "52px" : "44px";
90+
return type === "number" ? "30px" : "16px";
8791
}};
92+
93+
/* Chrome, Safari, Edge, Opera */
94+
::-webkit-outer-spin-button,
95+
::-webkit-inner-spin-button {
96+
-webkit-appearance: none;
97+
margin: 0;
98+
}
99+
100+
/* Firefox */
101+
-moz-appearance: textfield;
102+
appearance: textfield;
88103
${baseInputStyle}
89104
`;
90105

106+
const ArrowsContainer = styled.div<{
107+
variant?: string;
108+
Icon?: React.FC<React.SVGAttributes<SVGElement>>;
109+
}>`
110+
display: flex;
111+
flex-direction: column;
112+
align-items: center;
113+
position: absolute;
114+
height: 17px;
115+
width: 14px;
116+
top: 14px;
117+
right: ${({ Icon, variant }) => {
118+
if (Icon) return "48px";
119+
if (variant) return "36px";
120+
return "12px";
121+
}};
122+
`;
123+
124+
const ArrowButton = styled.button`
125+
height: 10px;
126+
width: 100%;
127+
display: flex;
128+
justify-content: center;
129+
align-items: center;
130+
background: transparent;
131+
border: none;
132+
padding: 0;
133+
134+
cursor: pointer;
135+
:hover {
136+
background: ${(props) => props.theme.klerosUIComponentsStroke};
137+
}
138+
`;
139+
140+
const StyledArrowIcon = styled.svg`
141+
width: 8px;
142+
height: 8px;
143+
path {
144+
fill: ${(props) => props.theme.klerosUIComponentsSecondaryText};
145+
}
146+
`;
147+
91148
const StyledSVG = styled.svg``;
92149

93150
export const StyledMessage = styled.small<VariantProp>`
@@ -141,27 +198,49 @@ const Field: React.FC<FieldProps> = ({
141198
Icon,
142199
className,
143200
...props
144-
}) => (
145-
<Wrapper {...{ className }}>
146-
<StyledInput {...{ variant, Icon, ...props }} />
147-
{variant === "success" && <StyledSuccessIcon className="field-svg" />}
148-
{variant === "warning" && <StyledWarningIcon className="field-svg" />}
149-
{variant === "error" && <StyledErrorIcon className="field-svg" />}
150-
{Icon && (
151-
<IconContainer>
152-
<StyledIconSVG as={Icon} />
153-
</IconContainer>
154-
)}
155-
{message && (
156-
<StyledMessage {...{ variant }}>
157-
{variant === "info" && (
158-
<InfoIcon className={StyledSVG.styledComponentId} />
159-
)}
160-
<StyledSmall {...{ variant }}>{message}</StyledSmall>
161-
</StyledMessage>
162-
)}
163-
</Wrapper>
164-
);
201+
}) => {
202+
const wrapperRef = useRef<HTMLDivElement>(null);
203+
const inputRef = useRef<HTMLInputElement>(null);
204+
const hovering = useHover(wrapperRef);
205+
206+
return (
207+
<Wrapper ref={wrapperRef} {...{ className }}>
208+
<StyledInput ref={inputRef} {...{ variant, Icon, ...props }} />
209+
{props.type === "number" && hovering && (
210+
<ArrowsContainer className="field-arrows" {...{ variant, Icon }}>
211+
<ArrowButton
212+
aria-label="increment"
213+
onClick={() => inputRef?.current?.stepUp()}
214+
>
215+
<StyledArrowIcon as={UpArrowIcon} />
216+
</ArrowButton>
217+
<ArrowButton aria-label="decrement">
218+
<StyledArrowIcon
219+
as={DownArrowIcon}
220+
onClick={() => inputRef?.current?.stepDown()}
221+
/>
222+
</ArrowButton>
223+
</ArrowsContainer>
224+
)}
225+
{variant === "success" && <StyledSuccessIcon className="field-svg" />}
226+
{variant === "warning" && <StyledWarningIcon className="field-svg" />}
227+
{variant === "error" && <StyledErrorIcon className="field-svg" />}
228+
{Icon && (
229+
<IconContainer>
230+
<StyledIconSVG as={Icon} />
231+
</IconContainer>
232+
)}
233+
{message && (
234+
<StyledMessage {...{ variant }}>
235+
{variant === "info" && (
236+
<InfoIcon className={StyledSVG.styledComponentId} />
237+
)}
238+
<StyledSmall {...{ variant }}>{message}</StyledSmall>
239+
</StyledMessage>
240+
)}
241+
</Wrapper>
242+
);
243+
};
165244

166245
Field.displayName = "Field";
167246

0 commit comments

Comments
 (0)