@axinom/mosaic-ui 0.72.0-rc.9 → 0.72.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/dist/cjs/components/Badge/Badge.d.ts +13 -0
- package/dist/cjs/components/Badge/Badge.d.ts.map +1 -0
- package/dist/cjs/components/Badge/Badge.model.d.ts +42 -0
- package/dist/cjs/components/Badge/Badge.model.d.ts.map +1 -0
- package/dist/cjs/components/Badge/index.d.ts +3 -0
- package/dist/cjs/components/Badge/index.d.ts.map +1 -0
- package/dist/cjs/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/cjs/components/Explorer/helpers/useActions.d.ts +1 -4
- package/dist/cjs/components/Explorer/helpers/useActions.d.ts.map +1 -1
- package/dist/cjs/components/Explorer/helpers/useFilters.d.ts +0 -2
- package/dist/cjs/components/Explorer/helpers/useFilters.d.ts.map +1 -1
- package/dist/cjs/components/Filters/Filters.d.ts +12 -10
- package/dist/cjs/components/Filters/Filters.d.ts.map +1 -1
- package/dist/cjs/components/Filters/index.d.ts +1 -0
- package/dist/cjs/components/Filters/index.d.ts.map +1 -1
- package/dist/cjs/components/Icons/Icons.d.ts.map +1 -1
- package/dist/cjs/components/Icons/Icons.models.d.ts +17 -16
- package/dist/cjs/components/Icons/Icons.models.d.ts.map +1 -1
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.d.ts.map +1 -1
- package/dist/cjs/index.js +4 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/Badge/Badge.d.ts +13 -0
- package/dist/esm/components/Badge/Badge.d.ts.map +1 -0
- package/dist/esm/components/Badge/Badge.model.d.ts +42 -0
- package/dist/esm/components/Badge/Badge.model.d.ts.map +1 -0
- package/dist/esm/components/Badge/index.d.ts +3 -0
- package/dist/esm/components/Badge/index.d.ts.map +1 -0
- package/dist/esm/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/esm/components/Explorer/helpers/useActions.d.ts +1 -4
- package/dist/esm/components/Explorer/helpers/useActions.d.ts.map +1 -1
- package/dist/esm/components/Explorer/helpers/useFilters.d.ts +0 -2
- package/dist/esm/components/Explorer/helpers/useFilters.d.ts.map +1 -1
- package/dist/esm/components/Filters/Filters.d.ts +12 -10
- package/dist/esm/components/Filters/Filters.d.ts.map +1 -1
- package/dist/esm/components/Filters/index.d.ts +1 -0
- package/dist/esm/components/Filters/index.d.ts.map +1 -1
- package/dist/esm/components/Icons/Icons.d.ts.map +1 -1
- package/dist/esm/components/Icons/Icons.models.d.ts +17 -16
- package/dist/esm/components/Icons/Icons.models.d.ts.map +1 -1
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/Badge/Badge.model.ts +46 -0
- package/src/components/Badge/Badge.scss +62 -0
- package/src/components/Badge/Badge.spec.tsx +40 -0
- package/src/components/Badge/Badge.stories.tsx +43 -0
- package/src/components/Badge/Badge.tsx +59 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Explorer/Explorer.tsx +40 -55
- package/src/components/Explorer/helpers/useActions.ts +0 -29
- package/src/components/Explorer/helpers/useFilters.spec.tsx +9 -25
- package/src/components/Explorer/helpers/useFilters.tsx +17 -21
- package/src/components/Filters/Filters.module.scss +39 -0
- package/src/components/Filters/Filters.tsx +102 -10
- package/src/components/Filters/index.ts +1 -0
- package/src/components/Icons/Icons.models.ts +1 -0
- package/src/components/Icons/Icons.tsx +10 -0
- package/src/components/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-ui",
|
|
3
|
-
"version": "0.72.0
|
|
3
|
+
"version": "0.72.0",
|
|
4
4
|
"description": "UI components for building Axinom Mosaic applications",
|
|
5
5
|
"author": "Axinom",
|
|
6
6
|
"license": "PROPRIETARY",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"build-storybook": "storybook build"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@axinom/mosaic-core": "^0.4.34
|
|
44
|
+
"@axinom/mosaic-core": "^0.4.34",
|
|
45
45
|
"@faker-js/faker": "^7.4.0",
|
|
46
46
|
"@geoffcox/react-splitter": "^2.1.2",
|
|
47
47
|
"@mui/base": "5.0.0-beta.40",
|
|
@@ -113,5 +113,5 @@
|
|
|
113
113
|
"publishConfig": {
|
|
114
114
|
"access": "public"
|
|
115
115
|
},
|
|
116
|
-
"gitHead": "
|
|
116
|
+
"gitHead": "083814ceecc8fa2672930b9ffbf21b544a8b89c8"
|
|
117
117
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The corner of the wrapped component that the badge attaches to.
|
|
3
|
+
*/
|
|
4
|
+
export type BadgePosition =
|
|
5
|
+
| 'top-left'
|
|
6
|
+
| 'top-right'
|
|
7
|
+
| 'bottom-left'
|
|
8
|
+
| 'bottom-right';
|
|
9
|
+
|
|
10
|
+
export interface BadgeProps {
|
|
11
|
+
/**
|
|
12
|
+
* The value shown inside the badge, typically a notification counter.
|
|
13
|
+
* When `null`, `undefined` or an empty string, the badge is not rendered
|
|
14
|
+
* (only the children are shown).
|
|
15
|
+
*/
|
|
16
|
+
content?: string | number;
|
|
17
|
+
/**
|
|
18
|
+
* The corner of the wrapped component the badge attaches to.
|
|
19
|
+
*
|
|
20
|
+
* @default 'top-right'
|
|
21
|
+
*/
|
|
22
|
+
position?: BadgePosition;
|
|
23
|
+
/**
|
|
24
|
+
* When `true`, the badge sits on the corner and half-overflows outside the
|
|
25
|
+
* bounds of the wrapped component (typical for notification counters). When
|
|
26
|
+
* `false`, the badge sits flush inside the corner.
|
|
27
|
+
*
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
overlap?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Caps a numeric `content`. When the number is greater than `max`, `${max}+`
|
|
33
|
+
* is displayed (e.g. `max={99}` renders `99+` for `100`). Ignored when
|
|
34
|
+
* `content` is a string.
|
|
35
|
+
*/
|
|
36
|
+
max?: number;
|
|
37
|
+
/**
|
|
38
|
+
* The element the badge attaches to. The badge is positioned relative to
|
|
39
|
+
* this content.
|
|
40
|
+
*/
|
|
41
|
+
children?: React.ReactNode;
|
|
42
|
+
/** Value for the `data-test-id` attribute on the badge element. */
|
|
43
|
+
dataTestId?: string;
|
|
44
|
+
/** CSS Class name for additional styles. */
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
@import '../../styles/common.scss';
|
|
2
|
+
|
|
3
|
+
.container {
|
|
4
|
+
@include boxSizing;
|
|
5
|
+
|
|
6
|
+
position: relative;
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.badge {
|
|
11
|
+
position: absolute;
|
|
12
|
+
z-index: 1;
|
|
13
|
+
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
|
|
18
|
+
height: 18px;
|
|
19
|
+
min-width: 18px;
|
|
20
|
+
padding: 0 6px;
|
|
21
|
+
border-radius: 9px;
|
|
22
|
+
|
|
23
|
+
background-color: var(--mosaic-primary);
|
|
24
|
+
color: #fff;
|
|
25
|
+
|
|
26
|
+
font-size: 11px;
|
|
27
|
+
white-space: nowrap;
|
|
28
|
+
pointer-events: none;
|
|
29
|
+
|
|
30
|
+
&.topRight {
|
|
31
|
+
top: 0;
|
|
32
|
+
right: 0;
|
|
33
|
+
}
|
|
34
|
+
&.topLeft {
|
|
35
|
+
top: 0;
|
|
36
|
+
left: 0;
|
|
37
|
+
}
|
|
38
|
+
&.bottomRight {
|
|
39
|
+
right: 0;
|
|
40
|
+
bottom: 0;
|
|
41
|
+
}
|
|
42
|
+
&.bottomLeft {
|
|
43
|
+
bottom: 0;
|
|
44
|
+
left: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Sit ON the corner, half-overflowing the parent bounds (notification style).
|
|
48
|
+
&.overlap {
|
|
49
|
+
&.topRight {
|
|
50
|
+
transform: translate(50%, -50%);
|
|
51
|
+
}
|
|
52
|
+
&.topLeft {
|
|
53
|
+
transform: translate(-50%, -50%);
|
|
54
|
+
}
|
|
55
|
+
&.bottomRight {
|
|
56
|
+
transform: translate(50%, 50%);
|
|
57
|
+
}
|
|
58
|
+
&.bottomLeft {
|
|
59
|
+
transform: translate(-50%, 50%);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { Badge } from './Badge';
|
|
5
|
+
|
|
6
|
+
describe('Badge', () => {
|
|
7
|
+
it('renders the component without crashing', () => {
|
|
8
|
+
const { container } = render(<Badge content={5}>child</Badge>);
|
|
9
|
+
expect(container.querySelector('.badge-container')).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders the children', () => {
|
|
13
|
+
const { getByText } = render(
|
|
14
|
+
<Badge content={5}>
|
|
15
|
+
<span>icon</span>
|
|
16
|
+
</Badge>,
|
|
17
|
+
);
|
|
18
|
+
expect(getByText('icon')).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('displays the content', () => {
|
|
22
|
+
const { container } = render(<Badge content={5} />);
|
|
23
|
+
expect(container.querySelector('.badge')?.textContent).toBe('5');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('caps numeric content with max', () => {
|
|
27
|
+
const { container } = render(<Badge content={1000} max={99} />);
|
|
28
|
+
expect(container.querySelector('.badge')?.textContent).toBe('99+');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('does not render the badge when content is empty', () => {
|
|
32
|
+
const { container } = render(<Badge content="">child</Badge>);
|
|
33
|
+
expect(container.querySelector('.badge')).not.toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('applies the position class', () => {
|
|
37
|
+
const { container } = render(<Badge content={1} position="bottom-left" />);
|
|
38
|
+
expect(container.querySelector('.bottomLeft')).toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Badge } from './Badge';
|
|
4
|
+
|
|
5
|
+
const SampleBox: React.FC = () => (
|
|
6
|
+
<div
|
|
7
|
+
style={{
|
|
8
|
+
width: 64,
|
|
9
|
+
height: 64,
|
|
10
|
+
borderRadius: 8,
|
|
11
|
+
backgroundColor: '#e0e0e0',
|
|
12
|
+
}}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const meta: Meta<typeof Badge> = {
|
|
17
|
+
title: 'Other Components/Badge',
|
|
18
|
+
component: Badge,
|
|
19
|
+
decorators: [
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
21
|
+
(Story) => (
|
|
22
|
+
<div style={{ padding: 40 }}>
|
|
23
|
+
<Story />
|
|
24
|
+
</div>
|
|
25
|
+
),
|
|
26
|
+
],
|
|
27
|
+
args: {
|
|
28
|
+
content: 5,
|
|
29
|
+
position: 'top-right',
|
|
30
|
+
children: <SampleBox />,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
export default meta;
|
|
34
|
+
|
|
35
|
+
export const Default: StoryObj<typeof Badge> = {};
|
|
36
|
+
|
|
37
|
+
export const MultiDigitCapped: StoryObj<typeof Badge> = {
|
|
38
|
+
args: { content: 1000, max: 99 },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const BottomLeftFlush: StoryObj<typeof Badge> = {
|
|
42
|
+
args: { position: 'bottom-left', overlap: false },
|
|
43
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import type { BadgePosition, BadgeProps } from './Badge.model';
|
|
4
|
+
import classes from './Badge.scss';
|
|
5
|
+
|
|
6
|
+
const positionClasses: Record<BadgePosition, string> = {
|
|
7
|
+
'top-left': classes.topLeft,
|
|
8
|
+
'top-right': classes.topRight,
|
|
9
|
+
'bottom-left': classes.bottomLeft,
|
|
10
|
+
'bottom-right': classes.bottomRight,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A small badge that attaches to a corner of the element it wraps, typically
|
|
15
|
+
* used to display a notification counter.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* <Badge content={5} position="top-right">
|
|
19
|
+
* <Icons icon={IconName.Bell} />
|
|
20
|
+
* </Badge>
|
|
21
|
+
*/
|
|
22
|
+
export const Badge: React.FC<BadgeProps> = ({
|
|
23
|
+
content,
|
|
24
|
+
position = 'top-right',
|
|
25
|
+
overlap = true,
|
|
26
|
+
max,
|
|
27
|
+
children,
|
|
28
|
+
dataTestId,
|
|
29
|
+
className = '',
|
|
30
|
+
}) => {
|
|
31
|
+
const displayContent =
|
|
32
|
+
typeof content === 'number' && typeof max === 'number' && content > max
|
|
33
|
+
? `${max}+`
|
|
34
|
+
: content;
|
|
35
|
+
|
|
36
|
+
const hasContent =
|
|
37
|
+
displayContent !== undefined &&
|
|
38
|
+
displayContent !== null &&
|
|
39
|
+
displayContent !== '';
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<span className={clsx(classes.container, 'badge-container', className)}>
|
|
43
|
+
{children}
|
|
44
|
+
{hasContent && (
|
|
45
|
+
<span
|
|
46
|
+
className={clsx(
|
|
47
|
+
classes.badge,
|
|
48
|
+
positionClasses[position],
|
|
49
|
+
overlap && classes.overlap,
|
|
50
|
+
'badge',
|
|
51
|
+
)}
|
|
52
|
+
data-test-id={dataTestId ?? 'badge'}
|
|
53
|
+
>
|
|
54
|
+
{displayContent}
|
|
55
|
+
</span>
|
|
56
|
+
)}
|
|
57
|
+
</span>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -262,13 +262,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
262
262
|
listRef.current?.resetSelection();
|
|
263
263
|
}, []);
|
|
264
264
|
|
|
265
|
-
const {
|
|
266
|
-
activeFilters,
|
|
267
|
-
Filters,
|
|
268
|
-
filtersVisible,
|
|
269
|
-
toggleFiltersVisible,
|
|
270
|
-
collapseFilters,
|
|
271
|
-
} = useFilters<T>({
|
|
265
|
+
const { activeFilters, Filters, collapseFilters } = useFilters<T>({
|
|
272
266
|
stationKey,
|
|
273
267
|
globalStateOptions,
|
|
274
268
|
filterOptions,
|
|
@@ -416,8 +410,6 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
416
410
|
quickEditAction,
|
|
417
411
|
bulkEditAction,
|
|
418
412
|
openBulkActionsOnStart,
|
|
419
|
-
filtersVisible,
|
|
420
|
-
hasFilters: filterOptions !== undefined,
|
|
421
413
|
resultCount,
|
|
422
414
|
activeFilterCount: Object.keys(activeFilters).length,
|
|
423
415
|
itemSelection,
|
|
@@ -426,7 +418,6 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
426
418
|
setStationMessage,
|
|
427
419
|
onBulkActionsToggled,
|
|
428
420
|
setIsBulkOpen,
|
|
429
|
-
toggleFiltersVisible,
|
|
430
421
|
closeBulkEdit,
|
|
431
422
|
});
|
|
432
423
|
|
|
@@ -461,51 +452,45 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
461
452
|
/>
|
|
462
453
|
{StationMessage}
|
|
463
454
|
{Filters}
|
|
464
|
-
<
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
onSortChanged(sortData);
|
|
504
|
-
setSortOrder(sortData);
|
|
505
|
-
}}
|
|
506
|
-
inlineMenuActions={inlineMenuActions && inlineMenuActionsHandler}
|
|
507
|
-
/>
|
|
508
|
-
</div>
|
|
455
|
+
<List<T>
|
|
456
|
+
ref={listRef}
|
|
457
|
+
columns={columns}
|
|
458
|
+
data={data}
|
|
459
|
+
isLoading={isLoading}
|
|
460
|
+
isError={Boolean(stationMessage?.type === 'error')}
|
|
461
|
+
handleRetry={false}
|
|
462
|
+
minimumWidth={minimumWidth}
|
|
463
|
+
columnGap={columnGap}
|
|
464
|
+
rowGap={rowGap}
|
|
465
|
+
headerRowHeight={headerRowHeight}
|
|
466
|
+
listRowHeight={listRowHeight}
|
|
467
|
+
listRowActionSize={listRowActionSize}
|
|
468
|
+
headerRowActionSize={headerRowActionSize}
|
|
469
|
+
horizontalTextAlign={horizontalTextAlign}
|
|
470
|
+
verticalTextAlign={verticalTextAlign}
|
|
471
|
+
textWrap={textWrap}
|
|
472
|
+
keyProperty={keyProperty}
|
|
473
|
+
showActionButton={
|
|
474
|
+
Boolean(generateItemLink) ||
|
|
475
|
+
Boolean(onItemClicked) ||
|
|
476
|
+
listSelectionMode !== ListSelectMode.None
|
|
477
|
+
} // or hard code to `true`?
|
|
478
|
+
selectionMode={listSelectionMode}
|
|
479
|
+
enableSelectAll={enableSelectAll}
|
|
480
|
+
enableSelectAllDeselect={enableSelectAll && !hasMoreData}
|
|
481
|
+
loadingTriggerOffset={loadingTriggerOffset}
|
|
482
|
+
defaultSortOrder={sortOrder}
|
|
483
|
+
generateItemLink={!isQuickEditMode ? generateItemLink : undefined}
|
|
484
|
+
onItemClicked={onItemClickedHandler}
|
|
485
|
+
onItemSelected={itemSelectedHandler}
|
|
486
|
+
onRequestMoreData={onRequestMoreData}
|
|
487
|
+
onSortChanged={(sortData: SortData<T>) => {
|
|
488
|
+
listRef.current?.resetSelection();
|
|
489
|
+
onSortChanged(sortData);
|
|
490
|
+
setSortOrder(sortData);
|
|
491
|
+
}}
|
|
492
|
+
inlineMenuActions={inlineMenuActions && inlineMenuActionsHandler}
|
|
493
|
+
/>
|
|
509
494
|
</div>
|
|
510
495
|
{isQuickEditMode && (
|
|
511
496
|
<QuickEditContextProvider>{quickEditStation}</QuickEditContextProvider>
|
|
@@ -7,7 +7,6 @@ import type { ItemSelectEventArgs } from '../../List';
|
|
|
7
7
|
import type { PageHeaderActionItemProps } from '../../PageHeader';
|
|
8
8
|
import {
|
|
9
9
|
type PageHeaderActionProps,
|
|
10
|
-
PageHeaderActionType,
|
|
11
10
|
type PageHeaderJsActionProps,
|
|
12
11
|
isPageHeaderNavigationAction,
|
|
13
12
|
} from '../../PageHeader/PageHeaderAction';
|
|
@@ -22,8 +21,6 @@ interface UseActionsProps<T extends Data> {
|
|
|
22
21
|
quickEditAction?: PageHeaderActionItemProps;
|
|
23
22
|
bulkEditAction?: ExplorerBulkAction<T>;
|
|
24
23
|
openBulkActionsOnStart: boolean | undefined;
|
|
25
|
-
filtersVisible: boolean;
|
|
26
|
-
hasFilters: boolean;
|
|
27
24
|
resultCount: ResultCounts;
|
|
28
25
|
activeFilterCount: number;
|
|
29
26
|
itemSelection: ItemSelectEventArgs<T>;
|
|
@@ -34,7 +31,6 @@ interface UseActionsProps<T extends Data> {
|
|
|
34
31
|
) => void;
|
|
35
32
|
onBulkActionsToggled: (expanded: boolean) => void;
|
|
36
33
|
setIsBulkOpen: (value: React.SetStateAction<boolean>) => void;
|
|
37
|
-
toggleFiltersVisible: () => void;
|
|
38
34
|
closeBulkEdit: () => void;
|
|
39
35
|
}
|
|
40
36
|
|
|
@@ -52,10 +48,6 @@ export const useActions = <T extends Data>({
|
|
|
52
48
|
openBulkActionsOnStart,
|
|
53
49
|
resultCount,
|
|
54
50
|
setIsBulkOpen,
|
|
55
|
-
hasFilters,
|
|
56
|
-
filtersVisible,
|
|
57
|
-
activeFilterCount,
|
|
58
|
-
toggleFiltersVisible,
|
|
59
51
|
quickEditAction,
|
|
60
52
|
bulkActions,
|
|
61
53
|
closeBulkEdit,
|
|
@@ -115,23 +107,6 @@ export const useActions = <T extends Data>({
|
|
|
115
107
|
const pageHeaderActions: PageHeaderActionItemProps[] = useMemo(() => {
|
|
116
108
|
const headerActions: PageHeaderActionItemProps[] = [];
|
|
117
109
|
|
|
118
|
-
if (hasFilters) {
|
|
119
|
-
headerActions.push({ kind: 'spacer' });
|
|
120
|
-
|
|
121
|
-
headerActions.push({
|
|
122
|
-
label:
|
|
123
|
-
activeFilterCount > 0 ? `Filters (${activeFilterCount})` : 'Filters',
|
|
124
|
-
icon: IconName.Filters,
|
|
125
|
-
kind: 'action',
|
|
126
|
-
actionType: filtersVisible
|
|
127
|
-
? PageHeaderActionType.Active
|
|
128
|
-
: PageHeaderActionType.Context,
|
|
129
|
-
onClick: async () => {
|
|
130
|
-
toggleFiltersVisible();
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
|
|
135
110
|
if (bulkActionItems.length > 0) {
|
|
136
111
|
headerActions.push({ kind: 'spacer' });
|
|
137
112
|
|
|
@@ -193,11 +168,8 @@ export const useActions = <T extends Data>({
|
|
|
193
168
|
return headerActions;
|
|
194
169
|
}, [
|
|
195
170
|
actions,
|
|
196
|
-
activeFilterCount,
|
|
197
171
|
bulkActionItems,
|
|
198
172
|
closeBulkEdit,
|
|
199
|
-
filtersVisible,
|
|
200
|
-
hasFilters,
|
|
201
173
|
itemSelection.items?.length,
|
|
202
174
|
onBulkActionsToggled,
|
|
203
175
|
openBulkActionsOnStart,
|
|
@@ -205,7 +177,6 @@ export const useActions = <T extends Data>({
|
|
|
205
177
|
resultCount?.filtered,
|
|
206
178
|
setIsBulkOpen,
|
|
207
179
|
setStationMessage,
|
|
208
|
-
toggleFiltersVisible,
|
|
209
180
|
]);
|
|
210
181
|
|
|
211
182
|
return {
|
|
@@ -46,7 +46,7 @@ describe('useFilters', () => {
|
|
|
46
46
|
expect(result.current.activeFilters).toEqual(defaultFilterValues);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
it('should
|
|
49
|
+
it('should expose collapseFilters and not throw when called', () => {
|
|
50
50
|
const { result } = renderHook(() =>
|
|
51
51
|
useFilters({
|
|
52
52
|
stationKey,
|
|
@@ -57,29 +57,13 @@ describe('useFilters', () => {
|
|
|
57
57
|
}),
|
|
58
58
|
);
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
result.current.toggleFiltersVisible();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
expect(result.current.filtersVisible).toBe(false);
|
|
65
|
-
});
|
|
60
|
+
expect(typeof result.current.collapseFilters).toBe('function');
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
stationKey,
|
|
71
|
-
globalStateOptions,
|
|
72
|
-
filterOptions,
|
|
73
|
-
defaultFilterValues,
|
|
74
|
-
onFiltersChange,
|
|
62
|
+
expect(() =>
|
|
63
|
+
act(() => {
|
|
64
|
+
result.current.collapseFilters();
|
|
75
65
|
}),
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
act(() => {
|
|
79
|
-
result.current.collapseFilters();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
expect(result.current.filtersVisible).toBe(false);
|
|
66
|
+
).not.toThrow();
|
|
83
67
|
});
|
|
84
68
|
|
|
85
69
|
it('should update filters and call onFiltersChange', () => {
|
|
@@ -95,7 +79,7 @@ describe('useFilters', () => {
|
|
|
95
79
|
);
|
|
96
80
|
|
|
97
81
|
act(() => {
|
|
98
|
-
result.current.Filters.props.
|
|
82
|
+
result.current.Filters.props.onFiltersChange(newFilters);
|
|
99
83
|
});
|
|
100
84
|
|
|
101
85
|
expect(result.current.activeFilters).toEqual(newFilters);
|
|
@@ -115,7 +99,7 @@ describe('useFilters', () => {
|
|
|
115
99
|
);
|
|
116
100
|
|
|
117
101
|
act(() => {
|
|
118
|
-
result.current.Filters.props.
|
|
102
|
+
result.current.Filters.props.onFiltersChange(newFilters);
|
|
119
103
|
});
|
|
120
104
|
|
|
121
105
|
expect(storeState).toHaveBeenCalledWith(stationKey, 'filters', newFilters);
|
|
@@ -134,7 +118,7 @@ describe('useFilters', () => {
|
|
|
134
118
|
);
|
|
135
119
|
|
|
136
120
|
act(() => {
|
|
137
|
-
result.current.Filters.props.
|
|
121
|
+
result.current.Filters.props.onFiltersChange(emptyFilters);
|
|
138
122
|
});
|
|
139
123
|
|
|
140
124
|
expect(storeState).toHaveBeenCalledWith(stationKey, 'filters', undefined);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { useExpand } from '../../../hooks';
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
2
|
import type { Data } from '../../../types';
|
|
4
3
|
import type { FilterType, FilterValues } from '../../Filters';
|
|
5
|
-
import { Filters } from '../../Filters/Filters';
|
|
4
|
+
import { Filters, type FiltersHandle } from '../../Filters/Filters';
|
|
6
5
|
import { getState, storeState } from '../../Utils/State/GlobalState';
|
|
7
6
|
import type { ExplorerStateOptions } from '../Explorer.model';
|
|
8
7
|
|
|
@@ -17,8 +16,6 @@ interface UseFiltersProps<T extends Data> {
|
|
|
17
16
|
interface UseFiltersReturnType<T extends Data> {
|
|
18
17
|
readonly Filters: JSX.Element;
|
|
19
18
|
readonly activeFilters: FilterValues<T>;
|
|
20
|
-
readonly filtersVisible: boolean;
|
|
21
|
-
readonly toggleFiltersVisible: () => void;
|
|
22
19
|
readonly collapseFilters: () => void;
|
|
23
20
|
}
|
|
24
21
|
|
|
@@ -29,7 +26,7 @@ export const useFilters = <T extends Data>({
|
|
|
29
26
|
defaultFilterValues,
|
|
30
27
|
onFiltersChange,
|
|
31
28
|
}: UseFiltersProps<T>): UseFiltersReturnType<T> => {
|
|
32
|
-
const
|
|
29
|
+
const filtersRef = useRef<FiltersHandle>(null);
|
|
33
30
|
const [activeFilters, setActiveFilters] = useState<FilterValues<T>>(
|
|
34
31
|
getState<FilterValues<T>>(stationKey, 'filters') ??
|
|
35
32
|
defaultFilterValues ??
|
|
@@ -53,26 +50,25 @@ export const useFilters = <T extends Data>({
|
|
|
53
50
|
stationKey,
|
|
54
51
|
]);
|
|
55
52
|
|
|
53
|
+
const collapseFilters = useCallback(() => {
|
|
54
|
+
filtersRef.current?.collapse();
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
56
57
|
const FilterComponent = (
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
/>
|
|
67
|
-
)}
|
|
68
|
-
</>
|
|
58
|
+
<Filters<T>
|
|
59
|
+
ref={filtersRef}
|
|
60
|
+
options={filterOptions}
|
|
61
|
+
defaultValues={activeFilters}
|
|
62
|
+
onFiltersChange={(args) => {
|
|
63
|
+
onFiltersChange(args);
|
|
64
|
+
setActiveFilters(args);
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
69
67
|
);
|
|
70
68
|
|
|
71
69
|
return {
|
|
72
70
|
Filters: FilterComponent,
|
|
73
71
|
activeFilters,
|
|
74
|
-
|
|
75
|
-
toggleFiltersVisible: toggleExpanded,
|
|
76
|
-
collapseFilters: collapse,
|
|
72
|
+
collapseFilters,
|
|
77
73
|
};
|
|
78
74
|
};
|
|
@@ -9,12 +9,51 @@
|
|
|
9
9
|
display: grid;
|
|
10
10
|
grid-template-columns: 1fr;
|
|
11
11
|
grid-auto-rows: min-content;
|
|
12
|
+
overflow-x: hidden;
|
|
12
13
|
overflow-y: auto;
|
|
13
14
|
gap: 5px;
|
|
14
15
|
padding: 5px;
|
|
15
16
|
|
|
17
|
+
transition: width 250ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
18
|
+
|
|
16
19
|
background-color: var(
|
|
17
20
|
--filter-controller-background-color,
|
|
18
21
|
$filter-controller-background-color
|
|
19
22
|
);
|
|
20
23
|
}
|
|
24
|
+
|
|
25
|
+
.header {
|
|
26
|
+
display: grid;
|
|
27
|
+
grid-template-columns: auto 1fr auto;
|
|
28
|
+
align-items: center;
|
|
29
|
+
|
|
30
|
+
background-color: color-mix(
|
|
31
|
+
in srgb,
|
|
32
|
+
var(--filter-background-color, white) 50%,
|
|
33
|
+
transparent
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
.headerButton {
|
|
37
|
+
width: 40px;
|
|
38
|
+
height: 40px;
|
|
39
|
+
|
|
40
|
+
svg {
|
|
41
|
+
height: 100%;
|
|
42
|
+
* {
|
|
43
|
+
stroke: var(--mosaic-secondary);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.count {
|
|
49
|
+
text-align: left;
|
|
50
|
+
font-weight: bold;
|
|
51
|
+
white-space: nowrap;
|
|
52
|
+
color: var(--mosaic-neutral-1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.collapsed {
|
|
57
|
+
width: 50px; // 40px button + container padding (5px each side, border-box)
|
|
58
|
+
overflow-y: hidden;
|
|
59
|
+
}
|