@ultraviolet/ui 1.30.0 → 1.31.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.
@@ -0,0 +1,299 @@
1
+ import _styled from '@emotion/styled/base';
2
+ import { Icon } from '@ultraviolet/icons';
3
+ import { forwardRef, useRef, useImperativeHandle, useId, useCallback, useMemo } from 'react';
4
+ import { Button } from '../Button/index.js';
5
+ import { Row } from '../Row/index.js';
6
+ import { Stack } from '../Stack/index.js';
7
+ import { Text } from '../Text/index.js';
8
+ import { Tooltip } from '../Tooltip/index.js';
9
+ import { jsxs, jsx } from '@emotion/react/jsx-runtime';
10
+
11
+ function _EMOTION_STRINGIFIED_CSS_ERROR__() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
12
+ const SIZES = {
13
+ small: '30px',
14
+ medium: '38px',
15
+ large: '46px'
16
+ };
17
+ const SideContainer = /*#__PURE__*/_styled(Stack, {
18
+ target: "e1b9qdjy4"
19
+ })("padding:", ({
20
+ theme
21
+ }) => `${theme.space['0.25']} ${theme.space['1']}`, ";&[data-size='small']{height:", SIZES.small, ";}&[data-size='medium']{height:", SIZES.medium, ";}&[data-size='large']{height:", SIZES.large, ";padding:", ({
22
+ theme
23
+ }) => `${theme.space['0.5']} ${theme.space['1']}`, ";}");
24
+ const InputContainer = /*#__PURE__*/_styled(Row, {
25
+ target: "e1b9qdjy3"
26
+ })(process.env.NODE_ENV === "production" ? {
27
+ name: "v1pgyp",
28
+ styles: "border-width:0 1px 0 1px;border-style:solid;border-color:inherit;background:inherit;width:100%"
29
+ } : {
30
+ name: "v1pgyp",
31
+ styles: "border-width:0 1px 0 1px;border-style:solid;border-color:inherit;background:inherit;width:100%",
32
+ toString: _EMOTION_STRINGIFIED_CSS_ERROR__
33
+ });
34
+ const Unit = /*#__PURE__*/_styled(Text, {
35
+ shouldForwardProp: prop => !['size'].includes(prop),
36
+ target: "e1b9qdjy2"
37
+ })("display:flex;align-items:center;padding:", ({
38
+ theme
39
+ }) => theme.space['1'], ";height:", ({
40
+ size
41
+ }) => SIZES[size], ";");
42
+ const Input = /*#__PURE__*/_styled("input", {
43
+ target: "e1b9qdjy1"
44
+ })("outline:none;border:none;padding:0;width:100%;color:", ({
45
+ theme
46
+ }) => theme.colors.neutral.text, ";font-size:", ({
47
+ theme
48
+ }) => theme.typography.body.fontSize, ";font-family:", ({
49
+ theme
50
+ }) => theme.typography.body.fontFamily, ";font-weight:", ({
51
+ theme
52
+ }) => theme.typography.body.fontWeight, ";line-height:", ({
53
+ theme
54
+ }) => theme.typography.body.lineHeight, ";text-align:center;padding:", ({
55
+ theme
56
+ }) => theme.space['1'], ";background:none;&[data-has-unit='true']{text-align:left;padding:", ({
57
+ theme
58
+ }) => `${theme.space['1']} 0 ${theme.space['1']} ${theme.space['1']}`, ";}&::-webkit-outer-spin-button,&::-webkit-inner-spin-button{-webkit-appearance:none;margin:0;}&{-moz-appearance:textfield;}&[data-size='small']{height:", SIZES.small, ";}&[data-size='medium']{height:", SIZES.medium, ";}&[data-size='large']{height:", SIZES.large, ";}&:read-only{color:", ({
59
+ theme
60
+ }) => theme.colors.neutral.text, ";background:", ({
61
+ theme
62
+ }) => theme.colors.neutral.backgroundWeak, ";&~", Unit, "{background:", ({
63
+ theme
64
+ }) => theme.colors.neutral.backgroundWeak, ";}}&:disabled{color:", ({
65
+ theme
66
+ }) => theme.colors.neutral.textDisabled, ";background:", ({
67
+ theme
68
+ }) => theme.colors.neutral.backgroundDisabled, ";cursor:not-allowed;&~", Unit, "{background:", ({
69
+ theme
70
+ }) => theme.colors.neutral.backgroundDisabled, ";cursor:not-allowed;user-select:none;}}&:placeholder-shown~", Unit, "{color:", ({
71
+ theme
72
+ }) => theme.colors.neutral.textWeak, ";}");
73
+ const Container = /*#__PURE__*/_styled("div", {
74
+ target: "e1b9qdjy0"
75
+ })("display:flex;align-items:center;justify-content:space-between;flex-direction:row;border:1px solid ", ({
76
+ theme
77
+ }) => theme.colors.neutral.border, ";border-radius:", ({
78
+ theme
79
+ }) => theme.radii.default, ";&:focus-within{border-color:", ({
80
+ theme
81
+ }) => theme.colors.primary.borderHover, ";box-shadow:", ({
82
+ theme
83
+ }) => theme.shadows.focusPrimary, ";}&[data-success='true']{border-color:", ({
84
+ theme
85
+ }) => theme.colors.success.border, ";}&[data-error='true']{border-color:", ({
86
+ theme
87
+ }) => theme.colors.danger.border, ";}&:hover{border-color:", ({
88
+ theme
89
+ }) => theme.colors.primary.borderHover, ";}&[data-readonly='true']{border-color:", ({
90
+ theme
91
+ }) => theme.colors.neutral.border, ";background:", ({
92
+ theme
93
+ }) => theme.colors.neutral.backgroundWeak, ";cursor:not-allowed;}&[data-disabled='true']{border-color:", ({
94
+ theme
95
+ }) => theme.colors.neutral.borderDisabled, ";background:", ({
96
+ theme
97
+ }) => theme.colors.neutral.backgroundDisabled, ";cursor:not-allowed;}");
98
+ /**
99
+ * NumberInputV2 component is used to increment / decrement a number value by clicking on + / - buttons or
100
+ * by typing into input. If the value is out of the min / max range, the input will automatically be the min / max value on blur.
101
+ */
102
+ const NumberInputV2 = /*#__PURE__*/forwardRef(({
103
+ disabled = false,
104
+ max = Infinity,
105
+ min = 0,
106
+ name,
107
+ onChange,
108
+ onFocus,
109
+ onBlur,
110
+ size = 'medium',
111
+ step,
112
+ unit,
113
+ value,
114
+ tooltip,
115
+ className,
116
+ label,
117
+ labelDescription,
118
+ id,
119
+ placeholder = '',
120
+ error,
121
+ success,
122
+ helper,
123
+ 'aria-label': ariaLabel,
124
+ 'data-testid': dataTestId,
125
+ required,
126
+ autoFocus,
127
+ readOnly
128
+ }, ref) => {
129
+ const localRef = useRef(null);
130
+ useImperativeHandle(ref, () => localRef.current);
131
+ const uniqueId = useId();
132
+ const localId = id ?? uniqueId;
133
+ const createChangeEvent = newValue => ({
134
+ target: {
135
+ value: newValue
136
+ }
137
+ });
138
+ const onClickSideButton = useCallback(direction => () => {
139
+ if (direction === 'up') {
140
+ localRef.current?.stepUp();
141
+ onChange?.(createChangeEvent(localRef.current?.value ?? min));
142
+ }
143
+ if (direction === 'down') {
144
+ localRef.current?.stepDown();
145
+ onChange?.(createChangeEvent(localRef.current?.value ?? min));
146
+ }
147
+ }, [localRef, min, onChange]);
148
+ const isMinusDisabled = useMemo(() => {
149
+ if (!localRef?.current?.value || localRef?.current?.value === '') {
150
+ return false;
151
+ }
152
+ const numericValue = Number(localRef?.current?.value);
153
+ if (Number.isNaN(numericValue)) return false;
154
+ const minValue = typeof min === 'number' ? min : Number(min);
155
+ return Number.isNaN(numericValue) || numericValue <= minValue;
156
+ },
157
+ // eslint-disable-next-line react-hooks/exhaustive-deps
158
+ [localRef?.current?.value, min]);
159
+ const isPlusDisabled = useMemo(() => {
160
+ if (!localRef?.current?.value || localRef?.current?.value === '') {
161
+ return false;
162
+ }
163
+ const numericValue = Number(localRef?.current?.value);
164
+ if (Number.isNaN(numericValue)) return false;
165
+ const maxValue = typeof max === 'number' ? max : Number(max);
166
+ return numericValue >= maxValue;
167
+ },
168
+ // eslint-disable-next-line react-hooks/exhaustive-deps
169
+ [localRef?.current?.value, max]);
170
+ const helperSentiment = useMemo(() => {
171
+ if (error) {
172
+ return 'danger';
173
+ }
174
+ if (success) {
175
+ return 'success';
176
+ }
177
+ return 'neutral';
178
+ }, [error, success]);
179
+ return jsxs(Stack, {
180
+ gap: "0.5",
181
+ className: className,
182
+ children: [jsxs(Stack, {
183
+ direction: "row",
184
+ gap: "1",
185
+ alignItems: "center",
186
+ children: [jsxs(Stack, {
187
+ direction: "row",
188
+ gap: "0.5",
189
+ alignItems: "start",
190
+ children: [jsx(Text, {
191
+ as: "label",
192
+ variant: "bodySmallStrong",
193
+ sentiment: "neutral",
194
+ htmlFor: id ?? localId,
195
+ children: label
196
+ }), required ? jsx(Icon, {
197
+ name: "asterisk",
198
+ color: "danger",
199
+ size: 8
200
+ }) : null]
201
+ }), labelDescription ?? null]
202
+ }), jsx("div", {
203
+ children: jsx(Tooltip, {
204
+ text: tooltip,
205
+ children: jsxs(Container, {
206
+ "data-disabled": disabled,
207
+ "data-readonly": readOnly,
208
+ "data-error": !!error,
209
+ "data-success": !!success,
210
+ "data-unit": !!unit,
211
+ children: [jsx(SideContainer, {
212
+ justifyContent: "center",
213
+ alignItems: "center",
214
+ "data-size": size,
215
+ children: jsx(Button, {
216
+ sentiment: "neutral",
217
+ variant: "ghost",
218
+ icon: "minus",
219
+ size: size === 'small' ? 'xsmall' : 'small',
220
+ disabled: disabled || readOnly || isMinusDisabled,
221
+ onClick: onClickSideButton('down'),
222
+ "aria-label": "minus"
223
+ })
224
+ }), jsxs(InputContainer, {
225
+ justifyContent: "space-between",
226
+ alignItems: "center",
227
+ templateColumns: "1fr auto",
228
+ children: [jsx(Input, {
229
+ ref: localRef,
230
+ type: "number",
231
+ name: name,
232
+ id: localId,
233
+ placeholder: placeholder,
234
+ onBlur: event => {
235
+ if (event.target.value === '') {
236
+ onBlur?.(event);
237
+ return;
238
+ }
239
+ const numericValue = Number(event.target.value);
240
+ const maxValue = typeof max === 'number' ? max : Number(max);
241
+ const minValue = typeof min === 'number' ? min : Number(min);
242
+ if (Number.isNaN(numericValue) || !Number.isNaN(minValue) && numericValue < minValue) {
243
+ onChange?.(createChangeEvent(min.toString()));
244
+ }
245
+ if (Number.isNaN(numericValue) || !Number.isNaN(maxValue) && numericValue > maxValue) {
246
+ onChange?.(createChangeEvent(max.toString()));
247
+ }
248
+ onBlur?.(event);
249
+ },
250
+ onFocus: onFocus,
251
+ onChange: onChange,
252
+ value: value,
253
+ "data-size": size,
254
+ step: step,
255
+ disabled: disabled,
256
+ "aria-label": ariaLabel,
257
+ "data-testid": dataTestId,
258
+ min: min,
259
+ max: max,
260
+ required: required,
261
+ autoFocus: autoFocus,
262
+ readOnly: readOnly,
263
+ "data-has-unit": !!unit
264
+ }), unit ? jsx(Unit, {
265
+ variant: "body",
266
+ sentiment: "neutral",
267
+ as: "span",
268
+ disabled: disabled,
269
+ size: size,
270
+ children: unit
271
+ }) : null]
272
+ }), jsx(SideContainer, {
273
+ justifyContent: "center",
274
+ alignItems: "center",
275
+ "data-size": size,
276
+ children: jsx(Button, {
277
+ sentiment: "neutral",
278
+ variant: "ghost",
279
+ icon: "plus",
280
+ size: size === 'small' ? 'xsmall' : 'small',
281
+ disabled: disabled || readOnly || isPlusDisabled,
282
+ onClick: onClickSideButton('up'),
283
+ "aria-label": "plus"
284
+ })
285
+ })]
286
+ })
287
+ })
288
+ }), error || success || helper ? jsx(Text, {
289
+ variant: "caption",
290
+ as: "span",
291
+ prominence: !error && !success ? 'weak' : undefined,
292
+ sentiment: helperSentiment,
293
+ disabled: disabled || readOnly,
294
+ children: error || success || helper
295
+ }) : null]
296
+ });
297
+ });
298
+
299
+ export { NumberInputV2 };
@@ -5,7 +5,7 @@ import _styled from '@emotion/styled/base';
5
5
  * it accepts few props to deal with spacing and align.
