@indico-data/design-system 1.0.52 → 1.0.53

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.
Files changed (58) hide show
  1. package/jest.config.js +1 -2
  2. package/lib/components/Icon/Icon.stories.d.ts +2 -2
  3. package/lib/components/Icon/indicons.d.ts +1 -0
  4. package/lib/components/Navigation/Drawer/constants.d.ts +3 -0
  5. package/lib/components/index.d.ts +1 -1
  6. package/lib/components/inputs/DatePicker/DatePicker.d.ts +15 -0
  7. package/lib/components/inputs/DatePicker/DatePicker.stories.d.ts +6 -0
  8. package/lib/components/inputs/DatePicker/DatePicker.styles.d.ts +2 -0
  9. package/lib/components/inputs/DatePicker/index.d.ts +1 -0
  10. package/lib/components/inputs/NoInputDatePicker/NoInputDatePicker.d.ts +19 -0
  11. package/lib/components/inputs/NoInputDatePicker/NoInputDatePicker.stories.d.ts +7 -0
  12. package/lib/components/inputs/NoInputDatePicker/NoInputDatePicker.styles.d.ts +2 -0
  13. package/lib/components/inputs/NoInputDatePicker/__tests__/NoInputDatePicker.test.d.ts +1 -0
  14. package/lib/components/inputs/NoInputDatePicker/index.d.ts +1 -0
  15. package/lib/components/inputs/index.d.ts +2 -0
  16. package/lib/components/text-truncate/TextTruncate.d.ts +1 -1
  17. package/lib/index.d.ts +61 -28
  18. package/lib/index.esm.js +21441 -13325
  19. package/lib/index.esm.js.map +1 -1
  20. package/lib/index.js +21462 -13328
  21. package/lib/index.js.map +1 -1
  22. package/lib/types.d.ts +1 -0
  23. package/lib/utils/color.d.ts +21 -0
  24. package/lib/utils/index.d.ts +4 -0
  25. package/lib/utils/number.d.ts +21 -0
  26. package/lib/utils/string.d.ts +12 -0
  27. package/package.json +10 -3
  28. package/src/components/Icon/indicons.tsx +6 -0
  29. package/src/components/ListTable/Header/Header.tsx +1 -1
  30. package/src/components/Pagination/Pagination.tsx +1 -1
  31. package/src/components/basic-section/SectionTable/SectionTable.styles.ts +1 -2
  32. package/src/components/basic-section/SectionTable/SectionTable.tsx +12 -10
  33. package/src/components/dropdowns/MultiCombobox/MultiCombobox.stories.tsx +1 -1
  34. package/src/components/dropdowns/MultiCombobox/MultiCombobox.tsx +1 -1
  35. package/src/components/dropdowns/SingleCombobox/SingleCombobox.tsx +1 -1
  36. package/src/components/index.ts +2 -0
  37. package/src/components/inputs/DatePicker/DatePicker.stories.tsx +30 -0
  38. package/src/components/inputs/DatePicker/DatePicker.styles.ts +437 -0
  39. package/src/components/inputs/DatePicker/DatePicker.tsx +165 -0
  40. package/src/components/inputs/DatePicker/index.ts +1 -0
  41. package/src/components/inputs/EditableInput/EditableInput.tsx +1 -1
  42. package/src/components/inputs/NoInputDatePicker/NoInputDatePicker.stories.tsx +52 -0
  43. package/src/components/inputs/NoInputDatePicker/NoInputDatePicker.styles.ts +449 -0
  44. package/src/components/inputs/NoInputDatePicker/NoInputDatePicker.tsx +204 -0
  45. package/src/components/inputs/NoInputDatePicker/__tests__/NoInputDatePicker.test.tsx +126 -0
  46. package/src/components/inputs/NoInputDatePicker/index.ts +1 -0
  47. package/src/components/inputs/TextInput/TextInput.stories.tsx +1 -1
  48. package/src/components/inputs/index.ts +2 -0
  49. package/src/components/modals/ModalBase/ModalBase.styles.tsx +1 -1
  50. package/src/components/text-truncate/TextTruncate.test.tsx +3 -4
  51. package/src/components/text-truncate/TextTruncate.tsx +3 -2
  52. package/src/index.ts +2 -0
  53. package/src/styles/globals/forms.ts +1 -9
  54. package/src/styles/globals/tables.ts +1 -5
  55. package/src/utils/color.ts +139 -0
  56. package/src/utils/index.ts +5 -0
  57. package/src/utils/number.ts +29 -0
  58. package/src/utils/string.ts +87 -0
