@lattice-ui/toggle-group 0.1.1
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/out/ToggleGroup/ToggleGroupItem.d.ts +3 -0
- package/out/ToggleGroup/ToggleGroupItem.luau +66 -0
- package/out/ToggleGroup/ToggleGroupRoot.d.ts +3 -0
- package/out/ToggleGroup/ToggleGroupRoot.luau +141 -0
- package/out/ToggleGroup/context.d.ts +3 -0
- package/out/ToggleGroup/context.luau +10 -0
- package/out/ToggleGroup/types.d.ts +38 -0
- package/out/ToggleGroup/types.luau +2 -0
- package/out/index.d.ts +3 -0
- package/out/init.luau +8 -0
- package/package.json +24 -0
- package/src/ToggleGroup/ToggleGroupItem.tsx +76 -0
- package/src/ToggleGroup/ToggleGroupRoot.tsx +121 -0
- package/src/ToggleGroup/context.ts +6 -0
- package/src/ToggleGroup/types.ts +45 -0
- package/src/index.ts +13 -0
- package/tsconfig.json +16 -0
- package/tsconfig.typecheck.json +25 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
|
|
4
|
+
local React = _core.React
|
|
5
|
+
local Slot = _core.Slot
|
|
6
|
+
local RovingFocusItem = TS.import(script, TS.getModule(script, "@lattice-ui", "focus").out).RovingFocusItem
|
|
7
|
+
local useToggleGroupContext = TS.import(script, script.Parent, "context").useToggleGroupContext
|
|
8
|
+
local function ToggleGroupItem(props)
|
|
9
|
+
local toggleGroupContext = useToggleGroupContext()
|
|
10
|
+
local disabled = toggleGroupContext.disabled or props.disabled == true
|
|
11
|
+
local pressed = toggleGroupContext.isPressed(props.value)
|
|
12
|
+
local handleToggle = React.useCallback(function()
|
|
13
|
+
if disabled then
|
|
14
|
+
return nil
|
|
15
|
+
end
|
|
16
|
+
toggleGroupContext.toggleValue(props.value)
|
|
17
|
+
end, { disabled, props.value, toggleGroupContext })
|
|
18
|
+
local handleInputBegan = React.useCallback(function(_rbx, inputObject)
|
|
19
|
+
if disabled then
|
|
20
|
+
return nil
|
|
21
|
+
end
|
|
22
|
+
local keyCode = inputObject.KeyCode
|
|
23
|
+
if keyCode ~= Enum.KeyCode.Return and keyCode ~= Enum.KeyCode.Space then
|
|
24
|
+
return nil
|
|
25
|
+
end
|
|
26
|
+
toggleGroupContext.toggleValue(props.value)
|
|
27
|
+
end, { disabled, props.value, toggleGroupContext })
|
|
28
|
+
local eventHandlers = React.useMemo(function()
|
|
29
|
+
return {
|
|
30
|
+
Activated = handleToggle,
|
|
31
|
+
InputBegan = handleInputBegan,
|
|
32
|
+
}
|
|
33
|
+
end, { handleInputBegan, handleToggle })
|
|
34
|
+
if props.asChild then
|
|
35
|
+
local child = props.children
|
|
36
|
+
if not child then
|
|
37
|
+
error("[ToggleGroupItem] `asChild` requires a child element.")
|
|
38
|
+
end
|
|
39
|
+
return React.createElement(RovingFocusItem, {
|
|
40
|
+
asChild = true,
|
|
41
|
+
disabled = disabled,
|
|
42
|
+
}, React.createElement(Slot, {
|
|
43
|
+
Active = not disabled,
|
|
44
|
+
Event = eventHandlers,
|
|
45
|
+
Selectable = not disabled,
|
|
46
|
+
}, child))
|
|
47
|
+
end
|
|
48
|
+
return React.createElement(RovingFocusItem, {
|
|
49
|
+
asChild = true,
|
|
50
|
+
disabled = disabled,
|
|
51
|
+
}, React.createElement("textbutton", {
|
|
52
|
+
Active = not disabled,
|
|
53
|
+
AutoButtonColor = false,
|
|
54
|
+
BackgroundColor3 = if pressed then Color3.fromRGB(88, 142, 255) else Color3.fromRGB(47, 53, 68),
|
|
55
|
+
BorderSizePixel = 0,
|
|
56
|
+
Event = eventHandlers,
|
|
57
|
+
Selectable = not disabled,
|
|
58
|
+
Size = UDim2.fromOffset(170, 34),
|
|
59
|
+
Text = props.value,
|
|
60
|
+
TextColor3 = if disabled then Color3.fromRGB(139, 146, 160) else Color3.fromRGB(236, 241, 249),
|
|
61
|
+
TextSize = 15,
|
|
62
|
+
}, props.children))
|
|
63
|
+
end
|
|
64
|
+
return {
|
|
65
|
+
ToggleGroupItem = ToggleGroupItem,
|
|
66
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
|
|
4
|
+
local React = _core.React
|
|
5
|
+
local Slot = _core.Slot
|
|
6
|
+
local useControllableState = _core.useControllableState
|
|
7
|
+
local RovingFocusGroup = TS.import(script, TS.getModule(script, "@lattice-ui", "focus").out).RovingFocusGroup
|
|
8
|
+
local ToggleGroupContextProvider = TS.import(script, script.Parent, "context").ToggleGroupContextProvider
|
|
9
|
+
local function normalizeMultiple(value)
|
|
10
|
+
local _value = value
|
|
11
|
+
if not (type(_value) == "table") then
|
|
12
|
+
return {}
|
|
13
|
+
end
|
|
14
|
+
local nextValues = {}
|
|
15
|
+
local seenValues = {}
|
|
16
|
+
for _, entry in value do
|
|
17
|
+
if not (type(entry) == "string") then
|
|
18
|
+
continue
|
|
19
|
+
end
|
|
20
|
+
if seenValues[entry] then
|
|
21
|
+
continue
|
|
22
|
+
end
|
|
23
|
+
seenValues[entry] = true
|
|
24
|
+
table.insert(nextValues, entry)
|
|
25
|
+
end
|
|
26
|
+
return nextValues
|
|
27
|
+
end
|
|
28
|
+
local function ToggleGroupRoot(props)
|
|
29
|
+
local disabled = props.disabled == true
|
|
30
|
+
local _condition = props.loop
|
|
31
|
+
if _condition == nil then
|
|
32
|
+
_condition = true
|
|
33
|
+
end
|
|
34
|
+
local loop = _condition
|
|
35
|
+
local orientation = props.orientation or "horizontal"
|
|
36
|
+
local _binding = useControllableState({
|
|
37
|
+
value = if props.type == "single" then props.value else nil,
|
|
38
|
+
defaultValue = if props.type == "single" then props.defaultValue else nil,
|
|
39
|
+
onChange = function(nextValue)
|
|
40
|
+
if props.type == "single" then
|
|
41
|
+
local _result = props.onValueChange
|
|
42
|
+
if _result ~= nil then
|
|
43
|
+
_result(nextValue)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end,
|
|
47
|
+
})
|
|
48
|
+
local singleValue = _binding[1]
|
|
49
|
+
local setSingleValueState = _binding[2]
|
|
50
|
+
local _binding_1 = useControllableState({
|
|
51
|
+
value = if props.type == "multiple" then (if props.value ~= nil then normalizeMultiple(props.value) else nil) else nil,
|
|
52
|
+
defaultValue = if props.type == "multiple" then normalizeMultiple(props.defaultValue or {}) else {},
|
|
53
|
+
onChange = function(nextValue)
|
|
54
|
+
if props.type == "multiple" then
|
|
55
|
+
local _result = props.onValueChange
|
|
56
|
+
if _result ~= nil then
|
|
57
|
+
_result(normalizeMultiple(nextValue))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end,
|
|
61
|
+
})
|
|
62
|
+
local multipleValue = _binding_1[1]
|
|
63
|
+
local setMultipleValueState = _binding_1[2]
|
|
64
|
+
local isPressed = React.useCallback(function(itemValue)
|
|
65
|
+
if props.type == "single" then
|
|
66
|
+
return singleValue == itemValue
|
|
67
|
+
end
|
|
68
|
+
local _itemValue = itemValue
|
|
69
|
+
return table.find(multipleValue, _itemValue) ~= nil
|
|
70
|
+
end, { multipleValue, props.type, singleValue })
|
|
71
|
+
local toggleValue = React.useCallback(function(itemValue)
|
|
72
|
+
if disabled then
|
|
73
|
+
return nil
|
|
74
|
+
end
|
|
75
|
+
if props.type == "single" then
|
|
76
|
+
setSingleValueState(if singleValue == itemValue then nil else itemValue)
|
|
77
|
+
return nil
|
|
78
|
+
end
|
|
79
|
+
local currentValues = normalizeMultiple(multipleValue)
|
|
80
|
+
local _itemValue = itemValue
|
|
81
|
+
local _result
|
|
82
|
+
if table.find(currentValues, _itemValue) ~= nil then
|
|
83
|
+
-- ▼ ReadonlyArray.filter ▼
|
|
84
|
+
local _newValue = {}
|
|
85
|
+
local _callback = function(value)
|
|
86
|
+
return value ~= itemValue
|
|
87
|
+
end
|
|
88
|
+
local _length = 0
|
|
89
|
+
for _k, _v in currentValues do
|
|
90
|
+
if _callback(_v, _k - 1, currentValues) == true then
|
|
91
|
+
_length += 1
|
|
92
|
+
_newValue[_length] = _v
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
-- ▲ ReadonlyArray.filter ▲
|
|
96
|
+
_result = _newValue
|
|
97
|
+
else
|
|
98
|
+
local _array = {}
|
|
99
|
+
local _length = #_array
|
|
100
|
+
local _currentValuesLength = #currentValues
|
|
101
|
+
table.move(currentValues, 1, _currentValuesLength, _length + 1, _array)
|
|
102
|
+
_length += _currentValuesLength
|
|
103
|
+
_array[_length + 1] = itemValue
|
|
104
|
+
_result = _array
|
|
105
|
+
end
|
|
106
|
+
local nextValues = _result
|
|
107
|
+
setMultipleValueState(nextValues)
|
|
108
|
+
end, { disabled, multipleValue, props.type, setMultipleValueState, setSingleValueState, singleValue })
|
|
109
|
+
local contextValue = React.useMemo(function()
|
|
110
|
+
return {
|
|
111
|
+
type = props.type,
|
|
112
|
+
disabled = disabled,
|
|
113
|
+
orientation = orientation,
|
|
114
|
+
loop = loop,
|
|
115
|
+
isPressed = isPressed,
|
|
116
|
+
toggleValue = toggleValue,
|
|
117
|
+
}
|
|
118
|
+
end, { disabled, isPressed, loop, orientation, props.type, toggleValue })
|
|
119
|
+
local groupNode = if props.asChild then ((function()
|
|
120
|
+
local child = props.children
|
|
121
|
+
if not React.isValidElement(child) then
|
|
122
|
+
error("[ToggleGroup] `asChild` requires a child element.")
|
|
123
|
+
end
|
|
124
|
+
return React.createElement(Slot, nil, child)
|
|
125
|
+
end)()) else (React.createElement("frame", {
|
|
126
|
+
BackgroundTransparency = 1,
|
|
127
|
+
BorderSizePixel = 0,
|
|
128
|
+
Size = UDim2.fromOffset(0, 0),
|
|
129
|
+
}, props.children))
|
|
130
|
+
return React.createElement(ToggleGroupContextProvider, {
|
|
131
|
+
value = contextValue,
|
|
132
|
+
}, React.createElement(RovingFocusGroup, {
|
|
133
|
+
active = not disabled,
|
|
134
|
+
autoFocus = "none",
|
|
135
|
+
loop = loop,
|
|
136
|
+
orientation = orientation,
|
|
137
|
+
}, groupNode))
|
|
138
|
+
end
|
|
139
|
+
return {
|
|
140
|
+
ToggleGroupRoot = ToggleGroupRoot,
|
|
141
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ToggleGroupContextValue } from "./types";
|
|
2
|
+
declare const ToggleGroupContextProvider: import("@rbxts/react").Provider<ToggleGroupContextValue | undefined>, useToggleGroupContext: () => ToggleGroupContextValue;
|
|
3
|
+
export { ToggleGroupContextProvider, useToggleGroupContext };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local createStrictContext = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out).createStrictContext
|
|
4
|
+
local _binding = createStrictContext("ToggleGroup")
|
|
5
|
+
local ToggleGroupContextProvider = _binding[1]
|
|
6
|
+
local useToggleGroupContext = _binding[2]
|
|
7
|
+
return {
|
|
8
|
+
ToggleGroupContextProvider = ToggleGroupContextProvider,
|
|
9
|
+
useToggleGroupContext = useToggleGroupContext,
|
|
10
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type React from "@rbxts/react";
|
|
2
|
+
export type ToggleGroupType = "single" | "multiple";
|
|
3
|
+
export type ToggleGroupValue = string | string[];
|
|
4
|
+
export type ToggleGroupOrientation = "horizontal" | "vertical" | "both";
|
|
5
|
+
export type ToggleGroupCommonProps = {
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
loop?: boolean;
|
|
8
|
+
orientation?: ToggleGroupOrientation;
|
|
9
|
+
asChild?: boolean;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
};
|
|
12
|
+
export type ToggleGroupSingleProps = {
|
|
13
|
+
type: "single";
|
|
14
|
+
value?: string;
|
|
15
|
+
defaultValue?: string;
|
|
16
|
+
onValueChange?: (value: string | undefined) => void;
|
|
17
|
+
};
|
|
18
|
+
export type ToggleGroupMultipleProps = {
|
|
19
|
+
type: "multiple";
|
|
20
|
+
value?: string[];
|
|
21
|
+
defaultValue?: string[];
|
|
22
|
+
onValueChange?: (value: string[]) => void;
|
|
23
|
+
};
|
|
24
|
+
export type ToggleGroupProps = ToggleGroupCommonProps & (ToggleGroupSingleProps | ToggleGroupMultipleProps);
|
|
25
|
+
export type ToggleGroupContextValue = {
|
|
26
|
+
type: ToggleGroupType;
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
orientation: ToggleGroupOrientation;
|
|
29
|
+
loop: boolean;
|
|
30
|
+
isPressed: (itemValue: string) => boolean;
|
|
31
|
+
toggleValue: (itemValue: string) => void;
|
|
32
|
+
};
|
|
33
|
+
export type ToggleGroupItemProps = {
|
|
34
|
+
value: string;
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
asChild?: boolean;
|
|
37
|
+
children?: React.ReactElement;
|
|
38
|
+
};
|
package/out/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { ToggleGroupItem } from "./ToggleGroup/ToggleGroupItem";
|
|
2
|
+
export { ToggleGroupRoot, ToggleGroupRoot as ToggleGroup } from "./ToggleGroup/ToggleGroupRoot";
|
|
3
|
+
export type { ToggleGroupCommonProps, ToggleGroupContextValue, ToggleGroupItemProps, ToggleGroupMultipleProps, ToggleGroupOrientation, ToggleGroupProps, ToggleGroupSingleProps, ToggleGroupType, ToggleGroupValue, } from "./ToggleGroup/types";
|
package/out/init.luau
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local exports = {}
|
|
4
|
+
exports.ToggleGroupItem = TS.import(script, script, "ToggleGroup", "ToggleGroupItem").ToggleGroupItem
|
|
5
|
+
local _ToggleGroupRoot = TS.import(script, script, "ToggleGroup", "ToggleGroupRoot")
|
|
6
|
+
exports.ToggleGroupRoot = _ToggleGroupRoot.ToggleGroupRoot
|
|
7
|
+
exports.ToggleGroup = _ToggleGroupRoot.ToggleGroupRoot
|
|
8
|
+
return exports
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lattice-ui/toggle-group",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"main": "out/init.luau",
|
|
6
|
+
"types": "out/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@lattice-ui/core": "0.1.1",
|
|
9
|
+
"@lattice-ui/focus": "0.1.1"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@rbxts/react": "17.3.7-ts.1",
|
|
13
|
+
"@rbxts/react-roblox": "17.3.7-ts.1"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@rbxts/react": "^17",
|
|
17
|
+
"@rbxts/react-roblox": "^17"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "rbxtsc -p tsconfig.json",
|
|
21
|
+
"watch": "rbxtsc -p tsconfig.json -w",
|
|
22
|
+
"typecheck": "tsc -p tsconfig.typecheck.json"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
+
import { RovingFocusItem } from "@lattice-ui/focus";
|
|
3
|
+
import { useToggleGroupContext } from "./context";
|
|
4
|
+
import type { ToggleGroupItemProps } from "./types";
|
|
5
|
+
|
|
6
|
+
export function ToggleGroupItem(props: ToggleGroupItemProps) {
|
|
7
|
+
const toggleGroupContext = useToggleGroupContext();
|
|
8
|
+
const disabled = toggleGroupContext.disabled || props.disabled === true;
|
|
9
|
+
const pressed = toggleGroupContext.isPressed(props.value);
|
|
10
|
+
|
|
11
|
+
const handleToggle = React.useCallback(() => {
|
|
12
|
+
if (disabled) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
toggleGroupContext.toggleValue(props.value);
|
|
17
|
+
}, [disabled, props.value, toggleGroupContext]);
|
|
18
|
+
|
|
19
|
+
const handleInputBegan = React.useCallback(
|
|
20
|
+
(_rbx: TextButton, inputObject: InputObject) => {
|
|
21
|
+
if (disabled) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const keyCode = inputObject.KeyCode;
|
|
26
|
+
if (keyCode !== Enum.KeyCode.Return && keyCode !== Enum.KeyCode.Space) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
toggleGroupContext.toggleValue(props.value);
|
|
31
|
+
},
|
|
32
|
+
[disabled, props.value, toggleGroupContext],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const eventHandlers = React.useMemo(
|
|
36
|
+
() => ({
|
|
37
|
+
Activated: handleToggle,
|
|
38
|
+
InputBegan: handleInputBegan,
|
|
39
|
+
}),
|
|
40
|
+
[handleInputBegan, handleToggle],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (props.asChild) {
|
|
44
|
+
const child = props.children;
|
|
45
|
+
if (!child) {
|
|
46
|
+
error("[ToggleGroupItem] `asChild` requires a child element.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<RovingFocusItem asChild disabled={disabled}>
|
|
51
|
+
<Slot Active={!disabled} Event={eventHandlers} Selectable={!disabled}>
|
|
52
|
+
{child}
|
|
53
|
+
</Slot>
|
|
54
|
+
</RovingFocusItem>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<RovingFocusItem asChild disabled={disabled}>
|
|
60
|
+
<textbutton
|
|
61
|
+
Active={!disabled}
|
|
62
|
+
AutoButtonColor={false}
|
|
63
|
+
BackgroundColor3={pressed ? Color3.fromRGB(88, 142, 255) : Color3.fromRGB(47, 53, 68)}
|
|
64
|
+
BorderSizePixel={0}
|
|
65
|
+
Event={eventHandlers}
|
|
66
|
+
Selectable={!disabled}
|
|
67
|
+
Size={UDim2.fromOffset(170, 34)}
|
|
68
|
+
Text={props.value}
|
|
69
|
+
TextColor3={disabled ? Color3.fromRGB(139, 146, 160) : Color3.fromRGB(236, 241, 249)}
|
|
70
|
+
TextSize={15}
|
|
71
|
+
>
|
|
72
|
+
{props.children}
|
|
73
|
+
</textbutton>
|
|
74
|
+
</RovingFocusItem>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { React, Slot, useControllableState } from "@lattice-ui/core";
|
|
2
|
+
import { RovingFocusGroup } from "@lattice-ui/focus";
|
|
3
|
+
import { ToggleGroupContextProvider } from "./context";
|
|
4
|
+
import type { ToggleGroupProps } from "./types";
|
|
5
|
+
|
|
6
|
+
function normalizeMultiple(value: unknown): Array<string> {
|
|
7
|
+
if (!typeIs(value, "table")) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const nextValues: Array<string> = [];
|
|
12
|
+
const seenValues: Record<string, true> = {};
|
|
13
|
+
|
|
14
|
+
for (const entry of value as Array<unknown>) {
|
|
15
|
+
if (!typeIs(entry, "string")) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (seenValues[entry]) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
seenValues[entry] = true;
|
|
24
|
+
nextValues.push(entry);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return nextValues;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ToggleGroupRoot(props: ToggleGroupProps) {
|
|
31
|
+
const disabled = props.disabled === true;
|
|
32
|
+
const loop = props.loop ?? true;
|
|
33
|
+
const orientation = props.orientation ?? "horizontal";
|
|
34
|
+
|
|
35
|
+
const [singleValue, setSingleValueState] = useControllableState<string | undefined>({
|
|
36
|
+
value: props.type === "single" ? props.value : undefined,
|
|
37
|
+
defaultValue: props.type === "single" ? props.defaultValue : undefined,
|
|
38
|
+
onChange: (nextValue) => {
|
|
39
|
+
if (props.type === "single") {
|
|
40
|
+
props.onValueChange?.(nextValue);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const [multipleValue, setMultipleValueState] = useControllableState<Array<string>>({
|
|
46
|
+
value:
|
|
47
|
+
props.type === "multiple" ? (props.value !== undefined ? normalizeMultiple(props.value) : undefined) : undefined,
|
|
48
|
+
defaultValue: props.type === "multiple" ? normalizeMultiple(props.defaultValue ?? []) : [],
|
|
49
|
+
onChange: (nextValue) => {
|
|
50
|
+
if (props.type === "multiple") {
|
|
51
|
+
props.onValueChange?.(normalizeMultiple(nextValue));
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const isPressed = React.useCallback(
|
|
57
|
+
(itemValue: string) => {
|
|
58
|
+
if (props.type === "single") {
|
|
59
|
+
return singleValue === itemValue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return multipleValue.includes(itemValue);
|
|
63
|
+
},
|
|
64
|
+
[multipleValue, props.type, singleValue],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const toggleValue = React.useCallback(
|
|
68
|
+
(itemValue: string) => {
|
|
69
|
+
if (disabled) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (props.type === "single") {
|
|
74
|
+
setSingleValueState(singleValue === itemValue ? undefined : itemValue);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const currentValues = normalizeMultiple(multipleValue);
|
|
79
|
+
const nextValues = currentValues.includes(itemValue)
|
|
80
|
+
? currentValues.filter((value) => value !== itemValue)
|
|
81
|
+
: [...currentValues, itemValue];
|
|
82
|
+
setMultipleValueState(nextValues);
|
|
83
|
+
},
|
|
84
|
+
[disabled, multipleValue, props.type, setMultipleValueState, setSingleValueState, singleValue],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const contextValue = React.useMemo(
|
|
88
|
+
() => ({
|
|
89
|
+
type: props.type,
|
|
90
|
+
disabled,
|
|
91
|
+
orientation,
|
|
92
|
+
loop,
|
|
93
|
+
isPressed,
|
|
94
|
+
toggleValue,
|
|
95
|
+
}),
|
|
96
|
+
[disabled, isPressed, loop, orientation, props.type, toggleValue],
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const groupNode = props.asChild ? (
|
|
100
|
+
(() => {
|
|
101
|
+
const child = props.children;
|
|
102
|
+
if (!React.isValidElement(child)) {
|
|
103
|
+
error("[ToggleGroup] `asChild` requires a child element.");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return <Slot>{child}</Slot>;
|
|
107
|
+
})()
|
|
108
|
+
) : (
|
|
109
|
+
<frame BackgroundTransparency={1} BorderSizePixel={0} Size={UDim2.fromOffset(0, 0)}>
|
|
110
|
+
{props.children}
|
|
111
|
+
</frame>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<ToggleGroupContextProvider value={contextValue}>
|
|
116
|
+
<RovingFocusGroup active={!disabled} autoFocus="none" loop={loop} orientation={orientation}>
|
|
117
|
+
{groupNode}
|
|
118
|
+
</RovingFocusGroup>
|
|
119
|
+
</ToggleGroupContextProvider>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createStrictContext } from "@lattice-ui/core";
|
|
2
|
+
import type { ToggleGroupContextValue } from "./types";
|
|
3
|
+
|
|
4
|
+
const [ToggleGroupContextProvider, useToggleGroupContext] = createStrictContext<ToggleGroupContextValue>("ToggleGroup");
|
|
5
|
+
|
|
6
|
+
export { ToggleGroupContextProvider, useToggleGroupContext };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type React from "@rbxts/react";
|
|
2
|
+
|
|
3
|
+
export type ToggleGroupType = "single" | "multiple";
|
|
4
|
+
export type ToggleGroupValue = string | string[];
|
|
5
|
+
export type ToggleGroupOrientation = "horizontal" | "vertical" | "both";
|
|
6
|
+
|
|
7
|
+
export type ToggleGroupCommonProps = {
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
loop?: boolean;
|
|
10
|
+
orientation?: ToggleGroupOrientation;
|
|
11
|
+
asChild?: boolean;
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ToggleGroupSingleProps = {
|
|
16
|
+
type: "single";
|
|
17
|
+
value?: string;
|
|
18
|
+
defaultValue?: string;
|
|
19
|
+
onValueChange?: (value: string | undefined) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type ToggleGroupMultipleProps = {
|
|
23
|
+
type: "multiple";
|
|
24
|
+
value?: string[];
|
|
25
|
+
defaultValue?: string[];
|
|
26
|
+
onValueChange?: (value: string[]) => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type ToggleGroupProps = ToggleGroupCommonProps & (ToggleGroupSingleProps | ToggleGroupMultipleProps);
|
|
30
|
+
|
|
31
|
+
export type ToggleGroupContextValue = {
|
|
32
|
+
type: ToggleGroupType;
|
|
33
|
+
disabled: boolean;
|
|
34
|
+
orientation: ToggleGroupOrientation;
|
|
35
|
+
loop: boolean;
|
|
36
|
+
isPressed: (itemValue: string) => boolean;
|
|
37
|
+
toggleValue: (itemValue: string) => void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type ToggleGroupItemProps = {
|
|
41
|
+
value: string;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
asChild?: boolean;
|
|
44
|
+
children?: React.ReactElement;
|
|
45
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { ToggleGroupItem } from "./ToggleGroup/ToggleGroupItem";
|
|
2
|
+
export { ToggleGroupRoot, ToggleGroupRoot as ToggleGroup } from "./ToggleGroup/ToggleGroupRoot";
|
|
3
|
+
export type {
|
|
4
|
+
ToggleGroupCommonProps,
|
|
5
|
+
ToggleGroupContextValue,
|
|
6
|
+
ToggleGroupItemProps,
|
|
7
|
+
ToggleGroupMultipleProps,
|
|
8
|
+
ToggleGroupOrientation,
|
|
9
|
+
ToggleGroupProps,
|
|
10
|
+
ToggleGroupSingleProps,
|
|
11
|
+
ToggleGroupType,
|
|
12
|
+
ToggleGroupValue,
|
|
13
|
+
} from "./ToggleGroup/types";
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "out",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"typeRoots": [
|
|
8
|
+
"./node_modules/@rbxts",
|
|
9
|
+
"../../node_modules/@rbxts",
|
|
10
|
+
"./node_modules/@lattice-ui",
|
|
11
|
+
"../../node_modules/@lattice-ui"
|
|
12
|
+
],
|
|
13
|
+
"types": ["types", "compiler-types"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": true,
|
|
5
|
+
"baseUrl": "..",
|
|
6
|
+
"rootDir": "..",
|
|
7
|
+
"paths": {
|
|
8
|
+
"@lattice-ui/checkbox": ["checkbox/src/index.ts"],
|
|
9
|
+
"@lattice-ui/core": ["core/src/index.ts"],
|
|
10
|
+
"@lattice-ui/dialog": ["dialog/src/index.ts"],
|
|
11
|
+
"@lattice-ui/focus": ["focus/src/index.ts"],
|
|
12
|
+
"@lattice-ui/layer": ["layer/src/index.ts"],
|
|
13
|
+
"@lattice-ui/menu": ["menu/src/index.ts"],
|
|
14
|
+
"@lattice-ui/popover": ["popover/src/index.ts"],
|
|
15
|
+
"@lattice-ui/popper": ["popper/src/index.ts"],
|
|
16
|
+
"@lattice-ui/radio-group": ["radio-group/src/index.ts"],
|
|
17
|
+
"@lattice-ui/style": ["style/src/index.ts"],
|
|
18
|
+
"@lattice-ui/switch": ["switch/src/index.ts"],
|
|
19
|
+
"@lattice-ui/system": ["system/src/index.ts"],
|
|
20
|
+
"@lattice-ui/tabs": ["tabs/src/index.ts"],
|
|
21
|
+
"@lattice-ui/toggle-group": ["toggle-group/src/index.ts"],
|
|
22
|
+
"@lattice-ui/tooltip": ["tooltip/src/index.ts"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|