@cerebruminc/cerebellum 17.2.0 → 17.2.1

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
+ ## [17.2.1](https://github.com/cerebruminc/cerebellum/compare/v17.2.0...v17.2.1) (2026-06-02)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **toggle-buttons:** add invertActive support ([15ec451](https://github.com/cerebruminc/cerebellum/commit/15ec451b7e0d36cd3851ab7acf2459eb2969ba33))
9
+
3
10
  ## [17.2.0](https://github.com/cerebruminc/cerebellum/compare/v17.1.2...v17.2.0) (2026-05-29)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerebruminc/cerebellum",
3
- "version": "17.2.0",
3
+ "version": "17.2.1",
4
4
  "description": "Cerebrum's React Component Library",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -105,6 +105,11 @@ import { ToggleButtons, ToggleButtonsType, ToggleButtonButtonsType } from "@cere
105
105
  <Description of={ToggleButtonsStories.FixedButtonWidth} />
106
106
  <Canvas of={ToggleButtonsStories.FixedButtonWidth} />
107
107
 
108
+ ## Inverted Active Button Color
109
+
110
+ <Description of={ToggleButtonsStories.InvertedColor} />
111
+ <Canvas of={ToggleButtonsStories.InvertedColor} />
112
+
108
113
  ### themeOverride
109
114
 
110
115
  <Description of={ToggleButtonsStories.ThemeOverride} />
@@ -1,9 +1,9 @@
1
1
  import { Meta, StoryObj } from "@storybook/react";
2
2
  import React from "react";
3
- import { Email, Mobile, Phone } from "../Icons";
3
+ import { useArgs } from "storybook/preview-api";
4
4
  import { colors } from "../../const/colors";
5
5
  import { ButtonColorFamilyEnum } from "../../sharedTypes/enums";
6
- import { useArgs } from "storybook/preview-api";
6
+ import { Email, Mobile, Phone } from "../Icons";
7
7
  import { ToggleButtons } from "./ToggleButtons";
8
8
 
9
9
  /**
@@ -152,6 +152,40 @@ export const FixedButtonWidth: Story = {
152
152
  },
153
153
  };
154
154
 
155
+ /**
156
+ * The `invertActive` prop will flip the active button's color and background. It's necessary to get the highContrast styles in the theme to work.
157
+ *
158
+ * In the `highContrast` theme, the default active-state color (`colorGroup.light`) is white, making it invisible against a white background. Setting `invertActive` swaps the active button's background to `colorGroup.hover` (a darker shade) and the text/icon color to `colorGroup.light`, restoring visibility. This prop is also safe to use with the default theme.
159
+ */
160
+ export const InvertedColor: Story = {
161
+ render: Template.bind({}),
162
+ args: {
163
+ activeId: "1",
164
+ buttons: [
165
+ {
166
+ id: "1",
167
+ buttonText: "Email",
168
+ Icon: Email,
169
+ iconGap: 10,
170
+ },
171
+ {
172
+ id: "2",
173
+ buttonText: "Text",
174
+ Icon: Mobile,
175
+ iconSize: 16,
176
+ iconGap: 5,
177
+ },
178
+ {
179
+ id: "3",
180
+ buttonText: "Phone",
181
+ Icon: Phone,
182
+ },
183
+ ],
184
+ invertActive: true,
185
+ fixedButtonWidth: 120,
186
+ },
187
+ };
188
+
155
189
  /** If you need to override the theme, these props are available through `themeOverride`. */
