@tcn/ui-table 2.3.6 → 2.3.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.
Files changed (49) hide show
  1. package/dist/components/cells/header_cell.d.ts.map +1 -1
  2. package/dist/components/cells/header_cell.js +6 -6
  3. package/dist/components/cells/header_cell.js.map +1 -1
  4. package/dist/components/table_filter_panel/field_filters/clear_button.d.ts +5 -0
  5. package/dist/components/table_filter_panel/field_filters/clear_button.d.ts.map +1 -0
  6. package/dist/components/table_filter_panel/field_filters/clear_button.js +15 -0
  7. package/dist/components/table_filter_panel/field_filters/clear_button.js.map +1 -0
  8. package/dist/components/table_filter_panel/field_filters/clearable_field.d.ts +9 -0
  9. package/dist/components/table_filter_panel/field_filters/clearable_field.d.ts.map +1 -0
  10. package/dist/components/table_filter_panel/field_filters/clearable_field.js +28 -0
  11. package/dist/components/table_filter_panel/field_filters/clearable_field.js.map +1 -0
  12. package/dist/components/table_filter_panel/field_filters/date_field_filter.d.ts.map +1 -1
  13. package/dist/components/table_filter_panel/field_filters/date_field_filter.js +45 -54
  14. package/dist/components/table_filter_panel/field_filters/date_field_filter.js.map +1 -1
  15. package/dist/components/table_filter_panel/field_filters/mulit_select_field_filter.d.ts.map +1 -1
  16. package/dist/components/table_filter_panel/field_filters/mulit_select_field_filter.js +25 -28
  17. package/dist/components/table_filter_panel/field_filters/mulit_select_field_filter.js.map +1 -1
  18. package/dist/components/table_filter_panel/field_filters/number_field_filter.d.ts.map +1 -1
  19. package/dist/components/table_filter_panel/field_filters/number_field_filter.js +37 -44
  20. package/dist/components/table_filter_panel/field_filters/number_field_filter.js.map +1 -1
  21. package/dist/components/table_filter_panel/field_filters/number_range_field_filter.d.ts.map +1 -1
  22. package/dist/components/table_filter_panel/field_filters/number_range_field_filter.js +46 -55
  23. package/dist/components/table_filter_panel/field_filters/number_range_field_filter.js.map +1 -1
  24. package/dist/components/table_filter_panel/field_filters/select_field_filter.d.ts.map +1 -1
  25. package/dist/components/table_filter_panel/field_filters/select_field_filter.js +19 -22
  26. package/dist/components/table_filter_panel/field_filters/select_field_filter.js.map +1 -1
  27. package/dist/components/table_filter_panel/field_filters/string_field_filter.d.ts.map +1 -1
  28. package/dist/components/table_filter_panel/field_filters/string_field_filter.js +45 -53
  29. package/dist/components/table_filter_panel/field_filters/string_field_filter.js.map +1 -1
  30. package/dist/components/table_filter_panel/table_filter_panel.d.ts +4 -3
  31. package/dist/components/table_filter_panel/table_filter_panel.d.ts.map +1 -1
  32. package/dist/components/table_filter_panel/table_filter_panel.js +38 -10
  33. package/dist/components/table_filter_panel/table_filter_panel.js.map +1 -1
  34. package/dist/table_filter_panel.css +1 -0
  35. package/package.json +4 -4
  36. package/src/__stories__/aip_table.stories.tsx +3 -3
  37. package/src/__stories__/demo.stories.tsx +4 -4
  38. package/src/__stories__/table.stories.tsx +14 -11
  39. package/src/components/cells/header_cell.tsx +1 -1
  40. package/src/components/table_filter_panel/field_filters/clear_button.tsx +17 -0
  41. package/src/components/table_filter_panel/field_filters/clearable_field.tsx +33 -0
  42. package/src/components/table_filter_panel/field_filters/date_field_filter.tsx +26 -50
  43. package/src/components/table_filter_panel/field_filters/mulit_select_field_filter.tsx +3 -9
  44. package/src/components/table_filter_panel/field_filters/number_field_filter.tsx +10 -20
  45. package/src/components/table_filter_panel/field_filters/number_range_field_filter.tsx +29 -48
  46. package/src/components/table_filter_panel/field_filters/select_field_filter.tsx +3 -9
  47. package/src/components/table_filter_panel/field_filters/string_field_filter.tsx +10 -21
  48. package/src/components/table_filter_panel/table_filter_panel.module.css +11 -0
  49. package/src/components/table_filter_panel/table_filter_panel.tsx +33 -4
