@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 +7 -0
- package/package.json +1 -1
- package/src/components/ToggleButtons/ToggleButtons.mdx +5 -0
- package/src/components/ToggleButtons/ToggleButtons.stories.tsx +36 -2
- package/src/components/ToggleButtons/ToggleButtons.test.tsx +58 -1
- package/src/components/ToggleButtons/ToggleButtons.tsx +10 -2
- package/src/components/ToggleButtons/ToggleButtonsStyles.tsx +8 -4
- package/src/components/ToggleButtons/types.ts +5 -1
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
|
@@ -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 {
|
|
3
|
+
import { useArgs } from "storybook/preview-api";
|
|
4
4
|
import { colors } from "../../const/colors";
|
|
5
5
|
import { ButtonColorFamilyEnum } from "../../sharedTypes/enums";
|
|
6
|
-
import {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|