@cerebruminc/cerebellum 20.0.1 → 20.0.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # react-component-lib-boilerplate
2
2
 
3
+ ## [20.0.2](https://github.com/cerebruminc/cerebellum/compare/v20.0.1...v20.0.2) (2026-08-04)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **styles:** move supported Mantine styles out of CSS modules ([dec41d7](https://github.com/cerebruminc/cerebellum/commit/dec41d72eac485c18c1e63d2dec2a599bff68b39))
9
+
3
10
  ## [20.0.1](https://github.com/cerebruminc/cerebellum/compare/v20.0.0...v20.0.1) (2026-08-04)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerebruminc/cerebellum",
3
- "version": "20.0.1",
3
+ "version": "20.0.2",
4
4
  "description": "Cerebrum's React Component Library",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -4,20 +4,8 @@
4
4
  * remove button to match ClearableTag's XBox styling.
5
5
  */
6
6
 
7
- .root {
8
- flex: 0 0 auto;
9
- overflow: hidden;
10
- }
11
-
12
- .remove {
13
- height: 100%;
14
- margin-inline-start: 4px;
15
- padding-inline: 8px;
16
- position: relative;
17
- }
18
-
19
7
  /* X overlay */
20
- .remove::before {
8
+ .remove.remove::before {
21
9
  content: "";
22
10
  background-color: currentColor;
23
11
  inset: 0;
@@ -27,6 +15,6 @@
27
15
  right: -10px;
28
16
  }
29
17
 
30
- .remove:hover::before {
18
+ .remove.remove:hover::before {
31
19
  opacity: 0.2;
32
20
  }
@@ -45,16 +45,21 @@ describe("openConfirmModal", () => {
45
45
  expect(props.modalId).toMatch(/^confirm-/);
46
46
  });
47
47
 
48
- it("applies confirmModalContent class to content classNames", () => {
48
+ it("applies default content styles", () => {
49
49
  openConfirmModal({ title: "Confirm" });
50
50
  const props = getModalProps();
51
- expect(props.classNames.content).toBe("confirmModalContent");
51
+ expect(props.styles.content.minWidth).toBe("600px");
52
+ expect(props.styles.content["@media (max-width: 639px)"].minWidth).toBe("unset");
52
53
  });
53
54
 
54
- it("applies confirmModalBody class to body classNames", () => {
55
+ it("applies default body styles", () => {
55
56
  openConfirmModal({ title: "Confirm" });
56
57
  const props = getModalProps();
57
- expect(props.classNames.body).toBe("confirmModalBody");
58
+ expect(props.styles.body).toMatchObject({
59
+ display: "flex",
60
+ flexDirection: "column",
61
+ gap: "27px",
62
+ });
58
63
  });
59
64
 
60
65
  it("sets default confirm button width to 198px", () => {
@@ -108,7 +113,7 @@ describe("openConfirmModal", () => {
108
113
  expect(props.withCloseButton).toBe(false);
109
114
  });
110
115
 
111
- it("allows callers to fully override content and body classNames", () => {
116
+ it("passes through caller-provided content and body classNames", () => {
112
117
  openConfirmModal({ title: "Confirm", ...({ classNames: { content: "myContent", body: "myBody" } } as object) } as Parameters<
113
118
  typeof openConfirmModal
114
119
  >[0]);
@@ -117,17 +122,10 @@ describe("openConfirmModal", () => {
117
122
  expect(props.classNames.body).toBe("myBody");
118
123
  });
119
124
 
120
- it("applies confirmModalGroup class to groupProps", () => {
121
- openConfirmModal({ title: "Confirm" });
122
- const childProps = getChildProps();
123
- expect(childProps.groupProps.className).toBe("confirmModalGroup");
124
- });
125
-
126
- it("merges caller-provided groupProps.className with confirmModalGroup", () => {
125
+ it("passes through caller-provided groupProps.className", () => {
127
126
  openConfirmModal({ title: "Confirm", groupProps: { className: "myGroup" } });
128
127
  const childProps = getChildProps();
129
- expect(childProps.groupProps.className).toContain("confirmModalGroup");
130
- expect(childProps.groupProps.className).toContain("myGroup");
128
+ expect(childProps.groupProps.className).toBe("myGroup");
131
129
  });
132
130
 
133
131
  it("defaults closeOnConfirm and closeOnCancel to true", () => {
@@ -8,9 +8,7 @@
8
8
  import { Box, Button, Group } from "@mantine/core";
9
9
  import { modals } from "@mantine/modals";
10
10
  import React, { ReactNode, useRef } from "react";
11
- import { clsx } from "clsx";
12
11
  import { useContainerQuery } from "../../hooks/useContainerQuery";
13
- import styles from "./modalHelpers.module.css";
14
12
 
15
13
  type ConfirmModalWithTitle = Parameters<typeof modals.openConfirmModal>[0] & {
16
14
  // title is strongly encouraged, but not required since some modals may use custom content that includes its own title element
@@ -99,6 +97,12 @@ const ResponsiveConfirmContent: React.FC<ResponsiveConfirmContentProps> = ({
99
97
  const resolvedCancelStyle = isStacked ? { ...cancelStyle, width: undefined } : cancelStyle;
100
98
 
101
99
  const { className: groupClassName, ...restGroupProps }: ConfirmGroupProps = groupProps ?? {};
100
+ const stackedGroupStyle = isStacked
101
+ ? {
102
+ alignItems: "center",
103
+ flexDirection: "column-reverse" as const,
104
+ }
105
+ : undefined;
102
106
 
103
107
  return (
104
108
  <>
@@ -108,7 +112,11 @@ const ResponsiveConfirmContent: React.FC<ResponsiveConfirmContentProps> = ({
108
112
  mt={children ? 0 : "md"}
109
113
  justify="flex-end"
110
114
  {...restGroupProps}
111
- className={clsx(groupClassName, isStacked && styles.confirmModalGroupStacked)}
115
+ className={groupClassName}
116
+ style={{
117
+ ...restGroupProps.style,
118
+ ...stackedGroupStyle,
119
+ }}
112
120
  >
113
121
  <Button
114
122
  {...restCancelProps}
@@ -136,6 +144,7 @@ const ResponsiveConfirmContent: React.FC<ResponsiveConfirmContentProps> = ({
136
144
  export const openConfirmModal = (props: ConfirmModalWithTitle) => {
137
145
  const {
138
146
  classNames,
147
+ styles,
139
148
  children,
140
149
  onCancel,
141
150
  onConfirm,
@@ -155,10 +164,20 @@ export const openConfirmModal = (props: ConfirmModalWithTitle) => {
155
164
  modals.open({
156
165
  ...restModalProps,
157
166
  modalId,
158
- classNames: {
159
- content: clsx(styles.confirmModalContent, classNames?.content),
160
- body: clsx(styles.confirmModalBody, classNames?.body),
161
- ...classNames,
167
+ classNames,
168
+ styles: {
169
+ content: {
170
+ minWidth: "600px",
171
+ "@media (max-width: 639px)": {
172
+ minWidth: "unset",
173
+ },
174
+ },
175
+ body: {
176
+ display: "flex",
177
+ flexDirection: "column",
178
+ gap: "27px",
179
+ },
180
+ ...styles,
162
181
  },
163
182
  children: (
164
183
  <ResponsiveConfirmContent
@@ -180,7 +199,7 @@ export const openConfirmModal = (props: ConfirmModalWithTitle) => {
180
199
  ...cancelProps?.style,
181
200
  },
182
201
  }}
183
- groupProps={{ ...groupProps, className: clsx(styles.confirmModalGroup, groupProps?.className) }}
202
+ groupProps={groupProps}
184
203
  labels={labels}
185
204
  onConfirm={onConfirm}
186
205
  onCancel={onCancel}
@@ -29,9 +29,7 @@ import type { NotificationTone, NotificationType, TextVariant } from "./types";
29
29
 
30
30
  import avatarClasses from "./components/Avatar.module.css";
31
31
  import buttonClasses from "./components/Button.module.css";
32
- import modalClasses from "./components/Modal.module.css";
33
32
  import pillClasses from "./components/Pill.module.css";
34
- import scrollAreaClasses from "./components/ScrollArea.module.css";
35
33
  import notificationClasses from "./services/Notification.module.css";
36
34
 
37
35
  type TonedNotificationProps = NotificationProps &
@@ -51,14 +49,17 @@ const getLabelStyles = (theme: MantineTheme) => {
51
49
  /**
52
50
  * Common variables for form input sizing.
53
51
  */
54
- const getInputVars = () => {
52
+ const getInputVars = (size?: MantineSize | (string & {})) => {
55
53
  const wrapper: Partial<
56
54
  Record<
55
+ | "--input-fz"
57
56
  | "--input-fz-xs"
58
57
  | "--input-fz-sm"
59
58
  | "--input-fz-md"
60
59
  | "--input-fz-lg"
61
60
  | "--input-fz-xl"
61
+ | "--input-height"
62
+ | "--input-font-family"
62
63
  | "--input-height-xs"
63
64
  | "--input-height-sm"
64
65
  | "--input-height-md"
@@ -68,12 +69,15 @@ const getInputVars = () => {
68
69
  >
69
70
  > = {};
70
71
 
72
+ const resolvedSize = size === "xs" || size === "sm" ? size : size === "xl" ? "xl" : "md";
73
+
71
74
  // TODO: FRN-2871 map sizes to supported sizes only
72
75
  wrapper["--input-fz-xs"] = "calc(0.875rem * var(--mantine-scale))"; // 14px
73
76
  wrapper["--input-fz-sm"] = "calc(0.875rem * var(--mantine-scale))"; // 14px
74
77
  wrapper["--input-fz-md"] = "calc(0.9375rem * var(--mantine-scale))"; // 15px
75
78
  wrapper["--input-fz-lg"] = "calc(0.9375rem * var(--mantine-scale))"; // 15px
76
79
  wrapper["--input-fz-xl"] = "calc(1rem * var(--mantine-scale))"; // 16px
80
+ wrapper["--input-font-family"] = "'Nunito Sans', sans-serif";
77
81
 
78
82
  wrapper["--input-height-xs"] = "calc(2.25rem * var(--mantine-scale))"; // 36px
79
83
  wrapper["--input-height-sm"] = "calc(2.5rem * var(--mantine-scale))"; // 40px
@@ -81,6 +85,9 @@ const getInputVars = () => {
81
85
  wrapper["--input-height-lg"] = "calc(3.4375rem * var(--mantine-scale))"; // 55px
82
86
  wrapper["--input-height-xl"] = "calc(3.75rem * var(--mantine-scale))"; // 60px
83
87
 
88
+ wrapper["--input-fz"] = wrapper[`--input-fz-${resolvedSize}` as const];
89
+ wrapper["--input-height"] = wrapper[`--input-height-${resolvedSize}` as const];
90
+
84
91
  return { wrapper };
85
92
  };
86
93
 
@@ -518,14 +525,14 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
518
525
  size: "lg",
519
526
  inputWrapperOrder: ["label", "error", "input", "description"],
520
527
  },
521
- vars: (_theme: MantineTheme, props: TextInputProps) => getInputVars(),
528
+ vars: (_theme: MantineTheme, props: TextInputProps) => getInputVars(props.size),
522
529
  },
523
530
  PasswordInput: {
524
531
  defaultProps: {
525
532
  size: "lg",
526
533
  inputWrapperOrder: ["label", "error", "input", "description"],
527
534
  },
528
- vars: (_theme: MantineTheme, props: PasswordInputProps) => getInputVars(),
535
+ vars: (_theme: MantineTheme, props: PasswordInputProps) => getInputVars(props.size),
529
536
  },
530
537
  Textarea: {
531
538
  defaultProps: {
@@ -533,7 +540,7 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
533
540
  inputWrapperOrder: ["label", "error", "input", "description"],
534
541
  },
535
542
  vars: (_theme: MantineTheme, props: TextareaProps) => {
536
- const inputVars = getInputVars();
543
+ const inputVars = getInputVars(props.size);
537
544
  const wrapper: Partial<
538
545
  Record<
539
546
  | "--input-fz-xs"
@@ -572,13 +579,9 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
572
579
  size: "lg",
573
580
  inputWrapperOrder: ["label", "error", "input", "description"],
574
581
  },
575
- vars: (_theme: MantineTheme, props: SelectProps) => getInputVars(),
582
+ vars: (_theme: MantineTheme, props: SelectProps) => getInputVars(props.size),
576
583
  },
577
584
  Modal: {
578
- classNames: {
579
- body: modalClasses.body,
580
- header: modalClasses.header,
581
- },
582
585
  defaultProps: {
583
586
  withCloseButton: false,
584
587
  radius: "10px",
@@ -598,6 +601,24 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
598
601
  fontWeight: 600,
599
602
  lineHeight: 1.33333,
600
603
  },
604
+ header: {
605
+ padding: "49px 71px 0",
606
+ "@media (max-width: 600px)": {
607
+ paddingInline: "50px",
608
+ },
609
+ "@media (max-width: 400px)": {
610
+ paddingInline: "30px",
611
+ },
612
+ },
613
+ body: {
614
+ padding: "20px 71px 61px",
615
+ "@media (max-width: 600px)": {
616
+ paddingInline: "50px",
617
+ },
618
+ "@media (max-width: 400px)": {
619
+ paddingInline: "30px",
620
+ },
621
+ },
601
622
  content: {
602
623
  // Custom scrollbar to match Cerebellum's Modal component
603
624
  scrollbarColor: "#DCE2ED transparent",
@@ -673,28 +694,28 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
673
694
  size: "lg",
674
695
  inputWrapperOrder: ["label", "error", "input", "description"],
675
696
  },
676
- vars: (_theme: MantineTheme, props: { size?: MantineSize }) => getInputVars(),
697
+ vars: (_theme: MantineTheme, props: { size?: MantineSize }) => getInputVars(props.size),
677
698
  },
678
699
  DateInput: {
679
700
  defaultProps: {
680
701
  size: "lg",
681
702
  inputWrapperOrder: ["label", "error", "input", "description"],
682
703
  },
683
- vars: (_theme: MantineTheme, props: { size?: MantineSize }) => getInputVars(),
704
+ vars: (_theme: MantineTheme, props: { size?: MantineSize }) => getInputVars(props.size),
684
705
  },
685
706
  DatePickerInput: {
686
707
  defaultProps: {
687
708
  size: "lg",
688
709
  inputWrapperOrder: ["label", "error", "input", "description"],
689
710
  },
690
- vars: (_theme: MantineTheme, props: { size?: MantineSize }) => getInputVars(),
711
+ vars: (_theme: MantineTheme, props: { size?: MantineSize }) => getInputVars(props.size),
691
712
  },
692
713
  Autocomplete: {
693
714
  defaultProps: {
694
715
  size: "lg",
695
716
  inputWrapperOrder: ["label", "error", "input", "description"],
696
717
  },
697
- vars: (_theme: MantineTheme, props: { size?: MantineSize }) => getInputVars(),
718
+ vars: (_theme: MantineTheme, props: { size?: MantineSize }) => getInputVars(props.size),
698
719
  },
699
720
  Combobox: {
700
721
  defaultProps: {
@@ -705,7 +726,7 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
705
726
  defaultProps: {
706
727
  size: "lg",
707
728
  },
708
- vars: (_theme: MantineTheme, props: { size?: MantineSize }) => getInputVars(),
729
+ vars: (_theme: MantineTheme, props: { size?: MantineSize }) => getInputVars(props.size),
709
730
  },
710
731
  InputWrapper: {
711
732
  defaultProps: {
@@ -713,14 +734,27 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
713
734
  },
714
735
  vars: (theme: MantineTheme) => {
715
736
  const label: Partial<Record<"--input-asterisk-color" | "--input-label-size", string | undefined>> = {};
737
+ const description: Partial<Record<"--input-description-size", string | undefined>> = {};
738
+ const error: Partial<Record<"--input-error-size", string | undefined>> = {};
716
739
 
717
740
  label["--input-label-size"] = "calc(0.875rem * var(--mantine-scale))"; // 14px
718
741
  label["--input-asterisk-color"] = theme.colors.blue[7];
742
+ description["--input-description-size"] = "calc(0.875rem * var(--mantine-scale))"; // 14px
743
+ error["--input-error-size"] = "calc(0.875rem * var(--mantine-scale))"; // 14px
719
744
 
720
- return { label };
745
+ return { label, description, error };
721
746
  },
722
747
  styles: (theme: MantineTheme) => ({
723
- label: getLabelStyles(theme),
748
+ label: {
749
+ ...getLabelStyles(theme),
750
+ fontFamily: "'Nunito Sans', sans-serif",
751
+ },
752
+ description: {
753
+ fontFamily: "'Nunito Sans', sans-serif",
754
+ },
755
+ error: {
756
+ fontFamily: "'Nunito Sans', sans-serif",
757
+ },
724
758
  }),
725
759
  },
726
760
  Notifications: {
@@ -731,7 +765,6 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
731
765
  Notification: {
732
766
  defaultProps: {
733
767
  classNames: {
734
- closeButton: notificationClasses.closeButton,
735
768
  root: notificationClasses.root,
736
769
  },
737
770
  },
@@ -813,7 +846,14 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
813
846
  // Make the button and icon larger
814
847
  "--cb-size": "calc(2rem * var(--mantine-scale))",
815
848
  "--cb-icon-size": "calc(1.5rem * var(--mantine-scale))",
849
+ backgroundColor: "transparent",
816
850
  color: theme.colors.gray[8],
851
+ opacity: 0.6,
852
+ transition: "opacity 350ms ease, background-color 350ms ease",
853
+ "&:hover": {
854
+ backgroundColor: "transparent",
855
+ opacity: 1,
856
+ },
817
857
  ...(isToast && {
818
858
  display: "none",
819
859
  }),
@@ -839,7 +879,6 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
839
879
  },
840
880
  },
841
881
  classNames: {
842
- root: pillClasses.root,
843
882
  remove: pillClasses.remove,
844
883
  },
845
884
  vars: (_theme: MantineTheme, _props: PillProps) => ({
@@ -869,6 +908,8 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
869
908
  root: {
870
909
  backgroundColor: colorScale[bgIndex],
871
910
  borderRadius: "8px",
911
+ flex: "0 0 auto",
912
+ overflow: "hidden",
872
913
  ...(theme.other.highContrast && {
873
914
  border: "1px solid #000000",
874
915
  }),
@@ -884,15 +925,36 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
884
925
  },
885
926
  remove: {
886
927
  color: colorScale[textIndex],
928
+ height: "100%",
929
+ marginInlineStart: "4px",
930
+ paddingInline: "8px",
931
+ position: "relative",
887
932
  },
888
933
  };
889
934
  },
890
935
  },
891
936
  ScrollArea: {
892
- classNames: {
893
- scrollbar: scrollAreaClasses.scrollbar,
894
- thumb: scrollAreaClasses.thumb,
895
- },
937
+ styles: (theme: MantineTheme) => ({
938
+ scrollbar: {
939
+ backgroundColor: "transparent",
940
+ width: "11px",
941
+ "&:hover": {
942
+ backgroundColor: "transparent",
943
+ },
944
+ "&:active": {
945
+ backgroundColor: "transparent",
946
+ },
947
+ },
948
+ thumb: {
949
+ backgroundColor: theme.colors.gray[4],
950
+ ".mantine-ScrollArea-scrollbar:hover > &": {
951
+ backgroundColor: theme.colors.gray[6],
952
+ },
953
+ ".mantine-ScrollArea-scrollbar:active > &": {
954
+ backgroundColor: theme.colors.gray[4],
955
+ },
956
+ },
957
+ }),
896
958
  },
897
959
  TagsInput: {
898
960
  defaultProps: {
@@ -901,6 +963,12 @@ export const createCustomMantineTheme = (optionsOrBrandColor?: CreateCustomManti
901
963
  },
902
964
  Switch: {
903
965
  styles: (theme: MantineTheme) => ({
966
+ body: {
967
+ alignItems: "center",
968
+ },
969
+ labelWrapper: {
970
+ justifyContent: "center",
971
+ },
904
972
  thumb: {
905
973
  boxShadow: theme.other.highContrast ? "0 0 4px rgba(0, 0, 0, 1)" : "0 2px 9px rgba(81, 118, 236, 0.44)",
906
974
  },
@@ -1,14 +1,3 @@
1
- .closeButton {
2
- background-color: transparent;
3
- opacity: 0.6;
4
- transition: opacity 350ms ease, background-color 350ms ease;
5
- }
6
-
7
- .closeButton:hover {
8
- background-color: transparent;
9
- opacity: 1;
10
- }
11
-
12
1
  /* Higher specificity than Mantine's single-class selector so this still wins
13
2
  even when Mantine global styles are loaded later. */
14
3
  .root.root::before {
@@ -1,21 +0,0 @@
1
- .body {
2
- padding: 20px 71px 61px;
3
- }
4
-
5
- .header {
6
- padding: 49px 71px 0px;
7
- }
8
-
9
- @media (max-width: 600px) {
10
- .body,
11
- .header {
12
- padding-inline: 50px;
13
- }
14
- }
15
-
16
- @media (max-width: 400px) {
17
- .body,
18
- .header {
19
- padding-inline: 30px;
20
- }
21
- }
@@ -1,32 +0,0 @@
1
- .scrollbar {
2
- background-color: transparent;
3
- width: 11px;
4
- }
5
-
6
- @media (hover: hover) {
7
- .scrollbar:hover {
8
- background-color: transparent;
9
- }
10
- }
11
-
12
- @media (hover: none) {
13
- .scrollbar:active {
14
- background-color: transparent;
15
- }
16
- }
17
-
18
- .thumb {
19
- background-color: var(--mantine-color-gray-4);
20
- }
21
-
22
- @media (hover: hover) {
23
- .scrollbar:hover > .thumb {
24
- background-color: var(--mantine-color-gray-6);
25
- }
26
- }
27
-
28
- @media (hover: none) {
29
- .scrollbar:active > .thumb {
30
- background-color: var(--mantine-color-gray-4);
31
- }
32
- }
@@ -1,25 +0,0 @@
1
- .confirmModalContent {
2
- min-width: 600px;
3
- }
4
-
5
- .confirmModalBody {
6
- display: flex;
7
- flex-direction: column;
8
- gap: 27px;
9
- }
10
-
11
- /* Applied programmatically via useContainerQuery when buttons don't fit side-by-side */
12
- .confirmModalGroupStacked {
13
- flex-direction: column-reverse !important;
14
- align-items: center !important;
15
- }
16
-
17
- .confirmModalGroupStacked > * {
18
- max-width: 100%;
19
- }
20
-
21
- @media (max-width: 639px) {
22
- .confirmModalContent {
23
- min-width: unset;
24
- }
25
- }