@cyber-harbour/ui 1.0.22 → 1.0.24

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.22",
3
+ "version": "1.0.24",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -114,21 +114,21 @@ const StyledButton = styled.button<{
114
114
  &:hover {
115
115
  background: ${theme.contextMenu.button.hover.background};
116
116
  color: ${theme.contextMenu.button.hover.text};
117
- border-color: ${theme.contextMenu.button.hover.border};
117
+ border-color: ${$hasBorder ? theme.contextMenu.button.hover.border : 'transparent'};
118
118
  box-shadow: ${theme.contextMenu.button.hover.boxShadow};
119
119
  }
120
120
 
121
121
  &:active {
122
122
  background: ${theme.contextMenu.button.active.background};
123
123
  color: ${theme.contextMenu.button.active.text};
124
- border-color: ${theme.contextMenu.button.active.border};
124
+ border-color: ${$hasBorder ? theme.contextMenu.button.active.border : 'transparent'};
125
125
  box-shadow: ${theme.contextMenu.button.active.boxShadow};
126
126
  }
127
127
 
128
128
  &:disabled {
129
129
  background: ${theme.contextMenu.button.disabled.background};
130
130
  color: ${theme.contextMenu.button.disabled.text};
131
- border-color: ${theme.contextMenu.button.disabled.border};
131
+ border-color: ${$hasBorder ? theme.contextMenu.button.disabled.border : 'transparent'};
132
132
  box-shadow: ${theme.contextMenu.button.disabled.boxShadow};
133
133
  }
134
134
 
@@ -0,0 +1,18 @@
1
+ import { SVGProps } from 'react';
2
+
3
+ interface InfoCircleIconProps extends SVGProps<SVGSVGElement> {
4
+ fill?: string;
5
+ }
6
+
7
+ export const InfoCircleFilledIcon = ({ fill = 'currentColor', ...props }: InfoCircleIconProps) => {
8
+ return (
9
+ <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path
11
+ fill-rule="evenodd"
12
+ clip-rule="evenodd"
13
+ d="M8 15C9.85652 15 11.637 14.2625 12.9497 12.9497C14.2625 11.637 15 9.85652 15 8C15 6.14348 14.2625 4.36301 12.9497 3.05025C11.637 1.7375 9.85652 1 8 1C6.14348 1 4.36301 1.7375 3.05025 3.05025C1.7375 4.36301 1 6.14348 1 8C1 9.85652 1.7375 11.637 3.05025 12.9497C4.36301 14.2625 6.14348 15 8 15ZM8 4C8.19891 4 8.38968 4.07902 8.53033 4.21967C8.67098 4.36032 8.75 4.55109 8.75 4.75V7.75C8.75 7.94891 8.67098 8.13968 8.53033 8.28033C8.38968 8.42098 8.19891 8.5 8 8.5C7.80109 8.5 7.61032 8.42098 7.46967 8.28033C7.32902 8.13968 7.25 7.94891 7.25 7.75V4.75C7.25 4.55109 7.32902 4.36032 7.46967 4.21967C7.61032 4.07902 7.80109 4 8 4ZM8 12C8.26522 12 8.51957 11.8946 8.70711 11.7071C8.89464 11.5196 9 11.2652 9 11C9 10.7348 8.89464 10.4804 8.70711 10.2929C8.51957 10.1054 8.26522 10 8 10C7.73478 10 7.48043 10.1054 7.29289 10.2929C7.10536 10.4804 7 10.7348 7 11C7 11.2652 7.10536 11.5196 7.29289 11.7071C7.48043 11.8946 7.73478 12 8 12Z"
14
+ fill={fill}
15
+ />
16
+ </svg>
17
+ );
18
+ };
@@ -38,3 +38,4 @@ export { ChevronDownIcon } from './ChevronDown';
38
38
  export { ChevronUpIcon } from './ChevronUp';
39
39
  export { PlusIcon } from './Plus';
40
40
  export { UsersIcon } from './Users';
41
+ export { InfoCircleFilledIcon } from './InfoCircleFilled';
@@ -0,0 +1,133 @@
1
+ import { InputSize, InputSizeStyle } from '../../Theme';
2
+ import { InputHTMLAttributes } from 'react';
3
+ import { styled } from 'styled-components';
4
+ import { InfoCircleFilledIcon } from '../IconComponents';
5
+
6
+ type StyledProps = {
7
+ $error?: boolean;
8
+ $sizes: InputSizeStyle;
9
+ };
10
+
11
+ type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> & {
12
+ error?: boolean;
13
+ append?: any;
14
+ prepend?: any;
15
+ size?: InputSize;
16
+ };
17
+
18
+ export const Input = ({ error, append, prepend, size = 'small', disabled, className, ...props }: InputProps) => {
19
+ return (
20
+ <Group className={className} $error={error} $size={size} $disabled={!!disabled}>
21
+ {!!prepend && prepend}
22
+ <InputGroup $size={size}>
23
+ <input {...props} disabled={disabled} />
24
+ {!!error && (
25
+ <IconWrapper>
26
+ <InfoCircleFilledIcon />
27
+ </IconWrapper>
28
+ )}
29
+ </InputGroup>
30
+ {!!append && append}
31
+ </Group>
32
+ );
33
+ };
34
+
35
+ const InputGroup = styled.div<{ $size: InputSize }>(
36
+ ({ theme, $size }) => `
37
+ display: inline-flex;
38
+ align-items: center;
39
+ width: 100%;
40
+ color: currentColor;
41
+
42
+ svg {
43
+ width: ${theme.input.sizes[$size].iconSize};
44
+ height: ${theme.input.sizes[$size].iconSize};
45
+ }
46
+
47
+ & input {
48
+ font-size: ${theme.input.sizes[$size].fontSize};
49
+ color: inherit;
50
+ background: transparent;
51
+ padding-block: ${theme.input.sizes[$size].paddingBlock};
52
+ padding-inline: ${theme.input.sizes[$size].paddingInline};
53
+ position: relative;
54
+ width: 100%;
55
+ outline: none;
56
+ border: none;
57
+
58
+ &::placeholder {
59
+ color: ${theme.input.outlined.default.placeholder};
60
+ font-size: ${theme.input.sizes[$size].fontSize};
61
+ }
62
+
63
+ &:disabled {
64
+ cursor: not-allowed;
65
+ color: inherit;
66
+ }
67
+ }
68
+ `
69
+ );
70
+
71
+ const IconWrapper = styled.span(
72
+ ({ theme }) => `
73
+ display: inline-flex;
74
+ flex: 0 0 auto;
75
+ align-items: center;
76
+ color: ${theme.input.outlined.error.icon};
77
+ margin-right: 10px;
78
+
79
+ `
80
+ );
81
+
82
+ const Group = styled.div<{ $disabled: boolean; $error?: boolean; $size: InputSize }>(
83
+ ({ theme, $disabled, $error, $size }) => `
84
+ display: inline-flex;
85
+ align-items: center;
86
+ width: 100%;
87
+ border: 1px solid;
88
+ border-radius: ${theme.input.sizes[$size].borderRadius};
89
+ height: ${theme.input.sizes[$size].height};
90
+ overflow: hidden;
91
+ transition: all 0.2s ease;
92
+
93
+ ${
94
+ $error
95
+ ? `
96
+ border-color: ${theme.input.outlined.error.border};
97
+ color: ${theme.input.outlined.error.text};
98
+ background: ${theme.input.outlined.error.background};
99
+ `
100
+ : `
101
+ border-color: ${theme.input.outlined.default.border};
102
+ color: ${theme.input.outlined.default.text};
103
+ background: ${theme.input.outlined.default.background};
104
+ `
105
+ }
106
+
107
+ ${
108
+ !$disabled &&
109
+ !$error &&
110
+ `
111
+ &:hover {
112
+ border-color: ${theme.input.outlined.focus.border};
113
+ }
114
+
115
+ &:focus-within {
116
+ border-color: ${theme.input.outlined.focus.border};
117
+ color: ${theme.input.outlined.focus.text};
118
+ background: ${theme.input.outlined.focus.background};
119
+ }
120
+ `
121
+ }
122
+
123
+ ${
124
+ $disabled &&
125
+ `
126
+ border-color: ${theme.input.outlined.disabled.border};
127
+ color: ${theme.input.outlined.disabled.text};
128
+ background: ${theme.input.outlined.disabled.background};
129
+ cursor: not-allowed;
130
+ `
131
+ }
132
+ `
133
+ );
@@ -0,0 +1 @@
1
+ export * from './Input';
@@ -0,0 +1,151 @@
1
+ import { ButtonColor, ButtonSize, getButtonSizeStyles } from '../../Theme';
2
+ import { useRef } from 'react';
3
+ import { Popover, PopoverAlign, PopoverPosition } from 'react-tiny-popover';
4
+ import { styled, useTheme } from 'styled-components';
5
+ import { BallsMenu } from '../IconComponents';
6
+ import { useContextMenuControl } from '../ContextMenu';
7
+ import { Button } from '../Button';
8
+
9
+ export type Action = {
10
+ label: string;
11
+ onClick: () => void;
12
+ color: ButtonColor;
13
+ };
14
+
15
+ interface RowActionsMenuProps {
16
+ items: Action[];
17
+ size?: ButtonSize;
18
+ disabled?: boolean;
19
+ className?: string;
20
+ positions?: PopoverPosition[] | PopoverPosition;
21
+ align?: PopoverAlign;
22
+ }
23
+
24
+ export const RowActionsMenu = ({
25
+ size = 'small',
26
+ disabled,
27
+ className,
28
+ positions = ['bottom'],
29
+ align = 'end',
30
+ items,
31
+ }: RowActionsMenuProps) => {
32
+ const buttonRef = useRef<HTMLButtonElement | null>(null);
33
+ const { isOpen, closeMenu, toggleMenu } = useContextMenuControl();
34
+
35
+ const theme = useTheme();
36
+
37
+ return (
38
+ <Popover
39
+ padding={theme.contextMenu.padding}
40
+ isOpen={isOpen}
41
+ positions={positions}
42
+ align={align}
43
+ onClickOutside={closeMenu}
44
+ content={
45
+ <ContentWrapper>
46
+ {items.map(({ label, onClick, color }, idx) => (
47
+ <Button
48
+ variant="empty"
49
+ key={`row-action-${idx}`}
50
+ color={color}
51
+ fullWidth
52
+ size={size}
53
+ onClick={() => {
54
+ onClick();
55
+ closeMenu();
56
+ }}
57
+ >
58
+ {label}
59
+ </Button>
60
+ ))}
61
+ </ContentWrapper>
62
+ }
63
+ containerStyle={{
64
+ backgroundColor: theme.colors.background,
65
+ border: `1px solid ${theme.colors.stroke.light}`,
66
+ boxShadow: '0px 0px 10px 0px rgba(0, 0, 0, 0.25)',
67
+ borderRadius: '5px',
68
+ zIndex: `${9999}`,
69
+ }}
70
+ >
71
+ <StyledButton
72
+ ref={buttonRef}
73
+ onClick={toggleMenu}
74
+ $disabled={disabled}
75
+ $size={size}
76
+ className={className}
77
+ type="button"
78
+ disabled={disabled}
79
+ >
80
+ <BallsMenu width={theme.rowActionsMenu.icon.size} height={theme.rowActionsMenu.icon.size} />
81
+ </StyledButton>
82
+ </Popover>
83
+ );
84
+ };
85
+
86
+ const StyledButton = styled.button<{
87
+ $size: ButtonSize;
88
+ $disabled?: boolean;
89
+ }>`
90
+ ${({ $size, $disabled, theme }) => {
91
+ const sizes = getButtonSizeStyles(theme, $size);
92
+ return `
93
+ background: ${theme.rowActionsMenu.button.default.background};
94
+ color: ${theme.rowActionsMenu.button.default.text};
95
+ border: none;
96
+ font-size: ${sizes.fontSize};
97
+ gap: ${sizes.gap};
98
+ padding: 4px;
99
+ border-radius: ${sizes.borderRadius};
100
+ width: auto;
101
+ cursor: ${$disabled ? 'not-allowed' : 'pointer'};
102
+ font-weight: 500;
103
+ display: inline-flex;
104
+ justify-content: center;
105
+ align-items: center;
106
+ text-decoration: none;
107
+ transition: all 0.2s ease;
108
+ outline: none;
109
+
110
+ &:hover {
111
+ background: ${theme.rowActionsMenu.button.hover.background};
112
+ color: ${theme.rowActionsMenu.button.hover.text};
113
+ }
114
+
115
+ &:active {
116
+ background: ${theme.rowActionsMenu.button.active.background};
117
+ color: ${theme.rowActionsMenu.button.active.text};
118
+ }
119
+
120
+ &:disabled {
121
+ background: ${theme.rowActionsMenu.button.disabled.background};
122
+ color: ${theme.rowActionsMenu.button.disabled.text};
123
+ }
124
+
125
+ `;
126
+ }}
127
+ `;
128
+ const ContentWrapper = styled.div(
129
+ ({ theme }) => `
130
+ padding: 5px 10px;
131
+ display: flex;
132
+ flex-direction: column;
133
+ button {
134
+ min-width: 110px;
135
+ justify-content: flex-start;
136
+ font-weight: 400;
137
+ }
138
+ button:not(:last-of-type) {
139
+ position: relative;
140
+ ::after {
141
+ position: absolute;
142
+ content: '';
143
+ bottom: 0;
144
+ left: 5px;
145
+ right: 5px;
146
+ height: 1px;
147
+ background-color: ${theme.rowActionsMenu.delimiterColor};
148
+ }
149
+ }
150
+ `
151
+ );
@@ -0,0 +1 @@
1
+ export * from './RowActionsMenu';
package/src/Core/index.ts CHANGED
@@ -8,3 +8,5 @@ export * from './Table';
8
8
  export * from './Pagination';
9
9
  export * from './ContextMenu';
10
10
  export * from './Select';
11
+ export * from './RowActionsMenu';
12
+ export * from './Input';
@@ -545,6 +545,94 @@ export const lightThemePx: Theme = {
545
545
  },
546
546
  },
