@cdx-ui/primitives 0.1.5 → 0.1.6
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/lib/commonjs/index.js +12 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/toggle-button/ToggleButtonGroup.js +185 -0
- package/lib/commonjs/toggle-button/ToggleButtonGroup.js.map +1 -0
- package/lib/commonjs/toggle-button/ToggleButtonRoot.js +131 -0
- package/lib/commonjs/toggle-button/ToggleButtonRoot.js.map +1 -0
- package/lib/commonjs/toggle-button/ToggleButtonRoot.web.js +180 -0
- package/lib/commonjs/toggle-button/ToggleButtonRoot.web.js.map +1 -0
- package/lib/commonjs/toggle-button/context.js +27 -0
- package/lib/commonjs/toggle-button/context.js.map +1 -0
- package/lib/commonjs/toggle-button/index.js +45 -0
- package/lib/commonjs/toggle-button/index.js.map +1 -0
- package/lib/commonjs/toggle-button/toggleState.js +23 -0
- package/lib/commonjs/toggle-button/toggleState.js.map +1 -0
- package/lib/commonjs/toggle-button/types.js +6 -0
- package/lib/commonjs/toggle-button/types.js.map +1 -0
- package/lib/commonjs/toggle-button/useToggleButtonGroupNavigation.js +80 -0
- package/lib/commonjs/toggle-button/useToggleButtonGroupNavigation.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/toggle-button/ToggleButtonGroup.js +181 -0
- package/lib/module/toggle-button/ToggleButtonGroup.js.map +1 -0
- package/lib/module/toggle-button/ToggleButtonRoot.js +127 -0
- package/lib/module/toggle-button/ToggleButtonRoot.js.map +1 -0
- package/lib/module/toggle-button/ToggleButtonRoot.web.js +176 -0
- package/lib/module/toggle-button/ToggleButtonRoot.web.js.map +1 -0
- package/lib/module/toggle-button/context.js +18 -0
- package/lib/module/toggle-button/context.js.map +1 -0
- package/lib/module/toggle-button/index.js +6 -0
- package/lib/module/toggle-button/index.js.map +1 -0
- package/lib/module/toggle-button/toggleState.js +19 -0
- package/lib/module/toggle-button/toggleState.js.map +1 -0
- package/lib/module/toggle-button/types.js +4 -0
- package/lib/module/toggle-button/types.js.map +1 -0
- package/lib/module/toggle-button/useToggleButtonGroupNavigation.js +75 -0
- package/lib/module/toggle-button/useToggleButtonGroupNavigation.js.map +1 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/toggle-button/ToggleButtonGroup.d.ts +5 -0
- package/lib/typescript/toggle-button/ToggleButtonGroup.d.ts.map +1 -0
- package/lib/typescript/toggle-button/ToggleButtonRoot.d.ts +5 -0
- package/lib/typescript/toggle-button/ToggleButtonRoot.d.ts.map +1 -0
- package/lib/typescript/toggle-button/ToggleButtonRoot.web.d.ts +5 -0
- package/lib/typescript/toggle-button/ToggleButtonRoot.web.d.ts.map +1 -0
- package/lib/typescript/toggle-button/context.d.ts +27 -0
- package/lib/typescript/toggle-button/context.d.ts.map +1 -0
- package/lib/typescript/toggle-button/index.d.ts +5 -0
- package/lib/typescript/toggle-button/index.d.ts.map +1 -0
- package/lib/typescript/toggle-button/toggleState.d.ts +14 -0
- package/lib/typescript/toggle-button/toggleState.d.ts.map +1 -0
- package/lib/typescript/toggle-button/types.d.ts +87 -0
- package/lib/typescript/toggle-button/types.d.ts.map +1 -0
- package/lib/typescript/toggle-button/useToggleButtonGroupNavigation.d.ts +19 -0
- package/lib/typescript/toggle-button/useToggleButtonGroupNavigation.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/toggle-button/ToggleButtonGroup.tsx +260 -0
- package/src/toggle-button/ToggleButtonRoot.tsx +151 -0
- package/src/toggle-button/ToggleButtonRoot.web.tsx +225 -0
- package/src/toggle-button/context.tsx +30 -0
- package/src/toggle-button/index.ts +16 -0
- package/src/toggle-button/toggleState.ts +19 -0
- package/src/toggle-button/types.ts +94 -0
- package/src/toggle-button/useToggleButtonGroupNavigation.ts +112 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { PressableProps, ViewProps } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type ToggleButtonGroupType = 'single' | 'multiple';
|
|
4
|
+
|
|
5
|
+
export type ToggleButtonGroupValue = string | string[] | undefined;
|
|
6
|
+
|
|
7
|
+
interface IToggleButtonGroupBaseProps extends ViewProps {
|
|
8
|
+
/** Merge group props onto a single child instead of rendering the default View. */
|
|
9
|
+
asChild?: boolean;
|
|
10
|
+
/** Disables every item in the group. */
|
|
11
|
+
isDisabled?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Layout direction and arrow-key axis.
|
|
14
|
+
* @default 'horizontal'
|
|
15
|
+
*/
|
|
16
|
+
orientation?: 'horizontal' | 'vertical';
|
|
17
|
+
/** Accessible name for the group (required when no visible label). */
|
|
18
|
+
'aria-label'?: string;
|
|
19
|
+
'aria-labelledby'?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IToggleButtonGroupSingleProps extends IToggleButtonGroupBaseProps {
|
|
23
|
+
/**
|
|
24
|
+
* Single-select (radio semantics). `value` is the selected item's `value`.
|
|
25
|
+
* @default 'single'
|
|
26
|
+
*/
|
|
27
|
+
type?: 'single';
|
|
28
|
+
/** Currently selected item `value` (controlled). */
|
|
29
|
+
value?: string;
|
|
30
|
+
/** Initially selected item `value` (uncontrolled). */
|
|
31
|
+
defaultValue?: string;
|
|
32
|
+
onValueChange?: (value: string) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface IToggleButtonGroupMultipleProps extends IToggleButtonGroupBaseProps {
|
|
36
|
+
/**
|
|
37
|
+
* Multi-select (pressed-button semantics). `value` is an array of selected item `value`s.
|
|
38
|
+
*/
|
|
39
|
+
type: 'multiple';
|
|
40
|
+
/** Currently selected item `value`s (controlled). */
|
|
41
|
+
value?: string[];
|
|
42
|
+
/** Initially selected item `value`s (uncontrolled). */
|
|
43
|
+
defaultValue?: string[];
|
|
44
|
+
onValueChange?: (value: string[]) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Discriminated union: `type` narrows `value` / `defaultValue` / `onValueChange`.
|
|
49
|
+
*/
|
|
50
|
+
export type IToggleButtonGroupProps =
|
|
51
|
+
| IToggleButtonGroupSingleProps
|
|
52
|
+
| IToggleButtonGroupMultipleProps;
|
|
53
|
+
|
|
54
|
+
export interface RegisteredToggleButton {
|
|
55
|
+
disabled: boolean;
|
|
56
|
+
node: { focus?: () => void } | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface IToggleButtonGroupContextValue {
|
|
60
|
+
type: ToggleButtonGroupType;
|
|
61
|
+
orientation: 'horizontal' | 'vertical';
|
|
62
|
+
value: ToggleButtonGroupValue;
|
|
63
|
+
toggleValue: (buttonValue: string) => void;
|
|
64
|
+
isSelected: (buttonValue: string) => boolean;
|
|
65
|
+
isGroupDisabled: boolean;
|
|
66
|
+
focusedValue?: string | undefined;
|
|
67
|
+
setFocusedValue?: (value: string | undefined) => void;
|
|
68
|
+
registerItem?: (value: string, entry: RegisteredToggleButton) => void;
|
|
69
|
+
unregisterItem?: (value: string) => void;
|
|
70
|
+
getItems?: () => [string, RegisteredToggleButton][];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface IToggleButtonProps extends PressableProps {
|
|
74
|
+
/** Render the consumer's child in place of the default `Pressable` host. */
|
|
75
|
+
asChild?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Identifies this button within a `ToggleButtonGroup`. Must be unique within the group.
|
|
78
|
+
* Standalone buttons still require a value.
|
|
79
|
+
*/
|
|
80
|
+
value: string;
|
|
81
|
+
/**
|
|
82
|
+
* Controlled selected state (standalone only; ignored inside `ToggleButtonGroup`).
|
|
83
|
+
*/
|
|
84
|
+
isSelected?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Uncontrolled initial selected state (standalone only).
|
|
87
|
+
* @default false
|
|
88
|
+
*/
|
|
89
|
+
defaultSelected?: boolean;
|
|
90
|
+
/** Called when standalone selected state changes. */
|
|
91
|
+
onSelectedChange?: (isSelected: boolean) => void;
|
|
92
|
+
/** @default false */
|
|
93
|
+
isDisabled?: boolean;
|
|
94
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
import type { RegisteredToggleButton, ToggleButtonGroupType } from './types';
|
|
3
|
+
|
|
4
|
+
function isEnabledItemValue(
|
|
5
|
+
value: string | null | undefined,
|
|
6
|
+
enabledItems: [string, RegisteredToggleButton][],
|
|
7
|
+
): value is string {
|
|
8
|
+
return value != null && enabledItems.some(([itemValue]) => itemValue === value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Resolves which item value should hold the roving tab stop / keyboard focus anchor. */
|
|
12
|
+
export function getRovingFocusValue(
|
|
13
|
+
focusedValue: string | undefined,
|
|
14
|
+
anchorValue: string | null | undefined,
|
|
15
|
+
enabledItems: [string, RegisteredToggleButton][],
|
|
16
|
+
): string | undefined {
|
|
17
|
+
const defaultFocusValue = isEnabledItemValue(anchorValue, enabledItems)
|
|
18
|
+
? anchorValue
|
|
19
|
+
: enabledItems[0]?.[0];
|
|
20
|
+
|
|
21
|
+
const resolvedFocusedValue = isEnabledItemValue(focusedValue, enabledItems)
|
|
22
|
+
? focusedValue
|
|
23
|
+
: undefined;
|
|
24
|
+
|
|
25
|
+
return resolvedFocusedValue ?? defaultFocusValue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UseToggleButtonGroupNavigationOptions {
|
|
29
|
+
type: ToggleButtonGroupType;
|
|
30
|
+
orientation: 'horizontal' | 'vertical';
|
|
31
|
+
getItems: () => [string, RegisteredToggleButton][];
|
|
32
|
+
focusedValue: string | undefined;
|
|
33
|
+
setFocusedValue: (value: string | undefined) => void;
|
|
34
|
+
anchorValue: string | null | undefined;
|
|
35
|
+
selectOnFocus?: (value: string) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useToggleButtonGroupNavigation({
|
|
39
|
+
type,
|
|
40
|
+
orientation,
|
|
41
|
+
getItems,
|
|
42
|
+
focusedValue,
|
|
43
|
+
setFocusedValue,
|
|
44
|
+
anchorValue,
|
|
45
|
+
selectOnFocus,
|
|
46
|
+
}: UseToggleButtonGroupNavigationOptions) {
|
|
47
|
+
const selectOnFocusRef = useRef(selectOnFocus);
|
|
48
|
+
selectOnFocusRef.current = selectOnFocus;
|
|
49
|
+
|
|
50
|
+
const getEnabledItems = useCallback(
|
|
51
|
+
() => getItems().filter(([, entry]) => !entry.disabled),
|
|
52
|
+
[getItems],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const moveFocus = useCallback(
|
|
56
|
+
([targetValue, targetEntry]: [string, RegisteredToggleButton], shouldSelect: boolean) => {
|
|
57
|
+
setFocusedValue(targetValue);
|
|
58
|
+
targetEntry.node?.focus?.();
|
|
59
|
+
if (shouldSelect) {
|
|
60
|
+
selectOnFocusRef.current?.(targetValue);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[setFocusedValue],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const handleKeyDown = useCallback(
|
|
67
|
+
(e: { key: string; preventDefault: () => void }) => {
|
|
68
|
+
const enabledItems = getEnabledItems();
|
|
69
|
+
if (enabledItems.length === 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const currentValue = getRovingFocusValue(focusedValue, anchorValue, enabledItems);
|
|
74
|
+
const idx = enabledItems.findIndex(([value]) => value === currentValue);
|
|
75
|
+
const shouldSelectOnFocus = type === 'single';
|
|
76
|
+
|
|
77
|
+
const nextKeys = orientation === 'vertical' ? ['ArrowDown'] : ['ArrowRight', 'ArrowDown'];
|
|
78
|
+
const prevKeys = orientation === 'vertical' ? ['ArrowUp'] : ['ArrowLeft', 'ArrowUp'];
|
|
79
|
+
|
|
80
|
+
if (nextKeys.includes(e.key)) {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
moveFocus(enabledItems[idx < enabledItems.length - 1 ? idx + 1 : 0], shouldSelectOnFocus);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (prevKeys.includes(e.key)) {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
moveFocus(enabledItems[idx > 0 ? idx - 1 : enabledItems.length - 1], shouldSelectOnFocus);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
switch (e.key) {
|
|
93
|
+
case 'Home': {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
moveFocus(enabledItems[0], shouldSelectOnFocus);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'End': {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
const last = enabledItems.at(-1);
|
|
101
|
+
if (last) {
|
|
102
|
+
moveFocus(last, shouldSelectOnFocus);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
[anchorValue, focusedValue, getEnabledItems, moveFocus, orientation, type],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return { handleKeyDown };
|
|
112
|
+
}
|