@addev-be/ui 0.20.4 → 0.20.8

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.20.4",
3
+ "version": "0.20.8",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "watch": "tsc -b --watch",
@@ -20,7 +20,7 @@
20
20
  "update-version": "../../node/update-version.mjs"
21
21
  },
22
22
  "devDependencies": {
23
- "@addev-be/framework-utils": "^0.20.4",
23
+ "@addev-be/framework-utils": "^0.20.8",
24
24
  "@types/lodash": "^4",
25
25
  "@types/react": "^18.3.3",
26
26
  "@types/react-dom": "^18.3.0",
@@ -1,26 +1,11 @@
1
-
2
-
3
-
4
1
  import * as styles from './styles';
5
2
 
6
- import {
7
- ArrowsRotateIcon,
8
- CopyIcon,
9
- FilterSlashIcon,
10
- FloppyDiskIcon,
11
- PlusIcon,
12
- TableColumnsIcon,
13
- } from '../../../Icons';
14
3
  import { useCallback, useState } from 'react';
15
4
 
16
- import { Button } from '../../forms';
17
5
  import { DataGridContext } from './types';
18
6
  import { DataGridHeaderCell } from './DataGridHeaderCell';
19
7
  import { IndeterminateCheckbox } from '../../forms/IndeterminateCheckbox';
20
- import { Loading } from '../../layout';
21
- import { useDataGridColumnsModal } from './DataGridColumnsModal/hooks';
22
8
  import { useDataGridContext } from './hooks';
23
- import { useRefreshModal } from './hooks/useRefreshModal';
24
9
 
25
10
  export const DataGridHeader = <R,>({
26
11
  context,
@@ -28,31 +13,18 @@ export const DataGridHeader = <R,>({
28
13
  context: DataGridContext<R>;
29
14
  }) => {
30
15
  const {
31
- name,
32
16
  visibleColumns,
33
17
  selectable,
34
18
  rows,
35
19
  selectedKeys,
36
20
  setSelectedKeys,
37
- copyTable,
38
- setFilters,
39
- refresh,
40
21
  headerColor,
41
22
  rowKeyGetter,
42
23
  gridTemplateColumns,
43
24
  getAllIds,
44
- editable,
45
- onSaveClicked,
46
- onAddClicked,
47
- addedRows,
48
- updatedRows,
49
- addRow,
50
- clearChangedRows,
51
25
  } = useDataGridContext(context);
52
26
  const [visibleFilter, setVisibleFilter] = useState<string | undefined>();
53
27
 
54
- const { openModal, modal } = useDataGridColumnsModal<R>(context);
55
-
56
28
  const checkboxStatus =
57
29
  selectedKeys.length === 0
58
30
  ? false
@@ -71,91 +43,8 @@ export const DataGridHeader = <R,>({
71
43
  setVisibleFilter((prev) => (prev === columnKey ? undefined : columnKey));
72
44
  }, []);
73
45
 
74
- const [isLoadingVisible, setIsLoadingVisible] = useState(false);
75
- const runCopyTable = useCallback(() => {
76
- setIsLoadingVisible(true);
77
- copyTable().then(() => setIsLoadingVisible(false));
78
- }, [copyTable]);
79
-
80
- const save = useCallback(async () => {
81
- if (
82
- Object.keys(addedRows).length + Object.keys(updatedRows).length > 0 &&
83
- onSaveClicked
84
- ) {
85
- const savedIds = await onSaveClicked(addedRows, updatedRows);
86
- clearChangedRows(savedIds);
87
- }
88
- }, [addedRows, clearChangedRows, onSaveClicked, updatedRows]);
89
-
90
- const add = useCallback(async () => {
91
- if (onAddClicked) {
92
- const row = await onAddClicked();
93
- addRow(row);
94
- }
95
- }, [addRow, onAddClicked]);
96
-
97
- const doRefresh = useCallback(() => {
98
- refresh?.();
99
- clearChangedRows();
100
- setSelectedKeys([]);
101
- }, [clearChangedRows, refresh, setSelectedKeys]);
102
-
103
- const { openModal: openRefreshModal, modal: refreshModal } =
104
- useRefreshModal(doRefresh);
105
-
106
- const onRefreshClicked = useCallback(async () => {
107
- if (addedRows.length + updatedRows.length > 0) {
108
- openRefreshModal();
109
- } else {
110
- doRefresh();
111
- }
112
- }, [addedRows.length, doRefresh, openRefreshModal, updatedRows.length]);
113
-
114
- const toolsRow = (
115
- <styles.DataGridToolsRow>
116
- <styles.DataGridToolsRowButtonsContainer>
117
- <Loading visible={isLoadingVisible} />
118
- {editable && onSaveClicked && (
119
- <Button size="small" $color="primary" onClick={save} bordered>
120
- <FloppyDiskIcon />
121
- Enregistrer
122
- </Button>
123
- )}
124
- {editable && onAddClicked && (
125
- <Button size="small" $color="success" onClick={add} bordered>
126
- <PlusIcon />
127
- Ajouter
128
- </Button>
129
- )}
130
- {
131
- <Button size="small" onClick={onRefreshClicked} bordered>
132
- <ArrowsRotateIcon />
133
- Rafraîchir
134
- </Button>
135
- }
136
- <Button $color="emerald" size="small" onClick={runCopyTable} bordered>
137
- <CopyIcon />
138
- Copier la table
139
- </Button>
140
- <Button size="small" $color="danger" onClick={() => setFilters({})} bordered>
141
- <FilterSlashIcon />
142
- Supprimer les filtres
143
- </Button>
144
- {name && (
145
- <Button $color="info" size="small" onClick={openModal} bordered>
146
- <TableColumnsIcon />
147
- Paramètres des colonnes
148
- </Button>
149
- )}
150
- </styles.DataGridToolsRowButtonsContainer>
151
- </styles.DataGridToolsRow>
152
- );
153
-
154
46
  return (
155
47
  <>
156
- {modal}
157
- {refreshModal}
158
- {toolsRow}
159
48
  <styles.DataGridHeaderRow
160
49
  $gridTemplateColumns={gridTemplateColumns}
161
50
  $headerColor={headerColor}
@@ -0,0 +1,134 @@
1
+ import * as styles from './styles';
2
+
3
+ import {
4
+ ArrowsRotateIcon,
5
+ CopyIcon,
6
+ FilterSlashIcon,
7
+ FloppyDiskIcon,
8
+ PlusIcon,
9
+ TableColumnsIcon,
10
+ } from '../../../Icons';
11
+ import { useCallback, useState } from 'react';
12
+
13
+ import { Button } from '../../forms';
14
+ import { DataGridContext } from './types';
15
+ import { Loading } from '../../layout';
16
+ import { useDataGridColumnsModal } from './DataGridColumnsModal/hooks';
17
+ import { useDataGridContext } from './hooks';
18
+ import { useRefreshModal } from './hooks/useRefreshModal';
19
+
20
+ export const DataGridToolbar = <R,>({
21
+ context,
22
+ }: {
23
+ context: DataGridContext<R>;
24
+ }) => {
25
+ const {
26
+ name,
27
+ setSelectedKeys,
28
+ copyTable,
29
+ setFilters,
30
+ refresh,
31
+ editable,
32
+ onSaveClicked,
33
+ onAddClicked,
34
+ addedRows,
35
+ updatedRows,
36
+ addRow,
37
+ clearChangedRows,
38
+ headerContent,
39
+ } = useDataGridContext(context);
40
+
41
+ const { openModal, modal } = useDataGridColumnsModal<R>(context);
42
+
43
+ const [isLoadingVisible, setIsLoadingVisible] = useState(false);
44
+ const runCopyTable = useCallback(() => {
45
+ setIsLoadingVisible(true);
46
+ copyTable().then(() => setIsLoadingVisible(false));
47
+ }, [copyTable]);
48
+
49
+ const save = useCallback(async () => {
50
+ if (
51
+ Object.keys(addedRows).length + Object.keys(updatedRows).length > 0 &&
52
+ onSaveClicked
53
+ ) {
54
+ const savedIds = await onSaveClicked(addedRows, updatedRows);
55
+ clearChangedRows(savedIds);
56
+ }
57
+ }, [addedRows, clearChangedRows, onSaveClicked, updatedRows]);
58
+
59
+ const add = useCallback(async () => {
60
+ if (onAddClicked) {
61
+ const row = await onAddClicked();
62
+ addRow(row);
63
+ }
64
+ }, [addRow, onAddClicked]);
65
+
66
+ const doRefresh = useCallback(() => {
67
+ refresh?.();
68
+ clearChangedRows();
69
+ setSelectedKeys([]);
70
+ }, [clearChangedRows, refresh, setSelectedKeys]);
71
+
72
+ const { openModal: openRefreshModal, modal: refreshModal } =
73
+ useRefreshModal(doRefresh);
74
+
75
+ const onRefreshClicked = useCallback(async () => {
76
+ if (addedRows.length + updatedRows.length > 0) {
77
+ openRefreshModal();
78
+ } else {
79
+ doRefresh();
80
+ }
81
+ }, [addedRows.length, doRefresh, openRefreshModal, updatedRows.length]);
82
+
83
+ return (
84
+ <>
85
+ {modal}
86
+ {refreshModal}
87
+ <styles.DataGridToolsRow>
88
+ <styles.DataGridToolsRowHeaderContent>
89
+ {headerContent}
90
+ </styles.DataGridToolsRowHeaderContent>
91
+ <styles.DataGridToolsRowButtonsContainer>
92
+ <Loading visible={isLoadingVisible} />
93
+ {editable && onSaveClicked && (
94
+ <Button size="small" $color="primary" onClick={save} bordered>
95
+ <FloppyDiskIcon />
96
+ Enregistrer
97
+ </Button>
98
+ )}
99
+ {editable && onAddClicked && (
100
+ <Button size="small" $color="success" onClick={add} bordered>
101
+ <PlusIcon />
102
+ Ajouter
103
+ </Button>
104
+ )}
105
+ {
106
+ <Button size="small" onClick={onRefreshClicked} bordered>
107
+ <ArrowsRotateIcon />
108
+ Rafraîchir
109
+ </Button>
110
+ }
111
+ <Button $color="emerald" size="small" onClick={runCopyTable} bordered>
112
+ <CopyIcon />
113
+ Copier la table
114
+ </Button>
115
+ <Button
116
+ size="small"
117
+ $color="danger"
118
+ onClick={() => setFilters({})}
119
+ bordered
120
+ >
121
+ <FilterSlashIcon />
122
+ Supprimer les filtres
123
+ </Button>
124
+ {name && (
125
+ <Button $color="info" size="small" onClick={openModal} bordered>
126
+ <TableColumnsIcon />
127
+ Paramètres des colonnes
128
+ </Button>
129
+ )}
130
+ </styles.DataGridToolsRowButtonsContainer>
131
+ </styles.DataGridToolsRow>
132
+ </>
133
+ );
134
+ };
@@ -18,12 +18,11 @@ import { DataGridContextProps, DataGridProps, DataGridRefProps } from './types';
18
18
  import { DataGridFooter } from './DataGridFooter';
19
19
  import { DataGridHeader } from './DataGridHeader';
20
20
  import { DataGridRowTemplate } from './DataGridRowTemplate';
21
+ import { DataGridToolbar } from './DataGridToolbar';
21
22
  import { VirtualScroller } from '../VirtualScroller';
23
+ import { extractSpaceProps } from '../../../helpers/styled';
22
24
  import { useDataGrid } from './hooks';
23
25
 
24
- /* eslint-disable @typescript-eslint/no-explicit-any */
25
- /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
26
-
27
26
  export const DataGridInner = <R,>(
28
27
  {
29
28
  contextOverride,
@@ -35,6 +34,7 @@ export const DataGridInner = <R,>(
35
34
  ) => {
36
35
  const scrollableRef = useRef<HTMLDivElement>(null);
37
36
  const { className } = props;
37
+ const spaceProps = extractSpaceProps(props);
38
38
  const [contextProps, DataGridContext] = useDataGrid(props, contextOverride);
39
39
  const {
40
40
  columns,
@@ -74,37 +74,45 @@ export const DataGridInner = <R,>(
74
74
  [columns, fixedColumnsCount, selectable]
75
75
  );
76
76
 
77
+ const itemProps = useMemo(
78
+ () => ({ context: DataGridContext }),
79
+ [DataGridContext]
80
+ );
81
+
77
82
  const rowTemplate = DataGridRowTemplate;
78
83
 
79
84
  return (
80
85
  <DataGridContext.Provider value={contextProps}>
81
- <styles.DataGridContainer
82
- ref={scrollableRef}
83
- $headerRowHeight={headerRowHeight}
84
- $rowHeight={rowHeight}
85
- $rowsCount={contextProps.sortedRows.length}
86
- $withFooter={hasFooter}
87
- className={className}
88
- $fixedColumnWidths={fixedColumnWidths}
89
- >
90
- {resizingColumnKey && (
91
- <styles.ResizeBackdrop
92
- onMouseMove={moveResizing}
93
- onMouseUp={endResizing}
86
+ <styles.DataGridWrapper {...spaceProps}>
87
+ <DataGridToolbar context={DataGridContext} />
88
+ <styles.DataGridContainer
89
+ ref={scrollableRef}
90
+ $headerRowHeight={headerRowHeight}
91
+ $rowHeight={rowHeight}
92
+ $rowsCount={contextProps.sortedRows.length}
93
+ $withFooter={hasFooter}
94
+ className={className}
95
+ $fixedColumnWidths={fixedColumnWidths}
96
+ >
97
+ {resizingColumnKey && (
98
+ <styles.ResizeBackdrop
99
+ onMouseMove={moveResizing}
100
+ onMouseUp={endResizing}
101
+ />
102
+ )}
103
+ <DataGridHeader context={DataGridContext} />
104
+ <VirtualScroller<R, { context: Context<DataGridContextProps<R>> }>
105
+ integrated
106
+ gridTemplateColumns={gridTemplateColumns}
107
+ items={sortedRows}
108
+ itemTemplate={rowTemplate}
109
+ itemProps={itemProps}
110
+ rowHeightInPx={rowHeight}
111
+ onRangeChanged={onVisibleRowsChange}
94
112
  />
95
- )}
96
- <DataGridHeader context={DataGridContext} />
97
- <VirtualScroller<R, { context: Context<DataGridContextProps<R>> }>
98
- integrated
99
- gridTemplateColumns={gridTemplateColumns}
100
- items={sortedRows}
101
- itemTemplate={rowTemplate}
102
- itemProps={{ context: DataGridContext }}
103
- rowHeightInPx={rowHeight}
104
- onRangeChanged={onVisibleRowsChange}
105
- />
106
- {hasFooter && <DataGridFooter context={DataGridContext} />}
107
- </styles.DataGridContainer>
113
+ {hasFooter && <DataGridFooter context={DataGridContext} />}
114
+ </styles.DataGridContainer>
115
+ </styles.DataGridWrapper>
108
116
  </DataGridContext.Provider>
109
117
  );
110
118
  };
@@ -5,12 +5,23 @@ import {
5
5
  SELECTION_CELL_WIDTH,
6
6
  TOOLBAR_HEIGHT,
7
7
  } from './constants';
8
+ import { SpaceProps, space } from '../../../helpers/styled';
8
9
  import styled, { css } from 'styled-components';
9
10
 
10
11
  import { DataGridCellFCProps } from './types';
11
12
  import { ThemeColor } from '../../../providers/ThemeProvider/types';
12
13
  import { VirtualScrollerFiller } from '../VirtualScroller/styles';
13
14
 
15
+ export const DataGridWrapper = styled.div<SpaceProps>`
16
+ display: flex;
17
+ flex-direction: column;
18
+ overflow: hidden;
19
+ width: 100%;
20
+ height: 100%;
21
+
22
+ ${space}
23
+ `;
24
+
14
25
  export const DataGridResizeGrip = styled.div<{ $headerColor?: ThemeColor }>`
15
26
  position: absolute;
16
27
  top: 0;
@@ -150,14 +161,21 @@ export const DataGridToolsRow = styled.div`
150
161
  grid-column-end: -1;
151
162
  grid-row: 1;
152
163
  display: flex;
153
- background-color: var(--color-neutral-200);
164
+ gap: var(--space-2);
165
+ padding: var(--space-2);
154
166
  position: sticky;
155
167
  top: 0;
156
168
  z-index: 10;
169
+ align-items: flex-end;
170
+ `;
171
+
172
+ export const DataGridToolsRowHeaderContent = styled.div`
173
+ flex: 1;
157
174
  `;
158
175
 
159
176
  export const DataGridToolsRowButtonsContainer = styled.div`
160
- display: inline-flex;
177
+ flex-shrink: 0;
178
+ display: flex;
161
179
  gap: var(--space-1);
162
180
  align-items: center;
163
181
  padding: 0 var(--space-1);
@@ -183,7 +201,7 @@ export const DataGridContainer = styled.div<{
183
201
  $headerRowHeight = DEFAULT_HEADER_ROW_HEIGHT,
184
202
  $withFooter = false,
185
203
  }) =>
186
- `${TOOLBAR_HEIGHT}px ${$headerRowHeight}px auto ${
204
+ `${$headerRowHeight}px auto ${
187
205
  $withFooter ? DEFAULT_FOOTER_ROW_HEIGHT + 'px' : ''
188
206
  }`};
189
207
 
@@ -242,7 +260,7 @@ export const DataGridContainer = styled.div<{
242
260
  ${VirtualScrollerFiller} {
243
261
  grid-column-start: 1;
244
262
  grid-column-end: -1;
245
- grid-row: 3;
263
+ grid-row: 2;
246
264
  }
247
265
  `;
248
266
  DataGridContainer.displayName = 'DataGridContainer';
@@ -271,6 +289,13 @@ const dataGridHeaderOrFooterRowCss = css<DataGridHeaderOrFooterRowProps>`
271
289
  background-color: var(--color-neutral-200);
272
290
  color: var(--color-neutral-900);
273
291
  `}
292
+
293
+ ${DataGridHeaderCellContainer}:first-child {
294
+ border-left: 1px solid var(--color-neutral-300);
295
+ }
296
+ ${DataGridHeaderCellContainer}:last-child {
297
+ border-left: 1px solid var(--color-neutral-300);
298
+ }
274
299
  `;
275
300
 
276
301
  export const DataGridHeaderRow = styled.div.attrs<DataGridHeaderOrFooterRowProps>(
@@ -281,7 +306,8 @@ export const DataGridHeaderRow = styled.div.attrs<DataGridHeaderOrFooterRowProps
281
306
  })
282
307
  )`
283
308
  ${dataGridHeaderOrFooterRowCss}
284
- top: ${TOOLBAR_HEIGHT}px;
309
+ border-top: 1px solid var(--color-neutral-300);
310
+ top: 0;
285
311
  `;
286
312
  DataGridHeaderRow.displayName = 'DataGridHeaderRow';
287
313
 
@@ -293,6 +319,7 @@ export const DataGridFooterRow = styled.div.attrs<DataGridHeaderOrFooterRowProps
293
319
  })
294
320
  )`
295
321
  ${dataGridHeaderOrFooterRowCss}
322
+ border-bottom: 1px solid var(--color-neutral-300);
296
323
  bottom: 0;
297
324
  `;
298
325
  DataGridFooterRow.displayName = 'DataGridFooterRow';
@@ -304,6 +331,13 @@ type DataGridRowProps = {
304
331
  export const DataGridRow = styled.div<DataGridRowProps>`
305
332
  display: contents;
306
333
 
334
+ ${DataGridCell}:first-child {
335
+ border-left: 1px solid var(--color-neutral-300);
336
+ }
337
+ ${DataGridCell}:last-child {
338
+ border-left: 1px solid var(--color-neutral-300);
339
+ }
340
+
307
341
  &:hover > ${DataGridCell} {
308
342
  background-color: var(--color-blue-100);
309
343
  }
@@ -14,6 +14,7 @@ import {
14
14
  } from 'react';
15
15
 
16
16
  import { DataGridEditableCellFC } from './DataGridEditableCell/types';
17
+ import { SpaceProps } from '../../../helpers/styled';
17
18
  import { ThemeColor } from '../../../providers/ThemeProvider/types';
18
19
 
19
20
  export type DataGridCellFCProps = {
@@ -177,7 +178,8 @@ export type DataGridProps<R> = {
177
178
  editedRows: DataGridEditedRows<R>
178
179
  ) => Promise<string[]>;
179
180
  onAddClicked?: () => Promise<R>;
180
- };
181
+ headerContent?: ReactNode;
182
+ } & SpaceProps;
181
183
 
182
184
  export type DataGridContextProps<R> = DataGridProps<R> & {
183
185
  setRows: Dispatch<SetStateAction<R[]>>;
@@ -1,5 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-empty-object-type */
2
-
3
1
  import * as styles from './styles';
4
2
 
5
3
  import { useEffect, useState } from 'react';
@@ -15,7 +15,6 @@ export const VirtualScrollerContainer = styled.div<{
15
15
  export const VirtualScrollerFiller = styled.div.attrs<{
16
16
  $height: number;
17
17
  }>(({ $height }) => ({
18
- className: 'VirtualScrollerFiller',
19
18
  style: {
20
19
  height: `${$height}px`,
21
20
  },
@@ -5,8 +5,9 @@ import { NumericFormat } from 'react-number-format';
5
5
  export const inputStyle = css`
6
6
  font-family: var(--font-sans);
7
7
  font-size: inherit;
8
- color: inherit;
9
- border: 1px solid var(--color-gray-300);
8
+ color: var(--color-neutral-900);
9
+ background-color: var(--color-neutral-50);
10
+ border: 1px solid var(--color-neutral-300);
10
11
  border-radius: var(--rounded-md);
11
12
  padding: var(--space-1);
12
13
  width: 100%;
@@ -0,0 +1,44 @@
1
+ import { FC, useEffect, useState } from 'react';
2
+ import { TabContainer, TabsListContainer } from './styles';
3
+
4
+ import { SpaceProps } from '../../../helpers';
5
+ import { Tab } from './types';
6
+
7
+ type TabsListProps = {
8
+ tabs: Tab[];
9
+ selectedIndex?: number;
10
+ onSelectedIndexChanged?: (index: number) => void;
11
+ } & SpaceProps;
12
+
13
+ export const TabsList: FC<TabsListProps> = ({
14
+ tabs,
15
+ selectedIndex,
16
+ onSelectedIndexChanged,
17
+ ...spaceProps
18
+ }) => {
19
+ const [activeIndex, setActiveIndex] = useState(selectedIndex ?? 0);
20
+
21
+ useEffect(() => {
22
+ setActiveIndex(selectedIndex ?? 0);
23
+ }, [selectedIndex]);
24
+
25
+ useEffect(() => {
26
+ onSelectedIndexChanged?.(activeIndex);
27
+ }, [activeIndex, onSelectedIndexChanged]);
28
+
29
+ return (
30
+ <TabsListContainer {...spaceProps}>
31
+ {tabs.map((tab, index) => (
32
+ <TabContainer
33
+ key={index}
34
+ $color={tab.color}
35
+ $isActive={activeIndex === index}
36
+ onClick={() => setActiveIndex(index)}
37
+ >
38
+ {tab.icon ? <tab.icon /> : null}
39
+ {tab.tabName}
40
+ </TabContainer>
41
+ ))}
42
+ </TabsListContainer>
43
+ );
44
+ };
@@ -0,0 +1,38 @@
1
+ import { FC, useEffect, useState } from 'react';
2
+ import { TabContentContainer, TabsViewContainer } from './styles';
3
+
4
+ import { Tab } from './types';
5
+ import { TabsList } from './TabsList';
6
+
7
+ type TabsViewProps = {
8
+ tabs: Tab[];
9
+ overflow?: boolean;
10
+ onSelectedTabChanged?: (tab: Tab, index: number) => void;
11
+ };
12
+
13
+ export const TabsView: FC<TabsViewProps> = ({
14
+ tabs,
15
+ overflow = false,
16
+ onSelectedTabChanged,
17
+ }) => {
18
+ const [activeIndex, setActiveIndex] = useState(0);
19
+ const activeTab = tabs[activeIndex];
20
+
21
+ useEffect(() => {
22
+ onSelectedTabChanged?.(tabs[activeIndex], activeIndex);
23
+ }, [activeIndex, tabs, onSelectedTabChanged]);
24
+
25
+ return (
26
+ <TabsViewContainer>
27
+ <TabsList
28
+ tabs={tabs}
29
+ selectedIndex={activeIndex}
30
+ onSelectedIndexChanged={setActiveIndex}
31
+ p="2"
32
+ />
33
+ <TabContentContainer $isOverflow={overflow}>
34
+ {activeTab.content}
35
+ </TabContentContainer>
36
+ </TabsViewContainer>
37
+ );
38
+ };
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './TabsView';
3
+ export * from './TabsList';
@@ -1,64 +1,66 @@
1
- import { ThemeColor, ThemeColorWithIntensity } from '../../../providers';
2
- import styled, { css } from 'styled-components';
3
-
4
- import { getColorWithIntensity } from '../../../providers/ThemeProvider/helpers';
5
-
6
- export const TabViewContainer = styled.div`
7
- display: flex;
8
- flex-direction: column;
9
- height: 100%;
10
- `;
11
-
12
- export const TabListContainer = styled.div<{}>`
13
- display: flex;
14
- gap: var(--space-4);
15
- flex-shrink: 0;
16
- overflow-x: scroll;
17
- scrollbar-width: none;
18
- padding: var(--space-2) var(--space-2);
19
- font-size: var(--text-base);
20
- white-space: nowrap;
21
- `;
22
-
23
- export const TabContainer = styled.div<{
24
- $color?: ThemeColorWithIntensity | ThemeColor;
25
- $isActive?: boolean;
26
- }>`
27
- display: flex;
28
- align-items: center;
29
- height: var(--space-8);
30
- gap: var(--space-2);
31
- cursor: pointer;
32
- padding: 0 var(--space-2);
33
-
34
- ${({ $color = 'primary' }) => css`
35
- &:hover {
36
- color: var(--color-${getColorWithIntensity($color, 500)});
37
- fill: var(--color-${getColorWithIntensity($color, 500)});
38
- }
39
- svg {
40
- height: var(--space-4);
41
- width: var(--space-4);
42
- }
43
- `};
44
-
45
- ${({ $isActive, $color = 'primary' }) =>
46
- $isActive &&
47
- css`
48
- border-bottom: var(--space-0_5) solid;
49
- color: var(--color-${getColorWithIntensity($color, 500)});
50
- fill: var(--color-${getColorWithIntensity($color, 500)});
51
- `}
52
- `;
53
-
54
- export const TabContentContainer = styled.div<{
55
- $isOverflow?: boolean;
56
- }>`
57
- display: flex;
58
- height: 100%;
59
-
60
- ${({ $isOverflow }) => css`
61
- overflow: ${$isOverflow ? 'auto' : 'hidden'};
62
- scroll-behavior: smooth;
63
- `}
64
- `;
1
+ import { SpaceProps, space } from '../../../helpers/styled';
2
+ import { ThemeColor, ThemeColorWithIntensity } from '../../../providers';
3
+ import styled, { css } from 'styled-components';
4
+
5
+ import { getColorWithIntensity } from '../../../providers/ThemeProvider/helpers';
6
+
7
+ export const TabsViewContainer = styled.div`
8
+ display: flex;
9
+ flex-direction: column;
10
+ height: 100%;
11
+ `;
12
+
13
+ export const TabsListContainer = styled.div<SpaceProps>`
14
+ display: flex;
15
+ gap: var(--space-4);
16
+ flex-shrink: 0;
17
+ overflow-x: scroll;
18
+ scrollbar-width: none;
19
+ font-size: var(--text-lg);
20
+ white-space: nowrap;
21
+
22
+ ${space}
23
+ `;
24
+
25
+ export const TabContainer = styled.div<{
26
+ $color?: ThemeColorWithIntensity | ThemeColor;
27
+ $isActive?: boolean;
28
+ }>`
29
+ display: flex;
30
+ align-items: center;
31
+ height: var(--space-8);
32
+ gap: var(--space-2);
33
+ cursor: pointer;
34
+ padding: 0 var(--space-2);
35
+
36
+ ${({ $color = 'primary' }) => css`
37
+ &:hover {
38
+ color: var(--color-${getColorWithIntensity($color, 500)});
39
+ fill: var(--color-${getColorWithIntensity($color, 500)});
40
+ }
41
+ svg {
42
+ height: var(--space-4);
43
+ width: var(--space-4);
44
+ }
45
+ `};
46
+
47
+ ${({ $isActive, $color = 'primary' }) =>
48
+ $isActive &&
49
+ css`
50
+ border-bottom: var(--space-0_5) solid;
51
+ color: var(--color-${getColorWithIntensity($color, 500)});
52
+ fill: var(--color-${getColorWithIntensity($color, 500)});
53
+ `}
54
+ `;
55
+
56
+ export const TabContentContainer = styled.div<{
57
+ $isOverflow?: boolean;
58
+ }>`
59
+ display: flex;
60
+ height: 100%;
61
+
62
+ ${({ $isOverflow }) => css`
63
+ overflow: ${$isOverflow ? 'auto' : 'hidden'};
64
+ scroll-behavior: smooth;
65
+ `}
66
+ `;
@@ -10,8 +10,3 @@ export type Tab = {
10
10
  key?: string;
11
11
  tabName: string;
12
12
  };
13
-
14
- export type TabProps = {
15
- tabs: Tab[];
16
- overflow?: boolean;
17
- };
@@ -4,4 +4,4 @@ export * from './ContextMenu';
4
4
  export * from './Ellipsis';
5
5
  export * from './Label';
6
6
  export * from './Message';
7
- export * from './Tab';
7
+ export * from './TabsView';
@@ -1,26 +1,50 @@
1
- import { ThemeSpace } from '../../providers';
1
+ import { ThemeSpace, ThemeSpaceWithNegative } from '../../providers';
2
+
2
3
  import { css } from 'styled-components';
3
4
 
4
5
  export type MarginProps = {
5
- m?: ThemeSpace;
6
- mx?: ThemeSpace;
7
- my?: ThemeSpace;
8
- mt?: ThemeSpace;
9
- mr?: ThemeSpace;
10
- mb?: ThemeSpace;
11
- ml?: ThemeSpace;
6
+ m?: ThemeSpace | ThemeSpaceWithNegative;
7
+ mx?: ThemeSpace | ThemeSpaceWithNegative;
8
+ my?: ThemeSpace | ThemeSpaceWithNegative;
9
+ mt?: ThemeSpace | ThemeSpaceWithNegative;
10
+ mr?: ThemeSpace | ThemeSpaceWithNegative;
11
+ mb?: ThemeSpace | ThemeSpaceWithNegative;
12
+ ml?: ThemeSpace | ThemeSpaceWithNegative;
12
13
  };
13
14
 
14
15
  export const margins = css<MarginProps>`
15
- ${({ m }) => m && `margin: var(--space-${m});`}
16
+ ${({ m }) =>
17
+ m && m.startsWith('-')
18
+ ? `margin: calc(0 - var(--space-${m.substring(1)}));`
19
+ : `margin: var(--space-${m});`}
16
20
  ${({ mx }) =>
17
- mx && `margin-left: var(--space-${mx}); margin-right: var(--space-${mx});`}
21
+ mx && mx.startsWith('-')
22
+ ? `margin-left: calc(0 - var(--space-${mx.substring(
23
+ 1
24
+ )})); margin-right: calc(0 - var(--space-${mx.substring(1)}));`
25
+ : `margin-left: var(--space-${mx}); margin-right: var(--space-${mx});`}
18
26
  ${({ my }) =>
19
- my && `margin-top: var(--space-${my}); margin-bottom: var(--space-${my});`}
20
- ${({ mt }) => mt && `margin-top: var(--space-${mt});`}
21
- ${({ mr }) => mr && `margin-right: var(--space-${mr});`}
22
- ${({ mb }) => mb && `margin-bottom: var(--space-${mb});`}
23
- ${({ ml }) => ml && `margin-left: var(--space-${ml});`}
27
+ my && my.startsWith('-')
28
+ ? `margin-top: calc(0 - var(--space-${my.substring(
29
+ 1
30
+ )})); margin-bottom: calc(0 - var(--space-${my.substring(1)}));`
31
+ : `margin-top: var(--space-${my}); margin-bottom: var(--space-${my});`}
32
+ ${({ mt }) =>
33
+ mt && mt.startsWith('-')
34
+ ? `margin-top: calc(0 - var(--space-${mt.substring(1)}));`
35
+ : `margin-top: var(--space-${mt});`}
36
+ ${({ mr }) =>
37
+ mr && mr.startsWith('-')
38
+ ? `margin-right: calc(0 - var(--space-${mr.substring(1)}));`
39
+ : `margin-right: var(--space-${mr});`}
40
+ ${({ mb }) =>
41
+ mb && mb.startsWith('-')
42
+ ? `margin-bottom: calc(0 - var(--space-${mb.substring(1)}));`
43
+ : `margin-bottom: var(--space-${mb});`}
44
+ ${({ ml }) =>
45
+ ml && ml.startsWith('-')
46
+ ? `margin-left: calc(0 - var(--space-${ml.substring(1)}));`
47
+ : `margin-left: var(--space-${ml});`}
24
48
  `;
25
49
 
26
50
  export type PaddingProps = {
@@ -49,6 +73,38 @@ export const paddings = css<PaddingProps>`
49
73
 
50
74
  export type SpaceProps = MarginProps & PaddingProps;
51
75
 
76
+ export const extractSpaceProps = ({
77
+ m,
78
+ mx,
79
+ my,
80
+ mt,
81
+ mr,
82
+ mb,
83
+ ml,
84
+ p,
85
+ px,
86
+ py,
87
+ pt,
88
+ pr,
89
+ pb,
90
+ pl,
91
+ }: SpaceProps) => ({
92
+ m,
93
+ mx,
94
+ my,
95
+ mt,
96
+ mr,
97
+ mb,
98
+ ml,
99
+ p,
100
+ px,
101
+ py,
102
+ pt,
103
+ pr,
104
+ pb,
105
+ pl,
106
+ });
107
+
52
108
  export const space = css<SpaceProps>`
53
109
  ${margins}
54
110
  ${paddings}
@@ -1,19 +1,17 @@
1
- import { cloneDeep, merge } from 'lodash';
2
- import { useCallback, useState } from 'react';
1
+ import { useCallback, useMemo, useState } from 'react';
3
2
 
4
3
  import { DeepPartial } from '../typings';
4
+ import { defaultsDeep } from 'lodash';
5
5
 
6
6
  export const useMutableState = <T>(initialValue: T | (() => T) = {} as T) => {
7
7
  const [value, setValue] = useState<T>(initialValue);
8
8
 
9
- const setPartialValue = useCallback(
10
- (partialValue: DeepPartial<T>) => {
11
- const oldValue = cloneDeep(value);
12
- const newValue = merge(oldValue, partialValue);
13
- setValue(() => newValue);
14
- },
15
- [value]
16
- );
9
+ const setPartialValue = useCallback((partialValue: DeepPartial<T>) => {
10
+ setValue((value) => defaultsDeep({}, partialValue, value));
11
+ }, []);
17
12
 
18
- return [value, setValue, setPartialValue] as const;
13
+ return useMemo(
14
+ () => [value, setValue, setPartialValue] as const,
15
+ [setPartialValue, value]
16
+ );
19
17
  };
@@ -89,6 +89,8 @@ export type ThemeSpace =
89
89
  | '80'
90
90
  | '96'
91
91
  | '128';
92
+ export type ThemeSpaceWithNegative = ThemeSpace | `-${ThemeSpace}`;
93
+
92
94
  export type ThemeSize =
93
95
  | '0'
94
96
  | '0_5'
@@ -1,35 +0,0 @@
1
- import { FC, useState } from 'react';
2
- import {
3
- TabContainer,
4
- TabContentContainer,
5
- TabListContainer,
6
- TabViewContainer,
7
- } from './styles';
8
-
9
- import { TabProps } from './types';
10
-
11
- export const TabView: FC<TabProps> = ({ tabs, overflow = false }) => {
12
- const [activeIndex, setActiveIndex] = useState(0);
13
- const activeTab = tabs[activeIndex];
14
-
15
- return (
16
- <TabViewContainer>
17
- <TabListContainer>
18
- {tabs.map((tab, index) => (
19
- <TabContainer
20
- key={index}
21
- $color={tab.color}
22
- $isActive={activeIndex === index}
23
- onClick={() => setActiveIndex(index)}
24
- >
25
- {tab.icon ? <tab.icon /> : null}
26
- {tab.tabName}
27
- </TabContainer>
28
- ))}
29
- </TabListContainer>
30
- <TabContentContainer $isOverflow={overflow}>
31
- {activeTab.content}
32
- </TabContentContainer>
33
- </TabViewContainer>
34
- );
35
- };