@edux-design/buttons 0.2.0 → 0.3.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.
@@ -1,4 +0,0 @@
1
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none"
2
- xmlns="http://www.w3.org/2000/svg">
3
- <path fill-rule="evenodd" clip-rule="evenodd" d="M13.5858 12L9.29289 7.70711C8.90237 7.31658 8.90237 6.68342 9.29289 6.29289C9.68342 5.90237 10.3166 5.90237 10.7071 6.29289L15.7071 11.2929C16.0976 11.6834 16.0976 12.3166 15.7071 12.7071L10.7071 17.7071C10.3166 18.0976 9.68342 18.0976 9.29289 17.7071C8.90237 17.3166 8.90237 16.6834 9.29289 16.2929L13.5858 12Z" fill="currentColor"/>
4
- </svg>
@@ -1,10 +0,0 @@
1
- // src/elements/Button.jsx
2
- import React from "react";
3
- function Button() {
4
- return /* @__PURE__ */ React.createElement("div", { className: "border-2 border-primary-700" }, "Button");
5
- }
6
- var Button_default = Button;
7
-
8
- export {
9
- Button_default
10
- };
@@ -1,39 +0,0 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
- // If the importer is in node compatibility mode or this is not an ESM
21
- // file that has been converted to a CommonJS file using a Babel-
22
- // compatible transform (i.e. "__esModule" has not been set), then set
23
- // "default" to the CommonJS "module.exports" for node compatibility.
24
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
- mod
26
- ));
27
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
-
29
- // src/elements/Button.jsx
30
- var Button_exports = {};
31
- __export(Button_exports, {
32
- default: () => Button_default
33
- });
34
- module.exports = __toCommonJS(Button_exports);
35
- var import_react = __toESM(require("react"));
36
- function Button() {
37
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "border-2 border-primary-700" }, "Button");
38
- }
39
- var Button_default = Button;
@@ -1,6 +0,0 @@
1
- import {
2
- Button_default
3
- } from "../chunk-M47FNL4P.mjs";
4
- export {
5
- Button_default as default
6
- };
@@ -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,115 +0,0 @@
1
- import React, { useState } from "react";
2
- import {
3
- Button,
4
- ButtonCounter,
5
- ButtonGroup,
6
- ButtonGroupItem,
7
- IconButton,
8
- MenuButton,
9
- } from "./../index";
10
-
11
- const sizes = ["small", "medium", "large"];
12
- const variants = [
13
- { label: "Primary", props: {} },
14
- { label: "Secondary", props: { isSecondary: true } },
15
- { label: "Bare", props: { isBare: true } },
16
- { label: "Danger", props: { isDanger: true } },
17
- { label: "Danger bare", props: { isDanger: true, isBare: true } },
18
- { label: "Disabled", props: { disabled: true } },
19
- ];
20
-
21
- function GroupExample() {
22
- const [value, setValue] = useState("one");
23
-
24
- return (
25
- <ButtonGroup value={value} onChange={setValue}>
26
- <ButtonGroupItem value="one">One</ButtonGroupItem>
27
- <ButtonGroupItem value="two">Two</ButtonGroupItem>
28
- <ButtonGroupItem value="three">Three</ButtonGroupItem>
29
- </ButtonGroup>
30
- );
31
- }
32
-
33
- function ControlMatrix() {
34
- return (
35
- <div className="space-y-24">
36
- <section className="space-y-12">
37
- <h3 className="text-lg font-semibold text-fg-emphasis">
38
- Button variants
39
- </h3>
40
- <div className="grid gap-12">
41
- {variants.map(({ label, props }) => (
42
- <div key={label} className="flex flex-wrap items-center gap-12">
43
- {sizes.map((size) => (
44
- <Button key={size} size={size} {...props}>
45
- {label} {size}
46
- </Button>
47
- ))}
48
- </div>
49
- ))}
50
- </div>
51
- </section>
52
-
53
- <section className="space-y-12">
54
- <h3 className="text-lg font-semibold text-fg-emphasis">
55
- Related controls
56
- </h3>
57
- <div className="flex flex-wrap items-center gap-12">
58
- {sizes.map((size) => (
59
- <IconButton
60
- key={size}
61
- size={size}
62
- icon={<span aria-hidden="true">×</span>}
63
- aria-label={`Close ${size}`}
64
- />
65
- ))}
66
- <MenuButton size="small" aria-expanded="false">
67
- Small menu
68
- </MenuButton>
69
- <MenuButton size="medium" aria-expanded="false">
70
- Medium menu
71
- </MenuButton>
72
- </div>
73
- <div className="flex flex-wrap items-center gap-12">
74
- {sizes.map((size) => (
75
- <ButtonCounter
76
- key={size}
77
- size={size}
78
- defaultValue={2}
79
- aria-label={`${size} quantity`}
80
- />
81
- ))}
82
- </div>
83
- <GroupExample />
84
- </section>
85
- </div>
86
- );
87
- }
88
-
89
- const themeCases = [
90
- { label: "Light · Blue", className: "theme--light brand--blue" },
91
- { label: "Light · Teal", className: "theme--light brand--teal" },
92
- { label: "Dark · Blue", className: "theme--dark brand--blue" },
93
- { label: "Dark · Teal", className: "theme--dark brand--teal" },
94
- ];
95
-
96
- export default {
97
- title: "Components/Button/Compatibility",
98
- parameters: {
99
- layout: "fullscreen",
100
- chromatic: { disableSnapshot: false },
101
- },
102
- };
103
-
104
- export const CompatibilityMatrix = {
105
- render: () => (
106
- <div className="grid grid-cols-1 2xl:grid-cols-2">
107
- {themeCases.map(({ label, className }) => (
108
- <div key={label} className={`${className} bg-bg-base p-24`}>
109
- <h2 className="mb-16 text-xl font-bold text-fg-emphasis">{label}</h2>
110
- <ControlMatrix />
111
- </div>
112
- ))}
113
- </div>
114
- ),
115
- };
@@ -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,60 +0,0 @@
1
- import React from "react";
2
- import { render } from "@testing-library/react";
3
- import { Button } from "./Button";
4
- import { IconButton } from "./IconButton";
5
- import { MenuButton } from "./MenuButton";
6
- import { ButtonGroup } from "./ButtonGroup";
7
- import { ButtonGroupItem } from "./ButtonGroupItem";
8
- import { ButtonCounter } from "./ButtonCounter";
9
- import { expectNoAxeViolations } from "./testA11y";
10
-
11
- describe("buttons accessibility", () => {
12
- it("Button has no detectable accessibility violations", async () => {
13
- const { container } = render(
14
- <Button startIcon={<span aria-hidden="true">+</span>}>Save</Button>,
15
- );
16
-
17
- await expectNoAxeViolations(container);
18
- });
19
-
20
- it("IconButton has no detectable accessibility violations when named", async () => {
21
- const { container } = render(
22
- <IconButton
23
- icon={<span aria-hidden="true">X</span>}
24
- aria-label="Close dialog"
25
- />,
26
- );
27
-
28
- await expectNoAxeViolations(container);
29
- });
30
-
31
- it("MenuButton has no detectable accessibility violations", async () => {
32
- const { container } = render(
33
- <MenuButton aria-expanded={false}>Options</MenuButton>,
34
- );
35
-
36
- await expectNoAxeViolations(container);
37
- });
38
-
39
- it("ButtonGroup has no detectable accessibility violations", async () => {
40
- const { container } = render(
41
- <ButtonGroup defaultValue="first">
42
- <ButtonGroupItem value="first">First</ButtonGroupItem>
43
- <ButtonGroupItem value="second">Second</ButtonGroupItem>
44
- <ButtonGroupItem value="third" disabled>
45
- Third
46
- </ButtonGroupItem>
47
- </ButtonGroup>,
48
- );
49
-
50
- await expectNoAxeViolations(container);
51
- });
52
-
53
- it("ButtonCounter has no detectable accessibility violations", async () => {
54
- const { container } = render(
55
- <ButtonCounter defaultValue={2} aria-label="Quantity" />,
56
- );
57
-
58
- await expectNoAxeViolations(container);
59
- });
60
- });
@@ -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
- );