@cyber-harbour/ui 1.0.43 → 1.0.45

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-harbour/ui",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -1,9 +1,9 @@
1
- import { InputSize, InputSizeStyle, InputVariant, getInputStyles } from '../../Theme';
2
- import { forwardRef, InputHTMLAttributes, Ref } from 'react';
3
- import { styled } from 'styled-components';
1
+ import { InputSize, InputVariant, remToPx } from '../../Theme';
2
+ import { forwardRef, InputHTMLAttributes, Ref, TextareaHTMLAttributes, useEffect, useRef, useState } from 'react';
3
+ import { styled, useTheme } from 'styled-components';
4
4
  import { InfoCircleFilledIcon } from '../IconComponents';
5
5
 
6
- export type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> & {
6
+ type BaseInputProps = {
7
7
  error?: boolean;
8
8
  append?: any;
9
9
  prepend?: any;
@@ -11,15 +11,49 @@ export type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> & {
11
11
  variant?: InputVariant;
12
12
  };
13
13
 
14
- export const Input: any = forwardRef<HTMLInputElement, InputProps>(function Input(
15
- { error, append, prepend, size = 'small', variant = 'outlined', disabled, className, ...props },
14
+ export type InputElementProps = BaseInputProps &
15
+ InputHTMLAttributes<HTMLInputElement> & {
16
+ multiline?: false;
17
+ };
18
+ export type TextAreaElementProps = BaseInputProps &
19
+ TextareaHTMLAttributes<HTMLTextAreaElement> & {
20
+ multiline: true;
21
+ autoResize?: boolean;
22
+ };
23
+ export type InputProps = InputElementProps | TextAreaElementProps;
24
+
25
+ type TextAreaInputProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
26
+ size?: InputSize;
27
+ autoResize?: boolean;
28
+ };
29
+
30
+ type InputRefElement<T> = T extends true ? HTMLTextAreaElement : HTMLInputElement | null;
31
+
32
+ export const Input: any = forwardRef<InputRefElement<InputProps['multiline']>, InputProps>(function Input(
33
+ { error, append, prepend, size = 'small', variant = 'outlined', multiline, disabled, className, ...props },
16
34
  ref
17
35
  ) {
18
36
  return (
19
- <Group className={className} $error={error} $size={size} $variant={variant} $disabled={!!disabled}>
37
+ <Group
38
+ $align={multiline ? 'flex-start' : 'center'}
39
+ className={className}
40
+ $error={error}
41
+ $size={size}
42
+ $variant={variant}
43
+ $disabled={!!disabled}
44
+ >
20
45
  {!!prepend && prepend}
21
46
  <InputGroup $size={size} $variant={variant}>
22
- <input ref={ref} disabled={disabled} {...props} />
47
+ {multiline ? (
48
+ <TextAreaInput
49
+ size={size}
50
+ disabled={disabled}
51
+ {...(props as TextAreaElementProps)}
52
+ ref={ref as Ref<InputRefElement<true>>}
53
+ />
54
+ ) : (
55
+ <input disabled={disabled} {...(props as InputElementProps)} ref={ref as Ref<InputRefElement<false>>} />
56
+ )}
23
57
  {!!error && (
24
58
  <IconWrapper $variant={variant}>
25
59
  <InfoCircleFilledIcon />
@@ -31,6 +65,52 @@ export const Input: any = forwardRef<HTMLInputElement, InputProps>(function Inpu
31
65
  );
32
66
  });
33
67
 
68
+ const TextAreaInput = forwardRef<HTMLTextAreaElement, TextAreaInputProps>(function Input(
69
+ { size = 'small', disabled, className, rows = '1', autoResize = false, ...props },
70
+ ref
71
+ ) {
72
+ const [areaSize, setAreaSize] = useState(Number(rows));
73
+ const rowsRef = useRef(Number(rows));
74
+ const divRef = useRef<HTMLDivElement>(null);
75
+ const theme = useTheme();
76
+ const rowHeight = useRef(remToPx(theme.input.sizes[size].lineHeight, theme.baseSize) || theme.baseSize);
77
+
78
+ useEffect(() => {
79
+ if (divRef.current && autoResize) {
80
+ const height = divRef.current.getBoundingClientRect().height;
81
+ const areaSize = Math.round(height / rowHeight.current);
82
+ if (rowsRef.current !== areaSize) {
83
+ rowsRef.current = areaSize;
84
+ setAreaSize(areaSize);
85
+ }
86
+ }
87
+ }, [props.value]);
88
+
89
+ return (
90
+ <div style={{ position: 'relative', width: '100%' }}>
91
+ <textarea disabled={disabled} {...props} rows={areaSize} ref={ref} />
92
+ <div
93
+ ref={divRef}
94
+ style={{
95
+ position: 'absolute',
96
+ top: 0,
97
+ left: 0,
98
+ right: 0,
99
+ height: 'auto',
100
+ opacity: 0,
101
+ zIndex: -1,
102
+ pointerEvents: 'none',
103
+ minHeight: rowHeight.current * Number(rows),
104
+ wordBreak: 'break-word',
105
+ whiteSpace: 'pre-wrap',
106
+ }}
107
+ >
108
+ {props.value}
109
+ </div>
110
+ </div>
111
+ );
112
+ });
113
+
34
114
  const InputGroup = styled.div<{ $size: InputSize; $variant?: InputVariant }>(
35
115
  ({ theme, $size, $variant = 'outlined' }) => `
36
116
  display: inline-flex;
@@ -43,7 +123,7 @@ const InputGroup = styled.div<{ $size: InputSize; $variant?: InputVariant }>(
43
123
  height: ${theme.input.sizes[$size].iconSize};
44
124
  }
45
125
 
46
- & input {
126
+ & input, & textarea, & ${EditableContainer} {
47
127
  font-size: ${theme.input.sizes[$size].fontSize};
48
128
  color: inherit;
49
129
  background: transparent;
@@ -63,6 +143,16 @@ const InputGroup = styled.div<{ $size: InputSize; $variant?: InputVariant }>(
63
143
  cursor: not-allowed;
64
144
  color: inherit;
65
145
  }
146
+
147
+ &:focus, &:focus-visible, &:focus:focus-visible {
148
+ outline: none;
149
+ }
150
+ }
151
+
152
+ & textarea {
153
+ resize: none;
154
+ margin: 0;
155
+ display: block;
66
156
  }
67
157
  `
68
158
  );
@@ -74,14 +164,20 @@ const IconWrapper = styled.span<{ $variant: InputVariant }>(
74
164
  align-items: center;
75
165
  color: ${theme.input[$variant].error.icon};
76
166
  margin-right: 10px;
77
-
167
+
78
168
  `
79
169
  );
80
170
 
81
- const Group = styled.div<{ $disabled: boolean; $error?: boolean; $size: InputSize; $variant: InputVariant }>(
82
- ({ theme, $disabled, $error, $size, $variant }) => `
171
+ const Group = styled.div<{
172
+ $align: 'flex-start' | 'center';
173
+ $disabled: boolean;
174
+ $error?: boolean;
175
+ $size: InputSize;
176
+ $variant: InputVariant;
177
+ }>(
178
+ ({ theme, $align = 'center', $disabled, $error, $size, $variant }) => `
83
179
  display: inline-flex;
84
- align-items: center;
180
+ align-items: ${$align};
85
181
  width: 100%;
86
182
  border: 1px solid;
87
183
  border-radius: ${theme.input.sizes[$size].borderRadius};
@@ -110,7 +206,7 @@ const Group = styled.div<{ $disabled: boolean; $error?: boolean; $size: InputSiz
110
206
  &:hover {
111
207
  border-color: ${theme.input[$variant].focus.border};
112
208
  }
113
-
209
+
114
210
  &:focus-within {
115
211
  border-color: ${theme.input[$variant].focus.border};
116
212
  color: ${theme.input[$variant].focus.text};
@@ -130,3 +226,27 @@ const Group = styled.div<{ $disabled: boolean; $error?: boolean; $size: InputSiz
130
226
  }
131
227
  `
132
228
  );
229
+
230
+ const EditableContainer = styled.div<{ $placeholder?: string }>(
231
+ ({ $placeholder, theme }) => `
232
+ outline-style: none;
233
+ outline-width: 0px;
234
+ position: relative;
235
+
236
+ ${
237
+ $placeholder
238
+ ? `
239
+ &:after {
240
+ content: '${$placeholder}';
241
+ position: absolute;
242
+ top: 0;
243
+ left: 0;
244
+ color: ${theme.input.outlined.default.placeholder};
245
+ }
246
+ `
247
+ : ''
248
+ }
249
+
250
+
251
+ `
252
+ );
@@ -488,7 +488,7 @@ export const darkThemePx: Theme = {
488
488
  boxShadow: 'none',
489
489
  },
490
490
  disabled: {
491
- background: '#FAFAFA',
491
+ background: '#080A0C',
492
492
  text: '#99989C',
493
493
  border: ' #1E2226',
494
494
  boxShadow: 'none',
@@ -529,7 +529,7 @@ export const darkThemePx: Theme = {
529
529
  boxShadow: 'none',
530
530
  },
531
531
  disabled: {
532
- background: '#FAFAFA',
532
+ background: '#080A0C',
533
533
  text: '#99989C',
534
534
  border: ' none',
535
535
  boxShadow: 'none',
@@ -558,7 +558,7 @@ export const darkThemePx: Theme = {
558
558
  boxShadow: 'none',
559
559
  },
560
560
  disabled: {
561
- background: '#FAFAFA',
561
+ background: '#080A0C',
562
562
  text: '#99989C',
563
563
  border: ' none',
564
564
  boxShadow: 'none',
@@ -579,6 +579,7 @@ export const darkThemePx: Theme = {
579
579
  borderRadius: 0,
580
580
  iconSize: 14,
581
581
  height: 'auto',
582
+ lineHeight: 16,
582
583
  },
583
584
  small: {
584
585
  fontSize: 14,
@@ -587,6 +588,7 @@ export const darkThemePx: Theme = {
587
588
  borderRadius: 5,
588
589
  iconSize: 14,
589
590
  height: 40,
591
+ lineHeight: 16,
590
592
  },
591
593
  medium: {
592
594
  fontSize: 16,
@@ -595,6 +597,7 @@ export const darkThemePx: Theme = {
595
597
  borderRadius: 5,
596
598
  iconSize: 16,
597
599
  height: 40,
600
+ lineHeight: 18,
598
601
  },
599
602
  },
600
603
  outlined: {
@@ -623,7 +626,7 @@ export const darkThemePx: Theme = {
623
626
  icon: '#C93939',
624
627
  },
625
628
  disabled: {
626
- background: '#FAFAFA',
629
+ background: '#080A0C',
627
630
  text: '#99989C',
628
631
  placeholder: '#99989C',
629
632
  border: ' #1E2226',
@@ -657,7 +660,7 @@ export const darkThemePx: Theme = {
657
660
  icon: '#C93939',
658
661
  },
659
662
  disabled: {
660
- background: '#FAFAFA',
663
+ background: 'transparent',
661
664
  text: '#99989C',
662
665
  placeholder: '#99989C',
663
666
  border: 'transparent',
@@ -578,6 +578,7 @@ export const lightThemePx: Theme = {
578
578
  borderRadius: 0,
579
579
  iconSize: 14,
580
580
  height: 'auto',
581
+ lineHeight: 16,
581
582
  },
582
583
  small: {
583
584
  fontSize: 14,
@@ -586,6 +587,7 @@ export const lightThemePx: Theme = {
586
587
  borderRadius: 5,
587
588
  iconSize: 14,
588
589
  height: 40,
590
+ lineHeight: 16,
589
591
  },
590
592
  medium: {
591
593
  fontSize: 16,
@@ -594,6 +596,7 @@ export const lightThemePx: Theme = {
594
596
  borderRadius: 5,
595
597
  iconSize: 16,
596
598
  height: 40,
599
+ lineHeight: 18,
597
600
  },
598
601
  },
599
602
  outlined: {
@@ -54,6 +54,7 @@ export type InputSizeStyle = {
54
54
  borderRadius: number | string;
55
55
  iconSize: number | string;
56
56
  height: number | string;
57
+ lineHeight: number;
57
58
  };
58
59
 
59
60
  // Тип для палітри
@@ -73,6 +73,16 @@ export const pxToRem = (pxValue: number | string, baseSize: number = 16): string
73
73
  return `${remValue}rem`;
74
74
  };
75
75
 
76
+ export const remToPx = (remValue: number | string, baseSize: number = 16): number => {
77
+ // If remValue is a string with 'rem' suffix, extract the numeric value
78
+ const numericValue = typeof remValue === 'string' ? parseFloat(remValue.replace('rem', '')) : remValue;
79
+ if (isNaN(numericValue)) {
80
+ return 0;
81
+ }
82
+
83
+ return numericValue * baseSize;
84
+ };
85
+
76
86
  const IGNORE_CONVERT_KEYS: Record<string, string[] | boolean> = {
77
87
  contextMenu: ['padding'],
78
88
  baseSize: true,