@@ -0,0 +1,126 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { NoInputDatePicker } from '../NoInputDatePicker';
4
+ import { DateRange } from 'react-day-picker';
5
+ import userEvent from '@testing-library/user-event';
6
+
7
+ const today = new Date();
8
+ const mockOnChange = jest.fn();
9
+ let trigger: any;
10
+
11
+ describe('NoInputDatePicker', () => {
12
+ describe('single', () => {
13
+ beforeEach(() => {
14
+ render(
15
+ <NoInputDatePicker
16
+ id={'date-picker'}
17
+ label={'Pick a date'}
18
+ value={today}
19
+ isRangePicker={false}
20
+ triggerIcon="calendar"
21
+ triggerIconSize={[20]}
22
+ onChange={(date: Date | DateRange | undefined) => {
23
+ mockOnChange(date);
24
+ }}
25
+ />,
26
+ );
27
+
28
+ trigger = screen.getByTestId('datepicker-trigger-for-date-picker');
29
+ });
30
+
31
+ it('shows single date picker', async () => {
32
+ await userEvent.click(trigger);
33
+ expect(screen.getByTestId('datepicker-dialog')).toBeVisible();
34
+ expect(screen.getByTestId('single-datepicker')).toBeVisible();
35
+ });
36
+
37
+ it('opens the date picker when clicking on the trigger', async () => {
38
+ await userEvent.click(trigger);
39
+ const datePickerDialogContainer = screen.getByTestId('datepicker-dialog');
40
+ expect(datePickerDialogContainer).toBeVisible();
41
+ });
42
+
43
+ it('emits the right date picked for single', async () => {
44
+ await userEvent.click(trigger);
45
+ const datePickerDialogContainer = screen.getByTestId('datepicker-dialog');
46
+ expect(datePickerDialogContainer).toBeVisible();
47
+
48
+ const day28 = screen.getByText('28');
49
+ await userEvent.click(day28);
50
+ expect(mockOnChange).toHaveBeenCalledWith(
51
+ new Date(today.getFullYear(), today.getMonth(), 28),
52
+ );
53
+ });
54
+
55
+ it('closes date picker on single selection', async () => {
56
+ await userEvent.click(trigger);
57
+ const datePickerDialogContainer = screen.getByTestId('datepicker-dialog');
58
+ expect(datePickerDialogContainer).toBeVisible();
59
+
60
+ const day28 = screen.getByText('28');
61
+ await userEvent.click(day28);
62
+ expect(mockOnChange).toHaveBeenCalledWith(
63
+ new Date(today.getFullYear(), today.getMonth(), 28),
64
+ );
65
+ expect(datePickerDialogContainer).not.toBeVisible();
66
+ });
67
+ });
68
+
69
+ describe('range', () => {
70
+ beforeEach(() => {
71
+ render(
72
+ <NoInputDatePicker
73
+ id={'date-picker'}
74
+ label={'Pick a date'}
75
+ value={today}
76
+ isRangePicker={true}
77
+ triggerIcon="calendar"
78
+ triggerIconSize={[20]}
79
+ onChange={(date: Date | DateRange | undefined) => {
80
+ mockOnChange(date);
81
+ }}
82
+ />,
83
+ );
84
+
85
+ trigger = screen.getByTestId('datepicker-trigger-for-date-picker');
86
+ });
87
+
88
+ it('shows ranged date picker', async () => {
89
+ await userEvent.click(trigger);
90
+ expect(screen.getByTestId('datepicker-dialog')).toBeVisible();
91
+ expect(screen.getByTestId('range-datepicker')).toBeVisible();
92
+ });
93
+
94
+ it('emits the right date picked for range', async () => {
95
+ await userEvent.click(trigger);
96
+ const datePickerDialogContainer = screen.getByTestId('datepicker-dialog');
97
+ expect(datePickerDialogContainer).toBeVisible();
98
+
99
+ const day27 = screen.getByText('27');
100
+ await userEvent.click(day27);
101
+ const day28 = screen.getByText('28');
102
+ await userEvent.click(day28);
103
+
104
+ expect(mockOnChange).toHaveBeenCalledWith({
105
+ from: new Date(today.getFullYear(), today.getMonth(), 27),
106
+ to: new Date(today.getFullYear(), today.getMonth(), 28),
107
+ });
108
+ });
109
+ it('closes date picker on both ranged selection', async () => {
110
+ await userEvent.click(trigger);
111
+ const datePickerDialogContainer = screen.getByTestId('datepicker-dialog');
112
+ expect(datePickerDialogContainer).toBeVisible();
113
+
114
+ const day27 = screen.getByText('27');
115
+ await userEvent.click(day27);
116
+ const day28 = screen.getByText('28');
117
+ await userEvent.click(day28);
118
+
119
+ expect(mockOnChange).toHaveBeenCalledWith({
120
+ from: new Date(today.getFullYear(), today.getMonth(), 27),
121
+ to: new Date(today.getFullYear(), today.getMonth(), 28),
122
+ });
123
+ expect(datePickerDialogContainer).not.toBeVisible();
124
+ });
125
+ });
126
+ });
@@ -0,0 +1 @@
1
+ export { NoInputDatePicker } from './NoInputDatePicker';
@@ -10,7 +10,7 @@ const validationErrors = [
10
10
  'Another error that may not be so helpful',
11
11
  ];