156
190
  export const ThemeOverride: Story = {
157
191
  render: Template.bind({}),
@@ -1,8 +1,8 @@
1
1
  import { render, screen } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
  import React from "react";
4
- import { Settings } from "../Icons";
5
4
  import { withTheme } from "../../hocs/withTheme";
5
+ import { Settings } from "../Icons";
6
6
  import { ToggleButtons } from "./ToggleButtons";
7
7
  import { ToggleButtonButtonsType } from "./types";
8
8
 
@@ -100,4 +100,61 @@ describe("ToggleButtons", () => {
100
100
  const icon = screen.getByTestId(`${buttonText} icon`);
101
101
  expect(icon).toBeInTheDocument();
102
102
  });
103
+
104
+ test("invertActive renders without error", () => {
105
+ const buttons: ToggleButtonButtonsType[] = [
106
+ { id: "1", buttonText: "Foo" },
107
+ { id: "2", buttonText: "Bar" },
108
+ ];
109
+ render(<ThemedToggleButtons activeId="1" buttons={buttons} invertActive />);
110
+
111
+ expect(screen.getByText("Foo")).toBeInTheDocument();
112
+ expect(screen.getByText("Bar")).toBeInTheDocument();
113
+ });
114
+
115
+ test("invertActive renders icons without error", () => {
116
+ const buttonId = "1";
117
+ const buttonText = "Foo";
118
+ const buttons: ToggleButtonButtonsType[] = [
119
+ { id: buttonId, buttonText: buttonText, Icon: Settings },
120
+ { id: "2", buttonText: "Bar", Icon: Settings },
121
+ ];
122
+
123
+ render(<ThemedToggleButtons activeId={buttonId} buttons={buttons} invertActive />);
124
+
125
+ const icon = screen.getByTestId(`${buttonText} icon`);
126
+ expect(icon).toBeInTheDocument();
127
+ });
128
+
129
+ test("active icon uses colorGroup.hover fill by default", () => {
130
+ const buttonId = "1";
131
+ const buttonText = "Foo";
132
+ const buttons: ToggleButtonButtonsType[] = [
133
+ { id: buttonId, buttonText: buttonText, Icon: Settings },
134
+ { id: "2", buttonText: "Bar", Icon: Settings },
135
+ ];
136
+
137
+ render(<ThemedToggleButtons activeId={buttonId} buttons={buttons} />);
138
+
139
+ const iconBox = screen.getByTestId(`${buttonText} icon`);
140
+ const path = iconBox.querySelector("path");
141
+ // Default: active icon fill = colorGroup.hover = BLUE_100
142
+ expect(path?.getAttribute("fill")).toBe("#4F6CEA");
143
+ });
144
+
145
+ test("active icon uses colorGroup.light fill when invertActive is true", () => {
146
+ const buttonId = "1";
147
+ const buttonText = "Foo";
148
+ const buttons: ToggleButtonButtonsType[] = [
149
+ { id: buttonId, buttonText: buttonText, Icon: Settings },
150
+ { id: "2", buttonText: "Bar", Icon: Settings },
151
+ ];
152
+
153
+ render(<ThemedToggleButtons activeId={buttonId} buttons={buttons} invertActive />);
154
+
155
+ const iconBox = screen.getByTestId(`${buttonText} icon`);
156
+ const path = iconBox.querySelector("path");
157
+ // invertActive: active icon fill = colorGroup.light = BLUE_5
158
+ expect(path?.getAttribute("fill")).toBe("#E5EBFC");
159
+ });
103
160
  });
