@arbor-education/design-system.components 0.1.5 → 0.2.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.
Files changed (105) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/components/formField/fieldset/Fieldset.d.ts +6 -0
  3. package/dist/components/formField/fieldset/Fieldset.d.ts.map +1 -0
  4. package/dist/components/formField/fieldset/Fieldset.js +7 -0
  5. package/dist/components/formField/fieldset/Fieldset.js.map +1 -0
  6. package/dist/components/formField/fieldset/Fieldset.stories.d.ts +28 -0
  7. package/dist/components/formField/fieldset/Fieldset.stories.d.ts.map +1 -0
  8. package/dist/components/formField/fieldset/Fieldset.stories.js +51 -0
  9. package/dist/components/formField/fieldset/Fieldset.stories.js.map +1 -0
  10. package/dist/components/formField/fieldset/Fieldset.test.d.ts +2 -0
  11. package/dist/components/formField/fieldset/Fieldset.test.d.ts.map +1 -0
  12. package/dist/components/formField/fieldset/Fieldset.test.js +62 -0
  13. package/dist/components/formField/fieldset/Fieldset.test.js.map +1 -0
  14. package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts +8 -0
  15. package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts.map +1 -0
  16. package/dist/components/formField/inputs/checkbox/CheckboxGroup.js +8 -0
  17. package/dist/components/formField/inputs/checkbox/CheckboxGroup.js.map +1 -0
  18. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.d.ts +2 -0
  19. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.d.ts.map +1 -0
  20. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.js +86 -0
  21. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.js.map +1 -0
  22. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +3 -1
  23. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
  24. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +7 -0
  25. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
  26. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts +11 -0
  27. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts.map +1 -0
  28. package/dist/components/formField/inputs/radio/RadioButtonGroup.js +8 -0
  29. package/dist/components/formField/inputs/radio/RadioButtonGroup.js.map +1 -0
  30. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.d.ts +2 -0
  31. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.d.ts.map +1 -0
  32. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.js +86 -0
  33. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.js.map +1 -0
  34. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
  35. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +8 -2
  36. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
  37. package/dist/components/separator/Separator.d.ts +7 -0
  38. package/dist/components/separator/Separator.d.ts.map +1 -0
  39. package/dist/components/separator/Separator.js +8 -0
  40. package/dist/components/separator/Separator.js.map +1 -0
  41. package/dist/components/separator/Separator.stories.d.ts +10 -0
  42. package/dist/components/separator/Separator.stories.d.ts.map +1 -0
  43. package/dist/components/separator/Separator.stories.js +12 -0
  44. package/dist/components/separator/Separator.stories.js.map +1 -0
  45. package/dist/components/separator/Separator.test.d.ts +2 -0
  46. package/dist/components/separator/Separator.test.d.ts.map +1 -0
  47. package/dist/components/separator/Separator.test.js +10 -0
  48. package/dist/components/separator/Separator.test.js.map +1 -0
  49. package/dist/components/table/Table.d.ts +13 -0
  50. package/dist/components/table/Table.d.ts.map +1 -1
  51. package/dist/components/table/Table.js +43 -7
  52. package/dist/components/table/Table.js.map +1 -1
  53. package/dist/components/table/Table.stories.d.ts.map +1 -1
  54. package/dist/components/table/Table.stories.js +8 -1
  55. package/dist/components/table/Table.stories.js.map +1 -1
  56. package/dist/components/table/Table.test.js +254 -2
  57. package/dist/components/table/Table.test.js.map +1 -1
  58. package/dist/components/table/pagination/TableSettingsDropdown.d.ts +2 -0
  59. package/dist/components/table/pagination/TableSettingsDropdown.d.ts.map +1 -0
  60. package/dist/components/table/pagination/TableSettingsDropdown.js +43 -0
  61. package/dist/components/table/pagination/TableSettingsDropdown.js.map +1 -0
  62. package/dist/components/table/useTableSettings.d.ts +22 -0
  63. package/dist/components/table/useTableSettings.d.ts.map +1 -0
  64. package/dist/components/table/useTableSettings.js +28 -0
  65. package/dist/components/table/useTableSettings.js.map +1 -0
  66. package/dist/index.css +31 -1
  67. package/dist/index.css.map +1 -1
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +2 -0
  71. package/dist/index.js.map +1 -1
  72. package/dist/utils/hooks/useComponentDidUpdate.d.ts +7 -0
  73. package/dist/utils/hooks/useComponentDidUpdate.d.ts.map +1 -0
  74. package/dist/utils/hooks/useComponentDidUpdate.js +18 -0
  75. package/dist/utils/hooks/useComponentDidUpdate.js.map +1 -0
  76. package/dist/utils/hooks/useComponentDidUpdate.test.d.ts +2 -0
  77. package/dist/utils/hooks/useComponentDidUpdate.test.d.ts.map +1 -0
  78. package/dist/utils/hooks/useComponentDidUpdate.test.js +69 -0
  79. package/dist/utils/hooks/useComponentDidUpdate.test.js.map +1 -0
  80. package/package.json +1 -1
  81. package/src/components/formField/fieldset/Fieldset.stories.tsx +89 -0
  82. package/src/components/formField/fieldset/Fieldset.test.tsx +85 -0
  83. package/src/components/formField/fieldset/Fieldset.tsx +17 -0
  84. package/src/components/formField/fieldset/fieldset.scss +19 -0
  85. package/src/components/formField/inputs/checkbox/CheckboxGroup.test.tsx +127 -0
  86. package/src/components/formField/inputs/checkbox/CheckboxGroup.tsx +17 -0
  87. package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +12 -1
  88. package/src/components/formField/inputs/radio/RadioButtonGroup.test.tsx +190 -0
  89. package/src/components/formField/inputs/radio/RadioButtonGroup.tsx +22 -0
  90. package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +16 -7
  91. package/src/components/formField/label/label.scss +1 -1
  92. package/src/components/separator/Separator.stories.tsx +15 -0
  93. package/src/components/separator/Separator.test.tsx +10 -0
  94. package/src/components/separator/Separator.tsx +15 -0
  95. package/src/components/separator/separator.scss +6 -0
  96. package/src/components/table/Table.stories.tsx +8 -1
  97. package/src/components/table/Table.test.tsx +444 -1
  98. package/src/components/table/Table.tsx +69 -24
  99. package/src/components/table/pagination/TableSettingsDropdown.tsx +90 -0
  100. package/src/components/table/table.scss +6 -0
  101. package/src/components/table/useTableSettings.ts +47 -0
  102. package/src/index.scss +2 -0
  103. package/src/index.ts +2 -0
  104. package/src/utils/hooks/useComponentDidUpdate.test.ts +107 -0
  105. package/src/utils/hooks/useComponentDidUpdate.ts +19 -0
