@addev-be/ui 0.12.3 → 0.14.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@addev-be/ui",
3
- "version": "0.12.3",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "watch": "tsc -b --watch",
package/src/Icons.tsx CHANGED
@@ -79,6 +79,10 @@ export const LoadingIcon: FC<IconFCProps> = ({ className, ...props }) => (
79
79
  <SpinnerIcon className={`animate-spin ${className}`} {...props} />
80
80
  );
81
81
 
82
+ export const BlankIcon: FC<IconFCProps> = (props) => (
83
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...props} />
84
+ );
85
+
82
86
  export {
83
87
  AngleLeftIcon,
84
88
  AngleRightIcon,
@@ -3,8 +3,8 @@
3
3
 
4
4
  import * as styles from './styles';
5
5
 
6
- import { MouseEvent, useCallback } from 'react';
7
6
  import { DataGridCellProps, DataGridColumn } from './types';
7
+ import { MouseEvent, useCallback } from 'react';
8
8
 
9
9
  import { DataGridEditableCell } from './DataGridEditableCell';
10
10
  import { useDataGridContext } from './hooks';
@@ -69,6 +69,7 @@ export const DataGridCell = <R,>({
69
69
  onDoubleClick={onDoubleClick}
70
70
  style={style}
71
71
  $userSelect={userSelect}
72
+ $color={column.color}
72
73
  >
73
74
  {(column.render ?? defaultRender)(row, column)}
74
75
  </DataGridCellComponent>
@@ -18,6 +18,7 @@ export const DataGridFooter = <R,>({
18
18
  selectedRows,
19
19
  sortedRows,
20
20
  selectable,
21
+ headerColor,
21
22
  footers = {},
22
23
  gridTemplateColumns,
23
24
  footerFunctions,
@@ -34,6 +35,7 @@ export const DataGridFooter = <R,>({
34
35
  <styles.DataGridHeaderCellContainer
35
36
  key={key}
36
37
  style={{ width: (col.width ?? DEFAULT_COLUMN_WIDTH) + 'px' }}
38
+ $headerColor={col.color ?? headerColor}
37
39
  >
38
40
  {footerFunctions?.[key]?.(rows, sortedRows, selectedRows)}
39
41
  </styles.DataGridHeaderCellContainer>
@@ -72,7 +72,7 @@ export const DataGridHeaderCell = <R,>({
72
72
 
73
73
  return (
74
74
  <styles.DataGridHeaderCellContainer
75
- $headerColor={headerColor}
75
+ $headerColor={column.color ?? headerColor}
76
76
  $isResizing={isResizing}
77
77
  >
78
78
  {filterDropdown}
@@ -63,6 +63,7 @@ export const DataGridRowTemplate = <R,>({
63
63
  columnIndex={index}
64
64
  context={context}
65
65
  columnKey={key}
66
+ color={col.color}
66
67
  />
67
68
  ))}
68
69
  </styles.DataGridRow>
@@ -7,6 +7,7 @@ import {
7
7
  } from './constants';
8
8
  import styled, { css } from 'styled-components';
9
9
 
10
+ import { DataGridCellFCProps } from './types';
10
11
  import { ThemeColor } from '../../../providers/ThemeProvider/types';
11
12
  import { VirtualScrollerFiller } from '../VirtualScroller/styles';
12
13
 
@@ -108,11 +109,7 @@ export const DataGridHeaderCellContainer = styled.div<DataGridHeaderCellContaine
108
109
  `;
109
110
  DataGridHeaderCellContainer.displayName = 'DataGridHeaderCellContainer';
110
111
 
111
- type DataGridCellProps = {
112
- $userSelect?: boolean;
113
- };
114
-
115
- export const DataGridCell = styled.div<DataGridCellProps>`
112
+ export const DataGridCell = styled.div<DataGridCellFCProps>`
116
113
  position: relative;
117
114
  padding: 0 var(--space-3);
118
115
  overflow: hidden;
@@ -121,6 +118,7 @@ export const DataGridCell = styled.div<DataGridCellProps>`
121
118
  white-space: nowrap;
122
119
  border-right: 1px solid var(--color-neutral-200);
123
120
  border-bottom: 1px solid var(--color-neutral-200);
121
+ background-color: ${({ $color = 'neutral' }) => `var(--color-${$color}-50)`};
124
122
  `;
125
123
  DataGridCell.displayName = 'DataGridCell';
126
124
 
@@ -318,6 +316,7 @@ SelectionCell.displayName = 'SelectionCell';
318
316
 
319
317
  export const HeaderSelectionCell = styled(DataGridHeaderCellContainer)`
320
318
  ${selectionCellStyle}
319
+ padding: 0;
321
320
  `;
322
321
  HeaderSelectionCell.displayName = 'HeaderSelectionCell';
323
322
 
@@ -16,10 +16,14 @@ import {
16
16
  import { SettingsContextProps } from '../../../providers/SettingsProvider';
17
17
  import { ThemeColor } from '../../../providers/ThemeProvider/types';
18
18
 
19
- export type DataGridCellFC = FC<{
19
+ export type DataGridCellFCProps = {
20
20
  onDoubleClick?: MouseEventHandler;
21
21
  style?: CSSProperties;
22
- }>;
22
+ $color?: ThemeColor;
23
+ $userSelect?: boolean;
24
+ };
25
+
26
+ export type DataGridCellFC = FC<DataGridCellFCProps>;
23
27
 
24
28
  export type DataGridFooterPredefinedFunction =
25
29
  | 'average'
@@ -37,6 +41,7 @@ export type DataGridFooterFunction<R> = (
37
41
  export type DataGridColumn<R> = {
38
42
  component?: DataGridCellFC;
39
43
  editable?: boolean;
44
+ color?: ThemeColor;
40
45
  excelFormatter?: (value: any) => string;
41
46
  excelBackgroundColor?: (value: any) => string;
42
47
  excelValue?: (value: any) => string;
@@ -176,6 +181,7 @@ export type DataGridCellProps<R> = {
176
181
  className?: string;
177
182
  style?: CSSProperties;
178
183
  userSelect?: boolean;
184
+ color?: ThemeColor;
179
185
  };
180
186
 
181
187
  export type DataGridHeaderCellProps<R> = {
@@ -185,6 +191,7 @@ export type DataGridHeaderCellProps<R> = {
185
191
  context: DataGridContext<R>;
186
192
  onFilterButtonClicked?: (columnKey: string) => void;
187
193
  isFilterOpen?: boolean;
194
+ color?: ThemeColor;
188
195
  };
189
196
 
190
197
  export type DataGridSort = 'asc' | 'desc';
@@ -0,0 +1,13 @@
1
+ import { FC, InputHTMLAttributes } from 'react';
2
+
3
+ import { InputContainerProps } from './types';
4
+ import { InputWithLabel } from './InputWithLabel';
5
+ import { StyledCheckbox } from './styles';
6
+
7
+ export const Checkbox: FC<
8
+ InputContainerProps & InputHTMLAttributes<HTMLInputElement>
9
+ > = ({ label, icon, ...props }) => (
10
+ <InputWithLabel label={label} icon={icon}>
11
+ <StyledCheckbox {...props} />
12
+ </InputWithLabel>
13
+ );
@@ -0,0 +1,27 @@
1
+ import { FC, PropsWithChildren } from 'react';
2
+ import { FormGroupContainer, FormGroupHeader } from './styles';
3
+
4
+ import { ThemeColorWithIntensity } from '../../../providers/ThemeProvider';
5
+
6
+ type FormGroupProps = PropsWithChildren<{
7
+ color?: ThemeColorWithIntensity;
8
+ title?: string;
9
+ subTitle?: string;
10
+ }>;
11
+
12
+ export const FormGroup: FC<FormGroupProps> = ({
13
+ children,
14
+ color,
15
+ title,
16
+ subTitle,
17
+ }) => (
18
+ <FormGroupContainer color={color}>
19
+ {(title || subTitle) && (
20
+ <FormGroupHeader>
21
+ {title && <h3>{title}</h3>}
22
+ {subTitle && <h4>{subTitle}</h4>}
23
+ </FormGroupHeader>
24
+ )}
25
+ {children}
26
+ </FormGroupContainer>
27
+ );
@@ -0,0 +1,13 @@
1
+ import { FC, InputHTMLAttributes } from 'react';
2
+
3
+ import { InputContainerProps } from './types';
4
+ import { InputWithLabel } from './InputWithLabel';
5
+ import { StyledInput } from './styles';
6
+
7
+ export const Input: FC<
8
+ InputContainerProps & InputHTMLAttributes<HTMLInputElement>
9
+ > = ({ label, icon, ...props }) => (
10
+ <InputWithLabel label={label} icon={icon}>
11
+ <StyledInput {...props} />
12
+ </InputWithLabel>
13
+ );
@@ -0,0 +1,18 @@
1
+ import { InputContainer } from './styles';
2
+ import { InputContainerProps } from './types';
3
+
4
+ export const InputWithLabel = ({
5
+ label,
6
+ icon: Icon,
7
+ children,
8
+ }: InputContainerProps) => (
9
+ <InputContainer>
10
+ {(label || Icon) && (
11
+ <span>
12
+ {Icon && <Icon />}
13
+ {label}
14
+ </span>
15
+ )}
16
+ {children}
17
+ </InputContainer>
18
+ );
@@ -0,0 +1,49 @@
1
+ import { InputContainerProps } from './types';
2
+ import { InputWithLabel } from './InputWithLabel';
3
+ import { SelectHTMLAttributes } from 'react';
4
+ import { StyledSelect } from './styles';
5
+
6
+ type SelectProps<T> = {
7
+ items: T[];
8
+ itemKey: keyof T | ((item: T, index: number) => string);
9
+ itemLabel: keyof T | ((item: T, index: number) => string);
10
+ readOnly?: boolean;
11
+ };
12
+
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ export const Select = <T,>({
15
+ icon,
16
+ label,
17
+ items,
18
+ itemKey,
19
+ itemLabel,
20
+ readOnly,
21
+ ...props
22
+ }: InputContainerProps &
23
+ SelectProps<T> &
24
+ SelectHTMLAttributes<HTMLSelectElement>) => {
25
+ const keyGetter =
26
+ typeof itemKey === 'function'
27
+ ? itemKey
28
+ : (item: T) => String(item[itemKey]);
29
+ const labelGetter =
30
+ typeof itemLabel === 'function'
31
+ ? itemLabel
32
+ : (item: T) => String(item[itemLabel]);
33
+
34
+ return (
35
+ <InputWithLabel label={label} icon={icon}>
36
+ <StyledSelect {...props} disabled={readOnly}>
37
+ {items.map((item, index) => {
38
+ const key = keyGetter(item, index);
39
+ const label = labelGetter(item, index);
40
+ return (
41
+ <option key={key} value={key}>
42
+ {label}
43
+ </option>
44
+ );
45
+ })}
46
+ </StyledSelect>
47
+ </InputWithLabel>
48
+ );
49
+ };
@@ -0,0 +1,13 @@
1
+ import { FC, TextareaHTMLAttributes } from 'react';
2
+
3
+ import { InputContainerProps } from './types';
4
+ import { InputWithLabel } from './InputWithLabel';
5
+ import { StyledTextArea } from './styles';
6
+
7
+ export const TextArea: FC<
8
+ InputContainerProps & TextareaHTMLAttributes<HTMLTextAreaElement>
9
+ > = ({ label, icon, ...props }) => (
10
+ <InputWithLabel label={label} icon={icon}>
11
+ <StyledTextArea {...props} />
12
+ </InputWithLabel>
13
+ );
@@ -0,0 +1,37 @@
1
+ import { FC, PropsWithChildren, useCallback } from 'react';
2
+
3
+ import { Checkbox } from './Checkbox';
4
+ import { FormGroup } from './FormGroup';
5
+ import { Input } from './Input';
6
+ import { Select } from './Select';
7
+ import { TextArea } from './TextArea';
8
+
9
+ export type FormProps = PropsWithChildren<{
10
+ onSubmit?: () => void;
11
+ }>;
12
+
13
+ export type FormFC = FC<FormProps> & {
14
+ Group: typeof FormGroup;
15
+ Input: typeof Input;
16
+ Select: typeof Select;
17
+ Checkbox: typeof Checkbox;
18
+ TextArea: typeof TextArea;
19
+ };
20
+
21
+ export const Form = ({ children, onSubmit }: FormProps) => {
22
+ const handleSubmit = useCallback(
23
+ (e: React.FormEvent<HTMLFormElement>) => {
24
+ e.preventDefault();
25
+ onSubmit?.();
26
+ },
27
+ [onSubmit]
28
+ );
29
+
30
+ return <form onSubmit={handleSubmit}>{children}</form>;
31
+ };
32
+
33
+ Form.Group = FormGroup;
34
+ Form.Input = Input;
35
+ Form.Select = Select;
36
+ Form.Checkbox = Checkbox;
37
+ Form.TextArea = TextArea;
@@ -0,0 +1,101 @@
1
+ import styled, { css } from 'styled-components';
2
+
3
+ import { ThemeColorWithIntensity } from '../../../providers/ThemeProvider';
4
+
5
+ export const FormGroupContainer = styled.fieldset<{
6
+ color?: ThemeColorWithIntensity;
7
+ }>`
8
+ display: flex;
9
+ flex-direction: column;
10
+ background: var(--color-neutral-100);
11
+ border-radius: var(--rounded-md);
12
+ box-shadow: var(--shadow-md);
13
+ padding: var(--space-4);
14
+ margin: 0;
15
+ border: 1px solid var(--color-neutral-100);
16
+ gap: var(--space-2);
17
+ border-left: 8px solid ${({ color = 'neutral' }) => `var(--color-${color})`};
18
+ `;
19
+
20
+ export const FormGroupHeader = styled.div`
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: var(--space-1);
24
+ margin-bottom: var(--space-1);
25
+
26
+ & > h3 {
27
+ margin: 0;
28
+ font-size: var(--text-lg);
29
+ font-weight: bold;
30
+ }
31
+ & > h4 {
32
+ margin: 0;
33
+ font-size: var(--text-sm);
34
+ font-weight: var(--font-normal);
35
+ color: var(--color-neutral-500);
36
+ }
37
+ `;
38
+
39
+ export const InputContainer = styled.label<{ readOnly?: boolean }>`
40
+ display: flex;
41
+ flex-direction: row;
42
+ align-items: flex-start;
43
+ gap: var(--space-2);
44
+ padding: var(--space-1) var(--space-2);
45
+ border: 1px solid var(--color-neutral-200);
46
+ background-color: ${({ readOnly }) =>
47
+ readOnly ? 'var(--color-neutral-50)' : 'var(--color-neutral-0)'};
48
+
49
+ & > span {
50
+ color: var(--color-neutral-500);
51
+ font-size: var(--text-base);
52
+ padding: var(--space-1);
53
+ display: flex;
54
+ flex-direction: row;
55
+ gap: var(--space-2);
56
+ line-height: 1.25;
57
+
58
+ & > svg {
59
+ width: var(--space-4);
60
+ height: var(--space-4);
61
+ fill: var(--color-neutral-500);
62
+ flex-shrink: 0;
63
+ }
64
+ }
65
+
66
+ &:focus-within {
67
+ outline: 2px solid var(--color-primary-500);
68
+ }
69
+ `;
70
+
71
+ const inputCss = css`
72
+ font-family: inherit;
73
+ font-size: var(--text-base);
74
+ color: var(--color-neutral-900);
75
+ border: none;
76
+ padding: var(--space-1);
77
+ flex: 1;
78
+ text-align: right;
79
+
80
+ &:focus {
81
+ outline: none;
82
+ }
83
+ `;
84
+
85
+ export const StyledInput = styled.input`
86
+ ${inputCss}
87
+ `;
88
+
89
+ export const StyledSelect = styled.select`
90
+ ${inputCss}
91
+ `;
92
+
93
+ export const StyledTextArea = styled.textarea`
94
+ ${inputCss}
95
+ resize: none;
96
+ `;
97
+
98
+ export const StyledCheckbox = styled.input.attrs({ type: 'checkbox' })`
99
+ width: var(--space-4);
100
+ height: var(--space-4);
101
+ `;
@@ -0,0 +1,7 @@
1
+ import { IconFC } from '../../../Icons';
2
+ import { PropsWithChildren } from 'react';
3
+
4
+ export type InputContainerProps = PropsWithChildren<{
5
+ label?: string;
6
+ icon?: IconFC;
7
+ }>;
@@ -3,4 +3,6 @@ export * from './Button';
3
3
  export * from './Select';
4
4
  export * from './IconButton';
5
5
  export * from './IndeterminateCheckbox';
6
+ export * from './Form';
7
+ export * from './Form/InputWithLabel';
6
8
  export * from './styles';
@@ -0,0 +1,20 @@
1
+ import styled from 'styled-components';
2
+
3
+ export type ColumnsProps = {
4
+ columns?: number | string[];
5
+ };
6
+
7
+ export const Columns = styled.div<ColumnsProps>`
8
+ display: grid;
9
+ grid-gap: var(--space-2);
10
+ grid-template-columns: ${({ columns }) =>
11
+ typeof columns === 'number'
12
+ ? `repeat(${columns}, 1fr)`
13
+ : columns?.join(' ') || 'none'};
14
+ `;
15
+
16
+ export const Column = styled.div`
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: var(--space-2);
20
+ `;
@@ -0,0 +1,8 @@
1
+ import { GridContainer, GridItemContainer } from './styles';
2
+
3
+ type GridFC = typeof GridContainer & {
4
+ Item: typeof GridItemContainer;
5
+ };
6
+
7
+ export const Grid = GridContainer as GridFC;
8
+ Grid.Item = GridItemContainer;
@@ -0,0 +1,34 @@
1
+ import styled from 'styled-components';
2
+
3
+ type GridProps = {
4
+ columns?: number;
5
+ };
6
+
7
+ export const GridContainer = styled.div<GridProps>`
8
+ display: grid;
9
+ grid-template-columns: repeat(
10
+ ${({ columns = 12 }) => columns},
11
+ minmax(0, 1fr)
12
+ );
13
+ grid-template-rows: masonry;
14
+ `;
15
+
16
+ type GridItemProps = {
17
+ row?: number;
18
+ col?: number;
19
+ rowSpan?: number;
20
+ colSpan?: number;
21
+ };
22
+
23
+ export const GridItemContainer = styled.div.attrs<GridItemProps>(
24
+ ({ col, colSpan, row, rowSpan }) => ({
25
+ style: {
26
+ gridColumn: [col, !!colSpan && `span ${colSpan}`]
27
+ .filter(Boolean)
28
+ .join(' / '),
29
+ gridRow: [row, !!rowSpan && `span ${rowSpan}`]
30
+ .filter(Boolean)
31
+ .join(' / '),
32
+ },
33
+ })
34
+ )``;
@@ -0,0 +1,29 @@
1
+ import { FC, PropsWithChildren, ReactNode, useMemo } from 'react';
2
+ import {
3
+ MasonryColumnContainer,
4
+ MasonryContainer,
5
+ MasonryContainerProps,
6
+ } from './styles';
7
+
8
+ export const Masonry: FC<PropsWithChildren<MasonryContainerProps>> = ({
9
+ children,
10
+ columns = 3,
11
+ }) => {
12
+ const childrenByColumn = useMemo(() => {
13
+ const arr = Array.from({ length: columns }, (): ReactNode[] => []);
14
+ if (Array.isArray(children)) {
15
+ children.forEach((child, index) => {
16
+ arr[index % columns].push(child);
17
+ });
18
+ }
19
+ return arr;
20
+ }, [children, columns]);
21
+
22
+ return (
23
+ <MasonryContainer columns={columns}>
24
+ {childrenByColumn.map((column, index) => (
25
+ <MasonryColumnContainer key={index}>{column}</MasonryColumnContainer>
26
+ ))}
27
+ </MasonryContainer>
28
+ );
29
+ };
@@ -0,0 +1,20 @@
1
+ import styled from 'styled-components';
2
+
3
+ export type MasonryContainerProps = {
4
+ columns?: number;
5
+ };
6
+
7
+ export const MasonryContainer = styled.div<MasonryContainerProps>`
8
+ display: grid;
9
+ grid-gap: var(--space-2);
10
+ grid-template-columns: repeat(
11
+ ${({ columns = 4 }) => columns},
12
+ minmax(0, 1fr)
13
+ );
14
+ `;
15
+
16
+ export const MasonryColumnContainer = styled.div`
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: var(--space-2);
20
+ `;
@@ -1,3 +1,6 @@
1
+ export * from './Columns';
1
2
  export * from './Dropdown';
2
- export * from './Modal';
3
+ export * from './Grid';
3
4
  export * from './Loading';
5
+ export * from './Masonry';
6
+ export * from './Modal';
@@ -0,0 +1,11 @@
1
+ import { ThemeColorWithIntensity } from './types';
2
+
3
+ export const getColor = (colorWithIntensity: ThemeColorWithIntensity) => {
4
+ const [colorName] = colorWithIntensity.split('-');
5
+ return colorName;
6
+ };
7
+
8
+ export const getIntensity = (colorWithIntensity: ThemeColorWithIntensity) => {
9
+ const [, intensity] = colorWithIntensity.split('-');
10
+ return intensity;
11
+ };
File without changes