@@ -11,6 +11,7 @@ export const ToggleButtons: FC<ToggleButtonsType> = ({
11
11
  activeId,
12
12
  buttons = [],
13
13
  fixedButtonWidth,
14
+ invertActive,
14
15
  onToggle,
15
16
  themeOverride,
16
17
  }) => {
@@ -32,6 +33,7 @@ export const ToggleButtons: FC<ToggleButtonsType> = ({
32
33
  disabled={isActive}
33
34
  $firstButton={index === 0}
34
35
  $fixedButtonWidth={fixedButtonWidth}
36
+ $invertActive={invertActive}
35
37
  $lastButton={index === buttons.length - 1}
36
38
  onClick={() => onToggle?.(buttonProps)}
37
39
  $themeOverride={themeOverride}
@@ -39,10 +41,16 @@ export const ToggleButtons: FC<ToggleButtonsType> = ({
39
41
  {/* We could use title instead of data-testid, but it would be redundant with buttonText for screen readers */}
40
42
  {Icon && (
41
43
  <IconBox $iconSize={iconSize} data-testid={`${buttonText} icon`} $iconGap={iconGap}>
42
- <Icon fill={isActive ? colorGroup.hover : inactiveTextColor} />
44
+ <Icon fill={isActive ? (invertActive ? colorGroup.light : colorGroup.hover) : inactiveTextColor} />
43
45
  </IconBox>
44
46
  )}
45
- <Text $active={isActive} $colorGroup={colorGroup} $themeOverride={themeOverride} data-sentry-unmask>
47
+ <Text
48
+ $active={isActive}
49
+ $colorGroup={colorGroup}
50
+ $invertActive={invertActive}
51
+ $themeOverride={themeOverride}
52
+ data-sentry-unmask
53
+ >
46
54
  {buttonText}
47
55
  </Text>
48
56
  </Button>
@@ -8,8 +8,10 @@ export const ButtonGroup = styled.div<ButtonGroupProps>`
8
8
  `;
9
9
  export const Button = styled.button<ButtonProps>`
10
10
  align-items: center;
11
- background-color: ${({ $active, $colorGroup, $themeOverride, theme }) =>
12
- $active ? $colorGroup.light : $themeOverride?.backgroundColor || theme.toggleButtons.backgroundColor};
11
+ background-color: ${({ $active, $colorGroup, $invertActive, $themeOverride, theme }) => {
12
+ if (!$active) return $themeOverride?.backgroundColor || theme.toggleButtons.backgroundColor;
13
+ return $invertActive ? $colorGroup.hover : $colorGroup.light;
14
+ }};
13
15
  border: 1px solid
14
16
  ${({ $active, $colorGroup, $themeOverride, theme }) =>
15
17
  $active ? $colorGroup.hover : $themeOverride?.borderColor || theme.toggleButtons.borderColor};
@@ -49,8 +51,10 @@ export const IconBox = styled.div<IconBoxProps>`
49
51
  width: ${({ $iconSize }) => $iconSize || 14}px;
50
52
  `;
51
53
  export const Text = styled.span<TextProps>`
52
- color: ${({ $active, $colorGroup, $themeOverride, theme }) =>
53
- $active ? $colorGroup.hover : $themeOverride?.textColor || theme.toggleButtons.textColor};
54
+ color: ${({ $active, $colorGroup, $invertActive, $themeOverride, theme }) => {
55
+ if (!$active) return $themeOverride?.textColor || theme.toggleButtons.textColor;
56
+ return $invertActive ? $colorGroup.light : $colorGroup.hover;
57
+ }};
54
58
  font-size: ${({ $themeOverride, theme }) => $themeOverride?.fontSize || theme.toggleButtons.fontSize}px;
55
59
  font-weight: 600;
56
60
  letter-spacing: 0.34px;
@@ -1,6 +1,6 @@
1
- import { IconType } from "../Icons";
2
1
  import { ButtonColorFamilyEnum } from "../../sharedTypes/enums";
3
2
  import { ColorFamilyType } from "../../sharedTypes/types";
3
+ import { IconType } from "../Icons";
4
4
 
5
5
  export interface ToggleButtonsType {
6
6
  /** The color family for the active buttons. */
@@ -13,6 +13,8 @@ export interface ToggleButtonsType {
13
13
  buttons?: Array<ToggleButtonButtonsType>;
14
14
  /** By default, buttons fit the text. This forces the same width for all buttons. This is recommended if text is close in length */
15
15
  fixedButtonWidth?: number;
16
+ /** If true, the active button text/background will be inverted. Necessary to get the highContrast styles in the theme to work */
17
+ invertActive?: boolean;
16
18
  /** Called when a button is clicked */
17
19
  onToggle?: (button: ToggleButtonButtonsType) => void;
18
20
  /** You can override the theme by passing a `themeOverride` object. See below for the available props. */
@@ -46,6 +48,7 @@ export interface ButtonProps {
46
48
  $colorGroup: ColorFamilyType;
47
49
  $firstButton: boolean;
48
50
  $fixedButtonWidth?: number;
51
+ $invertActive?: boolean;
49
52
  $lastButton: boolean;
50
53
  $themeOverride?: ThemeOverride;
51
54
  }
@@ -56,5 +59,6 @@ export interface IconBoxProps {
56
59
  export interface TextProps {
57
60
  $active: boolean;
58
61
  $colorGroup: ColorFamilyType;
62
+ $invertActive?: boolean;
59
63
  $themeOverride?: ThemeOverride;
60
64
  }