@@ -1,9 +1,10 @@
1
1
  import { describe, expect, expectTypeOf, test, vi } from 'vitest';
2
2
  import { render, screen, waitFor } from '@testing-library/react';
3
- import { Table } from './Table';
3
+ import { Table, TABLE_SPACING } from './Table';
4
4
  import '@testing-library/jest-dom/vitest';
5
5
  import { BulkActionsDropdown } from 'Components/table/BulkActionsDropdown';
6
6
  import { HideColumnsDropdown } from 'Components/table/HideColumnsDropdown';
7
+ import { TableSettingsDropdown } from './pagination/TableSettingsDropdown';
7
8
  import userEvent from '@testing-library/user-event';
8
9
  import type { GridApi } from 'ag-grid-enterprise';
9
10
 
@@ -409,4 +410,446 @@ describe('Table', () => {
409
410
  expect(container.querySelector('button')).toBeNull();
410
411
  });
411
412
  });
413
+
414
+ describe('TableSettingsDropdown', () => {
415
+ test('renders settings dropdown button in header', async () => {
416
+ render(
417
+ <Table
418
+ headerContent={<TableSettingsDropdown />}
419
+ />,
420
+ );
421
+
422
+ await waitFor(() => {
423
+ const button = screen.getByRole('button', { name: /Table settings/i });
424
+ expect(button).toBeInTheDocument();
425
+ });
426
+ });
427
+
428
+ test('opens dropdown when settings button is clicked', async () => {
429
+ render(
430
+ <Table
431
+ headerContent={<TableSettingsDropdown />}
432
+ />,
433
+ );
434
+
435
+ const settingsButton = screen.getByRole('button', { name: /Table settings/i });
436
+ await userEvent.click(settingsButton);
437
+
438
+ await waitFor(() => {
439
+ expect(screen.getByText('Table spacing')).toBeInTheDocument();
440
+ expect(screen.getByText('Style')).toBeInTheDocument();
441
+ expect(screen.getByText('Reset Table Settings')).toBeInTheDocument();
442
+ });
443
+ });
444
+
445
+ test('displays all table spacing options', async () => {
446
+ render(
447
+ <Table
448
+ headerContent={<TableSettingsDropdown />}
449
+ />,
450
+ );
451
+
452
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
453
+
454
+ await waitFor(() => {
455
+ expect(screen.getByLabelText('X-Small')).toBeInTheDocument();
456
+ expect(screen.getByLabelText('Small')).toBeInTheDocument();
457
+ expect(screen.getByLabelText('Medium')).toBeInTheDocument();
458
+ expect(screen.getByLabelText('Large')).toBeInTheDocument();
459
+ });
460
+ });
461
+
462
+ test('Medium spacing is selected by default', async () => {
463
+ render(
464
+ <Table
465
+ headerContent={<TableSettingsDropdown />}
466
+ />,
467
+ );
468
+
469
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
470
+
471
+ await waitFor(() => {
472
+ const mediumRadio = screen.getByLabelText('Medium') as HTMLInputElement;
473
+ expect(mediumRadio.checked).toBe(true);
474
+ });
475
+ });
476
+
477
+ test('can change table spacing to Small', async () => {
478
+ render(
479
+ <Table
480
+ headerContent={<TableSettingsDropdown />}
481
+ />,
482
+ );
483
+
484
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
485
+
486
+ await waitFor(() => {
487
+ expect(screen.getByLabelText('Small')).toBeInTheDocument();
488
+ });
489
+
490
+ const smallRadio = screen.getByLabelText('Small');
491
+ await userEvent.click(smallRadio);
492
+
493
+ await waitFor(() => {
494
+ expect((smallRadio as HTMLInputElement).checked).toBe(true);
495
+ });
496
+ });
497
+
498
+ test('can change table spacing to Large', async () => {
499
+ render(
500
+ <Table
501
+ headerContent={<TableSettingsDropdown />}
502
+ />,
503
+ );
504
+
505
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
506
+
507
+ const largeRadio = screen.getByLabelText('Large');
508
+ await userEvent.click(largeRadio);
509
+
510
+ await waitFor(() => {
511
+ expect((largeRadio as HTMLInputElement).checked).toBe(true);
512
+ });
513
+ });
514
+
515
+ test('can change table spacing to X-Small', async () => {
516
+ render(
517
+ <Table
518
+ headerContent={<TableSettingsDropdown />}
519
+ />,
520
+ );
521
+
522
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
523
+
524
+ const xsmallRadio = screen.getByLabelText('X-Small');
525
+ await userEvent.click(xsmallRadio);
526
+
527
+ await waitFor(() => {
528
+ expect((xsmallRadio as HTMLInputElement).checked).toBe(true);
529
+ });
530
+ });
531
+
532
+ test('displays style options with Column borders and Cell colours', async () => {
533
+ render(
534
+ <Table
535
+ headerContent={<TableSettingsDropdown />}
536
+ />,
537
+ );
538
+
539
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
540
+
541
+ await waitFor(() => {
542
+ expect(screen.getByLabelText('Column borders')).toBeInTheDocument();
543
+ expect(screen.getByLabelText('Cell colours')).toBeInTheDocument();
544
+ });
545
+ });
546
+
547
+ test('Column borders checkbox is unchecked by default', async () => {
548
+ render(
549
+ <Table
550
+ headerContent={<TableSettingsDropdown />}
551
+ />,
552
+ );
553
+
554
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
555
+
556
+ await waitFor(() => {
557
+ const columnBordersCheckbox = screen.getByLabelText('Column borders') as HTMLInputElement;
558
+ expect(columnBordersCheckbox.checked).toBe(false);
559
+ });
560
+ });
561
+
562
+ test('can toggle Column borders checkbox on', async () => {
563
+ render(
564
+ <Table
565
+ headerContent={<TableSettingsDropdown />}
566
+ />,
567
+ );
568
+
569
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
570
+
571
+ const columnBordersCheckbox = screen.getByLabelText('Column borders');
572
+ await userEvent.click(columnBordersCheckbox);
573
+
574
+ await waitFor(() => {
575
+ expect((columnBordersCheckbox as HTMLInputElement).checked).toBe(true);
576
+ });
577
+ });
578
+
579
+ test('can toggle Column borders checkbox off after turning it on', async () => {
580
+ render(
581
+ <Table
582
+ headerContent={<TableSettingsDropdown />}
583
+ />,
584
+ );
585
+
586
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
587
+
588
+ const columnBordersCheckbox = screen.getByLabelText('Column borders');
589
+ await userEvent.click(columnBordersCheckbox);
590
+ await userEvent.click(columnBordersCheckbox);
591
+
592
+ await waitFor(() => {
593
+ expect((columnBordersCheckbox as HTMLInputElement).checked).toBe(false);
594
+ });
595
+ });
596
+
597
+ test('Cell colours checkbox is unchecked by default', async () => {
598
+ render(
599
+ <Table
600
+ headerContent={<TableSettingsDropdown />}
601
+ />,
602
+ );
603
+
604
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
605
+
606
+ await waitFor(() => {
607
+ const cellColoursCheckbox = screen.getByLabelText('Cell colours') as HTMLInputElement;
608
+ expect(cellColoursCheckbox.checked).toBe(false);
609
+ });
610
+ });
611
+
612
+ test('can toggle Cell colours checkbox on', async () => {
613
+ render(
614
+ <Table
615
+ headerContent={<TableSettingsDropdown />}
616
+ />,
617
+ );
618
+
619
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
620
+
621
+ const cellColoursCheckbox = screen.getByLabelText('Cell colours');
622
+ await userEvent.click(cellColoursCheckbox);
623
+
624
+ await waitFor(() => {
625
+ expect((cellColoursCheckbox as HTMLInputElement).checked).toBe(true);
626
+ });
627
+ });
628
+
629
+ test('can toggle Cell colours checkbox off after turning it on', async () => {
630
+ render(
631
+ <Table
632
+ headerContent={<TableSettingsDropdown />}
633
+ />,
634
+ );
635
+
636
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
637
+
638
+ const cellColoursCheckbox = screen.getByLabelText('Cell colours');
639
+ await userEvent.click(cellColoursCheckbox);
640
+ await userEvent.click(cellColoursCheckbox);
641
+
642
+ await waitFor(() => {
643
+ expect((cellColoursCheckbox as HTMLInputElement).checked).toBe(false);
644
+ });
645
+ });
646
+
647
+ test('displays Reset Table Settings button', async () => {
648
+ render(
649
+ <Table
650
+ headerContent={<TableSettingsDropdown />}
651
+ />,
652
+ );
653
+
654
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
655
+
656
+ await waitFor(() => {
657
+ expect(screen.getByRole('button', { name: /Reset Table Settings/i })).toBeInTheDocument();
658
+ });
659
+ });
660
+
661
+ test('reset button resets table spacing to default (Medium)', async () => {
662
+ render(
663
+ <Table
664
+ headerContent={<TableSettingsDropdown />}
665
+ />,
666
+ );
667
+
668
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
669
+
670
+ // Change to Large
671
+ const largeRadio = screen.getByLabelText('Large');
672
+ await userEvent.click(largeRadio);
673
+
674
+ await waitFor(() => {
675
+ expect((largeRadio as HTMLInputElement).checked).toBe(true);
676
+ });
677
+
678
+ // Click reset button
679
+ const resetButton = screen.getByRole('button', { name: /Reset Table Settings/i });
680
+ await userEvent.click(resetButton);
681
+
682
+ await waitFor(() => {
683
+ const mediumRadio = screen.getByLabelText('Medium') as HTMLInputElement;
684
+ expect(mediumRadio.checked).toBe(true);
685
+ });
686
+ });
687
+
688
+ test('reset button resets column borders to default (off)', async () => {
689
+ render(
690
+ <Table
691
+ headerContent={<TableSettingsDropdown />}
692
+ />,
693
+ );
694
+
695
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
696
+
697
+ // Turn on column borders
698
+ const columnBordersCheckbox = screen.getByLabelText('Column borders');
699
+ await userEvent.click(columnBordersCheckbox);
700
+
701
+ await waitFor(() => {
702
+ expect((columnBordersCheckbox as HTMLInputElement).checked).toBe(true);
703
+ });
704
+
705
+ // Click reset button
706
+ const resetButton = screen.getByRole('button', { name: /Reset Table Settings/i });
707
+ await userEvent.click(resetButton);
708
+
709
+ await waitFor(() => {
710
+ expect((columnBordersCheckbox as HTMLInputElement).checked).toBe(false);
711
+ });
712
+ });
713
+
714
+ test('reset button resets all settings together', async () => {
715
+ render(
716
+ <Table
717
+ headerContent={<TableSettingsDropdown />}
718
+ />,
719
+ );
720
+
721
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
722
+
723
+ // Change multiple settings
724
+ const largeRadio = screen.getByLabelText('Large');
725
+ await userEvent.click(largeRadio);
726
+
727
+ const columnBordersCheckbox = screen.getByLabelText('Column borders');
728
+ await userEvent.click(columnBordersCheckbox);
729
+
730
+ await waitFor(() => {
731
+ expect((largeRadio as HTMLInputElement).checked).toBe(true);
732
+ expect((columnBordersCheckbox as HTMLInputElement).checked).toBe(true);
733
+ });
734
+
735
+ // Click reset button
736
+ const resetButton = screen.getByRole('button', { name: /Reset Table Settings/i });
737
+ await userEvent.click(resetButton);
738
+
739
+ await waitFor(() => {
740
+ const mediumRadio = screen.getByLabelText('Medium') as HTMLInputElement;
741
+ expect(mediumRadio.checked).toBe(true);
742
+ expect((columnBordersCheckbox as HTMLInputElement).checked).toBe(false);
743
+ });
744
+ });
745
+
746
+ test('can combine settings dropdown with other header content', async () => {
747
+ render(
748
+ <Table
749
+ headerContent={(
750
+ <>
751
+ <BulkActionsDropdown
752
+ actions={[
753
+ {
754
+ displayName: 'Test Action',
755
+ callback: () => {},
756
+ },
757
+ ]}
758
+ />
759
+ <TableSettingsDropdown />
760
+ </>
761
+ )}
762
+ />,
763
+ );
764
+
765
+ await waitFor(() => {
766
+ expect(screen.getByText('Actions (1)')).toBeInTheDocument();
767
+ expect(screen.getByRole('button', { name: /Table settings/i })).toBeInTheDocument();
768
+ });
769
+ });
770
+
771
+ test('settings persist when dropdown is closed and reopened', async () => {
772
+ render(
773
+ <Table
774
+ headerContent={<TableSettingsDropdown />}
775
+ />,
776
+ );
777
+
778
+ // Open dropdown and change settings
779
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
780
+
781
+ const smallRadio = screen.getByLabelText('Small');
782
+ await userEvent.click(smallRadio);
783
+
784
+ const columnBordersCheckbox = screen.getByLabelText('Column borders');
785
+ await userEvent.click(columnBordersCheckbox);
786
+
787
+ // Close dropdown
788
+ await userEvent.keyboard('{Escape}');
789
+
790
+ // Reopen dropdown
791
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
792
+
793
+ // Check settings persisted
794
+ await waitFor(() => {
795
+ expect((screen.getByLabelText('Small') as HTMLInputElement).checked).toBe(true);
796
+ expect((screen.getByLabelText('Column borders') as HTMLInputElement).checked).toBe(true);
797
+ });
798
+ });
799
+ });
800
+
801
+ test('onTableSettingsChanged gets called appropriately', async () => {
802
+ const onTableSettingsChanged = vi.fn();
803
+
804
+ render(
805
+ <Table
806
+ headerContent={<TableSettingsDropdown />}
807
+ onTableSettingsChanged={onTableSettingsChanged}
808
+ />,
809
+ );
810
+
811
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
812
+
813
+ const largeRadio = screen.getByLabelText('Large');
814
+ await userEvent.click(largeRadio);
815
+
816
+ await waitFor(() => {
817
+ expect((largeRadio as HTMLInputElement).checked).toBe(true);
818
+ });
819
+ expect(onTableSettingsChanged).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({
820
+ tableSpacing: TABLE_SPACING.L,
821
+ }));
822
+
823
+ const columnBordersCheckbox = screen.getByLabelText('Column borders');
824
+ await userEvent.click(columnBordersCheckbox);
825
+
826
+ expect(onTableSettingsChanged).toHaveBeenCalledTimes(2);
827
+ expect(onTableSettingsChanged).toHaveBeenNthCalledWith(2, expect.objectContaining({ hasColumnBorders: true }));
828
+ });
829
+
830
+ test('resetSettings is called appropriately', async () => {
831
+ const onTableSettingsReset = vi.fn();
832
+
833
+ render(
834
+ <Table
835
+ headerContent={<TableSettingsDropdown />}
836
+ onTableSettingsReset={onTableSettingsReset}
837
+ />,
838
+ );
839
+
840
+ await userEvent.click(screen.getByRole('button', { name: /Table settings/i }));
841
+
842
+ const largeRadio = screen.getByLabelText('Large');
843
+ await userEvent.click(largeRadio);
844
+
845
+ await waitFor(() => {
846
+ expect((largeRadio as HTMLInputElement).checked).toBe(true);
847
+ });
848
+
849
+ // Click reset button
850
+ const resetButton = screen.getByRole('button', { name: /Reset Table Settings/i });
851
+ await userEvent.click(resetButton);
852
+
853
+ expect(onTableSettingsReset).toHaveBeenCalledOnce();
854
+ });
412
855
  });