547
547
  },
548
+ // Компонент RowActionsMenu
549
+ rowActionsMenu: {
550
+ button: {
551
+ default: {
552
+ background: 'transparent',
553
+ text: '#101010',
554
+ border: ' none',
555
+ boxShadow: 'none',
556
+ },
557
+ hover: {
558
+ background: 'transparent',
559
+ text: '#0042EC',
560
+ border: ' none',
561
+ boxShadow: 'none',
562
+ },
563
+ active: {
564
+ background: 'transparent',
565
+ text: '#0042EC',
566
+ border: ' none',
567
+ boxShadow: 'none',
568
+ },
569
+ disabled: {
570
+ background: '#FAFAFA',
571
+ text: '#99989C',
572
+ border: ' none',
573
+ boxShadow: 'none',
574
+ },
575
+ },
576
+ delimiterColor: '#EBEBEB',
577
+ icon: {
578
+ size: 16,
579
+ },
580
+ },
581
+ // Компонент Input
582
+ input: {
583
+ sizes: {
584
+ small: {
585
+ fontSize: 14,
586
+ paddingInline: 10,
587
+ paddingBlock: 10,
588
+ borderRadius: 5,
589
+ iconSize: 14,
590
+ height: 40,
591
+ },
592
+ medium: {
593
+ fontSize: 16,
594
+ paddingInline: 10,
595
+ paddingBlock: 10,
596
+ borderRadius: 5,
597
+ iconSize: 16,
598
+ height: 40,
599
+ },
600
+ },
601
+ outlined: {
602
+ default: {
603
+ background: 'transparent',
604
+ text: '#101010',
605
+ placeholder: '#99989C',
606
+ border: ' #EBEBEB',
607
+ boxShadow: 'none',
608
+ icon: '#101010',
609
+ },
610
+ focus: {
611
+ background: 'transparent',
612
+ text: '#101010',
613
+ placeholder: '#99989C',
614
+ border: ' #0042EC',
615
+ boxShadow: 'none',
616
+ icon: '#101010',
617
+ },
618
+ error: {
619
+ background: 'transparent',
620
+ text: '#101010',
621
+ placeholder: '#99989C',
622
+ border: ' #C93939',
623
+ boxShadow: 'none',
624
+ icon: '#C93939',
625
+ },
626
+ disabled: {
627
+ background: '#FAFAFA',
628
+ text: '#99989C',
629
+ placeholder: '#99989C',
630
+ border: ' #EBEBEB',
631
+ boxShadow: 'none',
632
+ icon: '#99989C',
633
+ },
634
+ },
635
+ },
548
636
  };
