@sk-web-gui/segmentedcontrol 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Sundsvalls Kommun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ ## Installation
2
+
3
+ ```sh
4
+ yarn add @sk-web-gui/segmentedcontrol
5
+ ```
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "SegmentedControlContext", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return SegmentedControlContext;
9
+ }
10
+ });
11
+ const _react = /*#__PURE__*/ _interop_require_default(require("react"));
12
+ function _interop_require_default(obj) {
13
+ return obj && obj.__esModule ? obj : {
14
+ default: obj
15
+ };
16
+ }
17
+ const SegmentedControlContext = _react.default.createContext({});
18
+
19
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/context.ts"],"sourcesContent":["import React from 'react';\r\nimport { UseSegmentedControlProps } from './use-segmented-control';\r\n\r\nexport interface UseSegmentedControlContext extends UseSegmentedControlProps {\r\n /** Array of currently selected indices */\r\n selected?: number[];\r\n /** Toggle an item's selection state */\r\n toggleItem?: (index: number) => void;\r\n /** Currently focused item index (for keyboard navigation) */\r\n active?: number;\r\n /** Set the focused item index */\r\n setActive?: (index: number) => void;\r\n /** Total number of items */\r\n total?: number;\r\n size?: 'md' | 'lg';\r\n disabled?: boolean;\r\n}\r\n\r\nexport const SegmentedControlContext = React.createContext<UseSegmentedControlContext>({});\r\n"],"names":["SegmentedControlContext","React","createContext"],"mappings":";;;;+BAkBaA;;;eAAAA;;;8DAlBK;;;;;;AAkBX,MAAMA,0BAA0BC,cAAK,CAACC,aAAa,CAA6B,CAAC"}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get SegmentedControl () {
13
+ return SegmentedControl;
14
+ },
15
+ get default () {
16
+ return _default;
17
+ }
18
+ });
19
+ const _segmentedcontrol = /*#__PURE__*/ _interop_require_default(require("./segmented-control"));
20
+ const _segmentedcontrolitem = require("./segmented-control-item");
21
+ function _interop_require_default(obj) {
22
+ return obj && obj.__esModule ? obj : {
23
+ default: obj
24
+ };
25
+ }
26
+ const SegmentedControl = {
27
+ ..._segmentedcontrol.default,
28
+ Component: _segmentedcontrol.default,
29
+ Item: _segmentedcontrolitem.SegmentedControlItem
30
+ };
31
+ const _default = SegmentedControl;
32
+
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/index.ts"],"sourcesContent":["import SegmentedControlComponent, { SegmentedControlComponentProps } from \"./segmented-control\";\r\nimport { SegmentedControlItem } from \"./segmented-control-item\";\r\n\r\ninterface SegmentedControlProps extends React.ForwardRefExoticComponent<SegmentedControlComponentProps> {\r\n Component: typeof SegmentedControlComponent;\r\n Item: typeof SegmentedControlItem;\r\n}\r\n\r\nconst SegmentedControl = {\r\n ...SegmentedControlComponent,\r\n Component: SegmentedControlComponent,\r\n Item: SegmentedControlItem,\r\n} as SegmentedControlProps;\r\n\r\nexport { SegmentedControl };\r\nexport type { SegmentedControlProps, SegmentedControlComponentProps };\r\nexport default SegmentedControl;\r\n"],"names":["SegmentedControl","SegmentedControlComponent","Component","Item","SegmentedControlItem"],"mappings":";;;;;;;;;;;QAcSA;eAAAA;;QAET;eAAA;;;yEAhB0E;sCACrC;;;;;;AAOrC,MAAMA,mBAAmB;IACvB,GAAGC,yBAAyB;IAC5BC,WAAWD,yBAAyB;IACpCE,MAAMC,0CAAoB;AAC5B;MAIA,WAAeJ"}
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "SegmentedControlItem", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return SegmentedControlItem;
9
+ }
10
+ });
11
+ const _jsxruntime = require("react/jsx-runtime");
12
+ const _utils = require("@sk-web-gui/utils");
13
+ const _popupmenu = require("@sk-web-gui/popup-menu");
14
+ const _react = /*#__PURE__*/ _interop_require_default(require("react"));
15
+ const _usesegmentedcontrol = require("./use-segmented-control");
16
+ function _interop_require_default(obj) {
17
+ return obj && obj.__esModule ? obj : {
18
+ default: obj
19
+ };
20
+ }
21
+ const SegmentedControlItem = /*#__PURE__*/ _react.default.forwardRef((props, ref)=>{
22
+ const { className, children, menuIndex, wrapper, size: _size, disabled: _disabled, ...rest } = props;
23
+ const { selected, toggleItem, active, setActive, total, size: contextSize, disabled: contextDisabled } = (0, _usesegmentedcontrol.useSegmentedControl)();
24
+ const menuRef = _react.default.useRef(null);
25
+ const prevActiveRef = _react.default.useRef(undefined);
26
+ const size = _size || contextSize || 'lg';
27
+ const disabled = _disabled || contextDisabled;
28
+ const isSelected = selected?.includes(menuIndex) ?? false;
29
+ _react.default.useEffect(()=>{
30
+ // Only focus when active changes (not on initial mount)
31
+ if (prevActiveRef.current !== undefined && active === menuIndex && menuRef.current) {
32
+ walkFocus(menuRef.current.children);
33
+ }
34
+ prevActiveRef.current = active;
35
+ }, [
36
+ active,
37
+ menuIndex
38
+ ]);
39
+ const walkFocus = (collection)=>{
40
+ for (const element of Array.from(collection)){
41
+ if (element.getAttribute('role') === 'button') {
42
+ element.focus();
43
+ return true;
44
+ }
45
+ if (element.children && walkFocus(element.children)) {
46
+ return true;
47
+ }
48
+ }
49
+ return false;
50
+ };
51
+ const handleKeyboard = (event)=>{
52
+ if (disabled) return;
53
+ const currentIndex = menuIndex;
54
+ const itemCount = total || 0;
55
+ if (event.key === 'ArrowLeft') {
56
+ event.preventDefault();
57
+ event.stopPropagation();
58
+ const newIndex = currentIndex === 0 ? itemCount - 1 : currentIndex - 1;
59
+ setActive?.(newIndex);
60
+ }
61
+ if (event.key === 'ArrowRight') {
62
+ event.preventDefault();
63
+ event.stopPropagation();
64
+ const newIndex = currentIndex === itemCount - 1 ? 0 : currentIndex + 1;
65
+ setActive?.(newIndex);
66
+ }
67
+ if (event.key === ' ' || event.key === 'Enter') {
68
+ event.preventDefault();
69
+ event.stopPropagation();
70
+ toggleItem?.(currentIndex);
71
+ }
72
+ };
73
+ const handleClick = ()=>{
74
+ if (disabled) return;
75
+ toggleItem?.(menuIndex);
76
+ };
77
+ const getClonedChild = (child)=>{
78
+ if (child.type === _react.default.Fragment) {
79
+ const grandchildren = (0, _utils.getValidChildren)(child.props.children).map((grandchild, index)=>{
80
+ return index === 0 ? getClonedChild(grandchild) : grandchild;
81
+ });
82
+ return /*#__PURE__*/ _react.default.cloneElement(child, {
83
+ ...child.props,
84
+ children: grandchildren
85
+ });
86
+ } else if (child.type === _popupmenu.PopupMenu) {
87
+ const grandchildren = (0, _utils.getValidChildren)(child.props.children).map((grandchild)=>{
88
+ if (grandchild.type === _popupmenu.PopupMenu.Button) {
89
+ return getClonedChild(grandchild);
90
+ }
91
+ return grandchild;
92
+ });
93
+ return /*#__PURE__*/ _react.default.cloneElement(child, {
94
+ ...child.props,
95
+ children: grandchildren
96
+ });
97
+ } else {
98
+ return /*#__PURE__*/ _react.default.cloneElement(child, {
99
+ ...child.props,
100
+ onKeyDown: handleKeyboard,
101
+ onClick: handleClick,
102
+ role: 'button',
103
+ 'aria-pressed': isSelected ? 'true' : 'false',
104
+ 'aria-disabled': disabled ? 'true' : undefined,
105
+ tabIndex: disabled ? -1 : active === menuIndex ? 0 : -1
106
+ });
107
+ }
108
+ };
109
+ const getChildWithWrapper = ()=>{
110
+ if (wrapper) {
111
+ return /*#__PURE__*/ _react.default.cloneElement(wrapper, {
112
+ ...wrapper.props,
113
+ children: getClonedChild(children)
114
+ });
115
+ } else {
116
+ return getClonedChild(children);
117
+ }
118
+ };
119
+ return /*#__PURE__*/ (0, _jsxruntime.jsx)("li", {
120
+ "data-size": size,
121
+ ref: (0, _utils.useForkRef)(ref, menuRef),
122
+ className: (0, _utils.cx)('sk-segmentedcontrol-item', className),
123
+ role: "none",
124
+ ...rest,
125
+ children: getChildWithWrapper()
126
+ });
127
+ });
128
+
129
+ //# sourceMappingURL=segmented-control-item.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/segmented-control-item.tsx"],"sourcesContent":["import { DefaultProps, cx, getValidChildren, useForkRef } from '@sk-web-gui/utils';\r\nimport { PopupMenu } from '@sk-web-gui/popup-menu';\r\nimport React from 'react';\r\nimport { useSegmentedControl } from './use-segmented-control';\r\n\r\nexport interface SegmentedControlItemProps extends DefaultProps, React.ComponentPropsWithRef<'li'> {\r\n /** Set automatic by parent */\r\n menuIndex?: number;\r\n /** Use <a> or <button>. For dropdown, use <PopupMenu> */\r\n children: React.JSX.Element;\r\n /** For e.g. Next Links to work, they need to wrapped this way */\r\n wrapper?: React.JSX.Element;\r\n size?: 'md' | 'lg';\r\n disabled?: boolean;\r\n}\r\n\r\nexport const SegmentedControlItem = React.forwardRef<HTMLLIElement, SegmentedControlItemProps>((props, ref) => {\r\n const { className, children, menuIndex, wrapper, size: _size, disabled: _disabled, ...rest } = props;\r\n\r\n const {\r\n selected,\r\n toggleItem,\r\n active,\r\n setActive,\r\n total,\r\n size: contextSize,\r\n disabled: contextDisabled,\r\n } = useSegmentedControl();\r\n\r\n const menuRef = React.useRef<HTMLElement>(null);\r\n const prevActiveRef = React.useRef<number | undefined>(undefined);\r\n const size = _size || contextSize || 'lg';\r\n const disabled = _disabled || contextDisabled;\r\n const isSelected = selected?.includes(menuIndex as number) ?? false;\r\n\r\n React.useEffect(() => {\r\n // Only focus when active changes (not on initial mount)\r\n if (prevActiveRef.current !== undefined && active === menuIndex && menuRef.current) {\r\n walkFocus(menuRef.current.children);\r\n }\r\n prevActiveRef.current = active;\r\n }, [active, menuIndex]);\r\n\r\n const walkFocus = (collection: HTMLCollection): boolean => {\r\n for (const element of Array.from(collection)) {\r\n if (element.getAttribute('role') === 'button') {\r\n (element as HTMLElement).focus();\r\n return true;\r\n }\r\n if (element.children && walkFocus(element.children)) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n };\r\n\r\n const handleKeyboard = (event: React.KeyboardEvent) => {\r\n if (disabled) return;\r\n\r\n const currentIndex = menuIndex as number;\r\n const itemCount = total || 0;\r\n\r\n if (event.key === 'ArrowLeft') {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n const newIndex = currentIndex === 0 ? itemCount - 1 : currentIndex - 1;\r\n setActive?.(newIndex);\r\n }\r\n\r\n if (event.key === 'ArrowRight') {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n const newIndex = currentIndex === itemCount - 1 ? 0 : currentIndex + 1;\r\n setActive?.(newIndex);\r\n }\r\n\r\n if (event.key === ' ' || event.key === 'Enter') {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n toggleItem?.(currentIndex);\r\n }\r\n };\r\n\r\n const handleClick = () => {\r\n if (disabled) return;\r\n toggleItem?.(menuIndex as number);\r\n };\r\n\r\n const getClonedChild = (child: React.JSX.Element): React.ReactNode => {\r\n if (child.type === React.Fragment) {\r\n const grandchildren = getValidChildren(child.props.children).map((grandchild, index) => {\r\n return index === 0 ? getClonedChild(grandchild) : grandchild;\r\n });\r\n return React.cloneElement(child, { ...child.props, children: grandchildren });\r\n } else if (child.type === PopupMenu) {\r\n const grandchildren = getValidChildren(child.props.children).map((grandchild) => {\r\n if (grandchild.type === PopupMenu.Button) {\r\n return getClonedChild(grandchild);\r\n }\r\n return grandchild;\r\n });\r\n\r\n return React.cloneElement(child, { ...child.props, children: grandchildren });\r\n } else {\r\n return React.cloneElement(child, {\r\n ...child.props,\r\n onKeyDown: handleKeyboard,\r\n onClick: handleClick,\r\n role: 'button',\r\n 'aria-pressed': isSelected ? 'true' : 'false',\r\n 'aria-disabled': disabled ? 'true' : undefined,\r\n tabIndex: disabled ? -1 : active === menuIndex ? 0 : -1,\r\n });\r\n }\r\n };\r\n\r\n const getChildWithWrapper = () => {\r\n if (wrapper) {\r\n return React.cloneElement(wrapper, { ...wrapper.props, children: getClonedChild(children) });\r\n } else {\r\n return getClonedChild(children);\r\n }\r\n };\r\n\r\n return (\r\n <li\r\n data-size={size}\r\n ref={useForkRef(ref, menuRef)}\r\n className={cx('sk-segmentedcontrol-item', className)}\r\n role=\"none\"\r\n {...rest}\r\n >\r\n {getChildWithWrapper()}\r\n </li>\r\n );\r\n});\r\n"],"names":["SegmentedControlItem","React","forwardRef","props","ref","className","children","menuIndex","wrapper","size","_size","disabled","_disabled","rest","selected","toggleItem","active","setActive","total","contextSize","contextDisabled","useSegmentedControl","menuRef","useRef","prevActiveRef","undefined","isSelected","includes","useEffect","current","walkFocus","collection","element","Array","from","getAttribute","focus","handleKeyboard","event","currentIndex","itemCount","key","preventDefault","stopPropagation","newIndex","handleClick","getClonedChild","child","type","Fragment","grandchildren","getValidChildren","map","grandchild","index","cloneElement","PopupMenu","Button","onKeyDown","onClick","role","tabIndex","getChildWithWrapper","li","data-size","useForkRef","cx"],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get SegmentedControlComponent () {
13
+ return SegmentedControlComponent;
14
+ },
15
+ get default () {
16
+ return _default;
17
+ }
18
+ });
19
+ const _jsxruntime = require("react/jsx-runtime");
20
+ const _utils = require("@sk-web-gui/utils");
21
+ const _react = /*#__PURE__*/ _interop_require_default(require("react"));
22
+ const _context = require("./context");
23
+ function _interop_require_default(obj) {
24
+ return obj && obj.__esModule ? obj : {
25
+ default: obj
26
+ };
27
+ }
28
+ const SegmentedControlComponent = /*#__PURE__*/ _react.default.forwardRef((props, ref)=>{
29
+ const { className, children, value, defaultValue, onChange, id: _id, disabled = false, size = 'lg', multiSelect = false, ...rest } = props;
30
+ const [internalSelected, setInternalSelected] = _react.default.useState(defaultValue || []);
31
+ const isControlled = value !== undefined;
32
+ const selected = isControlled ? value : internalSelected;
33
+ const [active, setActive] = _react.default.useState(0);
34
+ const autoId = _react.default.useId();
35
+ const id = _id || `sk-segmentedcontrol-${autoId}`;
36
+ const total = _react.default.Children.count(children);
37
+ const toggleItem = (index)=>{
38
+ if (disabled) return;
39
+ const newSelected = !multiSelect ? [
40
+ index
41
+ ] : selected.includes(index) ? selected.filter((i)=>i !== index) : [
42
+ ...selected,
43
+ index
44
+ ];
45
+ if (!isControlled) {
46
+ setInternalSelected(newSelected);
47
+ }
48
+ onChange?.(newSelected);
49
+ };
50
+ const context = {
51
+ selected,
52
+ toggleItem,
53
+ active,
54
+ setActive,
55
+ total,
56
+ size,
57
+ disabled
58
+ };
59
+ const validChildren = (0, _utils.getValidChildren)(children);
60
+ const menuItems = validChildren.map((child, index)=>{
61
+ const itemProps = {
62
+ ...child.props,
63
+ menuIndex: index
64
+ };
65
+ return /*#__PURE__*/ _react.default.cloneElement(child, itemProps);
66
+ });
67
+ return /*#__PURE__*/ (0, _jsxruntime.jsx)(_context.SegmentedControlContext.Provider, {
68
+ value: context,
69
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)("ul", {
70
+ id: id,
71
+ role: "toolbar",
72
+ ref: ref,
73
+ className: (0, _utils.cx)('sk-segmentedcontrol', className),
74
+ "data-size": size,
75
+ ...rest,
76
+ children: menuItems
77
+ })
78
+ });
79
+ });
80
+ if (_utils.__DEV__) {
81
+ SegmentedControlComponent.displayName = 'SegmentedControl';
82
+ }
83
+ const _default = SegmentedControlComponent;
84
+
85
+ //# sourceMappingURL=segmented-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/segmented-control.tsx"],"sourcesContent":["import { DefaultProps, __DEV__, cx, getValidChildren } from '@sk-web-gui/utils';\r\nimport React from 'react';\r\nimport { SegmentedControlContext } from './context';\r\nimport { SegmentedControlItemProps } from './segmented-control-item';\r\n\r\nexport interface SegmentedControlComponentProps\r\n extends DefaultProps,\r\n Omit<React.ComponentPropsWithRef<'ul'>, 'defaultValue' | 'onChange'> {\r\n /** Array of selected indices (controlled mode) */\r\n value?: number[];\r\n /** Array of initially selected indices (uncontrolled mode) */\r\n defaultValue?: number[];\r\n /** Callback when selection changes */\r\n onChange?: (selected: number[]) => void;\r\n /** Disable all items */\r\n disabled?: boolean;\r\n /** Enables multi select */\r\n multiSelect?: boolean;\r\n /** Size variant */\r\n size?: 'md' | 'lg';\r\n}\r\n\r\nexport const SegmentedControlComponent = React.forwardRef<HTMLUListElement, SegmentedControlComponentProps>(\r\n (props, ref) => {\r\n const {\r\n className,\r\n children,\r\n value,\r\n defaultValue,\r\n onChange,\r\n id: _id,\r\n disabled = false,\r\n size = 'lg',\r\n multiSelect = false,\r\n ...rest\r\n } = props;\r\n\r\n const [internalSelected, setInternalSelected] = React.useState<number[]>(defaultValue || []);\r\n const isControlled = value !== undefined;\r\n const selected = isControlled ? value : internalSelected;\r\n\r\n const [active, setActive] = React.useState<number>(0);\r\n\r\n const autoId = React.useId();\r\n const id = _id || `sk-segmentedcontrol-${autoId}`;\r\n\r\n const total = React.Children.count(children);\r\n\r\n const toggleItem = (index: number) => {\r\n \r\n if (disabled) return;\r\n\r\n const newSelected = !multiSelect ? [index] : selected.includes(index) ? selected.filter((i) => i !== index) : [...selected, index];\r\n\r\n if (!isControlled) {\r\n setInternalSelected(newSelected);\r\n }\r\n onChange?.(newSelected);\r\n };\r\n\r\n const context = {\r\n selected,\r\n toggleItem,\r\n active,\r\n setActive,\r\n total,\r\n size,\r\n disabled,\r\n };\r\n\r\n const validChildren = getValidChildren<SegmentedControlItemProps>(children);\r\n const menuItems = validChildren.map((child, index) => {\r\n const itemProps = { ...child.props, menuIndex: index };\r\n return React.cloneElement(child, itemProps);\r\n });\r\n\r\n return (\r\n <SegmentedControlContext.Provider value={context}>\r\n <ul id={id} role=\"toolbar\" ref={ref} className={cx('sk-segmentedcontrol', className)} data-size={size} {...rest}>\r\n {menuItems}\r\n </ul>\r\n </SegmentedControlContext.Provider>\r\n );\r\n }\r\n);\r\n\r\nif (__DEV__) {\r\n SegmentedControlComponent.displayName = 'SegmentedControl';\r\n}\r\n\r\nexport default SegmentedControlComponent;\r\n"],"names":["SegmentedControlComponent","React","forwardRef","props","ref","className","children","value","defaultValue","onChange","id","_id","disabled","size","multiSelect","rest","internalSelected","setInternalSelected","useState","isControlled","undefined","selected","active","setActive","autoId","useId","total","Children","count","toggleItem","index","newSelected","includes","filter","i","context","validChildren","getValidChildren","menuItems","map","child","itemProps","menuIndex","cloneElement","SegmentedControlContext","Provider","ul","role","cx","data-size","__DEV__","displayName"],"mappings":";;;;;;;;;;;QAsBaA;eAAAA;;QAoEb;eAAA;;;;uBA1F4D;8DAC1C;yBACsB;;;;;;AAoBjC,MAAMA,0CAA4BC,cAAK,CAACC,UAAU,CACvD,CAACC,OAAOC;IACN,MAAM,EACJC,SAAS,EACTC,QAAQ,EACRC,KAAK,EACLC,YAAY,EACZC,QAAQ,EACRC,IAAIC,GAAG,EACPC,WAAW,KAAK,EAChBC,OAAO,IAAI,EACXC,cAAc,KAAK,EACnB,GAAGC,MACJ,GAAGZ;IAEJ,MAAM,CAACa,kBAAkBC,oBAAoB,GAAGhB,cAAK,CAACiB,QAAQ,CAAWV,gBAAgB,EAAE;IAC3F,MAAMW,eAAeZ,UAAUa;IAC/B,MAAMC,WAAWF,eAAeZ,QAAQS;IAExC,MAAM,CAACM,QAAQC,UAAU,GAAGtB,cAAK,CAACiB,QAAQ,CAAS;IAEnD,MAAMM,SAASvB,cAAK,CAACwB,KAAK;IAC1B,MAAMf,KAAKC,OAAO,CAAC,oBAAoB,EAAEa,QAAQ;IAEjD,MAAME,QAAQzB,cAAK,CAAC0B,QAAQ,CAACC,KAAK,CAACtB;IAEnC,MAAMuB,aAAa,CAACC;QAElB,IAAIlB,UAAU;QAEd,MAAMmB,cAAc,CAACjB,cAAc;YAACgB;SAAM,GAAGT,SAASW,QAAQ,CAACF,SAAST,SAASY,MAAM,CAAC,CAACC,IAAMA,MAAMJ,SAAS;eAAIT;YAAUS;SAAM;QAElI,IAAI,CAACX,cAAc;YACjBF,oBAAoBc;QACtB;QACAtB,WAAWsB;IACb;IAEA,MAAMI,UAAU;QACdd;QACAQ;QACAP;QACAC;QACAG;QACAb;QACAD;IACF;IAEA,MAAMwB,gBAAgBC,IAAAA,uBAAgB,EAA4B/B;IAClE,MAAMgC,YAAYF,cAAcG,GAAG,CAAC,CAACC,OAAOV;QAC1C,MAAMW,YAAY;YAAE,GAAGD,MAAMrC,KAAK;YAAEuC,WAAWZ;QAAM;QACrD,qBAAO7B,cAAK,CAAC0C,YAAY,CAACH,OAAOC;IACnC;IAEA,qBACE,qBAACG,gCAAuB,CAACC,QAAQ;QAACtC,OAAO4B;kBACvC,cAAA,qBAACW;YAAGpC,IAAIA;YAAIqC,MAAK;YAAU3C,KAAKA;YAAKC,WAAW2C,IAAAA,SAAE,EAAC,uBAAuB3C;YAAY4C,aAAWpC;YAAO,GAAGE,IAAI;sBAC5GuB;;;AAIT;AAGF,IAAIY,cAAO,EAAE;IACXlD,0BAA0BmD,WAAW,GAAG;AAC1C;MAEA,WAAenD"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "useSegmentedControl", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return useSegmentedControl;
9
+ }
10
+ });
11
+ const _react = /*#__PURE__*/ _interop_require_default(require("react"));
12
+ const _context = require("./context");
13
+ function _interop_require_default(obj) {
14
+ return obj && obj.__esModule ? obj : {
15
+ default: obj
16
+ };
17
+ }
18
+ const useSegmentedControl = ()=>_react.default.useContext(_context.SegmentedControlContext);
19
+
20
+ //# sourceMappingURL=use-segmented-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/use-segmented-control.ts"],"sourcesContent":["import React from 'react';\r\nimport { SegmentedControlContext } from './context';\r\n\r\nexport interface UseSegmentedControlProps {\r\n /** Array of selected indices (controlled) */\r\n value?: number[];\r\n /** Array of initially selected indices (uncontrolled) */\r\n defaultValue?: number[];\r\n size?: 'md' | 'lg';\r\n}\r\n\r\nexport const useSegmentedControl = () => React.useContext(SegmentedControlContext);\r\n"],"names":["useSegmentedControl","React","useContext","SegmentedControlContext"],"mappings":";;;;+BAWaA;;;eAAAA;;;8DAXK;yBACsB;;;;;;AAUjC,MAAMA,sBAAsB,IAAMC,cAAK,CAACC,UAAU,CAACC,gCAAuB"}
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ export const SegmentedControlContext = React.createContext({});
3
+
4
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/context.ts"],"sourcesContent":["import React from 'react';\r\nimport { UseSegmentedControlProps } from './use-segmented-control';\r\n\r\nexport interface UseSegmentedControlContext extends UseSegmentedControlProps {\r\n /** Array of currently selected indices */\r\n selected?: number[];\r\n /** Toggle an item's selection state */\r\n toggleItem?: (index: number) => void;\r\n /** Currently focused item index (for keyboard navigation) */\r\n active?: number;\r\n /** Set the focused item index */\r\n setActive?: (index: number) => void;\r\n /** Total number of items */\r\n total?: number;\r\n size?: 'md' | 'lg';\r\n disabled?: boolean;\r\n}\r\n\r\nexport const SegmentedControlContext = React.createContext<UseSegmentedControlContext>({});\r\n"],"names":["React","SegmentedControlContext","createContext"],"mappings":"AAAA,OAAOA,WAAW,QAAQ;AAkB1B,OAAO,MAAMC,0BAA0BD,MAAME,aAAa,CAA6B,CAAC,GAAG"}
@@ -0,0 +1,11 @@
1
+ import SegmentedControlComponent from './segmented-control.js';
2
+ import { SegmentedControlItem } from './segmented-control-item.js';
3
+ const SegmentedControl = {
4
+ ...SegmentedControlComponent,
5
+ Component: SegmentedControlComponent,
6
+ Item: SegmentedControlItem
7
+ };
8
+ export { SegmentedControl };
9
+ export default SegmentedControl;
10
+
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/index.ts"],"sourcesContent":["import SegmentedControlComponent, { SegmentedControlComponentProps } from \"./segmented-control\";\r\nimport { SegmentedControlItem } from \"./segmented-control-item\";\r\n\r\ninterface SegmentedControlProps extends React.ForwardRefExoticComponent<SegmentedControlComponentProps> {\r\n Component: typeof SegmentedControlComponent;\r\n Item: typeof SegmentedControlItem;\r\n}\r\n\r\nconst SegmentedControl = {\r\n ...SegmentedControlComponent,\r\n Component: SegmentedControlComponent,\r\n Item: SegmentedControlItem,\r\n} as SegmentedControlProps;\r\n\r\nexport { SegmentedControl };\r\nexport type { SegmentedControlProps, SegmentedControlComponentProps };\r\nexport default SegmentedControl;\r\n"],"names":["SegmentedControlComponent","SegmentedControlItem","SegmentedControl","Component","Item"],"mappings":"AAAA,OAAOA,+BAAmE,sBAAsB;AAChG,SAASC,oBAAoB,QAAQ,2BAA2B;AAOhE,MAAMC,mBAAmB;IACvB,GAAGF,yBAAyB;IAC5BG,WAAWH;IACXI,MAAMH;AACR;AAEA,SAASC,gBAAgB,GAAG;AAE5B,eAAeA,iBAAiB"}
@@ -0,0 +1,114 @@
1
+ import { jsx as _jsx } from 'react/jsx-runtime';
2
+ import { cx, getValidChildren, useForkRef } from '@sk-web-gui/utils';
3
+ import { PopupMenu } from '@sk-web-gui/popup-menu';
4
+ import React from 'react';
5
+ import { useSegmentedControl } from './use-segmented-control.js';
6
+ export const SegmentedControlItem = /*#__PURE__*/ React.forwardRef((props, ref)=>{
7
+ const { className, children, menuIndex, wrapper, size: _size, disabled: _disabled, ...rest } = props;
8
+ const { selected, toggleItem, active, setActive, total, size: contextSize, disabled: contextDisabled } = useSegmentedControl();
9
+ const menuRef = React.useRef(null);
10
+ const prevActiveRef = React.useRef(undefined);
11
+ const size = _size || contextSize || 'lg';
12
+ const disabled = _disabled || contextDisabled;
13
+ const isSelected = selected?.includes(menuIndex) ?? false;
14
+ React.useEffect(()=>{
15
+ // Only focus when active changes (not on initial mount)
16
+ if (prevActiveRef.current !== undefined && active === menuIndex && menuRef.current) {
17
+ walkFocus(menuRef.current.children);
18
+ }
19
+ prevActiveRef.current = active;
20
+ }, [
21
+ active,
22
+ menuIndex
23
+ ]);
24
+ const walkFocus = (collection)=>{
25
+ for (const element of Array.from(collection)){
26
+ if (element.getAttribute('role') === 'button') {
27
+ element.focus();
28
+ return true;
29
+ }
30
+ if (element.children && walkFocus(element.children)) {
31
+ return true;
32
+ }
33
+ }
34
+ return false;
35
+ };
36
+ const handleKeyboard = (event)=>{
37
+ if (disabled) return;
38
+ const currentIndex = menuIndex;
39
+ const itemCount = total || 0;
40
+ if (event.key === 'ArrowLeft') {
41
+ event.preventDefault();
42
+ event.stopPropagation();
43
+ const newIndex = currentIndex === 0 ? itemCount - 1 : currentIndex - 1;
44
+ setActive?.(newIndex);
45
+ }
46
+ if (event.key === 'ArrowRight') {
47
+ event.preventDefault();
48
+ event.stopPropagation();
49
+ const newIndex = currentIndex === itemCount - 1 ? 0 : currentIndex + 1;
50
+ setActive?.(newIndex);
51
+ }
52
+ if (event.key === ' ' || event.key === 'Enter') {
53
+ event.preventDefault();
54
+ event.stopPropagation();
55
+ toggleItem?.(currentIndex);
56
+ }
57
+ };
58
+ const handleClick = ()=>{
59
+ if (disabled) return;
60
+ toggleItem?.(menuIndex);
61
+ };
62
+ const getClonedChild = (child)=>{
63
+ if (child.type === React.Fragment) {
64
+ const grandchildren = getValidChildren(child.props.children).map((grandchild, index)=>{
65
+ return index === 0 ? getClonedChild(grandchild) : grandchild;
66
+ });
67
+ return /*#__PURE__*/ React.cloneElement(child, {
68
+ ...child.props,
69
+ children: grandchildren
70
+ });
71
+ } else if (child.type === PopupMenu) {
72
+ const grandchildren = getValidChildren(child.props.children).map((grandchild)=>{
73
+ if (grandchild.type === PopupMenu.Button) {
74
+ return getClonedChild(grandchild);
75
+ }
76
+ return grandchild;
77
+ });
78
+ return /*#__PURE__*/ React.cloneElement(child, {
79
+ ...child.props,
80
+ children: grandchildren
81
+ });
82
+ } else {
83
+ return /*#__PURE__*/ React.cloneElement(child, {
84
+ ...child.props,
85
+ onKeyDown: handleKeyboard,
86
+ onClick: handleClick,
87
+ role: 'button',
88
+ 'aria-pressed': isSelected ? 'true' : 'false',
89
+ 'aria-disabled': disabled ? 'true' : undefined,
90
+ tabIndex: disabled ? -1 : active === menuIndex ? 0 : -1
91
+ });
92
+ }
93
+ };
94
+ const getChildWithWrapper = ()=>{
95
+ if (wrapper) {
96
+ return /*#__PURE__*/ React.cloneElement(wrapper, {
97
+ ...wrapper.props,
98
+ children: getClonedChild(children)
99
+ });
100
+ } else {
101
+ return getClonedChild(children);
102
+ }
103
+ };
104
+ return /*#__PURE__*/ _jsx("li", {
105
+ "data-size": size,
106
+ ref: useForkRef(ref, menuRef),
107
+ className: cx('sk-segmentedcontrol-item', className),
108
+ role: "none",
109
+ ...rest,
110
+ children: getChildWithWrapper()
111
+ });
112
+ });
113
+
114
+ //# sourceMappingURL=segmented-control-item.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/segmented-control-item.tsx"],"sourcesContent":["import { DefaultProps, cx, getValidChildren, useForkRef } from '@sk-web-gui/utils';\r\nimport { PopupMenu } from '@sk-web-gui/popup-menu';\r\nimport React from 'react';\r\nimport { useSegmentedControl } from './use-segmented-control';\r\n\r\nexport interface SegmentedControlItemProps extends DefaultProps, React.ComponentPropsWithRef<'li'> {\r\n /** Set automatic by parent */\r\n menuIndex?: number;\r\n /** Use <a> or <button>. For dropdown, use <PopupMenu> */\r\n children: React.JSX.Element;\r\n /** For e.g. Next Links to work, they need to wrapped this way */\r\n wrapper?: React.JSX.Element;\r\n size?: 'md' | 'lg';\r\n disabled?: boolean;\r\n}\r\n\r\nexport const SegmentedControlItem = React.forwardRef<HTMLLIElement, SegmentedControlItemProps>((props, ref) => {\r\n const { className, children, menuIndex, wrapper, size: _size, disabled: _disabled, ...rest } = props;\r\n\r\n const {\r\n selected,\r\n toggleItem,\r\n active,\r\n setActive,\r\n total,\r\n size: contextSize,\r\n disabled: contextDisabled,\r\n } = useSegmentedControl();\r\n\r\n const menuRef = React.useRef<HTMLElement>(null);\r\n const prevActiveRef = React.useRef<number | undefined>(undefined);\r\n const size = _size || contextSize || 'lg';\r\n const disabled = _disabled || contextDisabled;\r\n const isSelected = selected?.includes(menuIndex as number) ?? false;\r\n\r\n React.useEffect(() => {\r\n // Only focus when active changes (not on initial mount)\r\n if (prevActiveRef.current !== undefined && active === menuIndex && menuRef.current) {\r\n walkFocus(menuRef.current.children);\r\n }\r\n prevActiveRef.current = active;\r\n }, [active, menuIndex]);\r\n\r\n const walkFocus = (collection: HTMLCollection): boolean => {\r\n for (const element of Array.from(collection)) {\r\n if (element.getAttribute('role') === 'button') {\r\n (element as HTMLElement).focus();\r\n return true;\r\n }\r\n if (element.children && walkFocus(element.children)) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n };\r\n\r\n const handleKeyboard = (event: React.KeyboardEvent) => {\r\n if (disabled) return;\r\n\r\n const currentIndex = menuIndex as number;\r\n const itemCount = total || 0;\r\n\r\n if (event.key === 'ArrowLeft') {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n const newIndex = currentIndex === 0 ? itemCount - 1 : currentIndex - 1;\r\n setActive?.(newIndex);\r\n }\r\n\r\n if (event.key === 'ArrowRight') {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n const newIndex = currentIndex === itemCount - 1 ? 0 : currentIndex + 1;\r\n setActive?.(newIndex);\r\n }\r\n\r\n if (event.key === ' ' || event.key === 'Enter') {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n toggleItem?.(currentIndex);\r\n }\r\n };\r\n\r\n const handleClick = () => {\r\n if (disabled) return;\r\n toggleItem?.(menuIndex as number);\r\n };\r\n\r\n const getClonedChild = (child: React.JSX.Element): React.ReactNode => {\r\n if (child.type === React.Fragment) {\r\n const grandchildren = getValidChildren(child.props.children).map((grandchild, index) => {\r\n return index === 0 ? getClonedChild(grandchild) : grandchild;\r\n });\r\n return React.cloneElement(child, { ...child.props, children: grandchildren });\r\n } else if (child.type === PopupMenu) {\r\n const grandchildren = getValidChildren(child.props.children).map((grandchild) => {\r\n if (grandchild.type === PopupMenu.Button) {\r\n return getClonedChild(grandchild);\r\n }\r\n return grandchild;\r\n });\r\n\r\n return React.cloneElement(child, { ...child.props, children: grandchildren });\r\n } else {\r\n return React.cloneElement(child, {\r\n ...child.props,\r\n onKeyDown: handleKeyboard,\r\n onClick: handleClick,\r\n role: 'button',\r\n 'aria-pressed': isSelected ? 'true' : 'false',\r\n 'aria-disabled': disabled ? 'true' : undefined,\r\n tabIndex: disabled ? -1 : active === menuIndex ? 0 : -1,\r\n });\r\n }\r\n };\r\n\r\n const getChildWithWrapper = () => {\r\n if (wrapper) {\r\n return React.cloneElement(wrapper, { ...wrapper.props, children: getClonedChild(children) });\r\n } else {\r\n return getClonedChild(children);\r\n }\r\n };\r\n\r\n return (\r\n <li\r\n data-size={size}\r\n ref={useForkRef(ref, menuRef)}\r\n className={cx('sk-segmentedcontrol-item', className)}\r\n role=\"none\"\r\n {...rest}\r\n >\r\n {getChildWithWrapper()}\r\n </li>\r\n );\r\n});\r\n"],"names":["cx","getValidChildren","useForkRef","PopupMenu","React","useSegmentedControl","SegmentedControlItem","forwardRef","props","ref","className","children","menuIndex","wrapper","size","_size","disabled","_disabled","rest","selected","toggleItem","active","setActive","total","contextSize","contextDisabled","menuRef","useRef","prevActiveRef","undefined","isSelected","includes","useEffect","current","walkFocus","collection","element","Array","from","getAttribute","focus","handleKeyboard","event","currentIndex","itemCount","key","preventDefault","stopPropagation","newIndex","handleClick","getClonedChild","child","type","Fragment","grandchildren","map","grandchild","index","cloneElement","Button","onKeyDown","onClick","role","tabIndex","getChildWithWrapper","li","data-size"],"mappings":";AAAA,SAAuBA,EAAE,EAAEC,gBAAgB,EAAEC,UAAU,QAAQ,oBAAoB;AACnF,SAASC,SAAS,QAAQ,yBAAyB;AACnD,OAAOC,WAAW,QAAQ;AAC1B,SAASC,mBAAmB,QAAQ,0BAA0B;AAa9D,OAAO,MAAMC,qCAAuBF,MAAMG,UAAU,CAA2C,CAACC,OAAOC;IACrG,MAAM,EAAEC,SAAS,EAAEC,QAAQ,EAAEC,SAAS,EAAEC,OAAO,EAAEC,MAAMC,KAAK,EAAEC,UAAUC,SAAS,EAAE,GAAGC,MAAM,GAAGV;IAE/F,MAAM,EACJW,QAAQ,EACRC,UAAU,EACVC,MAAM,EACNC,SAAS,EACTC,KAAK,EACLT,MAAMU,WAAW,EACjBR,UAAUS,eAAe,EAC1B,GAAGpB;IAEJ,MAAMqB,UAAUtB,MAAMuB,MAAM,CAAc;IAC1C,MAAMC,gBAAgBxB,MAAMuB,MAAM,CAAqBE;IACvD,MAAMf,OAAOC,SAASS,eAAe;IACrC,MAAMR,WAAWC,aAAaQ;IAC9B,MAAMK,aAAaX,UAAUY,SAASnB,cAAwB;IAE9DR,MAAM4B,SAAS,CAAC;QACd,wDAAwD;QACxD,IAAIJ,cAAcK,OAAO,KAAKJ,aAAaR,WAAWT,aAAac,QAAQO,OAAO,EAAE;YAClFC,UAAUR,QAAQO,OAAO,CAACtB,QAAQ;QACpC;QACAiB,cAAcK,OAAO,GAAGZ;IAC1B,GAAG;QAACA;QAAQT;KAAU;IAEtB,MAAMsB,YAAY,CAACC;QACjB,KAAK,MAAMC,WAAWC,MAAMC,IAAI,CAACH,YAAa;YAC5C,IAAIC,QAAQG,YAAY,CAAC,YAAY,UAAU;gBAC5CH,QAAwBI,KAAK;gBAC9B,OAAO;YACT;YACA,IAAIJ,QAAQzB,QAAQ,IAAIuB,UAAUE,QAAQzB,QAAQ,GAAG;gBACnD,OAAO;YACT;QACF;QACA,OAAO;IACT;IAEA,MAAM8B,iBAAiB,CAACC;QACtB,IAAI1B,UAAU;QAEd,MAAM2B,eAAe/B;QACrB,MAAMgC,YAAYrB,SAAS;QAE3B,IAAImB,MAAMG,GAAG,KAAK,aAAa;YAC7BH,MAAMI,cAAc;YACpBJ,MAAMK,eAAe;YACrB,MAAMC,WAAWL,iBAAiB,IAAIC,YAAY,IAAID,eAAe;YACrErB,YAAY0B;QACd;QAEA,IAAIN,MAAMG,GAAG,KAAK,cAAc;YAC9BH,MAAMI,cAAc;YACpBJ,MAAMK,eAAe;YACrB,MAAMC,WAAWL,iBAAiBC,YAAY,IAAI,IAAID,eAAe;YACrErB,YAAY0B;QACd;QAEA,IAAIN,MAAMG,GAAG,KAAK,OAAOH,MAAMG,GAAG,KAAK,SAAS;YAC9CH,MAAMI,cAAc;YACpBJ,MAAMK,eAAe;YACrB3B,aAAauB;QACf;IACF;IAEA,MAAMM,cAAc;QAClB,IAAIjC,UAAU;QACdI,aAAaR;IACf;IAEA,MAAMsC,iBAAiB,CAACC;QACtB,IAAIA,MAAMC,IAAI,KAAKhD,MAAMiD,QAAQ,EAAE;YACjC,MAAMC,gBAAgBrD,iBAAiBkD,MAAM3C,KAAK,CAACG,QAAQ,EAAE4C,GAAG,CAAC,CAACC,YAAYC;gBAC5E,OAAOA,UAAU,IAAIP,eAAeM,cAAcA;YACpD;YACA,qBAAOpD,MAAMsD,YAAY,CAACP,OAAO;gBAAE,GAAGA,MAAM3C,KAAK;gBAAEG,UAAU2C;YAAc;QAC7E,OAAO,IAAIH,MAAMC,IAAI,KAAKjD,WAAW;YACnC,MAAMmD,gBAAgBrD,iBAAiBkD,MAAM3C,KAAK,CAACG,QAAQ,EAAE4C,GAAG,CAAC,CAACC;gBAChE,IAAIA,WAAWJ,IAAI,KAAKjD,UAAUwD,MAAM,EAAE;oBACxC,OAAOT,eAAeM;gBACxB;gBACA,OAAOA;YACT;YAEA,qBAAOpD,MAAMsD,YAAY,CAACP,OAAO;gBAAE,GAAGA,MAAM3C,KAAK;gBAAEG,UAAU2C;YAAc;QAC7E,OAAO;YACL,qBAAOlD,MAAMsD,YAAY,CAACP,OAAO;gBAC/B,GAAGA,MAAM3C,KAAK;gBACdoD,WAAWnB;gBACXoB,SAASZ;gBACTa,MAAM;gBACN,gBAAgBhC,aAAa,SAAS;gBACtC,iBAAiBd,WAAW,SAASa;gBACrCkC,UAAU/C,WAAW,CAAC,IAAIK,WAAWT,YAAY,IAAI,CAAC;YACxD;QACF;IACF;IAEA,MAAMoD,sBAAsB;QAC1B,IAAInD,SAAS;YACX,qBAAOT,MAAMsD,YAAY,CAAC7C,SAAS;gBAAE,GAAGA,QAAQL,KAAK;gBAAEG,UAAUuC,eAAevC;YAAU;QAC5F,OAAO;YACL,OAAOuC,eAAevC;QACxB;IACF;IAEA,qBACE,KAACsD;QACCC,aAAWpD;QACXL,KAAKP,WAAWO,KAAKiB;QACrBhB,WAAWV,GAAG,4BAA4BU;QAC1CoD,MAAK;QACJ,GAAG5C,IAAI;kBAEP8C;;AAGP,GAAG"}
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx } from 'react/jsx-runtime';
2
+ import { __DEV__, cx, getValidChildren } from '@sk-web-gui/utils';
3
+ import React from 'react';
4
+ import { SegmentedControlContext } from './context.js';
5
+ export const SegmentedControlComponent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
6
+ const { className, children, value, defaultValue, onChange, id: _id, disabled = false, size = 'lg', multiSelect = false, ...rest } = props;
7
+ const [internalSelected, setInternalSelected] = React.useState(defaultValue || []);
8
+ const isControlled = value !== undefined;
9
+ const selected = isControlled ? value : internalSelected;
10
+ const [active, setActive] = React.useState(0);
11
+ const autoId = React.useId();
12
+ const id = _id || `sk-segmentedcontrol-${autoId}`;
13
+ const total = React.Children.count(children);
14
+ const toggleItem = (index)=>{
15
+ if (disabled) return;
16
+ const newSelected = !multiSelect ? [
17
+ index
18
+ ] : selected.includes(index) ? selected.filter((i)=>i !== index) : [
19
+ ...selected,
20
+ index
21
+ ];
22
+ if (!isControlled) {
23
+ setInternalSelected(newSelected);
24
+ }
25
+ onChange?.(newSelected);
26
+ };
27
+ const context = {
28
+ selected,
29
+ toggleItem,
30
+ active,
31
+ setActive,
32
+ total,
33
+ size,
34
+ disabled
35
+ };
36
+ const validChildren = getValidChildren(children);
37
+ const menuItems = validChildren.map((child, index)=>{
38
+ const itemProps = {
39
+ ...child.props,
40
+ menuIndex: index
41
+ };
42
+ return /*#__PURE__*/ React.cloneElement(child, itemProps);
43
+ });
44
+ return /*#__PURE__*/ _jsx(SegmentedControlContext.Provider, {
45
+ value: context,
46
+ children: /*#__PURE__*/ _jsx("ul", {
47
+ id: id,
48
+ role: "toolbar",
49
+ ref: ref,
50
+ className: cx('sk-segmentedcontrol', className),
51
+ "data-size": size,
52
+ ...rest,
53
+ children: menuItems
54
+ })
55
+ });
56
+ });
57
+ if (__DEV__) {
58
+ SegmentedControlComponent.displayName = 'SegmentedControl';
59
+ }
60
+ export default SegmentedControlComponent;
61
+
62
+ //# sourceMappingURL=segmented-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/segmented-control.tsx"],"sourcesContent":["import { DefaultProps, __DEV__, cx, getValidChildren } from '@sk-web-gui/utils';\r\nimport React from 'react';\r\nimport { SegmentedControlContext } from './context';\r\nimport { SegmentedControlItemProps } from './segmented-control-item';\r\n\r\nexport interface SegmentedControlComponentProps\r\n extends DefaultProps,\r\n Omit<React.ComponentPropsWithRef<'ul'>, 'defaultValue' | 'onChange'> {\r\n /** Array of selected indices (controlled mode) */\r\n value?: number[];\r\n /** Array of initially selected indices (uncontrolled mode) */\r\n defaultValue?: number[];\r\n /** Callback when selection changes */\r\n onChange?: (selected: number[]) => void;\r\n /** Disable all items */\r\n disabled?: boolean;\r\n /** Enables multi select */\r\n multiSelect?: boolean;\r\n /** Size variant */\r\n size?: 'md' | 'lg';\r\n}\r\n\r\nexport const SegmentedControlComponent = React.forwardRef<HTMLUListElement, SegmentedControlComponentProps>(\r\n (props, ref) => {\r\n const {\r\n className,\r\n children,\r\n value,\r\n defaultValue,\r\n onChange,\r\n id: _id,\r\n disabled = false,\r\n size = 'lg',\r\n multiSelect = false,\r\n ...rest\r\n } = props;\r\n\r\n const [internalSelected, setInternalSelected] = React.useState<number[]>(defaultValue || []);\r\n const isControlled = value !== undefined;\r\n const selected = isControlled ? value : internalSelected;\r\n\r\n const [active, setActive] = React.useState<number>(0);\r\n\r\n const autoId = React.useId();\r\n const id = _id || `sk-segmentedcontrol-${autoId}`;\r\n\r\n const total = React.Children.count(children);\r\n\r\n const toggleItem = (index: number) => {\r\n \r\n if (disabled) return;\r\n\r\n const newSelected = !multiSelect ? [index] : selected.includes(index) ? selected.filter((i) => i !== index) : [...selected, index];\r\n\r\n if (!isControlled) {\r\n setInternalSelected(newSelected);\r\n }\r\n onChange?.(newSelected);\r\n };\r\n\r\n const context = {\r\n selected,\r\n toggleItem,\r\n active,\r\n setActive,\r\n total,\r\n size,\r\n disabled,\r\n };\r\n\r\n const validChildren = getValidChildren<SegmentedControlItemProps>(children);\r\n const menuItems = validChildren.map((child, index) => {\r\n const itemProps = { ...child.props, menuIndex: index };\r\n return React.cloneElement(child, itemProps);\r\n });\r\n\r\n return (\r\n <SegmentedControlContext.Provider value={context}>\r\n <ul id={id} role=\"toolbar\" ref={ref} className={cx('sk-segmentedcontrol', className)} data-size={size} {...rest}>\r\n {menuItems}\r\n </ul>\r\n </SegmentedControlContext.Provider>\r\n );\r\n }\r\n);\r\n\r\nif (__DEV__) {\r\n SegmentedControlComponent.displayName = 'SegmentedControl';\r\n}\r\n\r\nexport default SegmentedControlComponent;\r\n"],"names":["__DEV__","cx","getValidChildren","React","SegmentedControlContext","SegmentedControlComponent","forwardRef","props","ref","className","children","value","defaultValue","onChange","id","_id","disabled","size","multiSelect","rest","internalSelected","setInternalSelected","useState","isControlled","undefined","selected","active","setActive","autoId","useId","total","Children","count","toggleItem","index","newSelected","includes","filter","i","context","validChildren","menuItems","map","child","itemProps","menuIndex","cloneElement","Provider","ul","role","data-size","displayName"],"mappings":";AAAA,SAAuBA,OAAO,EAAEC,EAAE,EAAEC,gBAAgB,QAAQ,oBAAoB;AAChF,OAAOC,WAAW,QAAQ;AAC1B,SAASC,uBAAuB,QAAQ,YAAY;AAoBpD,OAAO,MAAMC,0CAA4BF,MAAMG,UAAU,CACvD,CAACC,OAAOC;IACN,MAAM,EACJC,SAAS,EACTC,QAAQ,EACRC,KAAK,EACLC,YAAY,EACZC,QAAQ,EACRC,IAAIC,GAAG,EACPC,WAAW,KAAK,EAChBC,OAAO,IAAI,EACXC,cAAc,KAAK,EACnB,GAAGC,MACJ,GAAGZ;IAEJ,MAAM,CAACa,kBAAkBC,oBAAoB,GAAGlB,MAAMmB,QAAQ,CAAWV,gBAAgB,EAAE;IAC3F,MAAMW,eAAeZ,UAAUa;IAC/B,MAAMC,WAAWF,eAAeZ,QAAQS;IAExC,MAAM,CAACM,QAAQC,UAAU,GAAGxB,MAAMmB,QAAQ,CAAS;IAEnD,MAAMM,SAASzB,MAAM0B,KAAK;IAC1B,MAAMf,KAAKC,OAAO,CAAC,oBAAoB,EAAEa,QAAQ;IAEjD,MAAME,QAAQ3B,MAAM4B,QAAQ,CAACC,KAAK,CAACtB;IAEnC,MAAMuB,aAAa,CAACC;QAElB,IAAIlB,UAAU;QAEd,MAAMmB,cAAc,CAACjB,cAAc;YAACgB;SAAM,GAAGT,SAASW,QAAQ,CAACF,SAAST,SAASY,MAAM,CAAC,CAACC,IAAMA,MAAMJ,SAAS;eAAIT;YAAUS;SAAM;QAElI,IAAI,CAACX,cAAc;YACjBF,oBAAoBc;QACtB;QACAtB,WAAWsB;IACb;IAEA,MAAMI,UAAU;QACdd;QACAQ;QACAP;QACAC;QACAG;QACAb;QACAD;IACF;IAEA,MAAMwB,gBAAgBtC,iBAA4CQ;IAClE,MAAM+B,YAAYD,cAAcE,GAAG,CAAC,CAACC,OAAOT;QAC1C,MAAMU,YAAY;YAAE,GAAGD,MAAMpC,KAAK;YAAEsC,WAAWX;QAAM;QACrD,qBAAO/B,MAAM2C,YAAY,CAACH,OAAOC;IACnC;IAEA,qBACE,KAACxC,wBAAwB2C,QAAQ;QAACpC,OAAO4B;kBACvC,cAAA,KAACS;YAAGlC,IAAIA;YAAImC,MAAK;YAAUzC,KAAKA;YAAKC,WAAWR,GAAG,uBAAuBQ;YAAYyC,aAAWjC;YAAO,GAAGE,IAAI;sBAC5GsB;;;AAIT,GACA;AAEF,IAAIzC,SAAS;IACXK,0BAA0B8C,WAAW,GAAG;AAC1C;AAEA,eAAe9C,0BAA0B"}
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import { SegmentedControlContext } from './context.js';
3
+ export const useSegmentedControl = ()=>React.useContext(SegmentedControlContext);
4
+
5
+ //# sourceMappingURL=use-segmented-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/use-segmented-control.ts"],"sourcesContent":["import React from 'react';\r\nimport { SegmentedControlContext } from './context';\r\n\r\nexport interface UseSegmentedControlProps {\r\n /** Array of selected indices (controlled) */\r\n value?: number[];\r\n /** Array of initially selected indices (uncontrolled) */\r\n defaultValue?: number[];\r\n size?: 'md' | 'lg';\r\n}\r\n\r\nexport const useSegmentedControl = () => React.useContext(SegmentedControlContext);\r\n"],"names":["React","SegmentedControlContext","useSegmentedControl","useContext"],"mappings":"AAAA,OAAOA,WAAW,QAAQ;AAC1B,SAASC,uBAAuB,QAAQ,YAAY;AAUpD,OAAO,MAAMC,sBAAsB,IAAMF,MAAMG,UAAU,CAACF,yBAAyB"}
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { UseSegmentedControlProps } from './use-segmented-control';
3
+ export interface UseSegmentedControlContext extends UseSegmentedControlProps {
4
+ /** Array of currently selected indices */
5
+ selected?: number[];
6
+ /** Toggle an item's selection state */
7
+ toggleItem?: (index: number) => void;
8
+ /** Currently focused item index (for keyboard navigation) */
9
+ active?: number;
10
+ /** Set the focused item index */
11
+ setActive?: (index: number) => void;
12
+ /** Total number of items */
13
+ total?: number;
14
+ size?: 'md' | 'lg';
15
+ disabled?: boolean;
16
+ }
17
+ export declare const SegmentedControlContext: React.Context<UseSegmentedControlContext>;
@@ -0,0 +1,10 @@
1
+ import SegmentedControlComponent, { SegmentedControlComponentProps } from "./segmented-control";
2
+ import { SegmentedControlItem } from "./segmented-control-item";
3
+ interface SegmentedControlProps extends React.ForwardRefExoticComponent<SegmentedControlComponentProps> {
4
+ Component: typeof SegmentedControlComponent;
5
+ Item: typeof SegmentedControlItem;
6
+ }
7
+ declare const SegmentedControl: SegmentedControlProps;
8
+ export { SegmentedControl };
9
+ export type { SegmentedControlProps, SegmentedControlComponentProps };
10
+ export default SegmentedControl;
@@ -0,0 +1,13 @@
1
+ import { DefaultProps } from '@sk-web-gui/utils';
2
+ import React from 'react';
3
+ export interface SegmentedControlItemProps extends DefaultProps, React.ComponentPropsWithRef<'li'> {
4
+ /** Set automatic by parent */
5
+ menuIndex?: number;
6
+ /** Use <a> or <button>. For dropdown, use <PopupMenu> */
7
+ children: React.JSX.Element;
8
+ /** For e.g. Next Links to work, they need to wrapped this way */
9
+ wrapper?: React.JSX.Element;
10
+ size?: 'md' | 'lg';
11
+ disabled?: boolean;
12
+ }
13
+ export declare const SegmentedControlItem: React.ForwardRefExoticComponent<Omit<SegmentedControlItemProps, "ref"> & React.RefAttributes<HTMLLIElement>>;
@@ -0,0 +1,18 @@
1
+ import { DefaultProps } from '@sk-web-gui/utils';
2
+ import React from 'react';
3
+ export interface SegmentedControlComponentProps extends DefaultProps, Omit<React.ComponentPropsWithRef<'ul'>, 'defaultValue' | 'onChange'> {
4
+ /** Array of selected indices (controlled mode) */
5
+ value?: number[];
6
+ /** Array of initially selected indices (uncontrolled mode) */
7
+ defaultValue?: number[];
8
+ /** Callback when selection changes */
9
+ onChange?: (selected: number[]) => void;
10
+ /** Disable all items */
11
+ disabled?: boolean;
12
+ /** Enables multi select */
13
+ multiSelect?: boolean;
14
+ /** Size variant */
15
+ size?: 'md' | 'lg';
16
+ }
17
+ export declare const SegmentedControlComponent: React.ForwardRefExoticComponent<Omit<SegmentedControlComponentProps, "ref"> & React.RefAttributes<HTMLUListElement>>;
18
+ export default SegmentedControlComponent;
@@ -0,0 +1,8 @@
1
+ export interface UseSegmentedControlProps {
2
+ /** Array of selected indices (controlled) */
3
+ value?: number[];
4
+ /** Array of initially selected indices (uncontrolled) */
5
+ defaultValue?: number[];
6
+ size?: 'md' | 'lg';
7
+ }
8
+ export declare const useSegmentedControl: () => import("./context").UseSegmentedControlContext;
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@sk-web-gui/segmentedcontrol",
3
+ "version": "1.1.0",
4
+ "sideEffects": false,
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/cjs/index.js",
8
+ "module": "./dist/esm/index.js",
9
+ "types": "./dist/types/index.d.ts",
10
+ "typings": "./dist/types/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/esm/index.js",
14
+ "require": "./dist/cjs/index.js",
15
+ "types": "./dist/types/index.d.ts",
16
+ "default": "./dist/esm/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "scripts": {
26
+ "prebuild": "rimraf dist",
27
+ "start": "nodemon --watch src --exec yarn build -e ts,tsx",
28
+ "build": "yarn build:types && yarn build:esm && yarn build:cjs",
29
+ "test": "jest --env=jsdom --passWithNoTests",
30
+ "lint": "concurrently yarn:lint:*",
31
+ "version": "yarn build",
32
+ "build:esm": "swc src -d dist/esm --config-file ../../.swcrc.esm && node ../../fix-esm-extensions.mjs ./dist/esm",
33
+ "build:cjs": "swc src -d dist/cjs --config-file ../../.swcrc",
34
+ "build:types": "tsc -p ./tsconfig.production.json --emitDeclarationOnly --declaration --declarationDir dist/types",
35
+ "test:cov": "yarn test --coverage",
36
+ "lint:src": "eslint src"
37
+ },
38
+ "devDependencies": {
39
+ "react": "^19.0.0"
40
+ },
41
+ "dependencies": {
42
+ "@sk-web-gui/button": "2.1.8",
43
+ "@sk-web-gui/icon": "3.1.2",
44
+ "@sk-web-gui/utils": "2.2.2",
45
+ "lucide-react": "^0.537.0"
46
+ },
47
+ "peerDependencies": {
48
+ "react": ">=18.3.1"
49
+ },
50
+ "gitHead": "572bd789b743fdfccb74f6814e6229b2eae867ca"
51
+ }