12
12
 
13
- function StoryRender(props) {
13
+ function StoryRender(props: any) {
14
14
  const [value, setValue] = useState<string>(props.value);
15
15
 
16
16
  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
@@ -4,3 +4,5 @@ export { SearchInput } from './SearchInput';
4
4
  export { TextInput } from './TextInput';
5
5
  export { Radio, RadioGroup } from './RadioButtons';
6
6
  export { AbstractRadio, AbstractRadioGroup } from './RadioGroup';
7
+ export { DatePicker } from './DatePicker';
8
+ export { NoInputDatePicker } from './NoInputDatePicker';
@@ -3,7 +3,7 @@ import ReactModal from 'react-modal';
3
3
  import styled from 'styled-components';
4
4
 
5
5
  import { COLORS } from '@/tokens';
6
- import { colorUtils } from '@indico-data/utils';
6
+ import { colorUtils } from '@/utils';
7
7
 
8
8
  // see: https://github.com/reactjs/react-modal/issues/603
9
9
  function ModalAdapter(props: any): React.ReactElement {
@@ -6,13 +6,12 @@ describe('TextTruncate', () => {
6
6
  const longText = 'this is a really long string that should be truncated';
7
7
 
8
8
  it('truncates text when it exceeds the maximum length and adds a title attr', () => {
9
- const { getByTitle } = render(<TextTruncate string={longText} maxChars={20} />);
9
+ const { getByTitle } = render(<TextTruncate string={longText} maxChars={20}></TextTruncate>);
10
10
  expect(getByTitle(longText).textContent).toEqual('this is a really lon...');
11
11
  });
12
12
 
13
13
  it('does not truncate the text when it is below the max length', () => {
14
- const { getByText } = render(<TextTruncate string={longText} maxChars={200} />);
14
+ const { getByText } = render(<TextTruncate string={longText} maxChars={200}></TextTruncate>);
15
15
  expect(getByText(longText).textContent).toEqual(longText);
16
16
  });
17
-
18
- });
17
+ });
@@ -4,7 +4,7 @@ import { StyledTextTruncate } from './TextTruncate.styles';
4
4
  interface TextTruncateProps {
5
5
  string: string;
6
6
  maxChars: number;
7
- children: any;
7
+ children?: any;
8
8
  }
9
9
 
10
10
  export function TextTruncate({ string, maxChars, children }: TextTruncateProps) {
@@ -15,7 +15,8 @@ export function TextTruncate({ string, maxChars, children }: TextTruncateProps)
15
15
  </StyledTextTruncate>
16
16
  ) : (
17
17
  <StyledTextTruncate>
18
- {string} {children}
18
+ {string}
19
+ {children}
19
20
  </StyledTextTruncate>
20
21
  );
21
22
  }