549
637
 
550
638
  // Конвертуємо всі розміри з px в rem
@@ -6,9 +6,9 @@ export type ButtonColor = 'default' | 'primary' | 'secondary' | 'error';
6
6
  export type ButtonState = 'default' | 'hover' | 'active' | 'disabled';
7
7
  export type ButtonSize = 'small' | 'medium';
8
8
 
9
- export type InputVariant = 'default' | 'outlined' | 'filled';
9
+ export type InputVariant = 'outlined';
10
10
  export type InputState = 'default' | 'focus' | 'error' | 'disabled';
11
- export type InputSize = 'small' | 'medium' | 'large';
11
+ export type InputSize = 'small' | 'medium';
12
12
 
13
13
  // Типи для spacing та breakpoints
14
14
  export type Breakpoint = 'xs' | 's' | 'm' | 'l' | 'xl';
@@ -32,6 +32,26 @@ export type ButtonSizeStyle = {
32
32
  iconSize: number | string;
33
33
  };
34
34
 
35
+ // Тип для стилів елементів інпута
36
+ export type InputElementStyle = {
37
+ background: string;
38
+ text: string;
39
+ placeholder: string;
40
+ border: string;
41
+ boxShadow: string;
42
+ icon: string;
43
+ };
44
+
45
+ // Тип для розмірів інпута
46
+ export type InputSizeStyle = {
47
+ fontSize: number | string;
48
+ paddingInline: number | string;
49
+ paddingBlock: number | string;
50
+ borderRadius: number | string;
51
+ iconSize: number | string;
52
+ height: number | string;
53
+ };
54
+
35
55
  // Тип для палітри
36
56
  export type Theme = {
37
57
  mode: 'light' | 'dark';
@@ -151,6 +171,19 @@ export type Theme = {
151
171
  select: {
152
172
  item: Record<ButtonState, ButtonElementStyle>;
153
173
  };
174
+ // RowActionsMenu
175
+ rowActionsMenu: {
176
+ button: Record<ButtonState, ButtonElementStyle>;
177
+ delimiterColor: string;
178
+ icon: {
179
+ size: number | string;
180
+ };
181
+ };
182
+ //Input
183
+ input: {
184
+ sizes: Record<InputSize, InputSizeStyle>;
185
+ outlined: Record<InputState, InputElementStyle>;
186
+ };
154
187
  };
155
188
 
156
189
  //TODO check and refactoring
@@ -1,5 +1,5 @@
1
1
  import { DefaultTheme } from 'styled-components';
2
- import { Breakpoint, ButtonColor, ButtonSize, ButtonState, ButtonVariant } from './types';
2
+ import { Breakpoint, ButtonColor, ButtonSize, ButtonState, ButtonVariant, InputSize, InputState } from './types';
3
3
 
4
4
  /**
5
5
  * Helper function to resolve nested color paths from theme