diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index 0328b2c6..c36f1a3b 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -34,6 +34,7 @@ export const Button = ({
$align={align}
$fillWidth={fillWidth}
disabled={disabled || loading}
+ role="button"
{...delegated}
>
{iconLeft && (
diff --git a/src/components/ButtonGroup/ButtonGroup.stories.ts b/src/components/ButtonGroup/ButtonGroup.stories.ts
index 3b76d6fd..8d4a932d 100644
--- a/src/components/ButtonGroup/ButtonGroup.stories.ts
+++ b/src/components/ButtonGroup/ButtonGroup.stories.ts
@@ -4,6 +4,10 @@ export default {
component: ButtonGroup,
title: "Buttons/ButtonGroup",
tags: ["button-group", "autodocs"],
+ argTypes: {
+ labels: { control: "array", options: ["default", "checked", "unchecked"] },
+ activeIndex: { control: "number" },
+ }
};
export const Playground = {
diff --git a/src/components/ButtonGroup/ButtonGroup.test.tsx b/src/components/ButtonGroup/ButtonGroup.test.tsx
new file mode 100644
index 00000000..40d544e6
--- /dev/null
+++ b/src/components/ButtonGroup/ButtonGroup.test.tsx
@@ -0,0 +1,42 @@
+import { fireEvent } from "@testing-library/react";
+import { ButtonGroup, ButtonGroupProps } from "./ButtonGroup";
+import { renderCUI } from "@/utils/test-utils";
+
+describe("ButtonGroup", () => {
+ const renderButtonGroup = (props: ButtonGroupProps) =>
+ renderCUI();
+ const labels = ["Option 1", "Option 2", "Option 3"];
+
+ it("renders buttons with labels correctly", () => {
+ const { getByText } = renderButtonGroup({ labels: labels });
+
+ labels.forEach(label => {
+ expect(getByText(label).textContent).toBe(label);
+ });
+ });
+
+ it("calls onClick handler when a button is clicked", () => {
+ let counter = 0;
+ const handleClick = () => (counter = 1);
+
+ const { getByText } = renderButtonGroup({
+ labels: labels,
+ onClick: handleClick,
+ });
+
+ fireEvent.click(getByText("Option 2"));
+
+ expect(counter).toEqual(1);
+ });
+
+ it("adds 'active' class to the active button", () => {
+ const { getByText } = renderButtonGroup({
+ labels: labels,
+ activeIndex: 1,
+ });
+
+ const activeButton = getByText("Option 2");
+
+ expect(activeButton).active == true;
+ });
+});
diff --git a/src/components/ButtonGroup/ButtonGroup.tsx b/src/components/ButtonGroup/ButtonGroup.tsx
index 05c1ba2c..1b202987 100644
--- a/src/components/ButtonGroup/ButtonGroup.tsx
+++ b/src/components/ButtonGroup/ButtonGroup.tsx
@@ -1,4 +1,4 @@
-import styled from "styled-components";
+import styled, { DefaultTheme } from "styled-components";
export interface ButtonGroupProps {
labels: string[];
@@ -6,11 +6,7 @@ export interface ButtonGroupProps {
onClick?: (index: number) => void;
}
-export const ButtonGroup = ({
- labels,
- activeIndex,
- onClick,
-}: ButtonGroupProps) => {
+export const ButtonGroup = ({ labels, activeIndex, onClick }: ButtonGroupProps) => {
const btns = labels.map((label, index) => {
const position: ButtonPosition =
index === 0 ? "left" : index === labels.length - 1 ? "right" : "center";
@@ -20,6 +16,7 @@ export const ButtonGroup = ({
active={index === activeIndex}
position={position}
onClick={() => onClick?.(index)}
+ role="button"
>
{label}
@@ -29,20 +26,23 @@ export const ButtonGroup = ({
};
type ButtonPosition = "left" | "center" | "right";
+
interface ButtonProps {
active: boolean;
position: ButtonPosition;
+ theme: DefaultTheme;
}
const ButtonGroupWrapper = styled.div`
box-sizing: border-box;
- display: flex;
+ display: inline-flex;
flex-direction: row;
justify-content: center;
align-items: center;
- padding: 0px 1px;
- border: var(--click-button-group-color-stroke-panel);
- background: var(--click-button-group-color-background-panel);
+ padding: 0px;
+ border: 1px solid ${({ theme }) => theme.click.button.group.color.stroke.panel};
+ background: ${({ theme }) => theme.click.button.group.color.background.panel};
+ border-radius: ${({ theme }) => theme.click.button.group.radii.end};
`;
const endRadii = "var(--click-button-button-group-radii-end)";
@@ -57,13 +57,32 @@ const Button = styled.button`
justify-content: center;
align-items: center;
border: none;
- padding: var(--click-button-basic-space-y) var(--click-button-basic-space-x);
- gap: 10px;
-
- background: ${({ active }: ButtonProps) =>
+ background: ${({ active, theme }: ButtonProps) =>
active
- ? "var(--click-button-group-color-background-active)"
- : "var(--click-button-group-color-background-default)"};
+ ? theme.click.button.group.color.background.active
+ : theme.click.button.group.color.background.default};
+ color: ${({ theme }) => theme.click.button.group.color.text.default};
+ font: ${({ theme }) => theme.click.button.basic.typography.label.default};
+ border-radius: ${({ theme }) => theme.click.button.group.radii.end};
+ padding: ${({ theme }) => theme.click.button.basic.space.y}
+ ${({ theme }) => theme.click.button.basic.space.x};
+ gap: ${({ theme }) => theme.click.button.basic.space.group};
+ cursor: pointer;
+
+ &:hover {
+ background: ${({ theme }) => theme.click.button.group.color.background.hover};
+ }
+
+ &:active,
+ &:focus {
+ background: ${({ theme }) => theme.click.button.group.color.background.active};
+ }
+
+ &:disabled {
+ cursor: disabled;
+ background: ${({ theme }) =>
+ theme.click.button.basic.color.primary.background.disabled};
+ }
border-radius: ${({ position }: ButtonProps) =>
position === "left"
@@ -71,8 +90,4 @@ const Button = styled.button`
: position === "right"
? rightBorderRadius
: centerBorderRadius};
-
- &:hover {
- background: var(--click-button-group-color-background-hover);
- }
`;
diff --git a/vite.config.ts.timestamp-1694205012987-cf9ed025d3d95.mjs b/vite.config.ts.timestamp-1694205012987-cf9ed025d3d95.mjs
new file mode 100644
index 00000000..807b087f
--- /dev/null
+++ b/vite.config.ts.timestamp-1694205012987-cf9ed025d3d95.mjs
@@ -0,0 +1,60 @@
+// vite.config.ts
+import { defineConfig } from "file:///Users/garethjones/Sites/click-ui/node_modules/vite/dist/node/index.js";
+import react from "file:///Users/garethjones/Sites/click-ui/node_modules/@vitejs/plugin-react/dist/index.mjs";
+import path, { resolve } from "path";
+import dts from "file:///Users/garethjones/Sites/click-ui/node_modules/vite-plugin-dts/dist/index.mjs";
+var __vite_injected_original_dirname = "/Users/garethjones/Sites/click-ui";
+var vite_config_default = defineConfig({
+ plugins: [
+ react({
+ babel: {
+ plugins: [["babel-plugin-styled-components", { displayName: false }]],
+ env: {
+ development: {
+ plugins: [["babel-plugin-styled-components", { displayName: true }]]
+ }
+ }
+ }
+ }),
+ dts({
+ include: ["src/"],
+ exclude: ["**/*.stories.ts", "**/*.stories.tsx", "**/*.test.ts", "**/*.test.tsx"]
+ })
+ ],
+ resolve: {
+ alias: {
+ "@": path.resolve(__vite_injected_original_dirname, "./src")
+ }
+ },
+ build: {
+ lib: {
+ entry: resolve(__vite_injected_original_dirname, "src/index.ts"),
+ name: "click-ui",
+ formats: ["es", "umd"],
+ fileName: (format) => `click-ui.${format}.js`
+ },
+ rollupOptions: {
+ // Add _all_ external dependencies here
+ external: [
+ "react",
+ "react-dom",
+ "styled-components",
+ "**/*.stories.ts",
+ "**/*.stories.tsx",
+ "**/*.test.ts",
+ "**/*.test.tsx"
+ ],
+ output: {
+ globals: {
+ react: "React",
+ "styled-components": "styled",
+ "react-dom": "ReactDOM"
+ }
+ }
+ }
+ }
+});
+export {
+ vite_config_default as default
+};
+//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvVXNlcnMvZ2FyZXRoam9uZXMvU2l0ZXMvY2xpY2stdWlcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9Vc2Vycy9nYXJldGhqb25lcy9TaXRlcy9jbGljay11aS92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vVXNlcnMvZ2FyZXRoam9uZXMvU2l0ZXMvY2xpY2stdWkvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tIFwidml0ZVwiO1xuaW1wb3J0IHJlYWN0IGZyb20gXCJAdml0ZWpzL3BsdWdpbi1yZWFjdFwiO1xuaW1wb3J0IHBhdGgsIHsgcmVzb2x2ZSB9IGZyb20gXCJwYXRoXCI7XG5pbXBvcnQgZHRzIGZyb20gXCJ2aXRlLXBsdWdpbi1kdHNcIjtcblxuLy8gaHR0cHM6Ly92aXRlanMuZGV2L2NvbmZpZy9cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHBsdWdpbnM6IFtcbiAgICByZWFjdCh7XG4gICAgICBiYWJlbDoge1xuICAgICAgICBwbHVnaW5zOiBbW1wiYmFiZWwtcGx1Z2luLXN0eWxlZC1jb21wb25lbnRzXCIsIHsgZGlzcGxheU5hbWU6IGZhbHNlIH1dXSxcblxuICAgICAgICBlbnY6IHtcbiAgICAgICAgICBkZXZlbG9wbWVudDoge1xuICAgICAgICAgICAgcGx1Z2luczogW1tcImJhYmVsLXBsdWdpbi1zdHlsZWQtY29tcG9uZW50c1wiLCB7IGRpc3BsYXlOYW1lOiB0cnVlIH1dXSxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICB9KSxcbiAgICBkdHMoe1xuICAgICAgaW5jbHVkZTogW1wic3JjL1wiXSxcbiAgICAgIGV4Y2x1ZGU6IFtcIioqLyouc3Rvcmllcy50c1wiLCBcIioqLyouc3Rvcmllcy50c3hcIiwgXCIqKi8qLnRlc3QudHNcIiwgXCIqKi8qLnRlc3QudHN4XCJdLFxuICAgIH0pLFxuICBdLFxuICByZXNvbHZlOiB7XG4gICAgYWxpYXM6IHtcbiAgICAgIFwiQFwiOiBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCBcIi4vc3JjXCIpLFxuICAgIH0sXG4gIH0sXG4gIGJ1aWxkOiB7XG4gICAgbGliOiB7XG4gICAgICBlbnRyeTogcmVzb2x2ZShfX2Rpcm5hbWUsIFwic3JjL2luZGV4LnRzXCIpLFxuICAgICAgbmFtZTogXCJjbGljay11aVwiLFxuICAgICAgZm9ybWF0czogW1wiZXNcIiwgXCJ1bWRcIl0sXG4gICAgICBmaWxlTmFtZTogZm9ybWF0ID0+IGBjbGljay11aS4ke2Zvcm1hdH0uanNgLFxuICAgIH0sXG4gICAgcm9sbHVwT3B0aW9uczoge1xuICAgICAgLy8gQWRkIF9hbGxfIGV4dGVybmFsIGRlcGVuZGVuY2llcyBoZXJlXG4gICAgICBleHRlcm5hbDogW1xuICAgICAgICBcInJlYWN0XCIsXG4gICAgICAgIFwicmVhY3QtZG9tXCIsXG4gICAgICAgIFwic3R5bGVkLWNvbXBvbmVudHNcIixcbiAgICAgICAgXCIqKi8qLnN0b3JpZXMudHNcIixcbiAgICAgICAgXCIqKi8qLnN0b3JpZXMudHN4XCIsXG4gICAgICAgIFwiKiovKi50ZXN0LnRzXCIsXG4gICAgICAgIFwiKiovKi50ZXN0LnRzeFwiLFxuICAgICAgXSxcbiAgICAgIG91dHB1dDoge1xuICAgICAgICBnbG9iYWxzOiB7XG4gICAgICAgICAgcmVhY3Q6IFwiUmVhY3RcIixcbiAgICAgICAgICBcInN0eWxlZC1jb21wb25lbnRzXCI6IFwic3R5bGVkXCIsXG4gICAgICAgICAgXCJyZWFjdC1kb21cIjogXCJSZWFjdERPTVwiLFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICB9LFxuICB9LFxufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQXFSLFNBQVMsb0JBQW9CO0FBQ2xULE9BQU8sV0FBVztBQUNsQixPQUFPLFFBQVEsZUFBZTtBQUM5QixPQUFPLFNBQVM7QUFIaEIsSUFBTSxtQ0FBbUM7QUFNekMsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsU0FBUztBQUFBLElBQ1AsTUFBTTtBQUFBLE1BQ0osT0FBTztBQUFBLFFBQ0wsU0FBUyxDQUFDLENBQUMsa0NBQWtDLEVBQUUsYUFBYSxNQUFNLENBQUMsQ0FBQztBQUFBLFFBRXBFLEtBQUs7QUFBQSxVQUNILGFBQWE7QUFBQSxZQUNYLFNBQVMsQ0FBQyxDQUFDLGtDQUFrQyxFQUFFLGFBQWEsS0FBSyxDQUFDLENBQUM7QUFBQSxVQUNyRTtBQUFBLFFBQ0Y7QUFBQSxNQUNGO0FBQUEsSUFDRixDQUFDO0FBQUEsSUFDRCxJQUFJO0FBQUEsTUFDRixTQUFTLENBQUMsTUFBTTtBQUFBLE1BQ2hCLFNBQVMsQ0FBQyxtQkFBbUIsb0JBQW9CLGdCQUFnQixlQUFlO0FBQUEsSUFDbEYsQ0FBQztBQUFBLEVBQ0g7QUFBQSxFQUNBLFNBQVM7QUFBQSxJQUNQLE9BQU87QUFBQSxNQUNMLEtBQUssS0FBSyxRQUFRLGtDQUFXLE9BQU87QUFBQSxJQUN0QztBQUFBLEVBQ0Y7QUFBQSxFQUNBLE9BQU87QUFBQSxJQUNMLEtBQUs7QUFBQSxNQUNILE9BQU8sUUFBUSxrQ0FBVyxjQUFjO0FBQUEsTUFDeEMsTUFBTTtBQUFBLE1BQ04sU0FBUyxDQUFDLE1BQU0sS0FBSztBQUFBLE1BQ3JCLFVBQVUsWUFBVSxZQUFZLE1BQU07QUFBQSxJQUN4QztBQUFBLElBQ0EsZUFBZTtBQUFBO0FBQUEsTUFFYixVQUFVO0FBQUEsUUFDUjtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLE1BQ0Y7QUFBQSxNQUNBLFFBQVE7QUFBQSxRQUNOLFNBQVM7QUFBQSxVQUNQLE9BQU87QUFBQSxVQUNQLHFCQUFxQjtBQUFBLFVBQ3JCLGFBQWE7QUFBQSxRQUNmO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQSxFQUNGO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K