@versini/ui-button 12.3.1 → 13.1.0

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/README.md CHANGED
@@ -16,7 +16,7 @@ The Button package provides a complete set of button components including standa
16
16
 
17
17
  ## Features
18
18
 
19
- - **🎯 Multiple Button Types**: Button, ButtonIcon, ButtonLink, ButtonCopy
19
+ - **🎯 Multiple Button Types**: Button, ButtonIcon, ButtonLink, ButtonCopy, ButtonGroup
20
20
  - **♿ Accessible**: WCAG compliant with proper ARIA support and keyboard navigation
21
21
  - **🎨 Customizable**: Multiple variants, sizes, and styling options
22
22
  - **🌲 Tree-shakeable**: Import only the components you need
@@ -89,6 +89,39 @@ function App() {
89
89
  }
90
90
  ```
91
91
 
92
+ ### Button Group
93
+
94
+ ```tsx
95
+ import { Button } from "@versini/ui-button/button";
96
+ import { ButtonGroup } from "@versini/ui-button/button-group";
97
+
98
+ function App() {
99
+ return (
100
+ <ButtonGroup gap="medium">
101
+ <Button>OK</Button>
102
+ <Button>Cancel and go back</Button>
103
+ </ButtonGroup>
104
+ );
105
+ }
106
+ ```
107
+
108
+ ### Vertical Button Group
109
+
110
+ ```tsx
111
+ import { Button } from "@versini/ui-button/button";
112
+ import { ButtonGroup } from "@versini/ui-button/button-group";
113
+
114
+ function App() {
115
+ return (
116
+ <ButtonGroup orientation="vertical">
117
+ <Button>Save changes</Button>
118
+ <Button variant="secondary">Cancel</Button>
119
+ <Button variant="danger">Delete account</Button>
120
+ </ButtonGroup>
121
+ );
122
+ }
123
+ ```
124
+
92
125
  ### Different Variants
93
126
 
94
127
  ```tsx
