@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/dist/index.mjs CHANGED
@@ -1782,19 +1782,19 @@ var require_react_jsx_runtime_development = __commonJS({
1782
1782
  function validateChildKeys(node) {
1783
1783
  "object" === typeof node && null !== node && node.$$typeof === REACT_ELEMENT_TYPE && node._store && (node._store.validated = 1);
1784
1784
  }
1785
- 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");
1786
- 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() {
1785
+ 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");
1786
+ 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() {
1787
1787
  return null;
1788
1788
  };
1789
- React5 = {
1789
+ React7 = {
1790
1790
  react_stack_bottom_frame: function(callStackForError) {
1791
1791
  return callStackForError();
1792
1792
  }
1793
1793
  };
1794
1794
  var specialPropKeyWarningShown;
1795
1795
  var didWarnAboutElementRef = {};
1796
- var unknownOwnerDebugStack = React5.react_stack_bottom_frame.bind(
1797
- React5,
1796
+ var unknownOwnerDebugStack = React7.react_stack_bottom_frame.bind(
1797
+ React7,
1798
1798
  UnknownOwner
1799
1799
  )();
1800
1800
  var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
@@ -1843,7 +1843,8 @@ var import_jsx_runtime22 = __toESM(require_jsx_runtime());
1843
1843
  var import_jsx_runtime32 = __toESM(require_jsx_runtime());
1844
1844
  var import_jsx_runtime4 = __toESM(require_jsx_runtime());
1845
1845
  var import_jsx_runtime5 = __toESM(require_jsx_runtime());
1846
- var SvgChevron = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1846
+ var import_jsx_runtime6 = __toESM(require_jsx_runtime());
1847
+ var SvgChevron = (props) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1847
1848
  "svg",
1848
1849
  {
1849
1850
  xmlns: "http://www.w3.org/2000/svg",
@@ -1852,7 +1853,7 @@ var SvgChevron = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1852
1853
  fill: "none",
1853
1854
  viewBox: "0 0 24 24",
1854
1855
  ...props,
1855
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1856
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1856
1857
  "path",
1857
1858
  {
1858
1859
  fill: "currentColor",
@@ -1864,7 +1865,6 @@ var SvgChevron = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1864
1865
  }
1865
1866
  );
1866
1867
  var Chevron_default = SvgChevron;
1867
- var import_jsx_runtime6 = __toESM(require_jsx_runtime());
1868
1868
  var import_jsx_runtime7 = __toESM(require_jsx_runtime());
1869
1869
  var import_jsx_runtime8 = __toESM(require_jsx_runtime());
1870
1870
  var import_jsx_runtime9 = __toESM(require_jsx_runtime());
@@ -1907,6 +1907,13 @@ var import_jsx_runtime45 = __toESM(require_jsx_runtime());
1907
1907
  var import_jsx_runtime46 = __toESM(require_jsx_runtime());
1908
1908
  var import_jsx_runtime47 = __toESM(require_jsx_runtime());
1909
1909
  var import_jsx_runtime48 = __toESM(require_jsx_runtime());
1910
+ var import_jsx_runtime49 = __toESM(require_jsx_runtime());
1911
+ var import_jsx_runtime50 = __toESM(require_jsx_runtime());
1912
+ var import_jsx_runtime51 = __toESM(require_jsx_runtime());
1913
+ var import_jsx_runtime52 = __toESM(require_jsx_runtime());
1914
+ var import_jsx_runtime53 = __toESM(require_jsx_runtime());
1915
+ var import_jsx_runtime54 = __toESM(require_jsx_runtime());
1916
+ var import_jsx_runtime55 = __toESM(require_jsx_runtime());
1910
1917
 
1911
1918
  // src/elements/MenuButton.jsx
1912
1919
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
@@ -1923,8 +1930,323 @@ function MenuButton({ children, size }) {
1923
1930
  /* @__PURE__ */ jsx3(Chevron_default, {})
1924
1931
  ] });
1925
1932
  }
1933
+
1934
+ // src/elements/ButtonGroup.jsx
1935
+ import React5, {
1936
+ forwardRef as forwardRef3,
1937
+ useCallback,
1938
+ useEffect,
1939
+ useLayoutEffect,
1940
+ useMemo,
1941
+ useRef,
1942
+ useState
1943
+ } from "react";
1944
+ import { cx as cx4, filterChildrenByDisplayName } from "@edux-design/utils";
1945
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1946
+ var SIZE_STYLES = {
1947
+ small: {
1948
+ button: "text-xs px-12 py-6 rounded-md "
1949
+ },
1950
+ medium: {
1951
+ button: "text-sm px-12 py-6 rounded-md "
1952
+ },
1953
+ large: {
1954
+ button: "text-base px-12 py-6 rounded-md"
1955
+ }
1956
+ };
1957
+ var isBrowser = typeof window !== "undefined";
1958
+ var useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
1959
+ var ButtonGroup = forwardRef3(function ButtonGroup2({
1960
+ value,
1961
+ defaultValue,
1962
+ onChange,
1963
+ size = "medium",
1964
+ isStretched = false,
1965
+ className,
1966
+ children,
1967
+ ...rest
1968
+ }, forwardedRef) {
1969
+ const normalizedOptions = useMemo(() => {
1970
+ const childItems = filterChildrenByDisplayName(children, "ButtonGroupItem");
1971
+ return childItems.map((child, index) => {
1972
+ const props = child.props || {};
1973
+ const hasValue = props.value !== void 0;
1974
+ const safeValue = hasValue && props.value !== null ? props.value : index;
1975
+ return {
1976
+ ...props,
1977
+ label: props.label ?? props.children ?? `Option ${index + 1}`,
1978
+ value: safeValue
1979
+ };
1980
+ });
1981
+ }, [children]);
1982
+ const sizeStyles = SIZE_STYLES[size] ?? SIZE_STYLES.medium;
1983
+ const initialValue = value ?? defaultValue ?? (normalizedOptions.length ? normalizedOptions[0].value : void 0);
1984
+ const [internalValue, setInternalValue] = useState(initialValue);
1985
+ const isControlled = value !== void 0;
1986
+ const selectedValue = isControlled ? value : internalValue;
1987
+ useEffect(() => {
1988
+ if (!isControlled && defaultValue !== void 0) {
1989
+ setInternalValue(defaultValue);
1990
+ }
1991
+ }, [defaultValue, isControlled]);
1992
+ useEffect(() => {
1993
+ if (isControlled) return;
1994
+ if (normalizedOptions.length && !normalizedOptions.some((option) => option.value === internalValue)) {
1995
+ setInternalValue(normalizedOptions[0].value);
1996
+ }
1997
+ }, [internalValue, isControlled, normalizedOptions]);
1998
+ const buttonRefs = useRef(/* @__PURE__ */ new Map());
1999
+ const containerRef = useRef(null);
2000
+ const indicatorRef = useRef({ width: 0, height: 0, x: 0, y: 0 });
2001
+ const [indicator, setIndicator] = useState({
2002
+ width: 0,
2003
+ height: 0,
2004
+ x: 0,
2005
+ y: 0
2006
+ });
2007
+ const setContainerRef = useCallback(
2008
+ (node) => {
2009
+ containerRef.current = node;
2010
+ if (typeof forwardedRef === "function") {
2011
+ forwardedRef(node);
2012
+ } else if (forwardedRef) {
2013
+ forwardedRef.current = node;
2014
+ }
2015
+ },
2016
+ [forwardedRef]
2017
+ );
2018
+ const registerButtonRef = useCallback((optionValue) => {
2019
+ return (node) => {
2020
+ if (!buttonRefs.current) return;
2021
+ if (node) {
2022
+ buttonRefs.current.set(optionValue, node);
2023
+ } else {
2024
+ buttonRefs.current.delete(optionValue);
2025
+ }
2026
+ };
2027
+ }, []);
2028
+ const updateIndicator = useCallback(() => {
2029
+ const container = containerRef.current;
2030
+ const target = buttonRefs.current.get(selectedValue);
2031
+ if (!container || !target) {
2032
+ if (indicatorRef.current.width !== 0) {
2033
+ indicatorRef.current = { width: 0, height: 0, x: 0, y: 0 };
2034
+ setIndicator(indicatorRef.current);
2035
+ }
2036
+ return;
2037
+ }
2038
+ const containerRect = container.getBoundingClientRect();
2039
+ const targetRect = target.getBoundingClientRect();
2040
+ const next = {
2041
+ width: targetRect.width,
2042
+ height: targetRect.height,
2043
+ x: targetRect.left - containerRect.left,
2044
+ y: targetRect.top - containerRect.top
2045
+ };
2046
+ const prev = indicatorRef.current;
2047
+ if (prev.width !== next.width || prev.height !== next.height || prev.x !== next.x || prev.y !== next.y) {
2048
+ indicatorRef.current = next;
2049
+ setIndicator(next);
2050
+ }
2051
+ }, [selectedValue]);
2052
+ useIsomorphicLayoutEffect(() => {
2053
+ updateIndicator();
2054
+ }, [updateIndicator, normalizedOptions.length, size]);
2055
+ useEffect(() => {
2056
+ if (!isBrowser) return;
2057
+ const handleResize = () => updateIndicator();
2058
+ window.addEventListener("resize", handleResize);
2059
+ return () => window.removeEventListener("resize", handleResize);
2060
+ }, [updateIndicator]);
2061
+ useEffect(() => {
2062
+ if (!isBrowser) return;
2063
+ const node = containerRef.current;
2064
+ if (!node || typeof ResizeObserver === "undefined") return;
2065
+ const observer = new ResizeObserver(() => {
2066
+ updateIndicator();
2067
+ });
2068
+ observer.observe(node);
2069
+ return () => observer.disconnect();
2070
+ }, [updateIndicator]);
2071
+ const focusOption = useCallback((option) => {
2072
+ const node = buttonRefs.current.get(option.value);
2073
+ if (node) {
2074
+ node.focus();
2075
+ }
2076
+ }, []);
2077
+ const handleSelect = useCallback(
2078
+ (option) => {
2079
+ if (option.disabled || option.value === selectedValue) {
2080
+ return;
2081
+ }
2082
+ if (!isControlled) {
2083
+ setInternalValue(option.value);
2084
+ }
2085
+ if (typeof onChange === "function") {
2086
+ onChange(option.value, option);
2087
+ }
2088
+ },
2089
+ [isControlled, onChange, selectedValue]
2090
+ );
2091
+ const getNextEnabledIndex = useCallback(
2092
+ (startIndex, direction) => {
2093
+ if (!normalizedOptions.length) return -1;
2094
+ let nextIndex = startIndex;
2095
+ for (let i = 0; i < normalizedOptions.length; i += 1) {
2096
+ nextIndex = (nextIndex + direction + normalizedOptions.length) % normalizedOptions.length;
2097
+ if (!normalizedOptions[nextIndex].disabled) {
2098
+ return nextIndex;
2099
+ }
2100
+ }
2101
+ return startIndex;
2102
+ },
2103
+ [normalizedOptions]
2104
+ );
2105
+ const findFirstEnabledIndex = useCallback(() => {
2106
+ return normalizedOptions.findIndex((option) => !option.disabled);
2107
+ }, [normalizedOptions]);
2108
+ const findLastEnabledIndex = useCallback(() => {
2109
+ for (let i = normalizedOptions.length - 1; i >= 0; i -= 1) {
2110
+ if (!normalizedOptions[i].disabled) {
2111
+ return i;
2112
+ }
2113
+ }
2114
+ return -1;
2115
+ }, [normalizedOptions]);
2116
+ const indicatorVisible = indicator.width > 0 && indicator.height > 0;
2117
+ const rootClasses = cx4(
2118
+ "inline-flex h-[44px] rounded-lg border-2 border-border-subtle bg-bg-base/90 p-2 shadow-inner",
2119
+ isStretched ? "w-full" : "w-auto",
2120
+ className
2121
+ );
2122
+ const trackClasses = cx4(
2123
+ "relative isolate flex w-full items-center gap-8",
2124
+ !isStretched && "w-auto"
2125
+ );
2126
+ const buttonBaseClasses = cx4(
2127
+ "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",
2128
+ sizeStyles.button,
2129
+ isStretched ? "flex-1" : "w-auto"
2130
+ );
2131
+ return /* @__PURE__ */ jsx4(
2132
+ "div",
2133
+ {
2134
+ ...rest,
2135
+ ref: setContainerRef,
2136
+ role: "radiogroup",
2137
+ className: rootClasses,
2138
+ children: /* @__PURE__ */ jsxs2("div", { className: trackClasses, children: [
2139
+ /* @__PURE__ */ jsx4(
2140
+ "span",
2141
+ {
2142
+ "aria-hidden": "true",
2143
+ className: cx4(
2144
+ "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",
2145
+ !indicatorVisible && "opacity-0"
2146
+ ),
2147
+ style: {
2148
+ width: indicator.width || void 0,
2149
+ height: indicator.height || void 0,
2150
+ transform: `translate(${indicator.x - 4}px`
2151
+ }
2152
+ }
2153
+ ),
2154
+ normalizedOptions.map((option, index) => {
2155
+ const isActive = option.value === selectedValue;
2156
+ const handleKeyDown = (event) => {
2157
+ if (option.disabled) return;
2158
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") {
2159
+ event.preventDefault();
2160
+ const nextIndex = getNextEnabledIndex(index, 1);
2161
+ const nextOption = normalizedOptions[nextIndex];
2162
+ if (nextOption && nextOption !== option) {
2163
+ focusOption(nextOption);
2164
+ handleSelect(nextOption);
2165
+ }
2166
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
2167
+ event.preventDefault();
2168
+ const prevIndex = getNextEnabledIndex(index, -1);
2169
+ const prevOption = normalizedOptions[prevIndex];
2170
+ if (prevOption && prevOption !== option) {
2171
+ focusOption(prevOption);
2172
+ handleSelect(prevOption);
2173
+ }
2174
+ } else if (event.key === "Home") {
2175
+ event.preventDefault();
2176
+ const firstIndex = findFirstEnabledIndex();
2177
+ const firstOption = firstIndex >= 0 ? normalizedOptions[firstIndex] : void 0;
2178
+ if (firstOption && firstOption !== option) {
2179
+ focusOption(firstOption);
2180
+ handleSelect(firstOption);
2181
+ }
2182
+ } else if (event.key === "End") {
2183
+ event.preventDefault();
2184
+ const lastIndex = findLastEnabledIndex();
2185
+ const lastOption = lastIndex >= 0 ? normalizedOptions[lastIndex] : void 0;
2186
+ if (lastOption && lastOption !== option) {
2187
+ focusOption(lastOption);
2188
+ handleSelect(lastOption);
2189
+ }
2190
+ }
2191
+ };
2192
+ const buttonClasses = cx4(
2193
+ buttonBaseClasses,
2194
+ option.disabled ? "cursor-not-allowed text-fg-inactive opacity-60" : "cursor-pointer text-fg-subtle hover:text-fg-base",
2195
+ isActive && "text-fg-primary-emphasis",
2196
+ !isActive && "hover:bg-bg-minimal-5 active:bg-bg-inactive"
2197
+ );
2198
+ const hasStartAdornment = option.startAdornment !== void 0 && option.startAdornment !== null;
2199
+ const hasendIcon = option.endIcon !== void 0 && option.endIcon !== null;
2200
+ return /* @__PURE__ */ jsxs2(
2201
+ "button",
2202
+ {
2203
+ ref: registerButtonRef(option.value),
2204
+ type: "button",
2205
+ role: "radio",
2206
+ "aria-checked": isActive,
2207
+ disabled: option.disabled,
2208
+ className: buttonClasses,
2209
+ onClick: () => handleSelect(option),
2210
+ onKeyDown: handleKeyDown,
2211
+ "data-state": isActive ? "active" : "inactive",
2212
+ children: [
2213
+ hasStartAdornment && /* @__PURE__ */ jsx4(
2214
+ "span",
2215
+ {
2216
+ "aria-hidden": "true",
2217
+ className: "flex items-center text-current",
2218
+ children: option.startAdornment
2219
+ }
2220
+ ),
2221
+ /* @__PURE__ */ jsx4("span", { className: "flex items-center gap-1 leading-none text-current", children: option.label ?? `Option ${index + 1}` }),
2222
+ hasendIcon && /* @__PURE__ */ jsx4(
2223
+ "span",
2224
+ {
2225
+ "aria-hidden": "true",
2226
+ className: "flex items-center text-current",
2227
+ children: option.endIcon
2228
+ }
2229
+ )
2230
+ ]
2231
+ },
2232
+ String(option.value)
2233
+ );
2234
+ })
2235
+ ] })
2236
+ }
2237
+ );
2238
+ });
2239
+
2240
+ // src/elements/ButtonGroupItem.jsx
2241
+ import React6 from "react";
2242
+ function ButtonGroupItem(_props) {
2243
+ return null;
2244
+ }
2245
+ ButtonGroupItem.displayName = "ButtonGroupItem";
1926
2246
  export {
1927
2247
  Button,
2248
+ ButtonGroup,
2249
+ ButtonGroupItem,
1928
2250
  IconButton,
1929
2251
  MenuButton
1930
2252
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edux-design/buttons",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -0,0 +1,67 @@
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
+ };