@edux-design/buttons 0.0.3 → 0.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @edux-design/buttons
2
2
 
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - b9d0d32: Added documentation
8
+ - Updated dependencies [b9d0d32]
9
+ - Updated dependencies
10
+ - @edux-design/utils@0.0.3
11
+
3
12
  ## 0.0.3
4
13
 
5
14
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @edux-design/buttons
2
+
3
+ Headless-ish button primitives wired to the Edux design tokens. The package ships four components:
4
+
5
+ - `Button` – primary action button with size + variant helpers.
6
+ - `IconButton` – circular/square icon surface that reuses the same states.
7
+ - `MenuButton` – trigger wrapper that pairs nicely with popovers/menus.
8
+ - `ButtonGroup` – segmented control with a sliding active state meant for contextual filters.
9
+
10
+ Everything is built with Tailwind utilities via the shared `cx` helper so styling stays consistent with the rest of the system.
11
+
12
+ ---
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pnpm add @edux-design/buttons @edux-design/utils
18
+ # or
19
+ npm install @edux-design/buttons @edux-design/utils
20
+ ```
21
+
22
+ Peer deps: `react@^19.1.0`, `react-dom@^19.1.0`.
23
+
24
+ ---
25
+
26
+ ## Usage
27
+
28
+ ```jsx
29
+ import { Button, IconButton, MenuButton } from "@edux-design/buttons";
30
+ import { Close, Chevron } from "@edux-design/icons";
31
+
32
+ export function Actions() {
33
+ return (
34
+ <div className="flex gap-4">
35
+ <Button size="small">Save</Button>
36
+ <Button isSecondary isStretched>
37
+ Cancel
38
+ </Button>
39
+ <Button isDanger disabled>
40
+ Delete
41
+ </Button>
42
+ <IconButton icon={<Close />} aria-label="Close dialog" />
43
+ <MenuButton caret={<Chevron />} onClick={() => null}>
44
+ Options
45
+ </MenuButton>
46
+ </div>
47
+ );
48
+ }
49
+ ```
50
+
51
+ ### ButtonGroup
52
+
53
+ ```jsx
54
+ import React from "react";
55
+ import { ButtonGroup } from "@edux-design/buttons";
56
+ import { Warning } from "@edux-design/icons";
57
+
58
+ export function SegmentedFilter() {
59
+ const [value, setValue] = React.useState("inbox");
60
+
61
+ return (
62
+ <ButtonGroup
63
+ value={value}
64
+ onChange={(nextValue) => setValue(nextValue)}
65
+ options={[
66
+ { value: "inbox", label: "Label (11)" },
67
+ { value: "drafts", label: "Label (11)" },
68
+ {
69
+ value: "alerts",
70
+ label: "Label",
71
+ endAdornment: <Warning className="h-4 w-4 text-warning-600" />,
72
+ },
73
+ ]}
74
+ />
75
+ );
76
+ }
77
+ ```
78
+
79
+ Provide `options` as strings or objects (`{ value, label, startAdornment, endAdornment, disabled }`). The active pill animates over the newly selected option and the group behaves like an accessible radiogroup (arrow keys + home/end). Use `isStretched` when the control should fill the parent width.
80
+
81
+ ### Key props
82
+
83
+ | Component | Important props |
84
+ | ------------- | --------------- |
85
+ | `Button` | `size="small"|"medium"|"large"`, `isSecondary`, `isDanger`, `isBare`, `isStretched`, `disabled` |
86
+ | `IconButton` | `icon`, `size`, `isBare`, `isDanger`, `disabled` |
87
+ | `MenuButton` | Accepts all `Button` props plus caret slot and ARIA attributes for menu triggers |
88
+ | `ButtonGroup` | `options`, `value` / `defaultValue`, `onChange`, `size`, `isStretched`, per-option `startAdornment` / `endAdornment`, `disabled` |
89
+
90
+ Buttons warn in development if you pass conflicting variant booleans so you know which style wins.
91
+
92
+ ---
93
+
94
+ ## Development
95
+
96
+ ```bash
97
+ pnpm --filter @edux-design/buttons lint
98
+ pnpm --filter @edux-design/buttons check-types
99
+ pnpm --filter @edux-design/buttons build
100
+ ```
101
+
102
+ Stories live under `src/demo` (e.g. `Button.stories.jsx`, `ButtonGroup.stories.jsx`) for quick visual verification.
103
+
104
+ ---
105
+
106
+ ## Notes
107
+
108
+ - Variants map directly to the design tokens defined in `@edux-design/tokens`.
109
+ - Hover/focus styles are already applied; avoid overriding unless you have a matching token.
110
+ - If you need new variants (ghost, link, destructive-secondary, etc.), add them in `Button.jsx` and update the stories + README accordingly.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
2
3
 
3
4
  /**
4
5
  * Props for the IconButton component.
@@ -129,4 +130,30 @@ declare function MenuButton({ children, size }: {
129
130
  size: any;
130
131
  }): react_jsx_runtime.JSX.Element;
131
132
 
132
- export { Button, IconButton, MenuButton };
133
+ /**
134
+ * @typedef {Object} ButtonGroupOption
135
+ * @property {React.ReactNode} [label] - Main label for the option.
136
+ * @property {React.ReactNode} [startAdornment] - Optional leading element (icon, badge, etc.).
137
+ * @property {React.ReactNode} [endIcon] - Optional trailing element.
138
+ * @property {string|number} [value] - Unique value used for selection.
139
+ * @property {boolean} [disabled] - Whether the option is disabled.
140
+ */
141
+ /**
142
+ * Segmented button group with animated active state.
143
+ *
144
+ * @typedef {React.HTMLAttributes<HTMLDivElement> & {
145
+ * value?: string|number,
146
+ * defaultValue?: string|number,
147
+ * onChange?: (value: string|number, option: ButtonGroupOption) => void,
148
+ * size?: "small"|"medium"|"large",
149
+ * isStretched?: boolean,
150
+ * }} ButtonGroupProps
151
+ */
152
+ declare const ButtonGroup: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
153
+
154
+ declare function ButtonGroupItem(_props: any): any;
155
+ declare namespace ButtonGroupItem {
156
+ let displayName: string;
157
+ }
158
+
159
+ export { Button, ButtonGroup, ButtonGroupItem, IconButton, MenuButton };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
2
3
 
3
4
  /**
4
5
  * Props for the IconButton component.
@@ -129,4 +130,30 @@ declare function MenuButton({ children, size }: {
129
130
  size: any;
130
131
  }): react_jsx_runtime.JSX.Element;
131
132
 
132
- export { Button, IconButton, MenuButton };
133
+ /**
134
+ * @typedef {Object} ButtonGroupOption
135
+ * @property {React.ReactNode} [label] - Main label for the option.
136
+ * @property {React.ReactNode} [startAdornment] - Optional leading element (icon, badge, etc.).
137
+ * @property {React.ReactNode} [endIcon] - Optional trailing element.
138
+ * @property {string|number} [value] - Unique value used for selection.
139
+ * @property {boolean} [disabled] - Whether the option is disabled.
140
+ */
141
+ /**
142
+ * Segmented button group with animated active state.
143
+ *
144
+ * @typedef {React.HTMLAttributes<HTMLDivElement> & {
145
+ * value?: string|number,
146
+ * defaultValue?: string|number,
147
+ * onChange?: (value: string|number, option: ButtonGroupOption) => void,
148
+ * size?: "small"|"medium"|"large",
149
+ * isStretched?: boolean,
150
+ * }} ButtonGroupProps
151
+ */
152
+ declare const ButtonGroup: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
153
+
154
+ declare function ButtonGroupItem(_props: any): any;
155
+ declare namespace ButtonGroupItem {
156
+ let displayName: string;
157
+ }
158
+
159
+ export { Button, ButtonGroup, ButtonGroupItem, IconButton, MenuButton };
package/dist/index.js CHANGED
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
32
  Button: () => Button,
33
+ ButtonGroup: () => ButtonGroup,
34
+ ButtonGroupItem: () => ButtonGroupItem,
33
35
  IconButton: () => IconButton,
34
36
  MenuButton: () => MenuButton
35
37
  });
@@ -1819,19 +1821,19 @@ var require_react_jsx_runtime_development = __commonJS({
1819
1821
  function validateChildKeys(node) {
1820
1822
  "object" === typeof node && null !== node && node.$$typeof === REACT_ELEMENT_TYPE && node._store && (node._store.validated = 1);
1821
1823
  }
1822
- var React5 = require_react(), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler");
1823
- var REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React5.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function() {
1824
+ var React7 = require_react(), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler");
1825
+ var REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React7.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function() {
1824
1826
  return null;
1825
1827
  };
1826
- React5 = {
1828
+ React7 = {
1827
1829
  react_stack_bottom_frame: function(callStackForError) {
1828
1830
  return callStackForError();
1829
1831
  }
1830
1832
  };
1831
1833
  var specialPropKeyWarningShown;
1832
1834
  var didWarnAboutElementRef = {};
1833
- var unknownOwnerDebugStack = React5.react_stack_bottom_frame.bind(
1834
- React5,
1835
+ var unknownOwnerDebugStack = React7.react_stack_bottom_frame.bind(
1836
+ React7,
1835
1837
  UnknownOwner
1836
1838
  )();
1837
1839
  var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
@@ -1880,7 +1882,8 @@ var import_jsx_runtime22 = __toESM2(require_jsx_runtime());
1880
1882
  var import_jsx_runtime32 = __toESM2(require_jsx_runtime());
1881
1883
  var import_jsx_runtime4 = __toESM2(require_jsx_runtime());
1882
1884
  var import_jsx_runtime5 = __toESM2(require_jsx_runtime());
1883
- var SvgChevron = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1885
+ var import_jsx_runtime6 = __toESM2(require_jsx_runtime());
1886
+ var SvgChevron = (props) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1884
1887
  "svg",
1885
1888
  {
1886
1889
  xmlns: "http://www.w3.org/2000/svg",
@@ -1889,7 +1892,7 @@ var SvgChevron = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1889
1892
  fill: "none",
1890
1893
  viewBox: "0 0 24 24",
1891
1894
  ...props,
1892
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1895
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1893
1896
  "path",
1894
1897
  {
1895
1898
  fill: "currentColor",
@@ -1901,7 +1904,6 @@ var SvgChevron = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1901
1904
  }
1902
1905
  );
1903
1906
  var Chevron_default = SvgChevron;
1904
- var import_jsx_runtime6 = __toESM2(require_jsx_runtime());
1905
1907
  var import_jsx_runtime7 = __toESM2(require_jsx_runtime());
1906
1908
  var import_jsx_runtime8 = __toESM2(require_jsx_runtime());
1907
1909
  var import_jsx_runtime9 = __toESM2(require_jsx_runtime());
@@ -1944,9 +1946,16 @@ var import_jsx_runtime45 = __toESM2(require_jsx_runtime());
1944
1946
  var import_jsx_runtime46 = __toESM2(require_jsx_runtime());
1945
1947
  var import_jsx_runtime47 = __toESM2(require_jsx_runtime());
1946
1948
  var import_jsx_runtime48 = __toESM2(require_jsx_runtime());
1949
+ var import_jsx_runtime49 = __toESM2(require_jsx_runtime());
1950
+ var import_jsx_runtime50 = __toESM2(require_jsx_runtime());
1951
+ var import_jsx_runtime51 = __toESM2(require_jsx_runtime());
1952
+ var import_jsx_runtime52 = __toESM2(require_jsx_runtime());
1953
+ var import_jsx_runtime53 = __toESM2(require_jsx_runtime());
1954
+ var import_jsx_runtime54 = __toESM2(require_jsx_runtime());
1955
+ var import_jsx_runtime55 = __toESM2(require_jsx_runtime());
1947
1956
 
1948
1957
  // src/elements/MenuButton.jsx
1949
- var import_jsx_runtime49 = require("react/jsx-runtime");
1958
+ var import_jsx_runtime56 = require("react/jsx-runtime");
1950
1959
  function MenuButton({ children, size }) {
1951
1960
  const SPLIT_BUTTON_STYLES = (0, import_utils3.cx)(
1952
1961
  "w-[36px] border-l-[1px] border-l-black flex flex-row justify-center items-center",
@@ -1955,14 +1964,321 @@ function MenuButton({ children, size }) {
1955
1964
  "h-[40px] py-2": size === "medium" || size === void 0
1956
1965
  }
1957
1966
  );
1958
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(MenuButtonContext.Provider, { value: true, children: [
1959
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: SPLIT_BUTTON_STYLES, children }),
1960
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Chevron_default, {})
1967
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(MenuButtonContext.Provider, { value: true, children: [
1968
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("div", { className: SPLIT_BUTTON_STYLES, children }),
1969
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Chevron_default, {})
1961
1970
  ] });
1962
1971
  }
1972
+
1973
+ // src/elements/ButtonGroup.jsx
1974
+ var import_react5 = __toESM(require("react"));
1975
+ var import_utils4 = require("@edux-design/utils");
1976
+ var import_jsx_runtime57 = require("react/jsx-runtime");
1977
+ var SIZE_STYLES = {
1978
+ small: {
1979
+ button: "text-xs px-12 py-6 rounded-md "
1980
+ },
1981
+ medium: {
1982
+ button: "text-sm px-12 py-6 rounded-md "
1983
+ },
1984
+ large: {
1985
+ button: "text-base px-12 py-6 rounded-md"
1986
+ }
1987
+ };
1988
+ var isBrowser = typeof window !== "undefined";
1989
+ var useIsomorphicLayoutEffect = isBrowser ? import_react5.useLayoutEffect : import_react5.useEffect;
1990
+ var ButtonGroup = (0, import_react5.forwardRef)(function ButtonGroup2({
1991
+ value,
1992
+ defaultValue,
1993
+ onChange,
1994
+ size = "medium",
1995
+ isStretched = false,
1996
+ className,
1997
+ children,
1998
+ ...rest
1999
+ }, forwardedRef) {
2000
+ const normalizedOptions = (0, import_react5.useMemo)(() => {
2001
+ const childItems = (0, import_utils4.filterChildrenByDisplayName)(children, "ButtonGroupItem");
2002
+ return childItems.map((child, index) => {
2003
+ const props = child.props || {};
2004
+ const hasValue = props.value !== void 0;
2005
+ const safeValue = hasValue && props.value !== null ? props.value : index;
2006
+ return {
2007
+ ...props,
2008
+ label: props.label ?? props.children ?? `Option ${index + 1}`,
2009
+ value: safeValue
2010
+ };
2011
+ });
2012
+ }, [children]);
2013
+ const sizeStyles = SIZE_STYLES[size] ?? SIZE_STYLES.medium;
2014
+ const initialValue = value ?? defaultValue ?? (normalizedOptions.length ? normalizedOptions[0].value : void 0);
2015
+ const [internalValue, setInternalValue] = (0, import_react5.useState)(initialValue);
2016
+ const isControlled = value !== void 0;
2017
+ const selectedValue = isControlled ? value : internalValue;
2018
+ (0, import_react5.useEffect)(() => {
2019
+ if (!isControlled && defaultValue !== void 0) {
2020
+ setInternalValue(defaultValue);
2021
+ }
2022
+ }, [defaultValue, isControlled]);
2023
+ (0, import_react5.useEffect)(() => {
2024
+ if (isControlled) return;
2025
+ if (normalizedOptions.length && !normalizedOptions.some((option) => option.value === internalValue)) {
2026
+ setInternalValue(normalizedOptions[0].value);
2027
+ }
2028
+ }, [internalValue, isControlled, normalizedOptions]);
2029
+ const buttonRefs = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
2030
+ const containerRef = (0, import_react5.useRef)(null);
2031
+ const indicatorRef = (0, import_react5.useRef)({ width: 0, height: 0, x: 0, y: 0 });
2032
+ const [indicator, setIndicator] = (0, import_react5.useState)({
2033
+ width: 0,
2034
+ height: 0,
2035
+ x: 0,
2036
+ y: 0
2037
+ });
2038
+ const setContainerRef = (0, import_react5.useCallback)(
2039
+ (node) => {
2040
+ containerRef.current = node;
2041
+ if (typeof forwardedRef === "function") {
2042
+ forwardedRef(node);
2043
+ } else if (forwardedRef) {
2044
+ forwardedRef.current = node;
2045
+ }
2046
+ },
2047
+ [forwardedRef]
2048
+ );
2049
+ const registerButtonRef = (0, import_react5.useCallback)((optionValue) => {
2050
+ return (node) => {
2051
+ if (!buttonRefs.current) return;
2052
+ if (node) {
2053
+ buttonRefs.current.set(optionValue, node);
2054
+ } else {
2055
+ buttonRefs.current.delete(optionValue);
2056
+ }
2057
+ };
2058
+ }, []);
2059
+ const updateIndicator = (0, import_react5.useCallback)(() => {
2060
+ const container = containerRef.current;
2061
+ const target = buttonRefs.current.get(selectedValue);
2062
+ if (!container || !target) {
2063
+ if (indicatorRef.current.width !== 0) {
2064
+ indicatorRef.current = { width: 0, height: 0, x: 0, y: 0 };
2065
+ setIndicator(indicatorRef.current);
2066
+ }
2067
+ return;
2068
+ }
2069
+ const containerRect = container.getBoundingClientRect();
2070
+ const targetRect = target.getBoundingClientRect();
2071
+ const next = {
2072
+ width: targetRect.width,
2073
+ height: targetRect.height,
2074
+ x: targetRect.left - containerRect.left,
2075
+ y: targetRect.top - containerRect.top
2076
+ };
2077
+ const prev = indicatorRef.current;
2078
+ if (prev.width !== next.width || prev.height !== next.height || prev.x !== next.x || prev.y !== next.y) {
2079
+ indicatorRef.current = next;
2080
+ setIndicator(next);
2081
+ }
2082
+ }, [selectedValue]);
2083
+ useIsomorphicLayoutEffect(() => {
2084
+ updateIndicator();
2085
+ }, [updateIndicator, normalizedOptions.length, size]);
2086
+ (0, import_react5.useEffect)(() => {
2087
+ if (!isBrowser) return;
2088
+ const handleResize = () => updateIndicator();
2089
+ window.addEventListener("resize", handleResize);
2090
+ return () => window.removeEventListener("resize", handleResize);
2091
+ }, [updateIndicator]);
2092
+ (0, import_react5.useEffect)(() => {
2093
+ if (!isBrowser) return;
2094
+ const node = containerRef.current;
2095
+ if (!node || typeof ResizeObserver === "undefined") return;
2096
+ const observer = new ResizeObserver(() => {
2097
+ updateIndicator();
2098
+ });
2099
+ observer.observe(node);
2100
+ return () => observer.disconnect();
2101
+ }, [updateIndicator]);
2102
+ const focusOption = (0, import_react5.useCallback)((option) => {
2103
+ const node = buttonRefs.current.get(option.value);
2104
+ if (node) {
2105
+ node.focus();
2106
+ }
2107
+ }, []);
2108
+ const handleSelect = (0, import_react5.useCallback)(
2109
+ (option) => {
2110
+ if (option.disabled || option.value === selectedValue) {
2111
+ return;
2112
+ }
2113
+ if (!isControlled) {
2114
+ setInternalValue(option.value);
2115
+ }
2116
+ if (typeof onChange === "function") {
2117
+ onChange(option.value, option);
2118
+ }
2119
+ },
2120
+ [isControlled, onChange, selectedValue]
2121
+ );
2122
+ const getNextEnabledIndex = (0, import_react5.useCallback)(
2123
+ (startIndex, direction) => {
2124
+ if (!normalizedOptions.length) return -1;
2125
+ let nextIndex = startIndex;
2126
+ for (let i = 0; i < normalizedOptions.length; i += 1) {
2127
+ nextIndex = (nextIndex + direction + normalizedOptions.length) % normalizedOptions.length;
2128
+ if (!normalizedOptions[nextIndex].disabled) {
2129
+ return nextIndex;
2130
+ }
2131
+ }
2132
+ return startIndex;
2133
+ },
2134
+ [normalizedOptions]
2135
+ );
2136
+ const findFirstEnabledIndex = (0, import_react5.useCallback)(() => {
2137
+ return normalizedOptions.findIndex((option) => !option.disabled);
2138
+ }, [normalizedOptions]);
2139
+ const findLastEnabledIndex = (0, import_react5.useCallback)(() => {
2140
+ for (let i = normalizedOptions.length - 1; i >= 0; i -= 1) {
2141
+ if (!normalizedOptions[i].disabled) {
2142
+ return i;
2143
+ }
2144
+ }
2145
+ return -1;
2146
+ }, [normalizedOptions]);
2147
+ const indicatorVisible = indicator.width > 0 && indicator.height > 0;
2148
+ const rootClasses = (0, import_utils4.cx)(
2149
+ "inline-flex h-[44px] rounded-lg border-2 border-border-subtle bg-bg-base/90 p-2 shadow-inner",
2150
+ isStretched ? "w-full" : "w-auto",
2151
+ className
2152
+ );
2153
+ const trackClasses = (0, import_utils4.cx)(
2154
+ "relative isolate flex w-full items-center gap-8",
2155
+ !isStretched && "w-auto"
2156
+ );
2157
+ const buttonBaseClasses = (0, import_utils4.cx)(
2158
+ "relative z-10 h-[36px] inline-flex items-center justify-center gap-2 whitespace-nowrap font-semibold tracking-tight transition-colors duration-200 ease-out focus-visible:outline-none focus-visible:shadow-focus motion-reduce:duration-0",
2159
+ sizeStyles.button,
2160
+ isStretched ? "flex-1" : "w-auto"
2161
+ );
2162
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
2163
+ "div",
2164
+ {
2165
+ ...rest,
2166
+ ref: setContainerRef,
2167
+ role: "radiogroup",
2168
+ className: rootClasses,
2169
+ children: /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: trackClasses, children: [
2170
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
2171
+ "span",
2172
+ {
2173
+ "aria-hidden": "true",
2174
+ className: (0, import_utils4.cx)(
2175
+ "pointer-events-none h-[44px] absolute rounded-md border-2 border-border-primary-emphasis bg-bg-base shadow-[0_8px_20px_rgb(22_92_131/0.18)] transition-[transform,width,height] duration-300 ease-out motion-reduce:duration-0",
2176
+ !indicatorVisible && "opacity-0"
2177
+ ),
2178
+ style: {
2179
+ width: indicator.width || void 0,
2180
+ height: indicator.height || void 0,
2181
+ transform: `translate(${indicator.x - 4}px`
2182
+ }
2183
+ }
2184
+ ),
2185
+ normalizedOptions.map((option, index) => {
2186
+ const isActive = option.value === selectedValue;
2187
+ const handleKeyDown = (event) => {
2188
+ if (option.disabled) return;
2189
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") {
2190
+ event.preventDefault();
2191
+ const nextIndex = getNextEnabledIndex(index, 1);
2192
+ const nextOption = normalizedOptions[nextIndex];
2193
+ if (nextOption && nextOption !== option) {
2194
+ focusOption(nextOption);
2195
+ handleSelect(nextOption);
2196
+ }
2197
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
2198
+ event.preventDefault();
2199
+ const prevIndex = getNextEnabledIndex(index, -1);
2200
+ const prevOption = normalizedOptions[prevIndex];
2201
+ if (prevOption && prevOption !== option) {
2202
+ focusOption(prevOption);
2203
+ handleSelect(prevOption);
2204
+ }
2205
+ } else if (event.key === "Home") {
2206
+ event.preventDefault();
2207
+ const firstIndex = findFirstEnabledIndex();
2208
+ const firstOption = firstIndex >= 0 ? normalizedOptions[firstIndex] : void 0;
2209
+ if (firstOption && firstOption !== option) {
2210
+ focusOption(firstOption);
2211
+ handleSelect(firstOption);
2212
+ }
2213
+ } else if (event.key === "End") {
2214
+ event.preventDefault();
2215
+ const lastIndex = findLastEnabledIndex();
2216
+ const lastOption = lastIndex >= 0 ? normalizedOptions[lastIndex] : void 0;
2217
+ if (lastOption && lastOption !== option) {
2218
+ focusOption(lastOption);
2219
+ handleSelect(lastOption);
2220
+ }
2221
+ }
2222
+ };
2223
+ const buttonClasses = (0, import_utils4.cx)(
2224
+ buttonBaseClasses,
2225
+ option.disabled ? "cursor-not-allowed text-fg-inactive opacity-60" : "cursor-pointer text-fg-subtle hover:text-fg-base",
2226
+ isActive && "text-fg-primary-emphasis",
2227
+ !isActive && "hover:bg-bg-minimal-5 active:bg-bg-inactive"
2228
+ );
2229
+ const hasStartAdornment = option.startAdornment !== void 0 && option.startAdornment !== null;
2230
+ const hasendIcon = option.endIcon !== void 0 && option.endIcon !== null;
2231
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
2232
+ "button",
2233
+ {
2234
+ ref: registerButtonRef(option.value),
2235
+ type: "button",
2236
+ role: "radio",
2237
+ "aria-checked": isActive,
2238
+ disabled: option.disabled,
2239
+ className: buttonClasses,
2240
+ onClick: () => handleSelect(option),
2241
+ onKeyDown: handleKeyDown,
2242
+ "data-state": isActive ? "active" : "inactive",
2243
+ children: [
2244
+ hasStartAdornment && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
2245
+ "span",
2246
+ {
2247
+ "aria-hidden": "true",
2248
+ className: "flex items-center text-current",
2249
+ children: option.startAdornment
2250
+ }
2251
+ ),
2252
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("span", { className: "flex items-center gap-1 leading-none text-current", children: option.label ?? `Option ${index + 1}` }),
2253
+ hasendIcon && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
2254
+ "span",
2255
+ {
2256
+ "aria-hidden": "true",
2257
+ className: "flex items-center text-current",
2258
+ children: option.endIcon
2259
+ }
2260
+ )
2261
+ ]
2262
+ },
2263
+ String(option.value)
2264
+ );
2265
+ })
2266
+ ] })
2267
+ }
2268
+ );
2269
+ });
2270
+
2271
+ // src/elements/ButtonGroupItem.jsx
2272
+ var import_react6 = __toESM(require("react"));
2273
+ function ButtonGroupItem(_props) {
2274
+ return null;
2275
+ }
2276
+ ButtonGroupItem.displayName = "ButtonGroupItem";
1963
2277
  // Annotate the CommonJS export names for ESM import in node:
1964
2278
  0 && (module.exports = {
1965
2279
  Button,
2280
+ ButtonGroup,
2281
+ ButtonGroupItem,
1966
2282
  IconButton,
1967
2283
  MenuButton
1968
2284
  });