@@ -1,6 +1,7 @@
1
1
  import { Meta } from '@storybook/react-vite';
2
2
  import React, { useCallback, useState } from 'react';
3
3
 
4
+ import { CrossIcon } from '@tcn/icons/cross_icon.js';
4
5
  import {
5
6
  StaticDataSource,
6
7
  StaticDateField,
@@ -9,8 +10,9 @@ import {
9
10
  } from '@tcn/resource-store';
10
11
  import { useSignalValue } from '@tcn/state';
11
12
  import { Button } from '@tcn/ui/actions';
13
+ import { Footer, Header, HBody, Rail, Side, VBody } from '@tcn/ui/layouts';
12
14
  import { Box, HStack, Spacer, VStack, ZStack } from '@tcn/ui/stacks';
13
- import { VPanel } from '@tcn/ui/surfaces';
15
+ import { Panel } from '@tcn/ui/surfaces';
14
16
  import { Title } from '@tcn/ui/typography';
15
17
  import { GlobalSearch } from '../components/global_search.js';
16
18
  import { Table } from '../components/table/table.js';
@@ -22,9 +24,7 @@ import { StringFieldFilter } from '../components/table_filter_panel/field_filter
22
24
  import { TableFilterPanel } from '../components/table_filter_panel/table_filter_panel.js';
23
25
  import { TablePager } from '../components/table_pager.js';
24
26
  import { DataItem, items, stickyItems } from './sample_data.js';
25
- import { CrossIcon } from '@tcn/icons/cross_icon.js';
26
27
  import styles from './table.module.css';
27
- import { Footer, Header, Main, Rail, Side, VBody } from '@tcn/ui/layouts';
28
28
 
29
29
  const meta: Meta = {
30
30
  title: 'Table',
@@ -302,7 +302,7 @@ export function WithTableHeaderAndTableFooter() {
302
302
 
303
303
  return (
304
304
  <StoryWrapper>
305
- <VPanel>
305
+ <Panel>
306
306
  <Header className={styles.header} padding="8px" vAlign="center">
307
307
  <Title>The Table</Title>
308
308
  <Spacer />
@@ -327,7 +327,7 @@ export function WithTableHeaderAndTableFooter() {
327
327
  <Footer>
328
328
  <TablePager dataSource={source} />
329
329
  </Footer>
330
- </VPanel>
330
+ </Panel>
331
331
  </StoryWrapper>
332
332
  );
333
333
  }
@@ -409,11 +409,11 @@ export function WithFilterPanel() {
409
409
 
410
410
  return (
411
411
  <StoryWrapper>
412
- <VPanel height="100%">
412
+ <Panel height="100%">
413
413
  <Header>The Table</Header>
414
414
  <VBody>
415
415
  <Rail>
416
- <Side>
416
+ <Side padding="0px">
417
417
  <Box
418
418
  minWidth="300px"
419
419
  enableResizeOnEnd
@@ -423,7 +423,10 @@ export function WithFilterPanel() {
423
423
  scrollbarGutter: 'stable', // Not sure if there is a better way to prevent the scrollbar appearing causing a horizontal scroll - to to resizing, we fix the width of the content, so adding a vertical scrollbar causing a horizontal overflow.
424
424
  }}
425
425
  >
426
- <TableFilterPanel dataSource={source}>
426
+ <TableFilterPanel
427
+ dataSource={source}
428
+ onClose={() => window.alert('Closed')}
429
+ >
427
430
  <StringFieldFilter fieldName="name" label="Name (string)" />
428
431
  <NumberFieldFilter fieldName="age" label="Age (number)" />
429
432
  <DateFieldFilter fieldName="birthdate" label="Birthdate (date range)" />
@@ -431,7 +434,7 @@ export function WithFilterPanel() {
431
434
  </TableFilterPanel>
432
435
  </Box>
433
436
  </Side>
434
- <Main>
437
+ <HBody>
435
438
  <Table dataSource={source} height="100%" width="flex">
436
439
  <TableColumn heading="Name" fieldName="name" sticky="start" />
437
440
  <TableColumn heading="Age" fieldName="age" width={150} canSort />
@@ -446,13 +449,13 @@ export function WithFilterPanel() {
446
449
  <TableColumn heading="Occupation" fieldName="occupation" width={200} />
447
450
  <TableColumn heading="Active" fieldName="isActive" />
448
451
  </Table>
449
- </Main>
452
+ </HBody>
450
453
  </Rail>
451
454
  </VBody>
452
455
  <Footer>
453
456
  <TablePager dataSource={source} />
454
457
  </Footer>
455
- </VPanel>
458
+ </Panel>
456
459
  </StoryWrapper>
457
460
  );
458
461
  }
@@ -2,9 +2,9 @@ import React, { useCallback } from 'react';
2
2
 
3
3
  import { Box, HStack } from '@tcn/ui/stacks';
4
4
 
5
+ import { TH } from '@tcn/ui/layouts';
5
6
  import cellStyles from './cell.module.css';
6
7
  import { SortControl, type SortControlProps } from './sort_control.js';
7
- import { TH } from '@tcn/ui/layouts';
8
8
 
9
9
  export interface HeaderCellProps extends SortControlProps {
10
10
  heading: React.ReactNode;
@@ -0,0 +1,17 @@
1
+ import { CrossCircleIcon } from '@tcn/icons/cross_circle_icon.js';
2
+ import { Button, type ButtonProps } from '@tcn/ui/actions';
3
+
4
+ export interface ClearFilterButtonProps extends Omit<ButtonProps, 'children'> {}
5
+
6
+ export function ClearFilterButton({
7
+ onClick,
8
+ hierarchy = 'tertiary',
9
+ utility = true,
10
+ ...props
11
+ }: ClearFilterButtonProps) {
12
+ return (
13
+ <Button utility={utility} onClick={onClick} hierarchy={hierarchy} {...props}>
14
+ <CrossCircleIcon />
15
+ </Button>
16
+ );
17
+ }
@@ -0,0 +1,33 @@
1
+ import { HStack, VStack, type VStackProps } from '@tcn/ui/stacks';
2
+ import { ClearFilterButton } from './clear_button.js';
3
+ import { FieldLabel } from '@tcn/ui/form';
4
+
5
+ export interface ClearableFieldProps extends VStackProps {
6
+ label?: string;
7
+ onClear?: () => void;
8
+ isClearable?: boolean;
9
+ inline?: boolean;
10
+ }
11
+
12
+ export function ClearableField({
13
+ children,
14
+ label,
15
+ onClear,
16
+ isClearable,
17
+ vAlign = 'start',
18
+ hAlign = 'start',
19
+ gap = '4px',
20
+ inline = false,
21
+ ...props
22
+ }: ClearableFieldProps) {
23
+ return (
24
+ <VStack vAlign={vAlign} hAlign={hAlign} gap={gap} {...props}>
25
+ {!inline && <FieldLabel>{label}</FieldLabel>}
26
+ <HStack width="flex" gap="4px">
27
+ {inline && <FieldLabel>{label}</FieldLabel>}
28
+ <HStack width="flex">{children}</HStack>
29
+ <ClearFilterButton onClick={onClear} disabled={isClearable} />
30
+ </HStack>
31
+ </VStack>
32
+ );
33
+ }
@@ -1,12 +1,9 @@
1
- import { CrossCircleIcon } from '@tcn/icons/cross_circle_icon.js';
2
1
  import { useSignalValue } from '@tcn/state';
3
- import { Button } from '@tcn/ui/actions';
4
2
  import { DatePickerInput } from '@tcn/ui/inputs';
5
- import { Box, HStack, VStack } from '@tcn/ui/stacks';
6
- import { BodyText, Title } from '@tcn/ui/typography';
7
- import React from 'react';
8
3
  import { useFieldFilterStrategy } from './use_field_filter_strategy.js';
9
4
  import { DateFieldFilterPresenter } from './date_field_filter_presenter.js';
5
+ import { ClearableField } from './clearable_field.js';
6
+ import { FieldSet } from '@tcn/ui/form';
10
7
 
11
8
  export function DateFieldFilter({
12
9
  fieldName,
@@ -21,50 +18,29 @@ export function DateFieldFilter({
21
18
  const endDate = useSignalValue(presenter.broadcasts.endDate);
22
19
 
23
20
  return (
24
- <VStack gap="4px">
25
- <Box width="flex">
26
- <Title size="md">{label}</Title>
27
- </Box>
28
- <HStack gap="4px">
29
- <Box width="auto">
30
- <BodyText size="lg">Start</BodyText>
31
- </Box>
32
- <Box width="flex">
33
- <DatePickerInput
34
- value={startDate}
35
- onChange={value => presenter.setStartDate(value)}
36
- />
37
- </Box>
38
- <Box width="auto">
39
- <Button
40
- onClick={() => presenter.setStartDate(null)}
41
- hierarchy="tertiary"
42
- disabled={startDate == null}
43
- >
44
- <CrossCircleIcon />
45
- </Button>
46
- </Box>
47
- </HStack>
48
- <HStack gap="4px">
49
- <Box width="auto">
50
- <BodyText size="lg">End</BodyText>
51
- </Box>
52
- <Box width="flex">
53
- <DatePickerInput
54
- value={endDate}
55
- onChange={value => presenter.setEndDate(value)}
56
- />
57
- </Box>
58
- <Box width="auto">
59
- <Button
60
- onClick={() => presenter.setEndDate(null)}
61
- hierarchy="tertiary"
62
- disabled={endDate == null}
63
- >
64
- <CrossCircleIcon />
65
- </Button>
66
- </Box>
67
- </HStack>
68
- </VStack>
21
+ <FieldSet legend={label} gap="4px">
22
+ <ClearableField
23
+ inline
24
+ label="Start"
25
+ onClear={() => presenter.setStartDate(null)}
26
+ isClearable={startDate == null}
27
+ >
28
+ <DatePickerInput
29
+ value={startDate}
30
+ onChange={value => presenter.setStartDate(value)}
31
+ />
32
+ </ClearableField>
33
+ <ClearableField
34
+ inline
35
+ label="End"
36
+ onClear={() => presenter.setEndDate(null)}
37
+ isClearable={endDate == null}
38
+ >
39
+ <DatePickerInput
40
+ value={endDate}
41
+ onChange={value => presenter.setEndDate(value)}
42
+ />
43
+ </ClearableField>
44
+ </FieldSet>
69
45
  );
70
46
  }
@@ -1,14 +1,11 @@
1
- import React from 'react';
2
-
3
- import { CrossCircleIcon } from '@tcn/icons/cross_circle_icon.js';
4
1
  import { useSignalValue } from '@tcn/state';
5
- import { Button } from '@tcn/ui/actions';
6
2
  import { Multiselect, Option } from '@tcn/ui/inputs';
7
3
  import { Box, HStack, VStack } from '@tcn/ui/stacks';
8
4
  import { Title } from '@tcn/ui/typography';
9
5
  import { FieldFilterProps } from './field_filter_props.js';
10
6
  import { MultiSelectFieldFilterPresenter } from './multi_select_field_filter_presenter.js';
11
7
  import { useFieldFilterStrategy } from './use_field_filter_strategy.js';
8
+ import { ClearFilterButton } from './clear_button.js';
12
9
 
13
10
  export type MulitSelectFieldFilterProps = FieldFilterProps & {
14
11
  options: { label: string; value: string | boolean | number }[];
@@ -55,13 +52,10 @@ export function MulitSelectFieldFilter({
55
52
  ))}
56
53
  </Multiselect>
57
54
  </Box>
58
- <Button
55
+ <ClearFilterButton
59
56
  onClick={() => presenter.setValue(null)}
60
- hierarchy="tertiary"
61
57
  disabled={values == null || values.length === 0}
62
- >
63
- <CrossCircleIcon />
64
- </Button>
58
+ />
65
59
  </HStack>
66
60
  </VStack>
67
61
  );
@@ -1,14 +1,10 @@
1
- import { CrossCircleIcon } from '@tcn/icons/cross_circle_icon.js';
2
1
  import { useSignalValue } from '@tcn/state';
3
- import { Button } from '@tcn/ui/actions';
4
- import { Input, Option, Select } from '@tcn/ui/inputs';
5
- import { Box, HStack, VStack } from '@tcn/ui/stacks';
6
- import { Title } from '@tcn/ui/typography';
7
- import React from 'react';
2
+ import { Input, Option, Select, InputGroup } from '@tcn/ui/inputs';
8
3
  import { FieldFilterProps } from './field_filter_props.js';
9
4
  import { NumberFieldFilterPresenter } from './number_field_filter_presenter.js';
10
5
  import { useFieldFilterStrategy } from './use_field_filter_strategy.js';
11
6
  import { ComparisonOperator } from '../types.js';
7
+ import { ClearableField } from './clearable_field.js';
12
8
 
13
9
  const operators: ComparisonOperator[] = ['>=', '>', '<=', '<', '=', '!='];
14
10
 
@@ -19,11 +15,12 @@ export function NumberFieldFilter({ fieldName, label }: FieldFilterProps) {
19
15
  const operator = useSignalValue(presenter.broadcasts.operator);
20
16
 
21
17
  return (
22
- <VStack gap="4px">
23
- <Box width="flex">
24
- <Title size="md">{label}</Title>
25
- </Box>
26
- <HStack width="flex">
18
+ <ClearableField
19
+ label={label}
20
+ onClear={() => presenter.setValue(null)}
21
+ isClearable={value == null}
22
+ >
23
+ <InputGroup>
27
24
  <Select
28
25
  value={operator}
29
26
  onChange={value => presenter.setOperator(value as ComparisonOperator)}
@@ -40,14 +37,7 @@ export function NumberFieldFilter({ fieldName, label }: FieldFilterProps) {
40
37
  value={String(value ?? '')}
41
38
  onChange={value => presenter.setValue(Number(value))}
42
39
  />
43
- <Button
44
- onClick={() => presenter.setValue(null)}
45
- hierarchy="tertiary"
46
- disabled={value == null}
47
- >
48
- <CrossCircleIcon />
49
- </Button>
50
- </HStack>
51
- </VStack>
40
+ </InputGroup>
41
+ </ClearableField>
52
42
  );
53
43
  }
@@ -1,12 +1,9 @@
1
- import { CrossCircleIcon } from '@tcn/icons/cross_circle_icon.js';
2
1
  import { useSignalValue } from '@tcn/state';
3
- import { Button } from '@tcn/ui/actions';
4
2
  import { Input } from '@tcn/ui/inputs';
5
- import { Box, HStack, VStack } from '@tcn/ui/stacks';
6
- import { BodyText, Title } from '@tcn/ui/typography';
7
- import React from 'react';
8
3
  import { useFieldFilterStrategy } from './use_field_filter_strategy.js';
9
4
  import { NumberRangeFieldFilterPresenter } from './number_range_field_filter_presenter.js';
5
+ import { FieldSet } from '@tcn/ui/form';
6
+ import { ClearableField } from './clearable_field.js';
10
7
 
11
8
  export function NumberRangeFieldFilter({
12
9
  fieldName,
@@ -21,48 +18,32 @@ export function NumberRangeFieldFilter({
21
18
  const maxValue = useSignalValue(presenter.broadcasts.maxValue);
22
19
 
23
20
  return (
24
- <VStack gap="4px">
25
- <Box width="flex">
26
- <Title size="md">{label}</Title>
27
- </Box>
28
- <HStack gap="4px">
29
- <Box width="auto">
30
- <BodyText size="lg">Min</BodyText>
31
- </Box>
32
- <Box width="flex">
33
- <Input
34
- type="number"
35
- value={String(minValue ?? '')}
36
- onChange={value => presenter.setMinValue(Number(value))}
37
- />
38
- </Box>
39
- <Button
40
- onClick={() => presenter.setMinValue(null)}
41
- hierarchy="tertiary"
42
- disabled={minValue == null}
43
- >
44
- <CrossCircleIcon />
45
- </Button>
46
- </HStack>
47
- <HStack gap="4px">
48
- <Box width="auto">
49
- <BodyText size="lg">Max</BodyText>
50
- </Box>
51
- <Box width="flex">
52
- <Input
53
- type="number"
54
- value={String(maxValue ?? '')}
55
- onChange={value => presenter.setMaxValue(Number(value))}
56
- />
57
- </Box>
58
- <Button
59
- onClick={() => presenter.setMaxValue(null)}
60
- hierarchy="tertiary"
61
- disabled={maxValue == null}
62
- >
63
- <CrossCircleIcon />
64
- </Button>
65
- </HStack>
66
- </VStack>
21
+ <FieldSet legend={label} gap="4px">
22
+ <ClearableField
23
+ label="Min"
24
+ onClear={() => presenter.setMinValue(null)}
25
+ isClearable={minValue == null}
26
+ inline
27
+ >
28
+ <Input
29
+ type="number"
30
+ value={String(minValue ?? '')}
31
+ onChange={value => presenter.setMinValue(Number(value))}
32
+ />
33
+ </ClearableField>
34
+
35
+ <ClearableField
36
+ label="Max"
37
+ onClear={() => presenter.setMaxValue(null)}
38
+ isClearable={maxValue == null}
39
+ inline
40
+ >
41
+ <Input
42
+ type="number"
43
+ value={String(maxValue ?? '')}
44
+ onChange={value => presenter.setMaxValue(Number(value))}
45
+ />
46
+ </ClearableField>
47
+ </FieldSet>
67
48
  );
68
49
  }
@@ -1,14 +1,11 @@
1
- import React from 'react';
2
-
3
- import { CrossCircleIcon } from '@tcn/icons/cross_circle_icon.js';
4
1
  import { useSignalValue } from '@tcn/state';
5
- import { Button } from '@tcn/ui/actions';
6
2
  import { Option, Select } from '@tcn/ui/inputs';
7
3
  import { Box, HStack, VStack } from '@tcn/ui/stacks';
8
4
  import { Title } from '@tcn/ui/typography';
9
5
  import { FieldFilterProps } from './field_filter_props.js';
10
6
  import { SelectFieldFilterPresenter } from './select_field_filter_presenter.js';
11
7
  import { useFieldFilterStrategy } from './use_field_filter_strategy.js';
8
+ import { ClearFilterButton } from './clear_button.js';
12
9
 
13
10
  export type SelectFieldFilterProps = FieldFilterProps & {
14
11
  options: { label: string; value: string | boolean | number }[];
@@ -44,13 +41,10 @@ export function SelectFieldFilter({ fieldName, label, options }: SelectFieldFilt
44
41
  </Option>
45
42
  ))}
46
43
  </Select>
47
- <Button
44
+ <ClearFilterButton
48
45
  onClick={() => presenter.setValue(null)}
49
- hierarchy="tertiary"
50
46
  disabled={value == null}
51
- >
52
- <CrossCircleIcon />
53
- </Button>
47
+ />
54
48
  </HStack>
55
49
  </VStack>
56
50
  );
@@ -1,15 +1,11 @@
1
- import { CrossCircleIcon } from '@tcn/icons/cross_circle_icon.js';
2
1
  import { useSignalValue } from '@tcn/state';
3
- import { Button } from '@tcn/ui/actions';
4
- import { Input, Option, Select } from '@tcn/ui/inputs';
5
- import { Box, HStack, VStack } from '@tcn/ui/stacks';
6
- import { Title } from '@tcn/ui/typography';
7
- import React from 'react';
2
+ import { Input, Option, Select, InputGroup } from '@tcn/ui/inputs';
8
3
  import { FieldFilterProps } from './field_filter_props.js';
9
4
  import { StringFieldFilterPresenter } from './string_field_filter_presenter.js';
10
5
  import { ComparisonOperator } from '../types.js';
11
6
 
12
7
  import { useFieldFilterStrategy } from './use_field_filter_strategy.js';
8
+ import { ClearableField } from './clearable_field.js';
13
9
 
14
10
  const allOperators: ('is' | 'isNot' | 'has')[] = ['is', 'isNot', 'has'];
15
11
  const operatorSymbols: Record<'is' | 'isNot' | 'has', string> = {
@@ -32,11 +28,12 @@ export function StringFieldFilter({ fieldName, label, operators }: FieldFilterPr
32
28
  const availableOperators = operators || allOperators;
33
29
 
34
30
  return (
35
- <VStack gap="4px">
36
- <Box width="flex">
37
- <Title size="md">{label}</Title>
38
- </Box>
39
- <HStack width="flex">
31
+ <ClearableField
32
+ label={label}
33
+ onClear={() => presenter.setValue(null)}
34
+ isClearable={value == null}
35
+ >
36
+ <InputGroup>
40
37
  <Select
41
38
  value={operator}
42
39
  onChange={value => presenter.setOperator(value as ComparisonOperator)}
@@ -57,15 +54,7 @@ export function StringFieldFilter({ fieldName, label, operators }: FieldFilterPr
57
54
  value={value ?? ''}
58
55
  onChange={value => presenter.setValue(value)}
59
56
  />
60
- <Button
61
- onClick={() => presenter.setValue(null)}
62
- hierarchy="tertiary"
63
- utility
64
- disabled={value == null}
65
- >
66
- <CrossCircleIcon />
67
- </Button>
68
- </HStack>
69
- </VStack>
57
+ </InputGroup>
58
+ </ClearableField>
70
59
  );
71
60
  }
@@ -0,0 +1,11 @@
1
+ .table-filter-panel {
2
+ --material: #fafafa;
3
+ background: var(--material);
4
+ }
5
+
6
+ .table-filter-panel-body {
7
+ padding-block: 8px;
8
+ .table-filter-panel-section {
9
+ gap: 8px;
10
+ }
11
+ }
@@ -1,22 +1,51 @@
1
1
  import { DataSource } from '@tcn/resource-store';
2
- import { VStack } from '@tcn/ui/stacks';
3
- import React, { ReactElement, useState, createContext } from 'react';
2
+ import { ReactElement, useState, createContext } from 'react';
4
3
  import { FieldFilterProps } from './field_filters/field_filter_props.js';
5
4
  import { TableFilterPanelPresenter } from './table_filter_panel_presenter.js';
5
+ import { Panel } from '@tcn/ui/surfaces';
6
+ import { Header, Section, VBody } from '@tcn/ui/layouts';
7
+ import styles from './table_filter_panel.module.css';
8
+ import { Spacer } from '@tcn/ui/stacks';
9
+ import { Button } from '@tcn/ui/actions';
10
+ import { CrossIcon } from '@tcn/icons/cross_icon.js';
6
11
 
7
12
  export type TableFilterPanelProps = {
8
13
  children: ReactElement<FieldFilterProps>[] | ReactElement<FieldFilterProps>;
9
14
  dataSource: DataSource<any>;
15
+ onClose?: () => void;
10
16
  };
11
17
 
12
- export function TableFilterPanel({ children, dataSource }: TableFilterPanelProps) {
18
+ export function TableFilterPanel({
19
+ children,
20
+ dataSource,
21
+ onClose,
22
+ }: TableFilterPanelProps) {
13
23
  const [presenter] = useState(() => {
14
24
  return new TableFilterPanelPresenter(dataSource);
15
25
  });
16
26
 
17
27
  return (
18
28
  <TableFilterPanelContext.Provider value={presenter}>
19
- <VStack gap="16px">{children}</VStack>
29
+ <Panel className={`${styles['table-filter-panel']} tcn-table-filter-panel`}>
30
+ <Header>
31
+ Table Filters
32
+ <Spacer />
33
+ {onClose && (
34
+ <Button utility hierarchy="tertiary" onClick={onClose}>
35
+ <CrossIcon />
36
+ </Button>
37
+ )}
38
+ </Header>
39
+ <VBody
40
+ className={`${styles['table-filter-panel-body']} tcn-table-filter-panel-body`}
41
+ >
42
+ <Section
43
+ className={`${styles['table-filter-panel-section']} tcn-table-filter-panel-section`}
44
+ >
45
+ {children}
46
+ </Section>
47
+ </VBody>
48
+ </Panel>
20
49
  </TableFilterPanelContext.Provider>
21
50
  );
22
51
  }