@lattice-ui/tabs 0.3.1 → 0.4.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/README.md +5 -0
- package/out/Tabs/TabsList.luau +4 -12
- package/out/Tabs/TabsRoot.luau +29 -20
- package/out/Tabs/TabsTrigger.luau +29 -16
- package/out/Tabs/types.d.ts +4 -6
- package/out/index.d.ts +1 -1
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -9,3 +9,8 @@ Behavior-only Tabs primitives for Roblox UI, aligned with the lattice compound p
|
|
|
9
9
|
- `Tabs.List`
|
|
10
10
|
- `Tabs.Trigger`
|
|
11
11
|
- `Tabs.Content`
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
- `orientation` supports `horizontal` and `vertical` trigger navigation.
|
|
16
|
+
- Enabled triggers are selectable and activate immediately when selection focus moves onto them.
|
package/out/Tabs/TabsList.luau
CHANGED
|
@@ -3,27 +3,19 @@ local TS = _G[script]
|
|
|
3
3
|
local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
|
|
4
4
|
local React = _core.React
|
|
5
5
|
local Slot = _core.Slot
|
|
6
|
-
local RovingFocusGroup = TS.import(script, TS.getModule(script, "@lattice-ui", "focus").out).RovingFocusGroup
|
|
7
|
-
local useTabsContext = TS.import(script, script.Parent, "context").useTabsContext
|
|
8
6
|
local function TabsList(props)
|
|
9
|
-
|
|
10
|
-
local listNode = if props.asChild then ((function()
|
|
7
|
+
if props.asChild then
|
|
11
8
|
local child = props.children
|
|
12
9
|
if not React.isValidElement(child) then
|
|
13
10
|
error("[TabsList] `asChild` requires a child element.")
|
|
14
11
|
end
|
|
15
12
|
return React.createElement(Slot, nil, child)
|
|
16
|
-
end
|
|
13
|
+
end
|
|
14
|
+
return React.createElement("frame", {
|
|
17
15
|
BackgroundTransparency = 1,
|
|
18
16
|
BorderSizePixel = 0,
|
|
19
17
|
Size = UDim2.fromOffset(0, 0),
|
|
20
|
-
}, props.children)
|
|
21
|
-
return React.createElement(RovingFocusGroup, {
|
|
22
|
-
active = true,
|
|
23
|
-
autoFocus = "none",
|
|
24
|
-
loop = true,
|
|
25
|
-
orientation = tabsContext.orientation,
|
|
26
|
-
}, listNode)
|
|
18
|
+
}, props.children)
|
|
27
19
|
end
|
|
28
20
|
return {
|
|
29
21
|
TabsList = TabsList,
|
package/out/Tabs/TabsRoot.luau
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
local TS = _G[script]
|
|
3
3
|
local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
|
|
4
|
+
local findOrderedSelectionEntry = _core.findOrderedSelectionEntry
|
|
5
|
+
local focusOrderedSelectionEntry = _core.focusOrderedSelectionEntry
|
|
6
|
+
local getOrderedSelectionEntries = _core.getOrderedSelectionEntries
|
|
7
|
+
local getRelativeOrderedSelectionEntry = _core.getRelativeOrderedSelectionEntry
|
|
4
8
|
local React = _core.React
|
|
5
9
|
local useControllableState = _core.useControllableState
|
|
6
10
|
local TabsContextProvider = TS.import(script, script.Parent, "context").TabsContextProvider
|
|
7
|
-
local function getOrderedTriggers(triggers)
|
|
8
|
-
local _array = {}
|
|
9
|
-
local _length = #_array
|
|
10
|
-
table.move(triggers, 1, #triggers, _length + 1, _array)
|
|
11
|
-
local ordered = _array
|
|
12
|
-
table.sort(ordered, function(a, b)
|
|
13
|
-
return a.order < b.order
|
|
14
|
-
end)
|
|
15
|
-
return ordered
|
|
16
|
-
end
|
|
17
11
|
local function resolveNextValue(currentValue, orderedTriggers, fallbackOrder)
|
|
18
12
|
-- ▼ ReadonlyArray.filter ▼
|
|
19
13
|
local _newValue = {}
|
|
20
14
|
local _callback = function(trigger)
|
|
21
|
-
return not trigger.
|
|
15
|
+
return not trigger.getDisabled()
|
|
22
16
|
end
|
|
23
17
|
local _length = 0
|
|
24
18
|
for _k, _v in orderedTriggers do
|
|
@@ -102,6 +96,7 @@ local function resolveNextValue(currentValue, orderedTriggers, fallbackOrder)
|
|
|
102
96
|
return _result_3
|
|
103
97
|
end
|
|
104
98
|
local function TabsRoot(props)
|
|
99
|
+
local orientation = props.orientation or "horizontal"
|
|
105
100
|
local _binding = useControllableState({
|
|
106
101
|
value = props.value,
|
|
107
102
|
defaultValue = props.defaultValue,
|
|
@@ -116,8 +111,6 @@ local function TabsRoot(props)
|
|
|
116
111
|
})
|
|
117
112
|
local value = _binding[1]
|
|
118
113
|
local setValueState = _binding[2]
|
|
119
|
-
local orientation = props.orientation or "horizontal"
|
|
120
|
-
local activationMode = props.activationMode or "automatic"
|
|
121
114
|
local triggerRegistryRef = React.useRef({})
|
|
122
115
|
local lastSelectedOrderRef = React.useRef()
|
|
123
116
|
local registryRevision, setRegistryRevision = React.useState(0)
|
|
@@ -152,10 +145,10 @@ local function TabsRoot(props)
|
|
|
152
145
|
end
|
|
153
146
|
end, {})
|
|
154
147
|
local setValue = React.useCallback(function(nextValue)
|
|
155
|
-
local orderedTriggers =
|
|
148
|
+
local orderedTriggers = getOrderedSelectionEntries(triggerRegistryRef.current)
|
|
156
149
|
-- ▼ ReadonlyArray.find ▼
|
|
157
150
|
local _callback = function(trigger)
|
|
158
|
-
return trigger.value == nextValue and not trigger.
|
|
151
|
+
return trigger.value == nextValue and not trigger.getDisabled()
|
|
159
152
|
end
|
|
160
153
|
local _result
|
|
161
154
|
for _i, _v in orderedTriggers do
|
|
@@ -171,11 +164,27 @@ local function TabsRoot(props)
|
|
|
171
164
|
end
|
|
172
165
|
setValueState(nextValue)
|
|
173
166
|
end, { setValueState })
|
|
167
|
+
local moveSelection = React.useCallback(function(fromValue, direction)
|
|
168
|
+
local currentTrigger = findOrderedSelectionEntry(triggerRegistryRef.current, function(trigger)
|
|
169
|
+
return trigger.value == fromValue
|
|
170
|
+
end) or nil
|
|
171
|
+
local _exp = triggerRegistryRef.current
|
|
172
|
+
local _result = currentTrigger
|
|
173
|
+
if _result ~= nil then
|
|
174
|
+
_result = _result.id
|
|
175
|
+
end
|
|
176
|
+
local nextTrigger = getRelativeOrderedSelectionEntry(_exp, _result, direction)
|
|
177
|
+
if not nextTrigger then
|
|
178
|
+
return nil
|
|
179
|
+
end
|
|
180
|
+
focusOrderedSelectionEntry(nextTrigger)
|
|
181
|
+
setValue(nextTrigger.value)
|
|
182
|
+
end, { setValue })
|
|
174
183
|
React.useEffect(function()
|
|
175
|
-
local orderedTriggers =
|
|
184
|
+
local orderedTriggers = getOrderedSelectionEntries(triggerRegistryRef.current)
|
|
176
185
|
-- ▼ ReadonlyArray.find ▼
|
|
177
186
|
local _callback = function(trigger)
|
|
178
|
-
return trigger.value == value and not trigger.
|
|
187
|
+
return trigger.value == value and not trigger.getDisabled()
|
|
179
188
|
end
|
|
180
189
|
local _result
|
|
181
190
|
for _i, _v in orderedTriggers do
|
|
@@ -197,12 +206,12 @@ local function TabsRoot(props)
|
|
|
197
206
|
local contextValue = React.useMemo(function()
|
|
198
207
|
return {
|
|
199
208
|
value = value,
|
|
200
|
-
setValue = setValue,
|
|
201
209
|
orientation = orientation,
|
|
202
|
-
|
|
210
|
+
setValue = setValue,
|
|
203
211
|
registerTrigger = registerTrigger,
|
|
212
|
+
moveSelection = moveSelection,
|
|
204
213
|
}
|
|
205
|
-
end, {
|
|
214
|
+
end, { moveSelection, orientation, registerTrigger, setValue, value })
|
|
206
215
|
return React.createElement(TabsContextProvider, {
|
|
207
216
|
value = contextValue,
|
|
208
217
|
}, props.children)
|
|
@@ -3,7 +3,7 @@ local TS = _G[script]
|
|
|
3
3
|
local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
|
|
4
4
|
local React = _core.React
|
|
5
5
|
local Slot = _core.Slot
|
|
6
|
-
local
|
|
6
|
+
local useFocusNode = _core.useFocusNode
|
|
7
7
|
local useTabsContext = TS.import(script, script.Parent, "context").useTabsContext
|
|
8
8
|
local createTabsTriggerName = TS.import(script, script.Parent, "internals", "ids").createTabsTriggerName
|
|
9
9
|
local nextTriggerId = 0
|
|
@@ -19,6 +19,10 @@ local function TabsTrigger(props)
|
|
|
19
19
|
local triggerRef = React.useRef()
|
|
20
20
|
local selected = tabsContext.value == props.value
|
|
21
21
|
local disabled = props.disabled == true
|
|
22
|
+
local disabledRef = React.useRef(disabled)
|
|
23
|
+
React.useEffect(function()
|
|
24
|
+
disabledRef.current = disabled
|
|
25
|
+
end, { disabled })
|
|
22
26
|
local triggerIdRef = React.useRef(0)
|
|
23
27
|
if triggerIdRef.current == 0 then
|
|
24
28
|
nextTriggerId += 1
|
|
@@ -33,11 +37,19 @@ local function TabsTrigger(props)
|
|
|
33
37
|
return tabsContext.registerTrigger({
|
|
34
38
|
id = triggerIdRef.current,
|
|
35
39
|
value = props.value,
|
|
36
|
-
disabled = disabled,
|
|
37
40
|
ref = triggerRef,
|
|
38
41
|
order = triggerOrderRef.current,
|
|
42
|
+
getDisabled = function()
|
|
43
|
+
return disabledRef.current
|
|
44
|
+
end,
|
|
39
45
|
})
|
|
40
|
-
end, {
|
|
46
|
+
end, { props.value, tabsContext })
|
|
47
|
+
useFocusNode({
|
|
48
|
+
ref = triggerRef,
|
|
49
|
+
getDisabled = function()
|
|
50
|
+
return disabledRef.current
|
|
51
|
+
end,
|
|
52
|
+
})
|
|
41
53
|
local setTriggerRef = React.useCallback(function(instance)
|
|
42
54
|
triggerRef.current = toGuiObject(instance)
|
|
43
55
|
end, {})
|
|
@@ -48,16 +60,21 @@ local function TabsTrigger(props)
|
|
|
48
60
|
tabsContext.setValue(props.value)
|
|
49
61
|
end, { disabled, props.value, tabsContext })
|
|
50
62
|
local handleSelectionGained = React.useCallback(function()
|
|
51
|
-
if disabled
|
|
63
|
+
if disabled then
|
|
52
64
|
return nil
|
|
53
65
|
end
|
|
54
66
|
tabsContext.setValue(props.value)
|
|
55
67
|
end, { disabled, props.value, tabsContext })
|
|
56
68
|
local handleInputBegan = React.useCallback(function(_rbx, inputObject)
|
|
57
|
-
if disabled
|
|
69
|
+
if disabled then
|
|
58
70
|
return nil
|
|
59
71
|
end
|
|
60
72
|
local keyCode = inputObject.KeyCode
|
|
73
|
+
local direction = if tabsContext.orientation == "horizontal" then if keyCode == Enum.KeyCode.Left then -1 elseif keyCode == Enum.KeyCode.Right then 1 else nil elseif keyCode == Enum.KeyCode.Up then -1 elseif keyCode == Enum.KeyCode.Down then 1 else nil
|
|
74
|
+
if direction ~= nil then
|
|
75
|
+
tabsContext.moveSelection(props.value, direction)
|
|
76
|
+
return nil
|
|
77
|
+
end
|
|
61
78
|
if keyCode ~= Enum.KeyCode.Return and keyCode ~= Enum.KeyCode.Space then
|
|
62
79
|
return nil
|
|
63
80
|
end
|
|
@@ -66,8 +83,8 @@ local function TabsTrigger(props)
|
|
|
66
83
|
local eventHandlers = React.useMemo(function()
|
|
67
84
|
return {
|
|
68
85
|
Activated = handleActivated,
|
|
69
|
-
SelectionGained = handleSelectionGained,
|
|
70
86
|
InputBegan = handleInputBegan,
|
|
87
|
+
SelectionGained = handleSelectionGained,
|
|
71
88
|
}
|
|
72
89
|
end, { handleActivated, handleInputBegan, handleSelectionGained })
|
|
73
90
|
local triggerName = React.useMemo(function()
|
|
@@ -78,19 +95,15 @@ local function TabsTrigger(props)
|
|
|
78
95
|
if not child then
|
|
79
96
|
error("[TabsTrigger] `asChild` requires a child element.")
|
|
80
97
|
end
|
|
81
|
-
return React.createElement(
|
|
82
|
-
|
|
83
|
-
disabled = disabled,
|
|
84
|
-
}, React.createElement(Slot, {
|
|
98
|
+
return React.createElement(Slot, {
|
|
99
|
+
Active = not disabled,
|
|
85
100
|
Event = eventHandlers,
|
|
86
101
|
Name = triggerName,
|
|
102
|
+
Selectable = not disabled,
|
|
87
103
|
ref = setTriggerRef,
|
|
88
|
-
}, child)
|
|
104
|
+
}, child)
|
|
89
105
|
end
|
|
90
|
-
return React.createElement(
|
|
91
|
-
asChild = true,
|
|
92
|
-
disabled = disabled,
|
|
93
|
-
}, React.createElement("textbutton", {
|
|
106
|
+
return React.createElement("textbutton", {
|
|
94
107
|
Active = not disabled,
|
|
95
108
|
AutoButtonColor = false,
|
|
96
109
|
BackgroundColor3 = if selected then Color3.fromRGB(86, 137, 245) else Color3.fromRGB(47, 53, 68),
|
|
@@ -102,7 +115,7 @@ local function TabsTrigger(props)
|
|
|
102
115
|
TextColor3 = if disabled then Color3.fromRGB(136, 144, 159) else Color3.fromRGB(235, 240, 248),
|
|
103
116
|
TextSize = 15,
|
|
104
117
|
ref = setTriggerRef,
|
|
105
|
-
}, props.children)
|
|
118
|
+
}, props.children)
|
|
106
119
|
end
|
|
107
120
|
return {
|
|
108
121
|
TabsTrigger = TabsTrigger,
|
package/out/Tabs/types.d.ts
CHANGED
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import type React from "@rbxts/react";
|
|
2
|
-
export type TabsOrientation = "horizontal" | "vertical";
|
|
3
|
-
export type TabsActivationMode = "automatic" | "manual";
|
|
4
2
|
export type TabsSetValue = (value: string) => void;
|
|
3
|
+
export type TabsOrientation = "horizontal" | "vertical";
|
|
5
4
|
export type TabsTriggerRegistration = {
|
|
6
5
|
id: number;
|
|
7
6
|
value: string;
|
|
8
|
-
disabled: boolean;
|
|
9
7
|
ref: React.MutableRefObject<GuiObject | undefined>;
|
|
10
8
|
order: number;
|
|
9
|
+
getDisabled: () => boolean;
|
|
11
10
|
};
|
|
12
11
|
export type TabsContextValue = {
|
|
13
12
|
value?: string;
|
|
14
|
-
setValue: TabsSetValue;
|
|
15
13
|
orientation: TabsOrientation;
|
|
16
|
-
|
|
14
|
+
setValue: TabsSetValue;
|
|
17
15
|
registerTrigger: (trigger: TabsTriggerRegistration) => () => void;
|
|
16
|
+
moveSelection: (fromValue: string, direction: -1 | 1) => void;
|
|
18
17
|
};
|
|
19
18
|
export type TabsProps = {
|
|
20
19
|
value?: string;
|
|
21
20
|
defaultValue?: string;
|
|
22
21
|
onValueChange?: (value: string) => void;
|
|
23
22
|
orientation?: TabsOrientation;
|
|
24
|
-
activationMode?: TabsActivationMode;
|
|
25
23
|
children?: React.ReactNode;
|
|
26
24
|
};
|
|
27
25
|
export type TabsListProps = {
|
package/out/index.d.ts
CHANGED
|
@@ -8,4 +8,4 @@ export declare const Tabs: {
|
|
|
8
8
|
readonly Trigger: typeof TabsTrigger;
|
|
9
9
|
readonly Content: typeof TabsContent;
|
|
10
10
|
};
|
|
11
|
-
export type {
|
|
11
|
+
export type { TabsContentProps, TabsContextValue, TabsListProps, TabsOrientation, TabsProps, TabsTriggerProps, } from "./Tabs/types";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lattice-ui/tabs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "out/init.luau",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
"README.md"
|
|
10
10
|
],
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@lattice-ui/core": "0.
|
|
13
|
-
"@lattice-ui/
|
|
14
|
-
"@lattice-ui/layer": "0.3.1"
|
|
12
|
+
"@lattice-ui/core": "0.4.0",
|
|
13
|
+
"@lattice-ui/layer": "0.4.0"
|
|
15
14
|
},
|
|
16
15
|
"devDependencies": {
|
|
17
16
|
"@rbxts/react": "17.3.7-ts.1",
|