|
1 |
| -import React, { InputHTMLAttributes } from "react"; |
| 1 | +import React, { InputHTMLAttributes, useRef } from "react"; |
2 | 2 | import styled, { css } from "styled-components";
|
3 | 3 | import SuccessIcon from "../../assets/svgs/status-icons/success.svg";
|
4 | 4 | import WarningIcon from "../../assets/svgs/status-icons/warning.svg";
|
5 | 5 | import ErrorIcon from "../../assets/svgs/status-icons/error.svg";
|
6 | 6 | 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 | + |
7 | 10 | import { borderBox, small, svg } from "../../styles/common-style";
|
| 11 | +import { useHover } from "usehooks-ts"; |
8 | 12 |
|
9 | 13 | export type VariantProp = {
|
10 | 14 | variant?: "success" | "warning" | "error" | string;
|
@@ -80,14 +84,70 @@ const StyledInput = styled.input<{
|
80 | 84 | padding-top: 14px;
|
81 | 85 | padding-bottom: 14px;
|
82 | 86 | 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"; |
87 | 91 | }};
|
| 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; |
88 | 103 | ${baseInputStyle}
|
89 | 104 | `;
|
90 | 105 |
|
| 106 | +const ArrowsContainer = styled.div<{ |
| 107 | + variant?: string; |
| 108 | + Icon?: React.FC<React.SVGAttributes<SVGElement>>; |
| 109 | +}>` |
| 110 | + display: flex; |
| 111 | + flex-direction: column; |
| 112 | + justify-content: center; |
| 113 | + align-items: center; |
| 114 | + position: absolute; |
| 115 | + height: 100%; |
| 116 | + width: 14px; |
| 117 | + gap: 4px; |
| 118 | + top: 0; |
| 119 | + right: ${({ Icon, variant }) => { |
| 120 | + if (Icon) return "48px"; |
| 121 | + if (variant) return "36px"; |
| 122 | + return "12px"; |
| 123 | + }}; |
| 124 | +`; |
| 125 | + |
| 126 | +const ArrowButton = styled.button` |
| 127 | + height: 14px; |
| 128 | + width: 14px; |
| 129 | + display: flex; |
| 130 | + justify-content: center; |
| 131 | + align-items: center; |
| 132 | + background: transparent; |
| 133 | + border: none; |
| 134 | + border-radius: 3px; |
| 135 | + padding: 0; |
| 136 | +
|
| 137 | + cursor: pointer; |
| 138 | + :hover { |
| 139 | + background: ${(props) => props.theme.klerosUIComponentsStroke}; |
| 140 | + } |
| 141 | +`; |
| 142 | + |
| 143 | +const StyledArrowIcon = styled.svg` |
| 144 | + width: 8px; |
| 145 | + height: 8px; |
| 146 | + path { |
| 147 | + fill: ${(props) => props.theme.klerosUIComponentsSecondaryText}; |
| 148 | + } |
| 149 | +`; |
| 150 | + |
91 | 151 | const StyledSVG = styled.svg``;
|
92 | 152 |
|
93 | 153 | export const StyledMessage = styled.small<VariantProp>`
|
@@ -141,27 +201,49 @@ const Field: React.FC<FieldProps> = ({
|
141 | 201 | Icon,
|
142 | 202 | className,
|
143 | 203 | ...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 |
| -); |
| 204 | +}) => { |
| 205 | + const wrapperRef = useRef<HTMLDivElement>(null); |
| 206 | + const inputRef = useRef<HTMLInputElement>(null); |
| 207 | + const hovering = useHover(wrapperRef); |
| 208 | + |
| 209 | + return ( |
| 210 | + <Wrapper ref={wrapperRef} {...{ className }}> |
| 211 | + <StyledInput ref={inputRef} {...{ variant, Icon, ...props }} /> |
| 212 | + {props.type === "number" && hovering && ( |
| 213 | + <ArrowsContainer className="field-arrows" {...{ variant, Icon }}> |
| 214 | + <ArrowButton |
| 215 | + aria-label="increment" |
| 216 | + onClick={() => inputRef?.current?.stepUp()} |
| 217 | + > |
| 218 | + <StyledArrowIcon as={UpArrowIcon} /> |
| 219 | + </ArrowButton> |
| 220 | + <ArrowButton |
| 221 | + aria-label="decrement" |
| 222 | + onClick={() => inputRef?.current?.stepDown()} |
| 223 | + > |
| 224 | + <StyledArrowIcon as={DownArrowIcon} /> |
| 225 | + </ArrowButton> |
| 226 | + </ArrowsContainer> |
| 227 | + )} |
| 228 | + {variant === "success" && <StyledSuccessIcon className="field-svg" />} |
| 229 | + {variant === "warning" && <StyledWarningIcon className="field-svg" />} |
| 230 | + {variant === "error" && <StyledErrorIcon className="field-svg" />} |
| 231 | + {Icon && ( |
| 232 | + <IconContainer> |
| 233 | + <StyledIconSVG as={Icon} /> |
| 234 | + </IconContainer> |
| 235 | + )} |
| 236 | + {message && ( |
| 237 | + <StyledMessage {...{ variant }}> |
| 238 | + {variant === "info" && ( |
| 239 | + <InfoIcon className={StyledSVG.styledComponentId} /> |
| 240 | + )} |
| 241 | + <StyledSmall {...{ variant }}>{message}</StyledSmall> |
| 242 | + </StyledMessage> |
| 243 | + )} |
| 244 | + </Wrapper> |
| 245 | + ); |
| 246 | +}; |
165 | 247 |
|
166 | 248 | Field.displayName = "Field";
|
167 | 249 |
|
|
0 commit comments