@@ -2,7 +2,7 @@ import { AllEnterpriseModule, ModuleRegistry, type GridApi } from 'ag-grid-enter
2
2
  import { AgGridReact, type AgGridReactProps } from 'ag-grid-react';
3
3
  import { tableTheme } from './tableTheme';
4
4
  import classNames from 'classnames';
5
- import { useState, type ReactNode } from 'react';
5
+ import { createContext, useState, type ReactNode } from 'react';
6
6
  import { TableFooter } from './TableFooter';
7
7
  import { TableHeader } from './TableHeader';
8
8
  import { GridApiContext } from './GridApiContext';
@@ -12,6 +12,7 @@ import { PaginationControls } from './pagination/PaginationControls';
12
12
  import { RowCountInfo } from './pagination/RowCountInfo';
13
13
  import { BulkActionsDropdown } from './BulkActionsDropdown';
14
14
  import { HideColumnsDropdown } from './HideColumnsDropdown';
15
+ import { useTableSettings, type TableSettings } from './useTableSettings';
15
16
 
16
17
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
18
  type TableProps<TData = any> = {
@@ -22,10 +23,29 @@ type TableProps<TData = any> = {
22
23
  'footerTestId'?: string;
23
24
  'headerContent'?: ReactNode;
24
25
  'headerTestId'?: string;
26
+ 'onTableSettingsChanged'?: (val: TableSettings) => void;
27
+ 'onTableSettingsReset'?: () => void;
25
28
  } & AgGridReactProps<TData>;
26
29
 
27
30
  ModuleRegistry.registerModules([AllEnterpriseModule]);
28
31
 
32
+ export enum TABLE_SPACING {
33
+ XS = 'XS',
34
+ S = 'S',
35
+ M = 'M',
36
+ L = 'L',
37
+ }
38
+
39
+ export const TableSettingsContext = createContext<{ settings: TableSettings; resetSettings: () => void }>({
40
+ settings: {
41
+ hasColumnBorders: false,
42
+ setHasColumnBorders: () => {},
43
+ tableSpacing: TABLE_SPACING.S,
44
+ setTableSpacing: () => {},
45
+ },
46
+ resetSettings: () => {},
47
+ });
48
+
29
49
  export const Table = (props: TableProps) => {
30
50
  const {
31
51
  'data-testid': testId,
@@ -36,6 +56,8 @@ export const Table = (props: TableProps) => {
36
56
  footerContent,
37
57
  footerTestId,
38
58
  onGridReady,
59
+ onTableSettingsChanged,
60
+ onTableSettingsReset,
39
61
  ...rest
40
62
  } = props;
41
63
 
@@ -43,31 +65,54 @@ export const Table = (props: TableProps) => {
43
65
 
44
66
  const [searchValue, setSearchValue] = useState('');
45
67
 
68
+ const { settings, resetSettings } = useTableSettings({
69
+ onTableSettingsChanged,
70
+ onTableSettingsReset,
71
+ });
72
+
73
+ const { hasColumnBorders, tableSpacing } = settings;
74
+
46
75
  return (
47
76
  <GridApiContext.Provider value={gridApi}>
48
- <section data-testid={testId} className={classNames('ds-table__container', wrapperClassName)}>
49
- {headerContent && (
50
- <TableHeader data-testid={headerTestId} hasSearch={hasSearch} searchValue={searchValue} setSearchValue={setSearchValue}>
51
- {headerContent}
52
- </TableHeader>
53
- )}
54
- <AgGridReact
55
- theme={tableTheme}
56
- onGridReady={(event) => {
57
- const { api } = event;
58
- setGridApi(api);
59
- onGridReady?.(event);
60
- }}
61
- suppressPaginationPanel
62
- {...rest}
63
- {...(hasSearch && { quickFilterText: searchValue })}
64
- />
65
- {footerContent && (
66
- <TableFooter data-testid={footerTestId}>
67
- {footerContent}
68
- </TableFooter>
69
- )}
70
- </section>
77
+ <TableSettingsContext.Provider value={{ settings, resetSettings }}>
78
+ <section data-testid={testId} className={classNames('ds-table__container', wrapperClassName)}>
79
+ {headerContent && (
80
+ <TableHeader data-testid={headerTestId} hasSearch={hasSearch} searchValue={searchValue} setSearchValue={setSearchValue}>
81
+ {headerContent}
82
+ </TableHeader>
83
+ )}
84
+ <AgGridReact
85
+ theme={tableTheme.withParams({
86
+ headerRowBorder: hasColumnBorders,
87
+ rowBorder: hasColumnBorders,
88
+ wrapperBorder: hasColumnBorders,
89
+ columnBorder: hasColumnBorders,
90
+ spacing: {
91
+ // These are offset by one from the usual spacing tokens. We still use the token values to make spacing
92
+ // consistent with other parts of the DS, but --spacing-xsmall was too large for xsmall spacing, and so on
93
+ // for the other tokens
94
+ [TABLE_SPACING.XS]: 0,
95
+ [TABLE_SPACING.S]: 'var(--spacing-xsmall)',
96
+ [TABLE_SPACING.M]: 'var(--spacing-small)',
97
+ [TABLE_SPACING.L]: 'var(--spacing-medium)',
98
+ }[tableSpacing],
99
+ })}
100
+ onGridReady={(event) => {
101
+ const { api } = event;
102
+ setGridApi(api);
103
+ onGridReady?.(event);
104
+ }}
105
+ suppressPaginationPanel
106
+ {...rest}
107
+ {...(hasSearch && { quickFilterText: searchValue })}
108
+ />
109
+ {footerContent && (
110
+ <TableFooter data-testid={footerTestId}>
111
+ {footerContent}
112
+ </TableFooter>
113
+ )}
114
+ </section>
115
+ </TableSettingsContext.Provider>
71
116
  </GridApiContext.Provider>
72
117
  );
73
118
  };
@@ -0,0 +1,90 @@
1
+ import { Button } from 'Components/button/Button';
2
+ import { Dropdown } from 'Components/dropdown/Dropdown';
3
+ import { CheckboxGroup } from 'Components/formField/inputs/checkbox/CheckboxGroup';
4
+ import { RadioButtonGroup } from 'Components/formField/inputs/radio/RadioButtonGroup';
5
+ import { Separator } from 'Components/separator/Separator';
6
+ import { useContext, useState, type ChangeEvent } from 'react';
7
+ import { TABLE_SPACING, TableSettingsContext } from '../Table';
8
+
9
+ const TABLE_SPACING_NAMES = {
10
+ XS: 'X-Small',
11
+ S: 'Small',
12
+ M: 'Medium',
13
+ L: 'Large',
14
+ };
15
+
16
+ export const TableSettingsDropdown = () => {
17
+ const {
18
+ settings: {
19
+ hasColumnBorders,
20
+ setHasColumnBorders,
21
+ tableSpacing,
22
+ setTableSpacing,
23
+ },
24
+ resetSettings,
25
+ } = useContext(TableSettingsContext);
26
+
27
+ const [cellColours, setCellColours] = useState<boolean>(false);
28
+
29
+ return (
30
+ <Dropdown>
31
+ <Dropdown.Trigger>
32
+ <Button
33
+ variant="secondary"
34
+ iconRightName="settings"
35
+ iconRightScreenReaderText="Table settings"
36
+ borderless
37
+ />
38
+ </Dropdown.Trigger>
39
+ <Dropdown.Content>
40
+ <RadioButtonGroup
41
+ legend="Table spacing"
42
+ options={Object.values(TABLE_SPACING).map((option) => {
43
+ return {
44
+ name: 'ds-table-spacing',
45
+ label: TABLE_SPACING_NAMES[option],
46
+ value: option,
47
+ };
48
+ })}
49
+ name="ds-table-spacing"
50
+ checkedValue={tableSpacing}
51
+ onChange={(e: ChangeEvent<HTMLInputElement>) => {
52
+ setTableSpacing(e.target.value as TABLE_SPACING);
53
+ }}
54
+ />
55
+
56
+ <Separator />
57
+
58
+ <CheckboxGroup
59
+ legend="Style"
60
+ options={[
61
+ {
62
+ label: 'Column borders',
63
+ checked: hasColumnBorders,
64
+ onChange: () => {
65
+ setHasColumnBorders(!hasColumnBorders);
66
+ },
67
+ },
68
+ {
69
+ label: 'Cell colours',
70
+ checked: cellColours,
71
+ onChange: () => {
72
+ setCellColours(!cellColours);
73
+ },
74
+ },
75
+ ]}
76
+ />
77
+
78
+ <Separator />
79
+
80
+ <Button
81
+ variant="tertiary"
82
+ iconLeftName="redo"
83
+ onClick={resetSettings}
84
+ >
85
+ Reset Table Settings
86
+ </Button>
87
+ </Dropdown.Content>
88
+ </Dropdown>
89
+ );
90
+ };
@@ -40,4 +40,10 @@
40
40
  }
41
41
  }
42
42
  }
43
+
44
+ .ag-header-cell-resize {
45
+ // AG-Grid gives these a z-index of 2 which interferes with our stacking elements
46
+ // so we just unset it here
47
+ z-index: unset;
48
+ }
43
49
  }