@xqmsg/ui-core 0.24.11 → 0.25.1

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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.24.11",
2
+ "version": "0.25.1",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -3,6 +3,7 @@ import {
3
3
  ReadonlyTableColumns,
4
4
  TableBody,
5
5
  TableColumns,
6
+ TableColumnsWidths,
6
7
  TableHeaders,
7
8
  } from './TableTypes';
8
9
  import { generateTableColumnsAsConst } from './utils/generateTableColumns';
@@ -27,6 +28,8 @@ export interface SimpleTableProps<T extends ReadonlyTableColumns> {
27
28
  loading?: boolean;
28
29
  loadMore?: () => void;
29
30
  placeholder?: string;
31
+ layout?: 'fixed' | 'auto';
32
+ columnsWidths?: TableColumnsWidths<T>;
30
33
  }
31
34
 
32
35
  /**
@@ -38,6 +41,8 @@ export function SimpleTable<T extends ReadonlyTableColumns>({
38
41
  body,
39
42
  loading,
40
43
  loadMore,
44
+ layout = 'auto',
45
+ columnsWidths,
41
46
  }: SimpleTableProps<T>) {
42
47
  const columnsAsConst = generateTableColumnsAsConst(columns);
43
48
 
@@ -50,14 +55,16 @@ export function SimpleTable<T extends ReadonlyTableColumns>({
50
55
  style={{
51
56
  borderCollapse: 'separate',
52
57
  borderSpacing: '0px',
58
+ tableLayout: layout,
53
59
  }}
54
60
  >
55
- {headers && (
61
+ {(headers || columnsWidths) && (
56
62
  <Thead>
57
63
  <Tr _odd={{ bg: colors.label.primary.dark }}>
58
64
  {columnsAsConst.map((column, idx) => (
59
- // @ts-ignore
60
- <Th key={idx}>{headers[column]}</Th>
65
+ <Th key={idx} width={columnsWidths?.[column as T[number]]}>
66
+ {headers && headers[column as T[number]]}
67
+ </Th>
61
68
  ))}
62
69
  </Tr>
63
70
  </Thead>
@@ -8,6 +8,10 @@ export type TableHeaders<K extends ReadonlyTableColumns> = {
8
8
  [k in K[number]]: ReactNode;
9
9
  };
10
10
 
11
+ export type TableColumnsWidths<K extends ReadonlyTableColumns> = {
12
+ [k in K[number]]: string | number;
13
+ };
14
+
11
15
  export type TableRow<K extends ReadonlyTableColumns> = {
12
16
  [k in K[number]]: ReactNode;
13
17
  };
@@ -16,6 +16,7 @@ export const Close: React.FC<CloseProps> = ({ boxSize, onClick }) => {
16
16
  height={boxSize}
17
17
  onClick={onClick}
18
18
  cursor="pointer"
19
+ style={{ flexBasis: boxSize, cursor: 'pointer' }}
19
20
  />
20
21
  );
21
22
  };
@@ -112,6 +112,21 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
112
112
 
113
113
  return (
114
114
  <Form formHandler={formHandler}>
115
+ <Input
116
+ label="To"
117
+ name="recipients"
118
+ inputType="pilled-text"
119
+ placeholder="Enter email address..."
120
+ isInvalid={!!form.formState.errors['prop5']?.message}
121
+ errorText={form.formState.errors['prop5']?.message}
122
+ control={form.control}
123
+ setValue={form.setValue}
124
+ setError={form.setError}
125
+ clearErrors={form.clearErrors}
126
+ ariaLabel="email input"
127
+ isRequired
128
+ truncatePillLength={1000}
129
+ />
115
130
  <Input
116
131
  {...args}
117
132
  inputType="multi-select"
@@ -21,6 +21,7 @@ export interface StackedPilledInputProps extends InputFieldProps {
21
21
  variant?: string;
22
22
  label?: string;
23
23
  truncatePillLength?: number;
24
+ mode?: 'scroll' | 'wrap';
24
25
  }
25
26
 
26
27
  /**
@@ -41,6 +42,7 @@ const StackedPilledInput = React.forwardRef<
41
42
  variant,
42
43
  label,
43
44
  truncatePillLength,
45
+ mode = 'scroll',
44
46
  },
45
47
  _ref
46
48
  ) => {
@@ -60,7 +62,7 @@ const StackedPilledInput = React.forwardRef<
60
62
  const latestTokenElement = document.getElementById(
61
63
  `${name}_token_${lastestFormValueToArray.length - 1}`
62
64
  );
63
-
65
+ const scrollMode = mode === 'scroll';
64
66
  // gets latest watched form value (common delimited) from RHF state and creates a list
65
67
  useEffect(() => {
66
68
  if (watchedValue !== undefined && !watchedValue.length) {
@@ -148,19 +150,58 @@ const StackedPilledInput = React.forwardRef<
148
150
  });
149
151
  }
150
152
 
151
- if (!localValue.trim().length && lastestFormValueToArray.length) {
152
- if (e.key === 'Backspace' && tokenIndex !== null) {
153
- setLocalValue(
154
- lastestFormValueToArray[tokenIndex].substring(
155
- 0,
156
- lastestFormValueToArray[tokenIndex].length
157
- )
158
- );
153
+ if (e.key === 'Backspace') {
154
+ // If input is empty and there are tokens
155
+ if (!localValue.length && lastestFormValueToArray.length) {
156
+ // If a token is selected, move it to input
157
+ if (tokenIndex !== null) {
158
+ setLocalValue(
159
+ lastestFormValueToArray[tokenIndex].substring(
160
+ 0,
161
+ lastestFormValueToArray[tokenIndex].length
162
+ )
163
+ );
164
+
165
+ const filteredUniqueValues = Array.from(
166
+ new Set(
167
+ [...lastestFormValueToArray].filter(
168
+ value => value !== lastestFormValueToArray[tokenIndex]
169
+ )
170
+ )
171
+ );
172
+
173
+ setValue(name as string, filteredUniqueValues.toString(), {
174
+ shouldValidate: true,
175
+ shouldDirty: true,
176
+ });
177
+
178
+ return setTokenIndex(null);
179
+ } else {
180
+ // No token selected, move last token to input
181
+ const lastToken =
182
+ lastestFormValueToArray[lastestFormValueToArray.length - 1];
183
+ setLocalValue(lastToken);
159
184
 
185
+ const filteredUniqueValues = lastestFormValueToArray.slice(0, -1);
186
+
187
+ setValue(name as string, filteredUniqueValues.toString(), {
188
+ shouldValidate: true,
189
+ shouldDirty: true,
190
+ });
191
+
192
+ e.preventDefault();
193
+ return;
194
+ }
195
+ }
196
+ }
197
+
198
+ if (e.key === 'Delete') {
199
+ // If a token is selected, remove it completely
200
+ if (tokenIndex !== null && lastestFormValueToArray.length) {
160
201
  const filteredUniqueValues = Array.from(
161
202
  new Set(
162
203
  [...lastestFormValueToArray].filter(
163
- value => value !== lastestFormValueToArray[tokenIndex]
204
+ (_, index) => index !== tokenIndex
164
205
  )
165
206
  )
166
207
  );
@@ -170,9 +211,13 @@ const StackedPilledInput = React.forwardRef<
170
211
  shouldDirty: true,
171
212
  });
172
213
 
173
- return setTokenIndex(null);
214
+ setTokenIndex(null);
215
+ e.preventDefault();
216
+ return;
174
217
  }
218
+ }
175
219
 
220
+ if (!localValue.trim().length && lastestFormValueToArray.length) {
176
221
  if (e.key === 'ArrowLeft') {
177
222
  if (tokenIndex === 0) return;
178
223
 
@@ -290,12 +335,14 @@ const StackedPilledInput = React.forwardRef<
290
335
  bg={disabled ? colors.fill.light.quaternary : '#ffffff'}
291
336
  cursor={disabled ? 'not-allowed' : 'pointer'}
292
337
  ref={inputWrapperRef}
293
- h={isMobile ? '48px' : '26px'}
338
+ h={isMobile ? '48px' : scrollMode ? '26px' : 'auto'}
339
+ minH={isMobile ? '48px' : '26px'}
294
340
  >
295
341
  <Flex
296
342
  h="100%"
297
343
  alignItems="center"
298
- overflowX="scroll"
344
+ overflowX={scrollMode ? 'scroll' : 'hidden'}
345
+ flexWrap={scrollMode ? 'nowrap' : 'wrap'}
299
346
  overflowY="hidden"
300
347
  maxWidth={isFocussed ? '80%' : '100%'}
301
348
  style={{
@@ -308,26 +355,37 @@ const StackedPilledInput = React.forwardRef<
308
355
  },
309
356
  }}
310
357
  ref={scrollRef}
311
- zIndex={99}
312
358
  onKeyDown={onHandleKeyDown}
313
359
  >
314
360
  {lastestFormValueToArray.length
315
361
  ? lastestFormValueToArray.map((label, index) => (
316
362
  <Box
317
363
  key={index}
318
- mr="4px"
364
+ mr="2px"
319
365
  border={
320
366
  tokenIndex === index
321
367
  ? `1px solid ${colors.border.focus}`
322
368
  : 'none'
323
369
  }
324
- borderRadius="full"
325
- onClick={() => setTokenIndex(index)}
326
- width="100%"
370
+ borderRadius="md"
371
+ onClick={e => {
372
+ e.stopPropagation();
373
+ // Don't change selection if clicking on already selected token
374
+ if (tokenIndex === index) {
375
+ return;
376
+ }
377
+ setTokenIndex(index);
378
+ }}
379
+ width={scrollMode ? '100%' : 'auto'}
380
+ maxWidth={'100%'}
327
381
  id={`${name}_token_${index}`}
382
+ cursor="default"
328
383
  >
329
384
  <Token
385
+ maxWidth={'100%'}
330
386
  label={label}
387
+ showClose={true}
388
+ isSelected={tokenIndex === index}
331
389
  onDelete={(e: any) => {
332
390
  e.stopPropagation();
333
391
  e.preventDefault();
@@ -343,6 +401,7 @@ const StackedPilledInput = React.forwardRef<
343
401
  <Text
344
402
  color={colors.label.secondary.light}
345
403
  fontSize={isMobile ? '17px' : '13px'}
404
+ pointerEvents="none"
346
405
  >
347
406
  {placeholder}
348
407
  </Text>
@@ -366,10 +425,6 @@ const StackedPilledInput = React.forwardRef<
366
425
  onChange={handleOnChange}
367
426
  ref={inputRef}
368
427
  onFocus={() => setIsFocussed(true)}
369
- onBlur={() => {
370
- setIsFocussed(false);
371
- return setTokenIndex(null);
372
- }}
373
428
  placeholder={
374
429
  isMobile && label && lastestFormValueToArray.length === 0
375
430
  ? (label as string)
@@ -9,6 +9,9 @@ export interface TokenProps {
9
9
  onDelete: any;
10
10
  isMobile?: boolean;
11
11
  truncateLength?: number;
12
+ showClose?: boolean;
13
+ maxWidth?: string | number;
14
+ isSelected?: boolean;
12
15
  }
13
16
 
14
17
  // For v1 we are truncating the label at 15 characters to avoid overflow
@@ -17,33 +20,60 @@ const Token: React.FC<TokenProps> = ({
17
20
  onDelete,
18
21
  isMobile = false,
19
22
  truncateLength = 15,
23
+ showClose = true,
24
+ maxWidth = 'auto',
25
+ isSelected = false,
20
26
  }) => {
27
+ // in pixels
28
+ const closeBoxSize = isMobile ? 20 : 15;
29
+ const closeBoxGap = isMobile ? 4 : 2;
30
+ const closeBoxPadding = closeBoxSize + closeBoxGap;
21
31
  return (
22
32
  <Flex
23
33
  key={label}
24
- borderRadius={'full'}
34
+ borderRadius={'md'}
25
35
  backgroundColor="#7676801F"
26
36
  alignItems="center"
27
37
  width="fit-content"
28
38
  w="auto"
29
- h={isMobile ? '18px' : '16px'}
30
- pl="6px"
31
- pr="2px"
39
+ maxWidth={maxWidth}
40
+ h={'18px'}
41
+ px="4px"
32
42
  py="2px"
43
+ pr={showClose ? closeBoxPadding : '4px'}
33
44
  position="relative"
45
+ cursor="default"
34
46
  >
35
47
  <Text
36
48
  whiteSpace="nowrap"
49
+ wordBreak="break-word"
37
50
  color={colors.label.primary.light}
38
51
  fontSize={isMobile ? '17px' : '13px'}
39
- pr="4px"
52
+ lineHeight={isMobile ? '17px' : '13px'}
53
+ pr="2px"
54
+ maxWidth={maxWidth}
55
+ overflow="hidden"
56
+ textOverflow="ellipsis"
40
57
  >
41
58
  {truncate(label.trim(), {
42
59
  length: truncateLength,
43
60
  omission: '...',
44
61
  })}
45
62
  </Text>
46
- <Close boxSize={isMobile ? '17px' : '11px'} onClick={onDelete} />
63
+ {showClose && (
64
+ <Flex
65
+ height="100%"
66
+ position="absolute"
67
+ top={0}
68
+ bottom={0}
69
+ right={0}
70
+ justifyContent="center"
71
+ alignItems="center"
72
+ cursor={isSelected ? 'default' : 'pointer'}
73
+ >
74
+ <Close boxSize={`${closeBoxSize}px`} onClick={onDelete} />
75
+ </Flex>
76
+ )}
47
77
  </Flex>
48
78
  );
49
79
  };
@@ -53,6 +53,7 @@ export interface InputProps<T extends FieldValues = FieldValues>
53
53
  loadingOptions?: boolean;
54
54
  truncatePillLength?: number;
55
55
  searchable?: boolean;
56
+ overflowMode?: 'scroll' | 'wrap';
56
57
  }
57
58
 
58
59
  /**
@@ -88,6 +89,7 @@ export function Input<T extends FieldValues>({
88
89
  loadingOptions = false,
89
90
  truncatePillLength,
90
91
  searchable,
92
+ overflowMode = 'scroll',
91
93
  }: InputProps<T>) {
92
94
  function selectedInputField<T extends Element = Element>(
93
95
  onChange: ((e: ChangeEvent<T>) => void) | ((v?: string) => void),
@@ -238,6 +240,7 @@ export function Input<T extends FieldValues>({
238
240
  label={label}
239
241
  separators={separators}
240
242
  truncatePillLength={truncatePillLength}
243
+ mode={overflowMode}
241
244
  />
242
245
  );
243
246
  case 'switch':