@harborclient/sdk 1.0.15 → 1.0.17

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.
@@ -0,0 +1,24 @@
1
+ import type { JSX } from 'react';
2
+ import type { TabItem } from './types.js';
3
+ interface Props<T extends string> {
4
+ /**
5
+ * Tabs that can be shown or hidden via the menu.
6
+ */
7
+ tabs: TabItem<T>[];
8
+ /**
9
+ * Tab values currently shown in the tab strip.
10
+ */
11
+ visibleTabValues: T[];
12
+ /**
13
+ * Called when the user toggles a tab's visibility in the menu.
14
+ *
15
+ * @param tabValue - Tab value to toggle.
16
+ */
17
+ onToggle: (tabValue: T) => void;
18
+ }
19
+ /**
20
+ * Caret-triggered menu for toggling which segmented tabs are visible.
21
+ */
22
+ export declare function SegmentedTabsVisibilityMenu<T extends string>({ tabs, visibleTabValues, onToggle }: Props<T>): JSX.Element;
23
+ export {};
24
+ //# sourceMappingURL=SegmentedTabsVisibilityMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SegmentedTabsVisibilityMenu.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedTabs/SegmentedTabsVisibilityMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAA4B,MAAM,OAAO,CAAC;AAK3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,UAAU,KAAK,CAAC,CAAC,SAAS,MAAM;IAC9B;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnB;;OAEG;IACH,gBAAgB,EAAE,CAAC,EAAE,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;CACjC;AAKD;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,MAAM,EAAE,EAC5D,IAAI,EACJ,gBAAgB,EAChB,QAAQ,EACT,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAuLxB"}
@@ -0,0 +1,154 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
2
+ import { useCallback, useEffect, useId, useRef, useState } from '@harborclient/sdk/react';
3
+ import { faCaretDown, faCheck } from '@fortawesome/free-solid-svg-icons';
4
+ import { Button } from '../Button/index.js';
5
+ import { FaIcon } from '../FaIcon/index.js';
6
+ import { resolveTabListKeyAction } from '../utils.js';
7
+ const menuItemClass = 'flex w-full cursor-pointer items-center gap-2 border-none bg-transparent px-3.5 py-1.5 text-left text-[14px] text-text hover:bg-selection app-no-drag';
8
+ /**
9
+ * Caret-triggered menu for toggling which segmented tabs are visible.
10
+ */
11
+ export function SegmentedTabsVisibilityMenu({ tabs, visibleTabValues, onToggle }) {
12
+ const menuId = useId();
13
+ const menuElementId = `${menuId}-menu`;
14
+ const rootRef = useRef(null);
15
+ const triggerRef = useRef(null);
16
+ const itemRefs = useRef([]);
17
+ const wasOpenRef = useRef(false);
18
+ const [isOpen, setIsOpen] = useState(false);
19
+ const [focusedIndex, setFocusedIndex] = useState(0);
20
+ const visibleSet = new Set(visibleTabValues);
21
+ /**
22
+ * Closes the menu and returns focus to the trigger button.
23
+ */
24
+ const closeMenu = useCallback(() => {
25
+ setIsOpen(false);
26
+ requestAnimationFrame(() => {
27
+ triggerRef.current?.focus();
28
+ });
29
+ }, []);
30
+ /**
31
+ * Opens the menu and focuses the first or last item.
32
+ *
33
+ * @param focusLast - When true, focus the last item instead of the first.
34
+ */
35
+ const openMenu = useCallback((focusLast = false) => {
36
+ if (tabs.length === 0)
37
+ return;
38
+ setFocusedIndex(focusLast ? tabs.length - 1 : 0);
39
+ setIsOpen(true);
40
+ }, [tabs.length]);
41
+ /**
42
+ * Focuses a menu item by index and updates roving tabindex state.
43
+ *
44
+ * @param index - Index of the menu item to focus.
45
+ */
46
+ const focusItem = useCallback((index) => {
47
+ setFocusedIndex(index);
48
+ requestAnimationFrame(() => {
49
+ itemRefs.current[index]?.focus();
50
+ });
51
+ }, []);
52
+ /**
53
+ * Moves focus into the menu after it opens and item refs are mounted.
54
+ */
55
+ useEffect(() => {
56
+ if (isOpen && !wasOpenRef.current) {
57
+ requestAnimationFrame(() => {
58
+ itemRefs.current[focusedIndex]?.focus();
59
+ });
60
+ }
61
+ wasOpenRef.current = isOpen;
62
+ }, [focusedIndex, isOpen]);
63
+ /**
64
+ * Resets item refs when the menu closes.
65
+ */
66
+ useEffect(() => {
67
+ if (!isOpen) {
68
+ itemRefs.current = [];
69
+ setFocusedIndex(0);
70
+ }
71
+ }, [isOpen]);
72
+ /**
73
+ * Closes the menu on outside click or Escape while it is open.
74
+ */
75
+ useEffect(() => {
76
+ if (!isOpen)
77
+ return;
78
+ const handleMouseDown = (e) => {
79
+ if (rootRef.current && !rootRef.current.contains(e.target)) {
80
+ closeMenu();
81
+ }
82
+ };
83
+ const handleKeyDown = (e) => {
84
+ if (e.key === 'Escape') {
85
+ e.preventDefault();
86
+ closeMenu();
87
+ }
88
+ };
89
+ document.addEventListener('mousedown', handleMouseDown);
90
+ document.addEventListener('keydown', handleKeyDown);
91
+ return () => {
92
+ document.removeEventListener('mousedown', handleMouseDown);
93
+ document.removeEventListener('keydown', handleKeyDown);
94
+ };
95
+ }, [closeMenu, isOpen]);
96
+ /**
97
+ * Handles keyboard interaction on the menu trigger when closed.
98
+ *
99
+ * @param event - Keyboard event from the trigger button.
100
+ */
101
+ const handleTriggerKeyDown = (event) => {
102
+ if (isOpen)
103
+ return;
104
+ if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {
105
+ event.preventDefault();
106
+ openMenu(false);
107
+ return;
108
+ }
109
+ if (event.key === 'ArrowUp') {
110
+ event.preventDefault();
111
+ openMenu(true);
112
+ }
113
+ };
114
+ /**
115
+ * Handles keyboard navigation within the open menu.
116
+ *
117
+ * @param event - Keyboard event from the menu container.
118
+ */
119
+ const handleMenuKeyDown = (event) => {
120
+ if (tabs.length === 0)
121
+ return;
122
+ if (event.key === 'Tab') {
123
+ closeMenu();
124
+ return;
125
+ }
126
+ const arrowIndex = resolveTabListKeyAction(event.key, focusedIndex, tabs.length);
127
+ if (arrowIndex !== null) {
128
+ event.preventDefault();
129
+ focusItem(arrowIndex);
130
+ }
131
+ };
132
+ return (_jsxs("div", { ref: rootRef, className: "hc-segmented-tabs-visibility-menu relative shrink-0", children: [_jsx(Button, { innerRef: triggerRef, type: "button", variant: "icon", "aria-label": "Customize visible tabs", "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": isOpen ? menuElementId : undefined, onClick: () => {
133
+ if (isOpen) {
134
+ closeMenu();
135
+ }
136
+ else {
137
+ openMenu(false);
138
+ }
139
+ }, onKeyDown: handleTriggerKeyDown, children: _jsx(FaIcon, { icon: faCaretDown, className: "h-3.5 w-3.5" }) }), isOpen && (_jsx("div", { id: menuElementId, role: "menu", className: "absolute right-0 top-full z-10 mt-0.5 min-w-[140px] rounded-md border border-separator bg-surface py-1 shadow-md app-no-drag", onKeyDown: handleMenuKeyDown, children: tabs.map((tab, index) => {
140
+ const checked = visibleSet.has(tab.value);
141
+ return (_jsx(MenuCheckboxItem, { ref: (element) => {
142
+ itemRefs.current[index] = element;
143
+ }, checked: checked, tabIndex: index === focusedIndex ? 0 : -1, label: tab.label, onSelect: () => onToggle(tab.value) }, tab.value));
144
+ }) }))] }));
145
+ }
146
+ /**
147
+ * Single checkbox-style row in the tab visibility menu.
148
+ */
149
+ function MenuCheckboxItem({ checked, label, tabIndex, onSelect, ref }) {
150
+ return (_jsxs("button", { ref: ref, type: "button", role: "menuitemcheckbox", "aria-checked": checked, tabIndex: tabIndex, className: menuItemClass, onClick: (e) => {
151
+ e.stopPropagation();
152
+ onSelect();
153
+ }, children: [_jsx("span", { className: "inline-flex w-4 shrink-0 justify-center", "aria-hidden": true, children: checked ? _jsx(FaIcon, { icon: faCheck, className: "h-3 w-3" }) : null }), _jsx("span", { className: "min-w-0", children: label })] }));
154
+ }
@@ -19,6 +19,26 @@ interface Props<T extends string> {
19
19
  * @param value - Newly selected tab value.
20
20
  */
21
21
  onChange?: (value: T) => void;
22
+ /**
23
+ * When true, shows a menu for toggling tab visibility.
24
+ */
25
+ editable?: boolean;
26
+ /**
27
+ * Controlled set of tab values shown in the tab strip. Used with
28
+ * `onVisibleTabValuesChange` when tab visibility is persisted.
29
+ */
30
+ visibleTabValues?: T[];
31
+ /**
32
+ * Initial visible tab values for uncontrolled visibility. Defaults to all
33
+ * non-`hidden` tabs. Keep a copy in the parent to reset saved preferences.
34
+ */
35
+ defaultVisibleTabValues?: T[];
36
+ /**
37
+ * Called when the user toggles tab visibility in the edit menu.
38
+ *
39
+ * @param visibleTabValues - Updated visible tab values.
40
+ */
41
+ onVisibleTabValuesChange?: (visibleTabValues: T[]) => void;
22
42
  /**
23
43
  * When true, the group and each tab stretch to full width.
24
44
  */
@@ -41,5 +61,5 @@ interface Props<T extends string> {
41
61
  /**
42
62
  * macOS-style segmented tab control with WAI-ARIA tabs or radiogroup semantics.
43
63
  */
44
- export declare function SegmentedTabs<T extends string>({ tabs, value: valueProp, onChange: onChangeProp, fullWidth, className, pattern, ariaLabel: ariaLabelProp }: Props<T>): JSX.Element;
64
+ export declare function SegmentedTabs<T extends string>({ tabs, value: valueProp, onChange: onChangeProp, editable, visibleTabValues: visibleTabValuesProp, defaultVisibleTabValues, onVisibleTabValuesChange, fullWidth, className, pattern, ariaLabel: ariaLabelProp }: Props<T>): JSX.Element;
45
65
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedTabs/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAiB,MAAM,OAAO,CAAC;AAIhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,UAAU,KAAK,CAAC,CAAC,SAAS,MAAM;IAC9B;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC;IAEV;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAE9B;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAEhC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,EAC9C,IAAI,EACJ,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,YAAY,EACtB,SAAiB,EACjB,SAAS,EACT,OAAgB,EAChB,SAAS,EAAE,aAAa,EACzB,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CA+GxB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedTabs/index.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,GAAG,EAAiB,MAAM,OAAO,CAAC;AAKhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,UAAU,KAAK,CAAC,CAAC,SAAS,MAAM;IAC9B;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC;IAEV;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAE9B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,EAAE,CAAC;IAEvB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,CAAC,EAAE,CAAC;IAE9B;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;IAE3D;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAEhC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,EAC9C,IAAI,EACJ,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,YAAY,EACtB,QAAe,EACf,gBAAgB,EAAE,oBAAoB,EACtC,uBAAuB,EACvB,wBAAwB,EACxB,SAAiB,EACjB,SAAS,EACT,OAAgB,EAChB,SAAS,EAAE,aAAa,EACzB,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAkNxB"}
@@ -1,14 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
2
- import { useCallback, useContext, useId, useMemo, useRef } from '@harborclient/sdk/react';
2
+ import { useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from '@harborclient/sdk/react';
3
3
  import { resolveTabListKeyAction } from '../utils.js';
4
4
  import { segment, segmentGroup } from '../classes.js';
5
5
  import { SegmentedTabsContext } from './SegmentedTabsContext.js';
6
+ import { SegmentedTabsVisibilityMenu } from './SegmentedTabsVisibilityMenu.js';
6
7
  export { SegmentedTabsGroup } from './SegmentedTabsGroup.js';
7
8
  export { SegmentedTabPanel } from './SegmentedTabPanel.js';
8
9
  /**
9
10
  * macOS-style segmented tab control with WAI-ARIA tabs or radiogroup semantics.
10
11
  */
11
- export function SegmentedTabs({ tabs, value: valueProp, onChange: onChangeProp, fullWidth = false, className, pattern = 'tabs', ariaLabel: ariaLabelProp }) {
12
+ export function SegmentedTabs({ tabs, value: valueProp, onChange: onChangeProp, editable = true, visibleTabValues: visibleTabValuesProp, defaultVisibleTabValues, onVisibleTabValuesChange, fullWidth = false, className, pattern = 'tabs', ariaLabel: ariaLabelProp }) {
12
13
  const context = useContext(SegmentedTabsContext);
13
14
  const standaloneId = useId();
14
15
  const tabRefs = useRef(new Map());
@@ -32,8 +33,80 @@ export function SegmentedTabs({ tabs, value: valueProp, onChange: onChangeProp,
32
33
  const getPanelId = context
33
34
  ? (tabValue) => context.getPanelId(tabValue)
34
35
  : (tabValue) => `${standaloneId}-panel-${tabValue}`;
35
- const visibleTabs = tabs.filter((tab) => !tab.hidden);
36
- const groupClassName = ['hc-segmented-tabs', segmentGroup, fullWidth ? 'w-full' : '', className]
36
+ const editableTabs = useMemo(() => tabs.filter((tab) => !tab.hidden), [tabs]);
37
+ const defaultVisible = useMemo(() => defaultVisibleTabValues ?? editableTabs.map((tab) => tab.value), [defaultVisibleTabValues, editableTabs]);
38
+ const [internalVisibleTabValues, setInternalVisibleTabValues] = useState(defaultVisible);
39
+ const visibleTabValues = visibleTabValuesProp ?? internalVisibleTabValues;
40
+ const visibleSet = useMemo(() => new Set(visibleTabValues), [visibleTabValues]);
41
+ const visibleTabs = editable
42
+ ? editableTabs.filter((tab) => visibleSet.has(tab.value))
43
+ : editableTabs;
44
+ /**
45
+ * Updates visible tab values and notifies the parent when controlled props
46
+ * are used for persistence.
47
+ *
48
+ * @param nextVisibleTabValues - New visible tab values.
49
+ */
50
+ const updateVisibleTabValues = useCallback((nextVisibleTabValues) => {
51
+ if (visibleTabValuesProp === undefined) {
52
+ setInternalVisibleTabValues(nextVisibleTabValues);
53
+ }
54
+ onVisibleTabValuesChange?.(nextVisibleTabValues);
55
+ }, [onVisibleTabValuesChange, visibleTabValuesProp]);
56
+ /**
57
+ * Toggles a tab's visibility in the edit menu, keeping at least one tab
58
+ * visible and moving selection when the active tab is hidden.
59
+ *
60
+ * @param tabValue - Tab value to show or hide.
61
+ */
62
+ const handleVisibilityToggle = useCallback((tabValue) => {
63
+ const isVisible = visibleSet.has(tabValue);
64
+ if (isVisible && visibleTabs.length <= 1)
65
+ return;
66
+ const nextVisibleSet = new Set(visibleTabValues);
67
+ if (isVisible) {
68
+ nextVisibleSet.delete(tabValue);
69
+ }
70
+ else {
71
+ nextVisibleSet.add(tabValue);
72
+ }
73
+ const nextVisibleTabValues = editableTabs
74
+ .filter((tab) => nextVisibleSet.has(tab.value))
75
+ .map((tab) => tab.value);
76
+ updateVisibleTabValues(nextVisibleTabValues);
77
+ if (isVisible && tabValue === value) {
78
+ const nextSelectedTab = editableTabs.find((tab) => nextVisibleSet.has(tab.value));
79
+ if (nextSelectedTab) {
80
+ onChange(nextSelectedTab.value);
81
+ }
82
+ }
83
+ }, [
84
+ editableTabs,
85
+ onChange,
86
+ updateVisibleTabValues,
87
+ value,
88
+ visibleSet,
89
+ visibleTabValues,
90
+ visibleTabs.length
91
+ ]);
92
+ /**
93
+ * When visibility changes externally, select the first visible tab if the
94
+ * current selection is hidden.
95
+ */
96
+ useEffect(() => {
97
+ if (!editable || visibleSet.has(value))
98
+ return;
99
+ const nextSelectedTab = visibleTabs[0];
100
+ if (nextSelectedTab) {
101
+ onChange(nextSelectedTab.value);
102
+ }
103
+ }, [editable, onChange, value, visibleSet, visibleTabs]);
104
+ const groupClassName = [
105
+ 'hc-segmented-tabs',
106
+ segmentGroup,
107
+ fullWidth ? 'flex-1 min-w-0' : '',
108
+ className
109
+ ]
37
110
  .filter(Boolean)
38
111
  .join(' ');
39
112
  /**
@@ -62,21 +135,21 @@ export function SegmentedTabs({ tabs, value: valueProp, onChange: onChangeProp,
62
135
  });
63
136
  }, [visibleTabs, value, onChange]);
64
137
  const isRadiogroup = pattern === 'radiogroup';
65
- return (_jsx("div", { className: groupClassName, role: isRadiogroup ? 'radiogroup' : 'tablist', "aria-label": ariaLabel, ...(!isRadiogroup ? { 'aria-orientation': 'horizontal' } : {}), onKeyDown: handleKeyDown, children: visibleTabs.map((tab) => {
66
- const selected = value === tab.value;
67
- const tabClassName = `${segment(selected)}${fullWidth ? ' flex-1' : ''}`;
68
- return (_jsx("button", { ref: (element) => {
69
- if (element)
70
- tabRefs.current.set(tab.value, element);
71
- else
72
- tabRefs.current.delete(tab.value);
73
- }, type: "button", className: tabClassName, disabled: tab.disabled, tabIndex: selected ? 0 : -1, onClick: () => onChange(tab.value), ...(isRadiogroup
74
- ? { role: 'radio', 'aria-checked': selected }
75
- : {
76
- role: 'tab',
77
- id: getTabId(tab.value),
78
- 'aria-selected': selected,
79
- ...(context ? { 'aria-controls': getPanelId(tab.value) } : {})
80
- }), children: _jsxs("span", { className: "inline-flex items-center gap-1.5", children: [tab.label, tab.indicator && _jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-accent", "aria-hidden": true })] }) }, tab.value));
81
- }) }));
138
+ return (_jsxs("div", { className: "hc-segmented-tabs-row flex w-full items-center gap-1", children: [_jsx("div", { className: groupClassName, role: isRadiogroup ? 'radiogroup' : 'tablist', "aria-label": ariaLabel, ...(!isRadiogroup ? { 'aria-orientation': 'horizontal' } : {}), onKeyDown: handleKeyDown, children: visibleTabs.map((tab) => {
139
+ const selected = value === tab.value;
140
+ const tabClassName = `${segment(selected)}${fullWidth ? ' flex-1' : ''}`;
141
+ return (_jsx("button", { ref: (element) => {
142
+ if (element)
143
+ tabRefs.current.set(tab.value, element);
144
+ else
145
+ tabRefs.current.delete(tab.value);
146
+ }, type: "button", className: tabClassName, disabled: tab.disabled, tabIndex: selected ? 0 : -1, onClick: () => onChange(tab.value), ...(isRadiogroup
147
+ ? { role: 'radio', 'aria-checked': selected }
148
+ : {
149
+ role: 'tab',
150
+ id: getTabId(tab.value),
151
+ 'aria-selected': selected,
152
+ ...(context ? { 'aria-controls': getPanelId(tab.value) } : {})
153
+ }), children: _jsxs("span", { className: "inline-flex items-center gap-1.5", children: [tab.label, tab.indicator && (_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-accent", "aria-hidden": true }))] }) }, tab.value));
154
+ }) }), editable && (_jsx(SegmentedTabsVisibilityMenu, { tabs: editableTabs, visibleTabValues: visibleTabValues, onToggle: handleVisibilityToggle }))] }));
82
155
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
2
- import { radioCircle, radioInput } from './classes.js';
2
+ import { radioCircle, radioDot, radioInput } from './classes.js';
3
3
  /**
4
4
  * macOS-style radio button with a custom circle slightly larger than the native control.
5
5
  */
@@ -7,5 +7,5 @@ export function Radio({ ref, className, ...props }) {
7
7
  const wrapperClasses = className
8
8
  ? `hc-radio relative inline-flex h-[18px] w-[18px] shrink-0 leading-none ${className}`
9
9
  : 'hc-radio relative inline-flex h-[18px] w-[18px] shrink-0 leading-none';
10
- return (_jsxs("span", { className: wrapperClasses, children: [_jsx("input", { ref: ref, type: "radio", className: radioInput, ...props }), _jsx("span", { className: radioCircle, "aria-hidden": true, children: _jsx("span", { className: "h-2 w-2 rounded-full bg-accent" }) })] }));
10
+ return (_jsxs("span", { className: wrapperClasses, children: [_jsx("input", { ref: ref, type: "radio", className: radioInput, ...props }), _jsx("span", { className: radioCircle, "aria-hidden": true, children: _jsx("span", { className: radioDot }) })] }));
11
11
  }
@@ -20,7 +20,9 @@ export declare const checkboxBox = "pointer-events-none flex h-[18px] w-[18px] s
20
20
  /** Transparent overlay radio input sized to {@link radioCircle}. */
21
21
  export declare const radioInput = "peer absolute inset-0 m-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed";
22
22
  /** Custom radio circle styled via `peer-checked` / `peer-focus-visible` on {@link radioInput}. */
23
- export declare const radioCircle = "pointer-events-none flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-full border border-separator bg-field peer-checked:border-accent peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-accent peer-disabled:cursor-not-allowed peer-disabled:opacity-50 [&>span]:opacity-0 peer-checked:[&>span]:opacity-100";
23
+ export declare const radioCircle = "pointer-events-none relative h-[18px] w-[18px] shrink-0 leading-none rounded-full border border-separator bg-field peer-checked:border-accent peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-accent peer-disabled:cursor-not-allowed peer-disabled:opacity-50 [&>span]:opacity-0 peer-checked:[&>span]:opacity-100";
24
+ /** Checked-state dot centered inside {@link radioCircle}. */
25
+ export declare const radioDot = "absolute left-1/2 top-1/2 block h-2 w-2 -translate-x-1/2 -translate-y-1/2 shrink-0 rounded-full bg-accent";
24
26
  /**
25
27
  * Merges a field variant preset with optional caller classes.
26
28
  *
@@ -1 +1 @@
1
- {"version":3,"file":"classes.d.ts","sourceRoot":"","sources":["../../../src/components/forms/classes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE3D;;GAEG;AACH,eAAO,MAAM,UAAU,gEAAgE,CAAC;AAExF,mEAAmE;AACnE,eAAO,MAAM,KAAK,gGAC6E,CAAC;AAEhG,wDAAwD;AACxD,eAAO,MAAM,YAAY,yFAC+D,CAAC;AAEzF,uEAAuE;AACvE,eAAO,MAAM,aAAa,iGACsE,CAAC;AAEjG,qGAAqG;AACrG,eAAO,MAAM,WAAW,qaAC4Y,CAAC;AAEra,oEAAoE;AACpE,eAAO,MAAM,UAAU,iGACyE,CAAC;AAEjG,kGAAkG;AAClG,eAAO,MAAM,WAAW,0YACiX,CAAC;AAO1Y;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,YAAY,EACrB,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,GAAG,SAAS,CAOpB"}
1
+ {"version":3,"file":"classes.d.ts","sourceRoot":"","sources":["../../../src/components/forms/classes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE3D;;GAEG;AACH,eAAO,MAAM,UAAU,gEAAgE,CAAC;AAExF,mEAAmE;AACnE,eAAO,MAAM,KAAK,gGAC6E,CAAC;AAEhG,wDAAwD;AACxD,eAAO,MAAM,YAAY,yFAC+D,CAAC;AAEzF,uEAAuE;AACvE,eAAO,MAAM,aAAa,iGACsE,CAAC;AAEjG,qGAAqG;AACrG,eAAO,MAAM,WAAW,qaAC4Y,CAAC;AAEra,oEAAoE;AACpE,eAAO,MAAM,UAAU,iGACyE,CAAC;AAEjG,kGAAkG;AAClG,eAAO,MAAM,WAAW,+XACsW,CAAC;AAE/X,6DAA6D;AAC7D,eAAO,MAAM,QAAQ,8GACwF,CAAC;AAO9G;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,YAAY,EACrB,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,GAAG,SAAS,CAOpB"}
@@ -16,7 +16,9 @@ export const checkboxBox = 'pointer-events-none flex h-[18px] w-[18px] shrink-0
16
16
  /** Transparent overlay radio input sized to {@link radioCircle}. */
17
17
  export const radioInput = 'peer absolute inset-0 m-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed';
18
18
  /** Custom radio circle styled via `peer-checked` / `peer-focus-visible` on {@link radioInput}. */
19
- export const radioCircle = 'pointer-events-none flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-full border border-separator bg-field peer-checked:border-accent peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-accent peer-disabled:cursor-not-allowed peer-disabled:opacity-50 [&>span]:opacity-0 peer-checked:[&>span]:opacity-100';
19
+ export const radioCircle = 'pointer-events-none relative h-[18px] w-[18px] shrink-0 leading-none rounded-full border border-separator bg-field peer-checked:border-accent peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-accent peer-disabled:cursor-not-allowed peer-disabled:opacity-50 [&>span]:opacity-0 peer-checked:[&>span]:opacity-100';
20
+ /** Checked-state dot centered inside {@link radioCircle}. */
21
+ export const radioDot = 'absolute left-1/2 top-1/2 block h-2 w-2 -translate-x-1/2 -translate-y-1/2 shrink-0 rounded-full bg-accent';
20
22
  const VARIANT_CLASSES = {
21
23
  control: field,
22
24
  surface: surfaceField
@@ -1,4 +1,4 @@
1
- export { field, fieldFrame, surfaceField, checkboxInput, checkboxBox, radioInput, radioCircle, mergeFieldClasses, type FieldVariant } from './classes.js';
1
+ export { field, fieldFrame, surfaceField, checkboxInput, checkboxBox, radioInput, radioCircle, radioDot, mergeFieldClasses, type FieldVariant } from './classes.js';
2
2
  export { Checkbox } from './Checkbox.js';
3
3
  export { Radio } from './Radio.js';
4
4
  export { Input } from './Input.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/forms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,UAAU,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/forms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,UAAU,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,QAAQ,EACR,iBAAiB,EACjB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
@@ -1,4 +1,4 @@
1
- export { field, fieldFrame, surfaceField, checkboxInput, checkboxBox, radioInput, radioCircle, mergeFieldClasses } from './classes.js';
1
+ export { field, fieldFrame, surfaceField, checkboxInput, checkboxBox, radioInput, radioCircle, radioDot, mergeFieldClasses } from './classes.js';
2
2
  export { Checkbox } from './Checkbox.js';
3
3
  export { Radio } from './Radio.js';
4
4
  export { Input } from './Input.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harborclient/sdk",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "TypeScript SDK for HarborClient plugin development.",
5
5
  "keywords": [
6
6
  "harborclient",