package/src/index.ts CHANGED
@@ -26,6 +26,7 @@ export {
26
26
  CircleSpinner,
27
27
  ConfirmModal,
28
28
  Drawer,
29
+ DatePicker,
29
30
  EditableInput,
30
31
  Icon,
31
32
  IconButton,
@@ -34,6 +35,7 @@ export {
34
35
  LoadingList,
35
36
  ModalBase,
36
37
  MultiCombobox,
38
+ NoInputDatePicker,
37
39
  NumberInput,
38
40
  Pagination,
39
41
  PercentageRing,
@@ -1,7 +1,6 @@
1
1
  import { createGlobalStyle } from 'styled-components';
2
2
 
3
3
  import { COLORS, TYPOGRAPHY, ANIMATION } from '@/tokens';
4
- import { colorUtils } from '@indico-data/utils';
5
4
 
6
5
  const formBoxShadow = 'inset 0 0 0 rgba(#000, 0)';
7
6
  const formBoxShadowFocus = formBoxShadow;
@@ -59,10 +58,6 @@ export const Forms = createGlobalStyle`
59
58
  transition: border-color ${ANIMATION.duration} ${ANIMATION.timing};
60
59
  width: 100%;
61
60
 
62
- &:hover {
63
- // border-color: ${colorUtils.shade(COLORS.borderColor, 20)};
64
- }
65
-
66
61
  &:focus {
67
62
  border-color: ${COLORS.actionColor};
68
63
  box-shadow: ${formBoxShadowFocus};
@@ -70,7 +65,6 @@ export const Forms = createGlobalStyle`
70
65
  }
71
66
 
72
67
  &:disabled {
73
- // background-color: ${colorUtils.shade(COLORS.backgroundColor, 5)};
74
68
  cursor: not-allowed;
75
69
 
76
70
  &:hover {
@@ -78,9 +72,7 @@ export const Forms = createGlobalStyle`
78
72
  }
79
73
  }
80
74
 
81
- &::placeholder {
82
- // color: ${colorUtils.tint(COLORS.defaultFontColor, 40)};
83
- }
75
+
84
76
  }
85
77
 
86
78
  textarea {
@@ -1,8 +1,6 @@
1
1
  import { createGlobalStyle } from 'styled-components';
2
2
 
3
- import { colorUtils } from '@indico-data/utils';
4
-
5
- import { COLORS, TYPOGRAPHY } from '@/tokens';
3
+ import { TYPOGRAPHY } from '@/tokens';
6
4
 
7
5
  export const Tables = createGlobalStyle`
8
6
  table {
@@ -14,14 +12,12 @@ export const Tables = createGlobalStyle`
14
12
 
15
13
  th {
16
14
  font-size: ${TYPOGRAPHY.fontSize.subheadSmall};
17
- // border-bottom: 0.5px solid ${colorUtils.hexToRgb(COLORS.defaultFontColor, 0.1)};
18
15
  padding: 20px 15px 5px;
19
16
  font-weight: 400;
20
17
  text-align: left;
21
18
  }
22
19
 
23
20
  td {
24
- // border-bottom: 0.5px solid ${colorUtils.hexToRgb(COLORS.defaultFontColor, 0.1)};
25
21
  padding: 20px 15px;
26
22
  font-size: ${TYPOGRAPHY.fontSize.base};
27
23
  }
@@ -0,0 +1,139 @@
1
+ export const mix = function (color_1: string, color_2: string, weight: number): string {
2
+ function d2h(d: number) {
3
+ return d.toString(16);
4
+ } // convert a decimal value to hex
5
+ function h2d(h: string) {
6
+ return parseInt(h, 16);
7
+ } // convert a hex value to decimal
8
+ function s2l(hex: string) {
9
+ return `${hex}${hex}`;
10
+ }
11
+
12
+ weight = typeof weight !== 'undefined' ? weight : 50; // set the weight to 50%, if that argument is omitted
13
+ color_1 = color_1.replace(/#/g, '');
14
+ color_2 = color_2.replace(/#/g, '');
15
+
16
+ // check if colors are shorthand or longhand - covert to longhand
17
+ color_1 = color_1.length === 3 ? s2l(color_1) : color_1;
18
+ color_2 = color_2.length === 3 ? s2l(color_2) : color_2;
19
+
20
+ let color = '#';
21
+
22
+ for (let i = 0; i <= 5; i += 2) {
23
+ // loop through each of the 3 hex pairs—red, green, and blue
24
+ const v1 = h2d(color_1.substr(i, 2)); // extract the current pairs
25
+ const v2 = h2d(color_2.substr(i, 2));
26
+ // combine the current pairs from each source color, according to the specified weight
27
+ let val = d2h(Math.floor(v2 + (v1 - v2) * (weight / 100.0)));
28
+
29
+ while (val.length < 2) {
30
+ val = '0' + val;
31
+ } // prepend a '0' if val results in a single digit
32
+
33
+ color += val; // concatenate val to our new color string
34
+ }
35
+
36
+ return color; // PROFIT!
37
+ };
38
+
39
+ export const shade = (color: string, percentage: number): string => {
40
+ return mix('#000000', color, percentage);
41
+ };
42
+
43
+ export const tint = (color: string, percentage: number): string => {
44
+ return mix('#ffffff', color, percentage);
45
+ };
46
+
47
+ /**
48
+ * Converts hex color values to rgb or rgba values if a opacity is supplied
49
+ * @param hex
50
+ * @returns {*}
51
+ */
52
+ export const hexToRgb = (hex: string, opacity?: number): string | undefined => {
53
+ // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
54
+ const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
55
+ hex = hex.replace(shorthandRegex, function (m, r, g, b) {
56
+ return r + r + g + g + b + b;
57
+ });
58
+
59
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
60
+ if (result && opacity) {
61
+ return `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(
62
+ result[3],
63
+ 16,
64
+ )}, ${opacity})`;
65
+ } else if (result) {
66
+ return `rgb(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(
67
+ result[3],
68
+ 16,
69
+ )})`;
70
+ }
71
+ return undefined;
72
+ };
73
+
74
+ /**
75
+ * Takes a 3- or 6-character hex color value, and returns an object containing
76
+ * its equivalent HSL values.
77
+ *
78
+ * @see {@link https://css-tricks.com/converting-color-spaces-in-javascript/}
79
+ * @see {@link https://gist.github.com/mjackson/5311256}
80
+ */
81
+ export function hexToHsl(hexColor: string): { hue: number; saturation: number; lightness: number } {
82
+ // convert to 6-character hex string, if necessary
83
+ const fullHexColor = (() => {
84
+ if (hexColor.length === 4) {
85
+ return (
86
+ '#' +
87
+ hexColor
88
+ .replace('#', '')
89
+ .split('')
90
+ .reduce((acc, char) => (acc += char + char), '')
91
+ );
92
+ }
93
+
94
+ return hexColor;
95
+ })();
96
+
97
+ const rgbValues = {
98
+ red: parseInt(fullHexColor.slice(1, 3), 16) / 255,
99
+ green: parseInt(fullHexColor.slice(3, 5), 16) / 255,
100
+ blue: parseInt(fullHexColor.slice(-2), 16) / 255,
101
+ };
102
+
103
+ const allValues = Object.values(rgbValues);
104
+
105
+ const channelMax = Math.max(...allValues);
106
+ const channelMin = Math.min(...allValues);
107
+ const channelDelta = channelMax - channelMin;
108
+
109
+ const hslValues = {
110
+ hue: 0,
111
+ saturation: 0,
112
+ lightness: (channelMax + channelMin) / 2,
113
+ };
114
+
115
+ if (channelDelta !== 0) {
116
+ // calculate hue
117
+ if (channelMax === rgbValues.red) {
118
+ hslValues.hue = ((rgbValues.green - rgbValues.blue) / channelDelta) % 6;
119
+ } else if (channelMax === rgbValues.green) {
120
+ hslValues.hue = (rgbValues.blue - rgbValues.red) / channelDelta + 2;
121
+ } else {
122
+ hslValues.hue = (rgbValues.red - rgbValues.green) / channelDelta + 4;
123
+ }
124
+
125
+ // calculate saturation
126
+ hslValues.saturation = channelDelta / (1 - Math.abs(2 * hslValues.lightness - 1));
127
+ }
128
+
129
+ hslValues.hue = Math.round(hslValues.hue * 60);
130
+ hslValues.saturation = +(hslValues.saturation * 100).toFixed(0);
131
+ hslValues.lightness = +(hslValues.lightness * 100).toFixed(0);
132
+
133
+ // make negative hues positive
134
+ if (hslValues.hue < 0) {
135
+ hslValues.hue += 360;
136
+ }
137
+
138
+ return hslValues;
139
+ }
@@ -0,0 +1,5 @@
1
+ import * as colorUtils from './color';
2
+ import * as stringUtils from './string';
3
+ import * as numberUtils from './number';
4
+
5
+ export { colorUtils, stringUtils, numberUtils };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Takes a number and formats it nicely.
3
+ *
4
+ * @example numberWithCommas(12345)
5
+ * // returns 12,345
6
+ */
7
+ export function numberWithCommas(value: number): string {
8
+ return new Intl.NumberFormat(navigator.language || 'en-US').format(value);
9
+ }
10
+
11
+ /**
12
+ * Takes a number and returns it, rounded up to the maximum decimal places specified.
13
+ *
14
+ * @example maxDecimalPlaces(0.1694915254237288, 2)
15
+ * // returns 0.17
16
+ *
17
+ * @example maxDecimalPlaces(0.1694915254237288, 3)
18
+ * // returns 0.169
19
+ *
20
+ * @example maxDecimalPlaces(12.902, 2)
21
+ * // returns 12.9
22
+ */
23
+ export function maxDecimalPlaces(value: number, decimalPlaces: number): number {
24
+ return +value.toFixed(decimalPlaces);
25
+ }
26
+
27
+ export function formatConfidence(confidence: number): number {
28
+ return Math.min(Math.floor(confidence * 100), 99);
29
+ }
@@ -0,0 +1,87 @@
1
+ export const camelCaseToUpperUnderscore = (string: string) => {
2
+ return string
3
+ .replace(/([A-Z])/g, function ($1) {
4
+ return '_' + $1;
5
+ })
6
+ .toUpperCase();
7
+ };
8
+
9
+ export const camelCaseToSpaceUpper = (string: string) => {
10
+ return (
11
+ string
12
+ // insert a space before all caps
13
+ .replace(/([A-Z])/g, ' $1')
14
+ // uppercase the first character
15
+ .replace(/^./, function (str) {
16
+ return str.toUpperCase();
17
+ })
18
+ );
19
+ };
20
+
21
+ export const underscoreToCapitalize = (string: string) => {
22
+ return (
23
+ string
24
+ // replace all _ with spaces
25
+ .replace(/_/g, ' ')
26
+ // uppercase the first character
27
+ .replace(/^./, function (str) {
28
+ return str.toUpperCase();
29
+ })
30
+ );
31
+ };
32
+
33
+ export const snakeCaseToCamelCase = (string: string) => {
34
+ const parts = string.split('_').map((part, i) => {
35
+ if (i > 0) {
36
+ return part.replace(/^./, function (str) {
37
+ return str.toUpperCase();
38
+ });
39
+ } else {
40
+ return part;
41
+ }
42
+ });
43
+
44
+ return parts.join().replace(/,/g, '');
45
+ };
46
+
47
+ export function capitalize(string: string): string {
48
+ if (typeof string !== 'string') return '';
49
+
50
+ return string.slice(0, 1).toUpperCase() + string.slice(1).toLowerCase();
51
+ }
52
+
53
+ export function capitalizeFirstOnly(string: string): string {
54
+ if (typeof string !== 'string') return '';
55
+
56
+ return string.slice(0, 1).toUpperCase() + string.slice(1);
57
+ }
58
+
59
+ /**
60
+ * Generates a random string of English alphabet characters. Defaults to a length of 8.
61
+ */
62
+ export function createRandomString(length?: number): string {
63
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
64
+ let result = '';
65
+
66
+ for (let i = 0; i < (length || 8); i++) {
67
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
68
+ }
69
+
70
+ return result;
71
+ }
72
+
73
+ export const maxLengthWithEllipse = (string: string, maxLength: number) => {
74
+ if (string.length <= maxLength) {
75
+ return string;
76
+ } else {
77
+ return string.slice(0, maxLength - 2).concat('...');
78
+ }
79
+ };
80
+
81
+ export const maxLengthWithMiddleEllipsis = (string: string, maxLength: number): string => {
82
+ const frontSegment = string.slice(0, Math.floor(maxLength / 2) - 2);
83
+ const endSegmentLength = string.length - (Math.floor(maxLength / 2) - 1);
84
+ const endSegment = string.slice(endSegmentLength, string.length);
85
+
86
+ return string.length <= maxLength ? string : `${frontSegment}...${endSegment}`;
87
+ };