@sats-group/ui-lib 75.10.0 → 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,96 +107,35 @@
|
|
|
88
107
|
}
|
|
89
108
|
}
|
|
90
109
|
|
|
91
|
-
&
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
}
|
|
110
|
+
&__help,
|
|
111
|
+
&__error {
|
|
112
|
+
display: flex;
|
|
113
|
+
gap: spacing.$xs;
|
|
156
114
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
~ #{$block}__label {
|
|
160
|
-
transform: translate(10px, -50%) scale(0.8);
|
|
161
|
-
}
|
|
115
|
+
> * {
|
|
116
|
+
align-self: flex-start;
|
|
162
117
|
}
|
|
163
118
|
}
|
|
164
119
|
|
|
165
120
|
&__help {
|
|
166
|
-
|
|
167
|
-
align-items: center;
|
|
168
|
-
gap: spacing.$xs;
|
|
169
|
-
margin-top: spacing.$xxs;
|
|
170
|
-
color: light.$on-background-primary-disabled;
|
|
121
|
+
color: light.$on-surface-primary-alternate;
|
|
171
122
|
}
|
|
172
123
|
|
|
173
124
|
&__error {
|
|
174
|
-
display: flex;
|
|
175
|
-
align-items: center;
|
|
176
|
-
gap: spacing.$xs;
|
|
177
|
-
margin-top: spacing.$xxs;
|
|
178
125
|
color: light.$on-surface-error;
|
|
179
126
|
}
|
|
180
127
|
|
|
128
|
+
&__help-icon,
|
|
129
|
+
&__error-icon,
|
|
130
|
+
&__icon {
|
|
131
|
+
flex-shrink: 0;
|
|
132
|
+
}
|
|
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
|
+
|
|
181
139
|
&__asterisk {
|
|
182
140
|
color: light.$on-surface-featured;
|
|
183
141
|
margin-left: spacing.$xs;
|
|
@@ -189,6 +147,28 @@
|
|
|
189
147
|
#{$block}__help {
|
|
190
148
|
color: light.$on-background-primary-disabled;
|
|
191
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
|
+
}
|
|
192
172
|
}
|
|
193
173
|
|
|
194
174
|
&--theme-dark {
|
|
@@ -200,19 +180,25 @@
|
|
|
200
180
|
color: light.$on-fixed-background-primary-default;
|
|
201
181
|
}
|
|
202
182
|
|
|
183
|
+
#{$block}__length-counter,
|
|
203
184
|
#{$block}__help {
|
|
204
|
-
color: light.$on-
|
|
185
|
+
color: light.$on-background-primary-alternate;
|
|
205
186
|
}
|
|
206
187
|
|
|
207
188
|
#{$block}__input {
|
|
189
|
+
background-color: transparent;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#{$block}__input-wrapper {
|
|
208
193
|
background-color: light.$fixed-surface-secondary-default;
|
|
209
194
|
border-color: light.$ge-border-default;
|
|
210
195
|
color: light.$on-fixed-surface-primary-alternate;
|
|
211
196
|
|
|
212
|
-
&:focus {
|
|
213
|
-
background
|
|
197
|
+
&:focus-within {
|
|
198
|
+
background: light.$fixed-surface-primary-default;
|
|
214
199
|
color: light.$on-fixed-surface-primary-default;
|
|
215
200
|
outline: none;
|
|
201
|
+
border-color: light.$ge-border-focused;
|
|
216
202
|
|
|
217
203
|
~ #{$block}__icon {
|
|
218
204
|
color: light.$on-fixed-surface-primary-default;
|
|
@@ -227,33 +213,39 @@
|
|
|
227
213
|
&[disabled]::placeholder {
|
|
228
214
|
color: light.$on-fixed-surface-primary-disabled;
|
|
229
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;
|
|
230
228
|
}
|
|
231
229
|
|
|
232
230
|
&#{$block}--disabled {
|
|
233
231
|
#{$block}__label,
|
|
234
232
|
#{$block}__icon,
|
|
235
233
|
#{$block}__help {
|
|
236
|
-
color: light.$on-fixed-
|
|
234
|
+
color: light.$on-fixed-surface-primary-disabled;
|
|
237
235
|
}
|
|
238
236
|
|
|
239
237
|
&#{$block}--error #{$block}__icon {
|
|
240
238
|
color: light.$on-fixed-surface-error;
|
|
241
239
|
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
240
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
#{$block}__input {
|
|
251
|
-
outline: 2px solid light.$ge-signal-error;
|
|
252
|
-
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
|
+
}
|
|
253
246
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
outline-offset: -2px;
|
|
247
|
+
#{$block}__input {
|
|
248
|
+
background-color: transparent;
|
|
257
249
|
}
|
|
258
250
|
}
|
|
259
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,22 +81,69 @@ const RefTextInput = React.forwardRef<HTMLInputElement, Props>(
|
|
|
94
81
|
<span className="text-input__asterisk">*</span>
|
|
95
82
|
) : null}
|
|
96
83
|
</Text>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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}
|
|
104
97
|
</div>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<
|
|
98
|
+
|
|
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}
|
|
111
128
|
</div>
|
|
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>
|
|
135
|
+
</div>
|
|
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>
|
|
113
147
|
</label>
|
|
114
148
|
);
|
|
115
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;
|