@@ -175,7 +208,6 @@ function ThemeExamples() {
175
208
  | disabled | `boolean` | `false` | Whether or not the Button is disabled |
176
209
  | fullWidth | `boolean` | `false` | Whether or not the Button is full width |
177
210
  | mode | `"dark" \| "light" \| "system" \| "alt-system"` | `"system"` | The mode of Button |
178
- | focusMode | `"dark" \| "light" \| "system" \| "alt-system"` | `"system"` | The type of focus for the Button |
179
211
  | noBorder | `boolean` | `false` | Whether or not to render the Button with a border |
180
212
  | raw | `boolean` | `false` | Whether or not to render the Button with styles |
181
213
  | radius | `"small" \| "medium" \| "large"` | `"large"` | The rounded style of the Button |
@@ -198,7 +230,7 @@ function ThemeExamples() {
198
230
  | animated | `boolean` | `false` | Whether or not to animate the label when displayed |
199
231
  | iconClassName | `string` | - | The extra class name to pass to the icon itself |
200
232
 
201
- _Inherits all common button props (mode, focusMode, disabled, etc.)_
233
+ _Inherits all common button props (mode, disabled, etc.)_
202
234
 
203
235
  ### ButtonLink Props
204
236
 
@@ -217,3 +249,15 @@ _Inherits all Button props_
217
249
  | copyToClipboard | `string \| (() => string)` | - | What text to copy to the clipboard |
218
250
 
219
251
  _Inherits all ButtonIcon props except children, align, label, fullWidth, size_
252
+
253
+ ### ButtonGroup Props
254
+
255
+ | Prop | Type | Default | Description |
256
+ | ----------- | ---------------------------------- | -------------- | -------------------------------------------------- |
257
+ | children | `React.ReactNode` | - | The buttons to render in the group |
258
+ | gap | `"small" \| "medium" \| "large"` | `"medium"` | The spacing between buttons in the group |
259
+ | orientation | `"horizontal" \| "vertical"` | `"horizontal"` | The layout direction of the button group |
260
+ | className | `string` | - | CSS class(es) to add to the main component wrapper |
261
+ | aria-label | `string` | - | Accessible label for the button group |
262
+
263
+ _Renders with `role="group"`. All buttons share equal width via CSS Grid._
@@ -1,11 +1,9 @@
1
1
  /*!
2
- @versini/ui-button v12.3.1
2
+ @versini/ui-button v13.1.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
6
- import clsx from "clsx";
7
-
8
-
6
+ import { clsx } from "./726.js";
9
7
 
10
8
  const BUTTON_CLASSNAME = "av-button";
11
9
 
@@ -70,12 +68,6 @@ const BORDER_DANGER_DARK = "border-border-danger-dark";
70
68
  const BORDER_DARK_MEDIUM = "border-border-medium dark:border-border-medium";
71
69
  const BORDER_DANGER_DARK_DARK_MEDIUM = "border-border-danger-dark dark:border-border-danger-medium";
72
70
  const BORDER_DANGER_MEDIUM_DARK_DARK = "border-border-danger-medium dark:border-border-danger-dark";
73
- // Focus classes
74
- const FOCUS_LIGHT = "focus:outline-focus-light";
75
- const FOCUS_DARK = "focus:outline-focus-dark";
76
- // System mode compound focus classes
77
- const FOCUS_LIGHT_DARK_DARK = "focus:outline-focus-light dark:focus:outline-focus-dark";
78
- const FOCUS_DARK_DARK_LIGHT = "focus:outline-focus-dark dark:focus:outline-focus-light";
79
71
  // Common utility classes
80
72
  const NOT_PLUME = "not-plume";
81
73
  // Size classes
@@ -339,13 +331,8 @@ const getButtonBorderClasses = ({ mode, noBorder, variant })=>{
339
331
  return "border border-border-selected-dark";
340
332
  }
341
333
  /* v8 ignore stop */ };
342
- const getButtonFocusClasses = ({ focusMode })=>{
343
- return clsx("focus:outline-2 focus:outline-offset-2", {
344
- [FOCUS_DARK]: isDark(focusMode),
345
- [FOCUS_LIGHT]: isLight(focusMode),
346
- [FOCUS_LIGHT_DARK_DARK]: isAltSystem(focusMode),
347
- [FOCUS_DARK_DARK_LIGHT]: isSystem(focusMode)
348
- });
334
+ const getButtonFocusClasses = ()=>{
335
+ return clsx("focus:outline-2 focus:outline-offset-2 focus:outline-focus-dark");
349
336
  };
350
337
  const getIconClasses = ({ mode, raw, iconClassName, variant = "primary" })=>{
351
338
  if (raw) {
@@ -412,7 +399,7 @@ const getBadgeClasses = ({ size, badge })=>{
412
399
  displayValue
413
400
  };
414
401
  };
415
- const getButtonClasses = ({ type, className, raw, mode, focusMode, disabled, fullWidth, size, noBorder, labelRight, labelLeft, noBackground, variant, truncate, align, radius, animated, badge, splitBackground })=>{
402
+ const getButtonClasses = ({ type, className, raw, mode, disabled, fullWidth, size, noBorder, labelRight, labelLeft, noBackground, variant, truncate, align, radius, animated, badge, splitBackground })=>{
416
403
  if (!variant) {
417
404
  variant = "primary";
418
405
  }
@@ -445,9 +432,7 @@ const getButtonClasses = ({ type, className, raw, mode, focusMode, disabled, ful
445
432
  mode,
446
433
  variant,
447
434
  noBorder
448
- }), getButtonFocusClasses({
449
- focusMode
450
- }), getButtonHoverClasses({
435
+ }), getButtonFocusClasses(), getButtonHoverClasses({
451
436
  mode,
452
437
  variant,
453
438
  disabled
@@ -465,5 +450,4 @@ const getButtonClasses = ({ type, className, raw, mode, focusMode, disabled, ful
465
450
  }), className);
466
451
  };
467
452
 
468
- export { TYPE_BUTTON, TYPE_ICON, TYPE_LINK, clsx, getBadgeClasses, getButtonClasses, getButtonIconLabelClasses, getIconClasses };
469
- export { jsx, jsxs } from "react/jsx-runtime";
453
+ export { TYPE_BUTTON, TYPE_ICON, TYPE_LINK, getBadgeClasses, getButtonClasses, getButtonIconLabelClasses, getIconClasses };
package/dist/370.js CHANGED
@@ -1,12 +1,14 @@
1
1
  /*!
2
- @versini/ui-button v12.3.1
2
+ @versini/ui-button v13.1.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
6
6
  import { useMergeRefs } from "@versini/ui-hooks/use-merge-refs";
7
7
  import { useResizeObserver } from "@versini/ui-hooks/use-resize-observer";
8
- import { jsxs, getButtonClasses, getIconClasses, getButtonIconLabelClasses, TYPE_ICON, getBadgeClasses, jsx } from "./188.js";
9
- import { useLayoutEffect, useRef, useEffect, BaseButton_private } from "./795.js";
8
+ import { jsxs, jsx } from "./726.js";
9
+ import { useRef, useLayoutEffect, useEffect } from "./946.js";
10
+ import { getButtonClasses, getIconClasses, getButtonIconLabelClasses, TYPE_ICON, getBadgeClasses } from "./277.js";
11
+ import { BaseButton_private } from "./795.js";
10
12
 
11
13
 
12
14
 
@@ -28,11 +30,10 @@ const PADDINGS = {
28
30
  };
29
31
  const BORDERS = 2; // border x 2
30
32
  const ANIMATION_DURATION = 300; // ms - should match the CSS transition duration
31
- function ButtonIcon({ children, disabled = false, mode = "system", focusMode = "system", fullWidth = false, className, type = "button", raw = false, noBorder = false, "aria-label": ariaLabel, label, size = "medium", labelRight, labelLeft, noBackground = false, align = "center", radius = "large", variant = "secondary", iconClassName, animated = false, badge, splitBackground = false, ref, ...otherProps }) {
33
+ function ButtonIcon({ children, disabled = false, mode = "system", fullWidth = false, className, type = "button", raw = false, noBorder = false, "aria-label": ariaLabel, label, size = "medium", labelRight, labelLeft, noBackground = false, align = "center", radius = "large", variant = "secondary", iconClassName, animated = false, badge, splitBackground = false, ref, ...otherProps }) {
32
34
  const buttonClass = getButtonClasses({
33
35
  type: TYPE_ICON,
34
36
  mode,
35
- focusMode,
36
37
  fullWidth,
37
38
  disabled,
38
39
  raw,
package/dist/726.js ADDED
@@ -0,0 +1,9 @@
1
+ /*!
2
+ @versini/ui-button v13.1.0
3
+ © 2026 gizmette.com
4
+ */
5
+
6
+
7
+
8
+ export { jsx, jsxs } from "react/jsx-runtime";
9
+ export { default as clsx } from "clsx";
package/dist/795.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /*!
2
- @versini/ui-button v12.3.1
2
+ @versini/ui-button v13.1.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
6
- import { jsx } from "./188.js";
7
-
6
+ import { jsx } from "./726.js";
7
+ import "./946.js";
8
8
 
9
9
 
10
10
 
@@ -40,4 +40,3 @@ import { jsx } from "./188.js";
40
40
  }
41
41
 
42
42
  export { BaseButton_private };
43
- export { useEffect, useLayoutEffect, useRef, useState } from "react";
package/dist/946.js ADDED
@@ -0,0 +1,7 @@
1
+ /*!
2
+ @versini/ui-button v13.1.0
3
+ © 2026 gizmette.com
4
+ */
5
+
6
+
7
+ export { default as react, useEffect, useLayoutEffect, useRef, useState } from "react";
@@ -1,2 +1,2 @@
1
1
  import type { ButtonTypes } from "@versini/ui-types";
2
- export declare function Button({ children, disabled, mode, focusMode, fullWidth, className, size, raw, noBorder, variant, truncate, radius, splitBackground, ref, ...otherProps }: ButtonTypes.Props): import("react/jsx-runtime").JSX.Element;
2
+ export declare function Button({ children, disabled, mode, fullWidth, className, size, raw, noBorder, variant, truncate, radius, splitBackground, ref, ...otherProps }: ButtonTypes.Props): import("react/jsx-runtime").JSX.Element;
@@ -1,19 +1,19 @@
1
1
  /*!
2
- @versini/ui-button v12.3.1
2
+ @versini/ui-button v13.1.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
6
- import { getButtonClasses, TYPE_BUTTON, jsx } from "../../188.js";
6
+ import { jsx } from "../../726.js";
7
+ import { getButtonClasses, TYPE_BUTTON } from "../../277.js";
7
8
  import { BaseButton_private } from "../../795.js";
8
9
 
9
10
 
10
11
 
11
12
 
12
- function Button({ children, disabled = false, mode = "system", focusMode = "system", fullWidth = false, className, size = "medium", raw = false, noBorder = false, variant = "primary", truncate = false, radius = "large", splitBackground = false, ref, ...otherProps }) {
13
+ function Button({ children, disabled = false, mode = "system", fullWidth = false, className, size = "medium", raw = false, noBorder = false, variant = "primary", truncate = false, radius = "large", splitBackground = false, ref, ...otherProps }) {
13
14
  const buttonClass = getButtonClasses({
14
15
  type: TYPE_BUTTON,
15
16
  mode,
16
- focusMode,
17
17
  fullWidth,
18
18
  disabled,
19
19
  raw,
@@ -1,11 +1,11 @@
1
1
  /*!
2
- @versini/ui-button v12.3.1
2
+ @versini/ui-button v13.1.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
6
6
  import { IconCopied, IconCopy } from "@versini/ui-icons";
7
- import { jsx } from "../../188.js";
8
- import { useState, useEffect } from "../../795.js";
7
+ import { jsx } from "../../726.js";
8
+ import { useState, useEffect } from "../../946.js";
9
9
  import { ButtonIcon } from "../../370.js";
10
10
 
11
11
 
@@ -0,0 +1,2 @@
1
+ import type { ButtonGroupTypes } from "@versini/ui-types";
2
+ export declare function ButtonGroup({ children, gap, orientation, className, ...otherProps }: ButtonGroupTypes.Props): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,53 @@
1
+ /*!
2
+ @versini/ui-button v13.1.0
3
+ © 2026 gizmette.com
4
+ */
5
+
6
+ import { clsx, jsx } from "../../726.js";
7
+ import { react } from "../../946.js";
8
+
9
+
10
+ const getButtonGroupClasses = ({ gap = "medium", className })=>{
11
+ return clsx("grid", {
12
+ "gap-1": gap === "small",
13
+ "gap-2": gap === "medium",
14
+ "gap-4": gap === "large"
15
+ }, className);
16
+ };
17
+ const getButtonGroupStyle = ({ orientation = "horizontal", childCount })=>{
18
+ if (orientation === "vertical") {
19
+ return {
20
+ gridTemplateColumns: "1fr"
21
+ };
22
+ }
23
+ return {
24
+ gridTemplateColumns: `repeat(${childCount}, minmax(0, 1fr))`
25
+ };
26
+ };
27
+
28
+
29
+
30
+
31
+ const getValidChildCount = (children)=>{
32
+ return react.Children.toArray(children).length;
33
+ };
34
+ function ButtonGroup({ children, gap = "medium", orientation = "horizontal", className, ...otherProps }) {
35
+ const childCount = getValidChildCount(children);
36
+ const groupClass = getButtonGroupClasses({
37
+ gap,
38
+ className
39
+ });
40
+ const groupStyle = getButtonGroupStyle({
41
+ orientation,
42
+ childCount
43
+ });
44
+ return /*#__PURE__*/ jsx("div", {
45
+ role: "group",
46
+ className: groupClass,
47
+ style: groupStyle,
48
+ ...otherProps,
49
+ children: children
50
+ });
51
+ }
52
+
53
+ export { ButtonGroup };
@@ -0,0 +1,6 @@
1
+ import type { ButtonGroupTypes } from "@versini/ui-types";
2
+ export declare const getButtonGroupClasses: ({ gap, className, }: Pick<ButtonGroupTypes.Props, "gap" | "className">) => string;
3
+ export declare const getButtonGroupStyle: ({ orientation, childCount, }: {
4
+ orientation?: ButtonGroupTypes.Props["orientation"];
5
+ childCount: number;
6
+ }) => React.CSSProperties;
@@ -1,2 +1,2 @@
1
1
  import type { ButtonIconTypes } from "@versini/ui-types";
2
- export declare function ButtonIcon({ children, disabled, mode, focusMode, fullWidth, className, type, raw, noBorder, "aria-label": ariaLabel, label, size, labelRight, labelLeft, noBackground, align, radius, variant, iconClassName, animated, badge, splitBackground, ref, ...otherProps }: ButtonIconTypes.Props): import("react/jsx-runtime").JSX.Element;
2
+ export declare function ButtonIcon({ children, disabled, mode, fullWidth, className, type, raw, noBorder, "aria-label": ariaLabel, label, size, labelRight, labelLeft, noBackground, align, radius, variant, iconClassName, animated, badge, splitBackground, ref, ...otherProps }: ButtonIconTypes.Props): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-button v12.3.1
2
+ @versini/ui-button v13.1.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,2 +1,2 @@
1
1
  import type { ButtonLinkTypes } from "@versini/ui-types";
2
- export declare function ButtonLink({ children, mode, focusMode, fullWidth, className, size, raw, noBorder, target, truncate, noNewWindowIcon, radius, splitBackground, ref, ...otherProps }: ButtonLinkTypes.Props): import("react/jsx-runtime").JSX.Element;
2
+ export declare function ButtonLink({ children, mode, fullWidth, className, size, raw, noBorder, target, truncate, noNewWindowIcon, radius, splitBackground, ref, ...otherProps }: ButtonLinkTypes.Props): import("react/jsx-runtime").JSX.Element;
@@ -1,18 +1,18 @@
1
1
  /*!
2
- @versini/ui-button v12.3.1
2
+ @versini/ui-button v13.1.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
6
- import { jsxs, getButtonClasses, TYPE_LINK, clsx, jsx } from "../../188.js";
6
+ import { jsxs, clsx, jsx } from "../../726.js";
7
+ import { getButtonClasses, TYPE_LINK } from "../../277.js";
7
8
 
8
9
 
9
10
 
10
11
 
11
- function ButtonLink({ children, mode = "system", focusMode = "system", fullWidth = false, className, size = "small", raw = false, noBorder = false, target, truncate = false, noNewWindowIcon = false, radius = "large", splitBackground = false, ref, ...otherProps }) {
12
+ function ButtonLink({ children, mode = "system", fullWidth = false, className, size = "small", raw = false, noBorder = false, target, truncate = false, noNewWindowIcon = false, radius = "large", splitBackground = false, ref, ...otherProps }) {
12
13
  const buttonClass = getButtonClasses({
13
14
  type: TYPE_LINK,
14
15
  mode,
15
- focusMode,
16
16
  fullWidth,
17
17
  disabled: false,
18
18
  raw,
@@ -13,5 +13,5 @@ export declare const getBadgeClasses: ({ size, badge, }: Pick<ButtonIconTypes.Pr
13
13
  className: string;
14
14
  displayValue: string | null;
15
15
  } | null;
16
- export declare const getButtonClasses: ({ type, className, raw, mode, focusMode, disabled, fullWidth, size, noBorder, labelRight, labelLeft, noBackground, variant, truncate, align, radius, animated, badge, splitBackground, }: GetButtonClassesProps) => string;
16
+ export declare const getButtonClasses: ({ type, className, raw, mode, disabled, fullWidth, size, noBorder, labelRight, labelLeft, noBackground, variant, truncate, align, radius, animated, badge, splitBackground, }: GetButtonClassesProps) => string;
17
17
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versini/ui-button",
3
- "version": "12.3.1",
3
+ "version": "13.1.0",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "publishConfig": {
@@ -25,6 +25,10 @@
25
25
  "types": "./dist/components/Button/ButtonIcon.d.ts",
26
26
  "import": "./dist/components/Button/ButtonIcon.js"
27
27
  },
28
+ "./button-group": {
29
+ "types": "./dist/components/Button/ButtonGroup.d.ts",
30
+ "import": "./dist/components/Button/ButtonGroup.js"
31
+ },
28
32
  "./button-link": {
29
33
  "types": "./dist/components/Button/ButtonLink.d.ts",
30
34
  "import": "./dist/components/Button/ButtonLink.js"
@@ -59,7 +63,7 @@
59
63
  },
60
64
  "devDependencies": {
61
65
  "@testing-library/jest-dom": "6.9.1",
62
- "@versini/ui-types": "8.4.0"
66
+ "@versini/ui-types": "9.1.0"
63
67
  },
64
68
  "dependencies": {
65
69
  "@versini/ui-hooks": "6.1.1",
@@ -70,5 +74,5 @@
70
74
  "sideEffects": [
71
75
  "**/*.css"
72
76
  ],
73
- "gitHead": "2b4b581f8eb4e12cf4a2d6e0c40583f0f86d3b4c"
77
+ "gitHead": "e1d07ef9f8c39af8e1cf471a2d1688f4bca1771c"
74
78
  }