@sats-group/ui-lib 75.10.1 → 76.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -7,53 +7,72 @@
|
|
|
7
7
|
.text-input {
|
|
8
8
|
$block: &;
|
|
9
9
|
$line-height: 1;
|
|
10
|
-
$icon-width: 24px;
|
|
11
|
-
$icon-spacing: spacing.$xs;
|
|
12
10
|
$vertical-padding-xs: spacing.$xs;
|
|
13
11
|
$vertical-padding-s: spacing.$s;
|
|
14
12
|
|
|
15
13
|
&--variant-small {
|
|
16
|
-
#{$block}__input {
|
|
14
|
+
#{$block}__input-wrapper {
|
|
17
15
|
padding: $vertical-padding-xs spacing.$s;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
#{$block}__icon {
|
|
21
|
-
bottom: $vertical-padding-xs;
|
|
16
|
+
gap: spacing.$s;
|
|
22
17
|
}
|
|
23
18
|
}
|
|
24
19
|
|
|
25
20
|
&--variant-large {
|
|
26
|
-
#{$block}__input {
|
|
21
|
+
#{$block}__input-wrapper {
|
|
27
22
|
padding: $vertical-padding-s spacing.$m;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
#{$block}__icon {
|
|
31
|
-
bottom: $vertical-padding-s;
|
|
23
|
+
gap: spacing.$m;
|
|
32
24
|
}
|
|
33
25
|
}
|
|
34
26
|
|
|
35
27
|
&__wrapper {
|
|
36
28
|
position: relative;
|
|
37
29
|
display: flex;
|
|
38
|
-
flex-direction: column
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
gap: spacing.$xxs;
|
|
39
32
|
}
|
|
40
33
|
|
|
41
|
-
&
|
|
42
|
-
|
|
34
|
+
&__length-counter {
|
|
35
|
+
color: light.$on-background-primary-alternate;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&__label-wrapper {
|
|
39
|
+
display: flex;
|
|
40
|
+
justify-content: space-between;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&__input-wrapper {
|
|
43
44
|
border: 1px solid light.$ge-divider-default;
|
|
44
45
|
border-radius: corner-radius.$s;
|
|
45
|
-
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
46
48
|
background-color: light.$surface-primary-default;
|
|
47
49
|
color: light.$on-surface-primary-default;
|
|
48
50
|
width: 100%;
|
|
49
51
|
box-sizing: border-box;
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
&:focus-within {
|
|
54
|
+
border-color: light.$ge-border-focused;
|
|
55
|
+
outline: none;
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
@media (hover: hover) {
|
|
59
|
+
&:hover {
|
|
60
|
+
background-color: light.$surface-primary-hover;
|
|
61
|
+
cursor: text;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
&__input {
|
|
67
|
+
@include font-sizes.normal(input);
|
|
68
|
+
line-height: $line-height;
|
|
69
|
+
width: inherit;
|
|
70
|
+
border: none;
|
|
71
|
+
padding: 0;
|
|
72
|
+
background-color: transparent;
|
|
73
|
+
|
|
55
74
|
&:focus {
|
|
56
|
-
border-color:
|
|
75
|
+
border-color: none;
|
|
57
76
|
outline: none;
|
|
58
77
|
}
|
|
59
78
|
|
|
@@ -88,85 +107,10 @@
|
|
|
88
107
|
}
|
|
89
108
|
}
|
|
90
109
|
|
|
91
|
-
&__icon {
|
|
92
|
-
color: light.$on-surface-primary-disabled;
|
|
93
|
-
left: $icon-spacing;
|
|
94
|
-
height: $icon-width;
|
|
95
|
-
pointer-events: none;
|
|
96
|
-
position: absolute;
|
|
97
|
-
width: $icon-width;
|
|
98
|
-
|
|
99
|
-
svg {
|
|
100
|
-
display: block;
|
|
101
|
-
height: 100%;
|
|
102
|
-
width: 100%;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
&--moving-label {
|
|
107
|
-
#{$block}__input {
|
|
108
|
-
padding: spacing.$s spacing.$m;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
#{$block}__label {
|
|
112
|
-
position: absolute;
|
|
113
|
-
z-index: 1;
|
|
114
|
-
top: 0;
|
|
115
|
-
margin: 0;
|
|
116
|
-
padding: 0 6px;
|
|
117
|
-
transform: translate(10px, spacing.$s);
|
|
118
|
-
transition: transform 0.1s cubic-bezier(0.22, 0.57, 0.25, 1);
|
|
119
|
-
transform-origin: left center;
|
|
120
|
-
max-width: calc(100% - 25px);
|
|
121
|
-
overflow: hidden;
|
|
122
|
-
text-overflow: ellipsis;
|
|
123
|
-
white-space: nowrap;
|
|
124
|
-
line-height: $line-height;
|
|
125
|
-
|
|
126
|
-
@media (prefers-reduced-motion) {
|
|
127
|
-
transition: transform 0s;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
&::before {
|
|
131
|
-
content: '';
|
|
132
|
-
position: absolute;
|
|
133
|
-
z-index: -1;
|
|
134
|
-
left: 0;
|
|
135
|
-
bottom: 0;
|
|
136
|
-
height: 50%;
|
|
137
|
-
width: 100%;
|
|
138
|
-
background-color: light.$surface-primary-default;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
&.text-input--moving-label {
|
|
143
|
-
&.text-input--theme-dark {
|
|
144
|
-
#{$block}__label {
|
|
145
|
-
color: light.$on-fixed-surface-primary-default;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
#{$block}__label {
|
|
149
|
-
&::before {
|
|
150
|
-
content: '';
|
|
151
|
-
background-color: light.$fixed-surface-primary-default;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
& #{$block}__input:focus,
|
|
158
|
-
& #{$block}__input:not(:placeholder-shown) {
|
|
159
|
-
~ #{$block}__label {
|
|
160
|
-
transform: translate(10px, -50%) scale(0.8);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
110
|
&__help,
|
|
166
111
|
&__error {
|
|
167
112
|
display: flex;
|
|
168
113
|
gap: spacing.$xs;
|
|
169
|
-
margin-top: spacing.$xxs;
|
|
170
114
|
|
|
171
115
|
> * {
|
|
172
116
|
align-self: flex-start;
|
|
@@ -174,7 +118,7 @@
|
|
|
174
118
|
}
|
|
175
119
|
|
|
176
120
|
&__help {
|
|
177
|
-
color: light.$on-surface-primary-
|
|
121
|
+
color: light.$on-surface-primary-alternate;
|
|
178
122
|
}
|
|
179
123
|
|
|
180
124
|
&__error {
|
|
@@ -182,10 +126,16 @@
|
|
|
182
126
|
}
|
|
183
127
|
|
|
184
128
|
&__help-icon,
|
|
185
|
-
&__error-icon
|
|
129
|
+
&__error-icon,
|
|
130
|
+
&__icon {
|
|
186
131
|
flex-shrink: 0;
|
|
187
132
|
}
|
|
188
133
|
|
|
134
|
+
&__help-icon,
|
|
135
|
+
&__error-icon {
|
|
136
|
+
height: 16px; // Matches icon height. Otherwise, the auto height acts weirdly, by making the wrapper too tall.
|
|
137
|
+
}
|
|
138
|
+
|
|
189
139
|
&__asterisk {
|
|
190
140
|
color: light.$on-surface-featured;
|
|
191
141
|
margin-left: spacing.$xs;
|
|
@@ -197,6 +147,28 @@
|
|
|
197
147
|
#{$block}__help {
|
|
198
148
|
color: light.$on-background-primary-disabled;
|
|
199
149
|
}
|
|
150
|
+
|
|
151
|
+
#{$block}__input-wrapper {
|
|
152
|
+
background-color: light.$surface-primary-disabled;
|
|
153
|
+
border-color: light.$surface-primary-disabled;
|
|
154
|
+
cursor: auto;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
&--error {
|
|
159
|
+
#{$block}__icon {
|
|
160
|
+
color: light.$on-surface-error;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#{$block}__input-wrapper {
|
|
164
|
+
outline: 2px solid light.$ge-signal-error;
|
|
165
|
+
outline-offset: -2px;
|
|
166
|
+
|
|
167
|
+
&:focus {
|
|
168
|
+
outline: 2px solid light.$ge-signal-error;
|
|
169
|
+
outline-offset: -2px;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
200
172
|
}
|
|
201
173
|
|
|
202
174
|
&--theme-dark {
|
|
@@ -208,19 +180,25 @@
|
|
|
208
180
|
color: light.$on-fixed-background-primary-default;
|
|
209
181
|
}
|
|
210
182
|
|
|
183
|
+
#{$block}__length-counter,
|
|
211
184
|
#{$block}__help {
|
|
212
|
-
color: light.$on-
|
|
185
|
+
color: light.$on-background-primary-alternate;
|
|
213
186
|
}
|
|
214
187
|
|
|
215
188
|
#{$block}__input {
|
|
189
|
+
background-color: transparent;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#{$block}__input-wrapper {
|
|
216
193
|
background-color: light.$fixed-surface-secondary-default;
|
|
217
194
|
border-color: light.$ge-border-default;
|
|
218
195
|
color: light.$on-fixed-surface-primary-alternate;
|
|
219
196
|
|
|
220
|
-
&:focus {
|
|
221
|
-
background
|
|
197
|
+
&:focus-within {
|
|
198
|
+
background: light.$fixed-surface-primary-default;
|
|
222
199
|
color: light.$on-fixed-surface-primary-default;
|
|
223
200
|
outline: none;
|
|
201
|
+
border-color: light.$ge-border-focused;
|
|
224
202
|
|
|
225
203
|
~ #{$block}__icon {
|
|
226
204
|
color: light.$on-fixed-surface-primary-default;
|
|
@@ -235,33 +213,39 @@
|
|
|
235
213
|
&[disabled]::placeholder {
|
|
236
214
|
color: light.$on-fixed-surface-primary-disabled;
|
|
237
215
|
}
|
|
216
|
+
|
|
217
|
+
@media (hover: hover) {
|
|
218
|
+
&:hover {
|
|
219
|
+
background-color: light.$fixed-surface-primary-hover;
|
|
220
|
+
cursor: text;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#{$block}__input {
|
|
226
|
+
background-color: transparent;
|
|
227
|
+
color: light.$on-fixed-surface-primary-default;
|
|
238
228
|
}
|
|
239
229
|
|
|
240
230
|
&#{$block}--disabled {
|
|
241
231
|
#{$block}__label,
|
|
242
232
|
#{$block}__icon,
|
|
243
233
|
#{$block}__help {
|
|
244
|
-
color: light.$on-fixed-
|
|
234
|
+
color: light.$on-fixed-surface-primary-disabled;
|
|
245
235
|
}
|
|
246
236
|
|
|
247
237
|
&#{$block}--error #{$block}__icon {
|
|
248
238
|
color: light.$on-fixed-surface-error;
|
|
249
239
|
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
240
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
#{$block}__input {
|
|
259
|
-
outline: 2px solid light.$ge-signal-error;
|
|
260
|
-
outline-offset: -2px;
|
|
241
|
+
#{$block}__input-wrapper {
|
|
242
|
+
background-color: light.$fixed-surface-primary-selected;
|
|
243
|
+
border-color: light.$on-fixed-surface-primary-disabled;
|
|
244
|
+
cursor: auto;
|
|
245
|
+
}
|
|
261
246
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
outline-offset: -2px;
|
|
247
|
+
#{$block}__input {
|
|
248
|
+
background-color: transparent;
|
|
265
249
|
}
|
|
266
250
|
}
|
|
267
251
|
}
|
|
@@ -16,28 +16,40 @@ const RefTextInput = React.forwardRef<HTMLInputElement, Props>(
|
|
|
16
16
|
defaultValue,
|
|
17
17
|
disabled,
|
|
18
18
|
hasError,
|
|
19
|
-
hasMovingLabel,
|
|
20
19
|
helpText,
|
|
21
|
-
hiddenLabel,
|
|
22
|
-
icon,
|
|
23
20
|
label,
|
|
21
|
+
leadingIcon,
|
|
22
|
+
maxLength,
|
|
24
23
|
name,
|
|
25
24
|
onChange = () => {},
|
|
26
25
|
placeholder,
|
|
27
26
|
required,
|
|
28
27
|
theme,
|
|
28
|
+
trailingIcon,
|
|
29
29
|
type = 'text',
|
|
30
30
|
variant = variants.large,
|
|
31
31
|
...restProps
|
|
32
32
|
},
|
|
33
33
|
ref,
|
|
34
34
|
) => {
|
|
35
|
+
const [count, setCount] = React.useState(0);
|
|
35
36
|
const [isError, setIsError] = React.useState(hasError);
|
|
36
37
|
const [validationOnChange, onInvalid, error] = useInputValidation(
|
|
37
38
|
customErrorMessages,
|
|
38
39
|
customErrorMessages ? customErrorMessages.defaultError : undefined,
|
|
39
40
|
isError,
|
|
40
41
|
);
|
|
42
|
+
const isMaxLengthValid = maxLength ? maxLength > 0 : undefined;
|
|
43
|
+
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (defaultValue) {
|
|
46
|
+
if (typeof defaultValue === 'string') {
|
|
47
|
+
setCount(defaultValue.length);
|
|
48
|
+
} else if (typeof defaultValue === 'number') {
|
|
49
|
+
setCount(defaultValue);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}, [restProps]);
|
|
41
53
|
|
|
42
54
|
useEffect(() => {
|
|
43
55
|
setIsError(hasError);
|
|
@@ -50,38 +62,13 @@ const RefTextInput = React.forwardRef<HTMLInputElement, Props>(
|
|
|
50
62
|
'text-input--theme-light': theme === themes.light,
|
|
51
63
|
'text-input--disabled': disabled,
|
|
52
64
|
'text-input--error': error || isError,
|
|
53
|
-
'text-input--
|
|
54
|
-
'text-input--icon': icon,
|
|
65
|
+
'text-input--icon': leadingIcon || trailingIcon,
|
|
55
66
|
'text-input--variant-small': variant === variants.small,
|
|
56
67
|
'text-input--variant-large': variant === variants.large,
|
|
57
68
|
})}
|
|
58
69
|
>
|
|
59
70
|
<div className="text-input__wrapper">
|
|
60
|
-
<
|
|
61
|
-
{...restProps}
|
|
62
|
-
className="text-input__input"
|
|
63
|
-
defaultValue={defaultValue}
|
|
64
|
-
disabled={disabled}
|
|
65
|
-
required={required}
|
|
66
|
-
name={name}
|
|
67
|
-
onInvalid={e => onInvalid(e)}
|
|
68
|
-
onChange={e => {
|
|
69
|
-
onChange(e);
|
|
70
|
-
validationOnChange(e);
|
|
71
|
-
setIsError(false); // NOTE: We want to reset error state on change to not confuse users.
|
|
72
|
-
}}
|
|
73
|
-
// NOTE: Using " " as placeholder for moving label theme to enable using `:placeholder-shown` to determine when to move the label
|
|
74
|
-
placeholder={
|
|
75
|
-
icon ? placeholder : hasMovingLabel ? ' ' : placeholder
|
|
76
|
-
}
|
|
77
|
-
ref={ref}
|
|
78
|
-
type={type}
|
|
79
|
-
aria-label={icon || hiddenLabel ? label : undefined}
|
|
80
|
-
/>
|
|
81
|
-
|
|
82
|
-
{icon ? (
|
|
83
|
-
<div className="text-input__icon">{icon}</div>
|
|
84
|
-
) : hiddenLabel ? null : (
|
|
71
|
+
<div className="text-input__label-wrapper">
|
|
85
72
|
<Text
|
|
86
73
|
className="text-input__label"
|
|
87
74
|
theme={Text.themes.emphasis}
|
|
@@ -94,26 +81,69 @@ const RefTextInput = React.forwardRef<HTMLInputElement, Props>(
|
|
|
94
81
|
<span className="text-input__asterisk">*</span>
|
|
95
82
|
) : null}
|
|
96
83
|
</Text>
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
{isMaxLengthValid ? (
|
|
85
|
+
<div className="text-input__length-counter">
|
|
86
|
+
<Text
|
|
87
|
+
size={
|
|
88
|
+
variant === variants.small
|
|
89
|
+
? Text.sizes.small
|
|
90
|
+
: Text.sizes.basic
|
|
91
|
+
}
|
|
92
|
+
>
|
|
93
|
+
{count}/{maxLength}
|
|
94
|
+
</Text>
|
|
95
|
+
</div>
|
|
96
|
+
) : null}
|
|
97
|
+
</div>
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
<div className="text-input__input-wrapper">
|
|
100
|
+
{leadingIcon ? (
|
|
101
|
+
<div className="text-input__icon">{leadingIcon}</div>
|
|
102
|
+
) : null}
|
|
103
|
+
<input
|
|
104
|
+
className="text-input__input"
|
|
105
|
+
defaultValue={defaultValue}
|
|
106
|
+
disabled={disabled}
|
|
107
|
+
required={required}
|
|
108
|
+
name={name}
|
|
109
|
+
maxLength={isMaxLengthValid ? maxLength : undefined}
|
|
110
|
+
onInvalid={e => onInvalid(e)}
|
|
111
|
+
onChange={e => {
|
|
112
|
+
onChange(e);
|
|
113
|
+
validationOnChange(e);
|
|
114
|
+
setIsError(false); // NOTE: We want to reset error state on change to not confuse users.
|
|
115
|
+
if (isMaxLengthValid) {
|
|
116
|
+
setCount(e.target.value.length);
|
|
117
|
+
}
|
|
118
|
+
}}
|
|
119
|
+
placeholder={placeholder}
|
|
120
|
+
ref={ref}
|
|
121
|
+
type={type}
|
|
122
|
+
aria-label={label}
|
|
123
|
+
{...restProps}
|
|
124
|
+
/>
|
|
125
|
+
{trailingIcon ? (
|
|
126
|
+
<div className="text-input__icon">{trailingIcon}</div>
|
|
127
|
+
) : null}
|
|
106
128
|
</div>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<
|
|
129
|
+
{helpText ? (
|
|
130
|
+
<div className="text-input__help">
|
|
131
|
+
<div className="text-input__help-icon">
|
|
132
|
+
<SvgInfo />
|
|
133
|
+
</div>
|
|
134
|
+
<Text size={Text.sizes.interface}>{helpText}</Text>
|
|
113
135
|
</div>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
) : null}
|
|
137
|
+
{/* NOTE: This is aria-hidden because reporting of validation errors is handled by the browser */}
|
|
138
|
+
{error ? (
|
|
139
|
+
<div aria-hidden="true" className="text-input__error">
|
|
140
|
+
<div className="text-input__error-icon">
|
|
141
|
+
<SvgError />
|
|
142
|
+
</div>
|
|
143
|
+
<Text size={Text.sizes.interface}>{error}</Text>
|
|
144
|
+
</div>
|
|
145
|
+
) : null}
|
|
146
|
+
</div>
|
|
117
147
|
</label>
|
|
118
148
|
);
|
|
119
149
|
},
|
|
@@ -14,12 +14,11 @@ export const variants = {
|
|
|
14
14
|
export type TextInput = {
|
|
15
15
|
customErrorMessages?: Messages;
|
|
16
16
|
hasError?: boolean;
|
|
17
|
-
hasMovingLabel?: boolean;
|
|
18
17
|
helpText?: string;
|
|
19
|
-
hiddenLabel?: boolean;
|
|
20
|
-
icon?: React.ReactNode;
|
|
21
18
|
label: string;
|
|
19
|
+
leadingIcon?: React.ReactNode;
|
|
22
20
|
name: string;
|
|
23
21
|
theme?: ObjectValues<typeof themes>;
|
|
22
|
+
trailingIcon?: React.ReactNode;
|
|
24
23
|
variant?: ObjectValues<typeof variants>;
|
|
25
24
|
} & InputHtmlProps;
|