6
6
  */
7
7
  const Stack = /*#__PURE__*/_styled('div', {
8
- shouldForwardProp: prop => !['gap', 'direction', 'alignItems', 'justifyContent', 'wrap', 'width'].includes(prop),
8
+ shouldForwardProp: prop => !['gap', 'direction', 'alignItems', 'justifyContent', 'wrap', 'width', 'flex'].includes(prop),
9
9
  target: "ehpbis70"
10
10
  })("display:flex;", ({
11
11
  theme,
@@ -1,6 +1,6 @@
1
1
  import _styled from '@emotion/styled/base';
2
2
  import { Icon } from '@ultraviolet/icons';
3
- import { forwardRef, useId } from 'react';
3
+ import { forwardRef, useId, useMemo } from 'react';
4
4
  import { Button, SIZE_HEIGHT } from '../Button/index.js';
5
5
  import { Row } from '../Row/index.js';
6
6
  import { Stack } from '../Stack/index.js';
@@ -28,7 +28,7 @@ const StyledTextAreaAbsoluteStack = /*#__PURE__*/_styled(Stack, {
28
28
  theme
29
29
  }) => theme.space['1'], ";");
30
30
  const StyledTextArea = /*#__PURE__*/_styled('textarea', {
31
- shouldForwardProp: prop => !['isSuccess', 'isError', 'isClearable'].includes(prop),
31
+ shouldForwardProp: prop => !['hasSentimentIcon', 'isClearable'].includes(prop),
32
32
  target: "enu776d0"
33
33
  })("width:100%;resize:vertical;background:", ({
34
34
  theme
@@ -45,57 +45,29 @@ const StyledTextArea = /*#__PURE__*/_styled('textarea', {
45
45
  }) => `${theme.space['1.5']} ${theme.space['1']} ${theme.space['1.5']} ${theme.space['2']}`, ";padding-right:", ({
46
46
  theme,
47
47
  isClearable,
48
- isError,
49
- isSuccess
48
+ hasSentimentIcon
50
49
  }) => /* including 1 optional if both element is visible + 1 because content is absolute 1space unit from right */
51
- `calc(${theme.space[isClearable && (isSuccess || isError) ? '4' : '3']} + ${isClearable ? `${SIZE_HEIGHT.xsmall}px` : '0px'} + ${isSuccess || isError ? `${STATE_ICON_SIZE}px` : '0px'})`, ";", ({
52
- theme,
53
- disabled
54
- }) => !disabled ? `
55
- &:hover {
56
- border-color: ${theme.colors.primary.border};
57
- }
58
- &:focus {
59
- outline: none;
60
- border-color: ${theme.colors.primary.border};
61
- box-shadow: ${theme.shadows.focusPrimary};
62
- }
63
- ` : '', " ", ({
64
- theme,
65
- isSuccess,
66
- isError,
67
- readOnly,
68
- disabled
69
- }) => {
70
- if (disabled) {
71
- return `
72
- background : ${theme.colors.neutral.backgroundDisabled};
73
- border-color : ${theme.colors.neutral.borderDisabled};
74
- color : ${theme.colors.neutral.textDisabled};
75
- &::placeholder {
76
- color: ${theme.colors.neutral.textWeakDisabled};
77
- }
78
- cursor: not-allowed;
79
- `;
80
- }
81
- if (readOnly) {
82
- return `
83
- background : ${theme.colors.neutral.backgroundWeak};
84
- border-color : ${theme.colors.neutral.borderWeak};
85
- `;
86
- }
87
- if (isSuccess) {
88
- return `
89
- border-color : ${theme.colors.success.border};
90
- `;
91
- }
92
- if (isError) {
93
- return `
94
- border-color : ${theme.colors.danger.border};
95
- `;
96
- }
97
- return '';
98
- }, ";");
50
+ `calc(${theme.space[isClearable && hasSentimentIcon ? '4' : '3']} + ${isClearable ? `${SIZE_HEIGHT.xsmall}px` : '0px'} + ${hasSentimentIcon ? `${STATE_ICON_SIZE}px` : '0px'})`, ";&[data-success='true']{border-color:", ({
51
+ theme
52
+ }) => theme.colors.success.border, ";}&[data-error='true']{border-color:", ({
53
+ theme
54
+ }) => theme.colors.danger.border, ";}&[data-readOnly='true']{background:", ({
55
+ theme
56
+ }) => theme.colors.neutral.backgroundWeak, ";border-color:", ({
57
+ theme
58
+ }) => theme.colors.neutral.border, ";}&[data-disabled='true']{background:", ({
59
+ theme
60
+ }) => theme.colors.neutral.backgroundDisabled, ";border-color:", ({
61
+ theme
62
+ }) => theme.colors.neutral.borderDisabled, ";&::placeholder{color:", ({
63
+ theme
64
+ }) => theme.colors.neutral.textWeakDisabled, ";}}&:not([data-disabled='true']):hover{&:hover{border-color:", ({
65
+ theme
66
+ }) => theme.colors.primary.border, ";}&:focus{outline:none;border-color:", ({
67
+ theme
68
+ }) => theme.colors.primary.border, ";box-shadow:", ({
69
+ theme
70
+ }) => theme.shadows.focusPrimary, ";}}");
99
71
  /**
100
72
  * This component offers an extended textarea HTML
101
73
  */
@@ -126,12 +98,17 @@ const TextArea = /*#__PURE__*/forwardRef(({
126
98
  labelDescription
127
99
  }, ref) => {
128
100
  const localId = useId();
129
-
130
- // Avoid conflicts between properties
131
- const computedReadOnly = !disabled && readOnly;
132
- const computedSuccess = !disabled && !readOnly ? success : undefined;
133
- const computedError = !disabled && !readOnly && !success ? error : undefined;
134
- const computedHelper = !success && !error ? helper : undefined;
101
+ const sentiment = useMemo(() => {
102
+ if (error) {
103
+ return 'danger';
104
+ }
105
+ if (success) {
106
+ return 'success';
107
+ }
108
+ return 'neutral';
109
+ }, [error, success]);
110
+ const notice = success || error || helper;
111
+ const computedClearable = clearable && !!value;
135
112
  return jsxs(Stack, {
136
113
  gap: "0.5",
137
114
  className: className,
@@ -159,7 +136,7 @@ const TextArea = /*#__PURE__*/forwardRef(({
159
136
  text: tooltip,
160
137
  children: jsxs(StyledTextAreaWrapper, {
161
138
  children: [jsx(StyledTextArea, {
162
- "aria-invalid": !!computedError,
139
+ "aria-invalid": !!error,
163
140
  id: id ?? localId,
164
141
  tabIndex: tabIndex,
165
142
  autoFocus: autoFocus,
@@ -170,22 +147,24 @@ const TextArea = /*#__PURE__*/forwardRef(({
170
147
  onChange: event => {
171
148
  onChange(event.currentTarget.value);
172
149
  },
173
- isSuccess: !!computedSuccess,
174
- isError: !!computedError,
175
- isClearable: !!clearable,
150
+ hasSentimentIcon: !!success || !!error,
151
+ "data-disabled": disabled,
152
+ "data-readOnly": readOnly,
153
+ "data-success": !!success,
154
+ "data-error": !!error,
155
+ isClearable: !!computedClearable,
176
156
  minLength: minLength,
177
157
  maxLength: maxLength,
178
158
  placeholder: placeholder,
179
159
  "data-testid": dataTestId,
180
160
  name: name,
181
161
  onFocus: onFocus,
182
- onBlur: onBlur,
183
- readOnly: computedReadOnly
162
+ onBlur: onBlur
184
163
  }), jsxs(StyledTextAreaAbsoluteStack, {
185
164
  direction: "row",
186
165
  alignItems: "center",
187
166
  gap: "1",
188
- children: [clearable ? jsx(Button, {
167
+ children: [computedClearable ? jsx(Button, {
189
168
  "aria-label": "clear value",
190
169
  variant: "ghost",
191
170
  size: "xsmall",
@@ -204,28 +183,18 @@ const TextArea = /*#__PURE__*/forwardRef(({
204
183
  }) : null]
205
184
  })]
206
185
  })
207
- }), computedSuccess || computedError || computedHelper || maxLength ? jsxs(Row, {
186
+ }), notice || maxLength ? jsxs(Row, {
208
187
  templateColumns: "minmax(0, 1fr) min-content",
209
188
  gap: "1",
210
189
  children: [jsxs("div", {
211
- children: [computedSuccess ? jsx(Text, {
190
+ children: [error || success || typeof helper === 'string' ? jsx(Text, {
212
191
  as: "p",
213
192
  variant: "caption",
214
- sentiment: "success",
215
- children: computedSuccess
216
- }) : null, computedError ? jsx(Text, {
217
- as: "p",
218
- variant: "caption",
219
- sentiment: "danger",
220
- children: computedError
221
- }) : null, computedHelper && typeof computedHelper === 'string' ? jsx(Text, {
222
- as: "p",
223
- variant: "caption",
224
- sentiment: "neutral",
225
- prominence: "weak",
193
+ sentiment: sentiment,
194
+ prominence: !error && !success ? 'weak' : 'default',
226
195
  disabled: disabled,
227
- children: computedHelper
228
- }) : null, computedHelper && typeof computedHelper !== 'string' ? computedHelper : null]
196
+ children: error || success || helper
197
+ }) : null, !error && !success && typeof helper !== 'string' && helper ? helper : null]
229
198
  }), maxLength ? jsxs(Text, {
230
199
  as: "div",
231
200
  sentiment: "neutral",