@edux-design/buttons 0.1.3 → 0.1.4

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/package.json CHANGED
@@ -1,10 +1,15 @@
1
1
  {
2
2
  "name": "@edux-design/buttons",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css"
7
7
  ],
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "CHANGELOG.md"
12
+ ],
8
13
  "types": "./dist/index.d.ts",
9
14
  "exports": {
10
15
  ".": {
@@ -17,6 +22,7 @@
17
22
  "lint": "eslint . --max-warnings 0",
18
23
  "build": "tsup src/index.js --format esm,cjs --dts",
19
24
  "dev": "tsup src/index.js --watch --format esm,cjs --dts",
25
+ "prepack": "npm run build",
20
26
  "generate:component": "turbo gen react-component",
21
27
  "check-types": "tsc --noEmit"
22
28
  },
@@ -1,103 +0,0 @@
1
- import React from "react";
2
- import { Button, IconButton } from "./../index";
3
- import { Close, External, Warning } from "@edux-design/icons";
4
-
5
- export default {
6
- title: "Components/Button",
7
- component: Button,
8
- argTypes: {
9
- size: {
10
- control: { type: "select" },
11
- options: ["small", "medium", "large"],
12
- },
13
- isSecondary: { control: "boolean" },
14
- isDanger: { control: "boolean" },
15
- isBare: { control: "boolean" },
16
- isStretched: { control: "boolean" },
17
- disabled: { control: "boolean" },
18
- startIcon: { control: false },
19
- endIcon: { control: false },
20
- children: { control: "text" },
21
- },
22
- };
23
-
24
- // Base template
25
- const Template = (args) => <Button {...args} />;
26
-
27
- // Default story
28
- export const Primary = Template.bind({});
29
- Primary.args = {
30
- children: "Primary Button",
31
- size: "medium",
32
- };
33
-
34
- // Variants
35
- export const Secondary = Template.bind({});
36
- Secondary.args = {
37
- children: "Secondary Button",
38
- isSecondary: true,
39
- };
40
-
41
- export const Danger = Template.bind({});
42
- Danger.args = {
43
- children: "Delete",
44
- isDanger: true,
45
- };
46
-
47
- export const Bare = Template.bind({});
48
- Bare.args = {
49
- children: "Bare Button",
50
- isBare: true,
51
- };
52
-
53
- export const DangerBare = Template.bind({});
54
- DangerBare.args = {
55
- children: "Log out",
56
- isBare: true,
57
- isDanger: true,
58
- };
59
-
60
- export const Disabled = Template.bind({});
61
- Disabled.args = {
62
- children: "Disabled",
63
- disabled: true,
64
- };
65
-
66
- // Sizes
67
- export const Small = Template.bind({});
68
- Small.args = {
69
- children: "Small",
70
- size: "small",
71
- };
72
-
73
- export const Large = Template.bind({});
74
- Large.args = {
75
- children: "Large",
76
- size: "large",
77
- };
78
-
79
- // Stretched (full width)
80
- export const Stretched = Template.bind({});
81
- Stretched.args = {
82
- children: "Full Width",
83
- isStretched: true,
84
- };
85
-
86
- export const Default = () => (
87
- <div className="flex gap-4 items-center">
88
- <IconButton icon={<Close />} aria-label="Close" isBare />
89
- <IconButton icon={<Close />} isSecondary aria-label="Close" />
90
- <IconButton icon={<Close />} isDanger aria-label="Delete" />
91
- <IconButton icon={<Close />} disabled aria-label="Disabled" />
92
- </div>
93
- );
94
-
95
- export const WithIcons = () => (
96
- <div className="flex gap-4 items-center">
97
- <Button startIcon={<Warning />}>Start icon</Button>
98
- <Button endIcon={<External />}>End icon</Button>
99
- <Button isDanger isBare endIcon={<External />}>
100
- Log out
101
- </Button>
102
- </div>
103
- );
@@ -1,101 +0,0 @@
1
- import React from "react";
2
- import { ButtonCounter } from "./../index";
3
-
4
- export default {
5
- title: "Components/Button",
6
- component: ButtonCounter,
7
- argTypes: {
8
- size: {
9
- control: { type: "select" },
10
- options: ["small", "medium", "large"],
11
- },
12
- isSecondary: { control: "boolean" },
13
- isDanger: { control: "boolean" },
14
- isBare: { control: "boolean" },
15
- isStretched: { control: "boolean" },
16
- disabled: { control: "boolean" },
17
- isEditable: { control: "boolean" },
18
- inputProps: { control: false },
19
- defaultValue: { control: "number" },
20
- min: { control: "number" },
21
- max: { control: "number" },
22
- step: { control: "number" },
23
- value: { control: false },
24
- onChange: { control: false },
25
- decrementIcon: { control: false },
26
- incrementIcon: { control: false },
27
- decrementButtonProps: { control: false },
28
- incrementButtonProps: { control: false },
29
- children: { control: false },
30
- },
31
- };
32
-
33
- const Template = (args) => <ButtonCounter {...args} />;
34
-
35
- export const PrimaryCounter = Template.bind({});
36
- PrimaryCounter.args = {
37
- defaultValue: 1,
38
- size: "medium",
39
- };
40
-
41
- export const EditableCounter = Template.bind({});
42
- EditableCounter.args = {
43
- defaultValue: 1,
44
- isEditable: true,
45
- };
46
-
47
- export const SecondaryCounter = Template.bind({});
48
- SecondaryCounter.args = {
49
- defaultValue: 1,
50
- isSecondary: true,
51
- };
52
-
53
- export const DangerCounter = Template.bind({});
54
- DangerCounter.args = {
55
- defaultValue: 1,
56
- isDanger: true,
57
- };
58
-
59
- export const BareCounter = Template.bind({});
60
- BareCounter.args = {
61
- defaultValue: 1,
62
- isBare: true,
63
- };
64
-
65
- export const DangerBareCounter = Template.bind({});
66
- DangerBareCounter.args = {
67
- defaultValue: 1,
68
- isBare: true,
69
- isDanger: true,
70
- };
71
-
72
- export const DisabledCounter = Template.bind({});
73
- DisabledCounter.args = {
74
- defaultValue: 1,
75
- disabled: true,
76
- };
77
-
78
- export const SmallCounter = Template.bind({});
79
- SmallCounter.args = {
80
- defaultValue: 1,
81
- size: "small",
82
- };
83
-
84
- export const LargeCounter = Template.bind({});
85
- LargeCounter.args = {
86
- defaultValue: 1,
87
- size: "large",
88
- };
89
-
90
- export const StretchedCounter = Template.bind({});
91
- StretchedCounter.args = {
92
- defaultValue: 1,
93
- isStretched: true,
94
- };
95
-
96
- export const WithBoundsCounter = Template.bind({});
97
- WithBoundsCounter.args = {
98
- defaultValue: 2,
99
- min: 0,
100
- max: 3,
101
- };
@@ -1,67 +0,0 @@
1
- import React, { useState } from "react";
2
- import { ButtonGroup, ButtonGroupItem } from "./../index";
3
- import { Warning } from "@edux-design/icons";
4
-
5
- export default {
6
- title: "Components/ButtonGroup",
7
- component: ButtonGroup,
8
- parameters: {
9
- layout: "centered",
10
- },
11
- argTypes: {
12
- size: {
13
- control: { type: "select" },
14
- options: ["small", "medium", "large"],
15
- },
16
- isStretched: { control: "boolean" },
17
- },
18
- };
19
-
20
- const Template = (args) => {
21
- const [value, setValue] = useState(args.value ?? args.defaultValue ?? "one");
22
- return (
23
- <ButtonGroup
24
- {...args}
25
- value={value}
26
- onChange={(nextValue) => {
27
- setValue(nextValue);
28
- args.onChange?.(nextValue);
29
- }}
30
- >
31
- <ButtonGroupItem value="one">Label (11)</ButtonGroupItem>
32
- <ButtonGroupItem value="two">Label (11)</ButtonGroupItem>
33
- <ButtonGroupItem
34
- value="alert"
35
- endIcon={<Warning className="h-4 w-4 text-warning-600" />}
36
- >
37
- Label
38
- </ButtonGroupItem>
39
- </ButtonGroup>
40
- );
41
- };
42
-
43
- export const Playground = Template.bind({});
44
- Playground.args = { defaultValue: "one" };
45
-
46
- export const WithIconCounts = () => {
47
- const [value, setValue] = useState("inbox");
48
-
49
- return (
50
- <div className="w-[360px]">
51
- <ButtonGroup
52
- isStretched
53
- value={value}
54
- onChange={(next) => setValue(next)}
55
- >
56
- <ButtonGroupItem value="inbox">Inbox (11)</ButtonGroupItem>
57
- <ButtonGroupItem value="drafts">Drafts (11)</ButtonGroupItem>
58
- <ButtonGroupItem
59
- value="alerts"
60
- endAdornment={<Warning className="h-3.5 w-3.5 text-warning-600" />}
61
- >
62
- Alerts
63
- </ButtonGroupItem>
64
- </ButtonGroup>
65
- </div>
66
- );
67
- };
@@ -1,127 +0,0 @@
1
- import React, { forwardRef } from "react";
2
- import { cx } from "@edux-design/utils";
3
-
4
- /**
5
- * Props for the Button component.
6
- *
7
- * Extends all native HTML `<button>` attributes, while adding
8
- * design-system variants and state controls.
9
- *
10
- * @typedef {React.ButtonHTMLAttributes<HTMLButtonElement> & {
11
- * children?: React.ReactNode,
12
- * startIcon?: React.ReactNode,
13
- * endIcon?: React.ReactNode,
14
- * size?: "small"|"medium"|"large",
15
- * isSecondary?: boolean,
16
- * isDanger?: boolean,
17
- * isBare?: boolean,
18
- * isStretched?: boolean,
19
- * disabled?: boolean,
20
- * className?: string
21
- * }} ButtonProps
22
- */
23
-
24
- /**
25
- * Button component.
26
- *
27
- * Provides design-system styled button with support for
28
- * multiple visual variants, sizing, and states.
29
- *
30
- * @component
31
- * @type {React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>}
32
- *
33
- * @example
34
- * ```jsx
35
- * <Button size="small">Save</Button>
36
- * <Button isSecondary>Cancel</Button>
37
- * <Button isDanger disabled>Delete</Button>
38
- * ```
39
- */
40
- export const Button = forwardRef(
41
- (
42
- {
43
- children,
44
- startIcon,
45
- endIcon,
46
- size = "medium",
47
- isSecondary,
48
- isDanger,
49
- isBare,
50
- isStretched,
51
- disabled,
52
- className,
53
- ...props
54
- },
55
- ref
56
- ) => {
57
- let variant = "primary";
58
- if (disabled) {
59
- variant = "disabled";
60
- } else if (isDanger && isBare) {
61
- variant = "dangerBare";
62
- } else if (isDanger) {
63
- variant = "danger";
64
- } else if (isSecondary) {
65
- variant = "secondary";
66
- } else if (isBare) {
67
- variant = "bare";
68
- }
69
-
70
- // handle conflicting props - the last bool wins
71
- if (process.env.NODE_ENV !== "production") {
72
- const count = [isSecondary, isDanger, isBare].filter(Boolean).length;
73
- const hasAllowedCombo = isDanger && isBare && count === 2;
74
- if (count > 1 && !hasAllowedCombo) {
75
- console.warn(
76
- "[Button]: Multiple variant props passed. Using the highest priority only."
77
- );
78
- }
79
- }
80
-
81
- const defaults =
82
- "flex justify-center items-center gap-8 focus:shadow-focus focus-visible:shadow-focus outline-none transition-all duration-300 ease-in-out cursor-pointer";
83
-
84
- const fonts =
85
- "text-bold tracking-normal hover:underline hover:underline-offset-(--underline-offset-btn) active:underline active:underline-offset-(--underline-offset-btn)";
86
-
87
- const sizes = {
88
- small: "h-control-sm py-8 px-16 rounded-lg text-sm",
89
- medium: "h-control-md py-8 px-16 rounded-lg text-base",
90
- large: "h-control-lg py-8 px-16 rounded-lg text-lg",
91
- };
92
-
93
- const width = isStretched ? "w-full" : "w-fit-content";
94
-
95
- const variants = {
96
- primary:
97
- "border-2 border-border-primary-emphasis bg-bg-primary-base text-white hover:bg-border-focus hover:text-fg-focus hover:border-border-emphasis hover:shadow-md active:bg-border-focus active:bg-bg-focus-overlay active:text-fg-focus",
98
- secondary:
99
- "border-2 border-border-primary-base bg-bg-base text-fg-primary-emphasis hover:shadow-md hover:bg-bg-primary-subtle active:bg-bg-primary-subtle-90",
100
- bare: "bg-bg-base text-fg-primary-emphasis hover:bg-bg-minimal active:bg-bg-minimal-5",
101
- dangerBare:
102
- "bg-bg-base text-fg-danger-emphasis hover:bg-bg-danger-subtle active:bg-bg-danger-subtle",
103
- danger:
104
- "bg-bg-danger-base border-2 border-border-danger-emphasis text-white hover:bg-bg-danger-base-5 active:bg-bg-danger-base-10",
105
- disabled:
106
- "border-2 border-border-subtle bg-bg-inactive text-fg-inactive hover:no-underline",
107
- };
108
-
109
- const STYLES = cx(defaults, sizes[size], width, fonts, variants[variant]);
110
-
111
- return (
112
- <button
113
- role="button"
114
- type="button"
115
- aria-disabled={disabled}
116
- disabled={disabled}
117
- className={STYLES}
118
- ref={ref}
119
- {...props}
120
- >
121
- {startIcon}
122
- {children ? <span>{children}</span> : null}
123
- {endIcon}
124
- </button>
125
- );
126
- }
127
- );
@@ -1,120 +0,0 @@
1
- import React from "react";
2
- import { render, screen } from "@testing-library/react";
3
- import { Button } from "./Button";
4
-
5
- describe("Button", () => {
6
- it("renders children", () => {
7
- render(<Button>Click me</Button>);
8
- expect(screen.getByRole("button")).toHaveTextContent("Click me");
9
- });
10
-
11
- it("applies medium size by default", () => {
12
- render(<Button>Default size</Button>);
13
- const btn = screen.getByRole("button");
14
- expect(btn.className).toMatch(/h-control-md/);
15
- });
16
-
17
- it("applies small size when size='small'", () => {
18
- render(<Button size="small">Small</Button>);
19
- expect(screen.getByRole("button").className).toMatch(/h-control-sm/);
20
- });
21
-
22
- it("applies large size when size='large'", () => {
23
- render(<Button size="large">Large</Button>);
24
- expect(screen.getByRole("button").className).toMatch(/h-control-lg/);
25
- });
26
-
27
- it("uses stretched width when isStretched=true", () => {
28
- render(<Button isStretched>Stretch</Button>);
29
- expect(screen.getByRole("button").className).toMatch(/w-full/);
30
- });
31
-
32
- it("applies primary variant by default", () => {
33
- render(<Button>Primary</Button>);
34
- expect(screen.getByRole("button").className).toMatch(/bg-bg-primary-base/);
35
- });
36
-
37
- it("applies secondary variant when isSecondary=true", () => {
38
- render(<Button isSecondary>Secondary</Button>);
39
- expect(screen.getByRole("button").className).toMatch(/bg-bg-base/);
40
- });
41
-
42
- it("applies bare variant when isBare=true", () => {
43
- render(<Button isBare>Bare</Button>);
44
- expect(screen.getByRole("button").className).toMatch(/bg-bg-base/);
45
- expect(screen.getByRole("button").className).toMatch(
46
- /text-fg-primary-emphasis/
47
- );
48
- });
49
-
50
- it("applies danger variant when isDanger=true", () => {
51
- render(<Button isDanger>Danger</Button>);
52
- expect(screen.getByRole("button").className).toMatch(/bg-bg-danger-base/);
53
- });
54
-
55
- it("applies danger bare variant when isDanger and isBare", () => {
56
- render(
57
- <Button isDanger isBare>
58
- Danger Bare
59
- </Button>
60
- );
61
- const btn = screen.getByRole("button");
62
- expect(btn.className).toMatch(/text-fg-danger-emphasis/);
63
- expect(btn.className).toMatch(/bg-bg-base/);
64
- expect(btn.className).not.toMatch(/bg-bg-danger-base/);
65
- });
66
-
67
- it("applies disabled variant when disabled=true", () => {
68
- render(<Button disabled>Disabled</Button>);
69
- const btn = screen.getByRole("button");
70
- expect(btn).toBeDisabled();
71
- expect(btn).toHaveAttribute("aria-disabled", "true");
72
- expect(btn.className).toMatch(/bg-bg-inactive/);
73
- });
74
-
75
- it("logs a warning in dev mode if multiple variants are passed", () => {
76
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
77
- process.env.NODE_ENV = "development";
78
-
79
- render(
80
- <Button isSecondary isDanger>
81
- Conflict
82
- </Button>
83
- );
84
-
85
- expect(warnSpy).toHaveBeenCalledWith(
86
- "[Button]: Multiple variant props passed. Using the highest priority only."
87
- );
88
-
89
- warnSpy.mockRestore();
90
- });
91
-
92
- it("renders start and end icons when provided", () => {
93
- render(
94
- <Button
95
- startIcon={<span data-testid="start-icon">S</span>}
96
- endIcon={<span data-testid="end-icon">E</span>}
97
- >
98
- With icons
99
- </Button>
100
- );
101
-
102
- expect(screen.getByTestId("start-icon")).toBeInTheDocument();
103
- expect(screen.getByTestId("end-icon")).toBeInTheDocument();
104
- });
105
-
106
- it("does not warn for the danger bare combination", () => {
107
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
108
- process.env.NODE_ENV = "development";
109
-
110
- render(
111
- <Button isDanger isBare>
112
- Allowed
113
- </Button>
114
- );
115
-
116
- expect(warnSpy).not.toHaveBeenCalled();
117
-
118
- warnSpy.mockRestore();
119
- });
120
- });