@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 +9 -0
- package/README.md +110 -0
- package/dist/index.d.mts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +328 -12
- package/dist/index.mjs +330 -8
- package/package.json +1 -1
- package/src/demo/ButtonGroup.stories.jsx +67 -0
- package/src/elements/ButtonGroup.jsx +387 -0
- package/src/elements/ButtonGroup.test.jsx +69 -0
- package/src/elements/ButtonGroupItem.jsx +8 -0
- package/src/index.js +2 -0
package/CHANGELOG.md
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1834
|
-
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
1959
|
-
/* @__PURE__ */ (0,
|
|
1960
|
-
/* @__PURE__ */ (0,
|
|
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
|
});
|