@nrbx/topbar-components 1.0.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.txt ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 wad4444
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ <div align="center" id="top">
2
+ <img src="https://github.com/nn140/Branding/blob/main/LogoWhite-Full.png?raw=true" alt="NN140.UK logo" width="800"/>
3
+ <img src="https://github.com/nn140/Branding/blob/main/LogoBlack-Full.png?raw=true" alt="NN140.UK logo" width="800"/>
4
+ <br />
5
+ <br />
6
+ <img src="https://img.shields.io/badge/Stripe-Donate%20to%20support%20NN140.UK-1b1b1b?style=for-the-badge&labelColor=6860ff&logo=stripe&logoColor=ffffff&logoSize=auto&link=https%3A%2F%2Fdonate.stripe.com%2F9B6eVdbTd4n1a6H1yXa3u04&link=https%3A%2F%2Fdonate.stripe.com%2F9B6eVdbTd4n1a6H1yXa3u04" alt="Badge">
7
+ <img src="https://img.shields.io/badge/Stripe-Donate%20to%20Support%20NN140.UK%20(RECCURING)-1b1b1b?style=for-the-badge&labelColor=6860ff&logo=stripe&logoColor=ffffff&logoSize=auto&link=https%3A%2F%2Fdonate.stripe.com%2FdRm9ATe1laLpgv5b9xa3u05&link=https%3A%2F%2Fdonate.stripe.com%2FdRm9ATe1laLpgv5b9xa3u05" alt="Badge">
8
+ </div>
9
+
10
+ <hr />
11
+
12
+ ## @nrbx/topbar-components
13
+
14
+ - A Fork of @rbxts/topbar-components
15
+
16
+ **Topbar Components** is a react component package that mimics [*topbar-plus*](https://devforum.roblox.com/t/v3-topbarplus-v300-construct-intuitive-topbar-icons-customise-them-with-themes-dropdowns-captions-labels-and-much-more/1017485) for [Roblox-TS](https://roblox-ts.com), with JSX markup support.
17
+
18
+ ## 📦 Installation
19
+
20
+ **@nrbx/topbar-components** is available on NPM and can be installed with the following commands:
21
+
22
+ ```bash
23
+ npm install @nrbx/topbar-components
24
+ yarn add @nrbx/topbar-components
25
+ pnpm add @nrbx/topbar-components
26
+ ```
27
+
28
+ Then add the following to your Rojo project file, under your `node_modules` configuration.
29
+
30
+ ```json
31
+ "node_modules": {
32
+ "$className": "Folder",
33
+ "@rbxts": {
34
+ "$path": "node_modules/@rbxts"
35
+ },
36
+ "@nrbx": {
37
+ "$path": "node_modules/@nrbx"
38
+ }
39
+ }
40
+ ```
41
+
42
+ And this to your `tsconfig.json`
43
+
44
+ ```json
45
+ "typeRoots": ["node_modules/@rbxts", "node_modules/@nrbx"],
46
+ ```
47
+
48
+ ### ⚡ Quick Start
49
+
50
+ Instantiate `<TopbarProvider />` to be a root of your topbar component tree.
51
+
52
+ ```jsx
53
+ <TopbarProvider>
54
+ <Icon text="Hello, World!" />
55
+ </TopbarProvider>
56
+ ```
57
+
58
+ Every `<Icon />` can be in only two states `selected`, and `deselected`.
59
+ You can conditionally apply properties based on icon's current state, by providing a state markup object:
60
+
61
+ ```jsx
62
+ <Icon text={{
63
+ selected: "Selected!",
64
+ deselected: "Deselected!",
65
+ }} />
66
+ ```
67
+
68
+ You can add a dropdown to an icon by mounting `<Dropdown />` component as it's child:
69
+ Dropdowns & TopbarProvider have a property called `selectionMode`, which lets you specify how many icons can be selected at once.
70
+
71
+ ```jsx
72
+ <Icon text="Skins">
73
+ <Dropdown selectionMode="single">
74
+ <Icon text="yellow" selected={() => chooseSkin("yellow")} />
75
+ <Icon text="red" selected={() => chooseSkin("red")} />
76
+ </Dropdown>
77
+ </Icon>
78
+ ```
79
+
80
+ Dropdowns **can be nested.**
81
+
82
+ ### 🎨 Stylesheets
83
+
84
+ You can use stylesheets to override default properties of all components within:
85
+ Stylesheets are partial, and work like patches to already established default properties within the package:
86
+
87
+ ```jsx
88
+ <Stylesheet stylesheet={{
89
+ icon: {
90
+ textSize: 25,
91
+ cornerRadius: new UDim(0.5, 0),
92
+ }
93
+ }}>
94
+ <Icon text="Skins">
95
+ <Dropdown selectionMode="single">
96
+ <Icon text="yellow" selected={() => chooseSkin("yellow")} />
97
+ <Icon text="red" selected={() => chooseSkin("red")} />
98
+ </Dropdown>
99
+ </Icon>
100
+ </Stylesheet>
101
+ ```
102
+
103
+ ### 📝 License
104
+
105
+ Package is licensed under the MIT License.
106
+
107
+ <hr />
108
+
109
+ <div align="center" id="top">
110
+ <img src="https://img.shields.io/badge/Stripe-Donate%20to%20support%20NN140.UK-1b1b1b?style=for-the-badge&labelColor=6860ff&logo=stripe&logoColor=ffffff&logoSize=auto&link=https%3A%2F%2Fdonate.stripe.com%2F9B6eVdbTd4n1a6H1yXa3u04&link=https%3A%2F%2Fdonate.stripe.com%2F9B6eVdbTd4n1a6H1yXa3u04" alt="Badge">
111
+ <img src="https://img.shields.io/badge/Stripe-Donate%20to%20Support%20NN140.UK%20(RECCURING)-1b1b1b?style=for-the-badge&labelColor=6860ff&logo=stripe&logoColor=ffffff&logoSize=auto&link=https%3A%2F%2Fdonate.stripe.com%2FdRm9ATe1laLpgv5b9xa3u05&link=https%3A%2F%2Fdonate.stripe.com%2FdRm9ATe1laLpgv5b9xa3u05" alt="Badge">
112
+ <br />
113
+ <br />
114
+ <img src="https://github.com/nn140/Branding/blob/main/LogoBlack-Full.png?raw=true" alt="NN140.UK logo" width="800"/>
115
+ <img src="https://github.com/nn140/Branding/blob/main/LogoWhite-Full.png?raw=true" alt="NN140.UK logo" width="800"/>
116
+ </div>
@@ -0,0 +1,18 @@
1
+ import React from '@rbxts/react';
2
+ import type { SelectionMode } from './provider';
3
+ export interface DropdownProps extends React.PropsWithChildren {
4
+ minWidth?: number;
5
+ maxHeight?: number;
6
+ maxWidth?: number;
7
+ padding?: UDim;
8
+ forceHeight?: number;
9
+ iconCornerRadius?: UDim;
10
+ scrollBarThickness?: number;
11
+ scrollBarTransparency?: number;
12
+ topImage?: string;
13
+ bottomImage?: string;
14
+ midImage?: string;
15
+ scrollBarImageColor?: Color3;
16
+ selectionMode?: SelectionMode;
17
+ }
18
+ export declare function Dropdown(componentProps: DropdownProps): React.JSX.Element;
@@ -0,0 +1,193 @@
1
+ -- Compiled with roblox-ts v1.0.2
2
+ local TS = _G[script]
3
+ local _pretty_react_hooks = TS.import(script, TS.getModule(script, "@rbxts", "pretty-react-hooks").out)
4
+ local mapBinding = _pretty_react_hooks.mapBinding
5
+ local useMotion = _pretty_react_hooks.useMotion
6
+ local useMountEffect = _pretty_react_hooks.useMountEffect
7
+ local _react = TS.import(script, TS.getModule(script, "@rbxts", "react"))
8
+ local React = _react
9
+ local useEffect = _react.useEffect
10
+ local useMemo = _react.useMemo
11
+ local useState = _react.useState
12
+ local _context = TS.import(script, script.Parent.Parent, "context")
13
+ local LocationContext = _context.LocationContext
14
+ local useLocation = _context.useLocation
15
+ local useStylesheet = _context.useStylesheet
16
+ local function Dropdown(componentProps)
17
+ local location = useLocation()
18
+ local fullStylesheet = useStylesheet()
19
+ local stylesheet = fullStylesheet.dropdown
20
+ local selectedIcons, setSelectedIcons = useState({})
21
+ local contents, setContents = useState({})
22
+ local _arg0 = location.type == "icon"
23
+ assert(_arg0, "Dropdowns can only be located under icons")
24
+ local transition, transitionMotion = useMotion(if location.isVisible then 1 else 0)
25
+ local _object = table.clone(stylesheet)
26
+ setmetatable(_object, nil)
27
+ for _k, _v in componentProps do
28
+ _object[_k] = _v
29
+ end
30
+ local props = _object
31
+ local isNested = location.isUnderDropdown
32
+ local maxWidth = if isNested then location.width else props.maxWidth
33
+ local minWidth = if isNested then location.width else props.minWidth
34
+ local maxHeight = props.maxHeight
35
+ local contentSize = useMemo(function()
36
+ local y = 0
37
+ local x = minWidth
38
+ for _, size in contents do
39
+ x = math.min(maxWidth, math.max(x, size.X))
40
+ y += size.Y + stylesheet.padding.Offset
41
+ end
42
+ return Vector2.new(x, y)
43
+ end, { contents, maxWidth, minWidth, stylesheet.padding.Offset })
44
+ useEffect(function()
45
+ location.setAnimationState(true)
46
+ transitionMotion:linear(if location.isVisible then 1 else 0, {
47
+ speed = fullStylesheet.animation.dropdownTransitionSpeed,
48
+ })
49
+ end, { location.isVisible })
50
+ useMountEffect(function()
51
+ return transitionMotion:onComplete(function()
52
+ return location.setAnimationState(false)
53
+ end)
54
+ end)
55
+ useEffect(function()
56
+ location.setContentSize(contentSize)
57
+ end, { contentSize, location.setContentSize })
58
+ local scrollingEnabled = not isNested and contentSize.Y > maxHeight
59
+ return React.createElement(LocationContext.Provider, {
60
+ value = {
61
+ type = "dropdown",
62
+ selectedIcons = selectedIcons,
63
+ iconSelected = function(iconId)
64
+ if props.selectionMode == "Single" then
65
+ return setSelectedIcons({ iconId })
66
+ end
67
+ return setSelectedIcons(function(icons)
68
+ local _array = {}
69
+ local _length = #_array
70
+ local _iconsLength = #icons
71
+ table.move(icons, 1, _iconsLength, _length + 1, _array)
72
+ _length += _iconsLength
73
+ _array[_length + 1] = iconId
74
+ return _array
75
+ end)
76
+ end,
77
+ iconDeselected = function(iconId)
78
+ local _condition = props.selectionMode == "Single"
79
+ if _condition then
80
+ local _iconId = iconId
81
+ _condition = table.find(selectedIcons, _iconId) ~= nil
82
+ end
83
+ if _condition then
84
+ return setSelectedIcons({})
85
+ end
86
+ return setSelectedIcons(function(icons)
87
+ -- ▼ ReadonlyArray.filter ▼
88
+ local _newValue = {}
89
+ local _callback = function(T)
90
+ return T ~= iconId
91
+ end
92
+ local _length = 0
93
+ for _k, _v in icons do
94
+ if _callback(_v, _k - 1, icons) == true then
95
+ _length += 1
96
+ _newValue[_length] = _v
97
+ end
98
+ end
99
+ -- ▲ ReadonlyArray.filter ▲
100
+ return _newValue
101
+ end)
102
+ end,
103
+ registerChild = function(id, size)
104
+ setContents(function(contents)
105
+ local _array = {}
106
+ local _length = #_array
107
+ for _k, _v in contents do
108
+ _length += 1
109
+ _array[_length] = { _k, _v }
110
+ end
111
+ _array[_length + 1] = { id, size }
112
+ local _map = {}
113
+ for _, _v in _array do
114
+ _map[_v[1]] = _v[2]
115
+ end
116
+ return _map
117
+ end)
118
+ end,
119
+ removeChild = function(id)
120
+ setContents(function(contents)
121
+ local _array = {}
122
+ local _length = #_array
123
+ for _k, _v in contents do
124
+ _length += 1
125
+ _array[_length] = { _k, _v }
126
+ end
127
+ -- ▼ ReadonlyArray.filter ▼
128
+ local _newValue = {}
129
+ local _callback = function(T)
130
+ return T[1] ~= id
131
+ end
132
+ local _length_1 = 0
133
+ for _k, _v in _array do
134
+ if _callback(_v, _k - 1, _array) == true then
135
+ _length_1 += 1
136
+ _newValue[_length_1] = _v
137
+ end
138
+ end
139
+ -- ▲ ReadonlyArray.filter ▲
140
+ local _map = {}
141
+ for _, _v in _newValue do
142
+ _map[_v[1]] = _v[2]
143
+ end
144
+ return _map
145
+ end)
146
+ end,
147
+ desiredIconWidth = if isNested then location.width else contentSize.X,
148
+ },
149
+ }, React.createElement("scrollingframe", {
150
+ ClipsDescendants = true,
151
+ Size = mapBinding(transition, function(t)
152
+ return UDim2.fromOffset(contentSize.X + (if scrollingEnabled then props.scrollBarThickness else 0), t * math.min(contentSize.Y, if isNested then contentSize.Y else maxHeight))
153
+ end),
154
+ BorderSizePixel = fullStylesheet.dropdownTheme.borderSize,
155
+ BorderColor3 = fullStylesheet.dropdownTheme.borderColor,
156
+ BackgroundColor3 = fullStylesheet.dropdownTheme.backgroundColor,
157
+ Position = fullStylesheet.dropdownTheme.position,
158
+ ScrollBarImageColor3 = props.scrollBarImageColor,
159
+ ScrollBarImageTransparency = if scrollingEnabled and location.isVisible then props.scrollBarTransparency else 1,
160
+ ScrollingEnabled = scrollingEnabled,
161
+ AutomaticCanvasSize = Enum.AutomaticSize.None,
162
+ CanvasSize = UDim2.fromOffset(0, contentSize.Y),
163
+ ScrollBarThickness = if scrollingEnabled then props.scrollBarThickness else 0,
164
+ BackgroundTransparency = fullStylesheet.dropdownTheme.backgroundTransparency,
165
+ Change = {
166
+ AbsoluteSize = function(rbx)
167
+ return location.setDropdownSize(rbx.AbsoluteSize)
168
+ end,
169
+ },
170
+ MidImage = props.midImage,
171
+ TopImage = props.topImage,
172
+ BottomImage = props.bottomImage,
173
+ key = "Dropdown",
174
+ }, React.createElement("uicorner", {
175
+ key = "DropdownCorner",
176
+ CornerRadius = fullStylesheet.dropdownTheme.cornerRadius,
177
+ }), React.createElement("uistroke", {
178
+ key = "DropdownStroke",
179
+ Thickness = fullStylesheet.dropdownTheme.borderSize,
180
+ Color = fullStylesheet.dropdownTheme.borderColor,
181
+ Transparency = fullStylesheet.dropdownTheme.borderTransparency,
182
+ }), props.children, isNested and React.createElement("uipadding", {
183
+ key = "UIPadding",
184
+ PaddingTop = stylesheet.padding,
185
+ }), React.createElement("uilistlayout", {
186
+ key = "UIListLayout",
187
+ SortOrder = Enum.SortOrder.LayoutOrder,
188
+ Padding = stylesheet.padding,
189
+ })))
190
+ end
191
+ return {
192
+ Dropdown = Dropdown,
193
+ }
@@ -0,0 +1,40 @@
1
+ import React from '@rbxts/react';
2
+ export interface IconProps extends React.PropsWithChildren {
3
+ backgroundTransparency?: StateDependent<number>;
4
+ backgroundColor?: StateDependent<Color3>;
5
+ imageId?: StateDependent<string>;
6
+ imageColor?: StateDependent<Color3>;
7
+ textColor?: StateDependent<Color3>;
8
+ imageTransparency?: StateDependent<number>;
9
+ layoutOrder?: StateDependent<number>;
10
+ text?: StateDependent<string>;
11
+ textSize?: StateDependent<number>;
12
+ imageSizeOffset?: StateDependent<number>;
13
+ imageRectOffset?: StateDependent<Vector2>;
14
+ imageRectSize?: StateDependent<Vector2>;
15
+ defaultState?: IconState;
16
+ fontFace?: StateDependent<Font>;
17
+ forcedState?: IconState;
18
+ leftClickSound?: StateDependent<string>;
19
+ rightClickSound?: StateDependent<string>;
20
+ cornerRadius?: StateDependent<UDim>;
21
+ strokeTransparency?: StateDependent<number>;
22
+ strokeColor?: StateDependent<Color3>;
23
+ strokeThickness?: StateDependent<number>;
24
+ textAlignment?: StateDependent<Enum.TextXAlignment>;
25
+ richText?: StateDependent<boolean>;
26
+ toggleStateOnClick?: boolean;
27
+ selected?: () => void;
28
+ deselected?: () => void;
29
+ hover?: () => void;
30
+ unhover?: () => void;
31
+ stateChanged?: (state: IconState) => void;
32
+ onClick?: () => void;
33
+ onRightClick?: () => void;
34
+ playSound?: (id: string) => void;
35
+ }
36
+ export type IconState = 'selected' | 'deselected';
37
+ export type StateDependent<T> = Record<IconState, T> | T;
38
+ export type FromStateDependent<T> = T extends StateDependent<infer U> ? U : T;
39
+ export type IconId = number;
40
+ export declare function Icon({ children, ...componentProps }: IconProps): React.JSX.Element;
@@ -0,0 +1,237 @@
1
+ -- Compiled with roblox-ts v1.0.2
2
+ local TS = _G[script]
3
+ local deepEquals = TS.import(script, TS.getModule(script, "@rbxts", "object-utils")).deepEquals
4
+ local _pretty_react_hooks = TS.import(script, TS.getModule(script, "@rbxts", "pretty-react-hooks").out)
5
+ local mapBinding = _pretty_react_hooks.mapBinding
6
+ local useAsyncEffect = _pretty_react_hooks.useAsyncEffect
7
+ local useMountEffect = _pretty_react_hooks.useMountEffect
8
+ local useUnmountEffect = _pretty_react_hooks.useUnmountEffect
9
+ local useUpdateEffect = _pretty_react_hooks.useUpdateEffect
10
+ local _react = TS.import(script, TS.getModule(script, "@rbxts", "react"))
11
+ local React = _react
12
+ local useBinding = _react.useBinding
13
+ local useEffect = _react.useEffect
14
+ local useRef = _react.useRef
15
+ local useState = _react.useState
16
+ local TextService = TS.import(script, TS.getModule(script, "@rbxts", "services")).TextService
17
+ local _context = TS.import(script, script.Parent.Parent, "context")
18
+ local LocationContext = _context.LocationContext
19
+ local useLocation = _context.useLocation
20
+ local useStylesheet = _context.useStylesheet
21
+ local useAnimateableProps = TS.import(script, script.Parent.Parent, "hooks", "use-animateable-props").useAnimateableProps
22
+ local useGuiInset = TS.import(script, script.Parent.Parent, "hooks", "use-gui-inset").useGuiInset
23
+ local useId = TS.import(script, script.Parent.Parent, "hooks", "use-id").useId
24
+ local noop = TS.import(script, script.Parent.Parent, "style").noop
25
+ local stateful = TS.import(script, script.Parent.Parent, "utilities", "resolve-state-dependent").stateful
26
+ local ANIMATEABLE = { "backgroundColor", "backgroundTransparency", "imageColor", "imageTransparency" }
27
+ local function Icon(_param)
28
+ local children = _param.children
29
+ local _extracted = {
30
+ ["children"] = true,
31
+ }
32
+ local _rest = {}
33
+ for _k, _v in _param do
34
+ if not _extracted[_k] then
35
+ _rest[_k] = _v
36
+ end
37
+ end
38
+ local componentProps = _rest
39
+ local inset = useGuiInset()
40
+ local location = useLocation()
41
+ local id = useId()
42
+ local currentState, setState = useState(componentProps.forcedState or "deselected")
43
+ local dropdownAnimating, setAnimationState = useState(false)
44
+ local contentSize, setContentSize = useState(Vector2.new(0, 0))
45
+ local dropdownSize, setDropdownSize = useBinding(Vector2.new(0, 0))
46
+ local textBounds, setTextBounds = useState(Vector2.zero)
47
+ local stylesheet = useStylesheet()
48
+ local _arg0 = location.type ~= "icon"
49
+ assert(_arg0, "Icons cannot be nested")
50
+ local _object = table.clone(stylesheet.icon)
51
+ setmetatable(_object, nil)
52
+ for _k, _v in componentProps do
53
+ _object[_k] = _v
54
+ end
55
+ local animatedProps = useAnimateableProps(currentState, _object, unpack(ANIMATEABLE))
56
+ local _object_1 = table.clone(stylesheet.icon)
57
+ setmetatable(_object_1, nil)
58
+ for _k, _v in componentProps do
59
+ _object_1[_k] = _v
60
+ end
61
+ for _k, _v in animatedProps do
62
+ _object_1[_k] = _v
63
+ end
64
+ local props = _object_1
65
+ useMountEffect(function()
66
+ local _ = props.defaultState and not componentProps.forcedState and setState(props.defaultState)
67
+ end)
68
+ useEffect(function()
69
+ if not componentProps.forcedState then
70
+ return nil
71
+ end
72
+ setState(componentProps.forcedState)
73
+ end, { componentProps.forcedState })
74
+ useUpdateEffect(function()
75
+ props.stateChanged(currentState)
76
+ if currentState == "selected" then
77
+ location.iconSelected(id)
78
+ props.selected()
79
+ else
80
+ location.iconDeselected(id)
81
+ props.deselected()
82
+ end
83
+ end, { currentState })
84
+ useUpdateEffect(function()
85
+ if currentState == "selected" and not (table.find(location.selectedIcons, id) ~= nil) then
86
+ setState("deselected")
87
+ end
88
+ end, { location.selectedIcons })
89
+ local currentImage = stateful(props.imageId, currentState)
90
+ local currentText = stateful(props.text, currentState)
91
+ local previousQueryRef = useRef()
92
+ useAsyncEffect(TS.async(function()
93
+ local currentQuery = {
94
+ Font = stateful(props.fontFace, currentState),
95
+ Size = stateful(props.textSize, currentState),
96
+ Text = currentText,
97
+ }
98
+ local _condition = previousQueryRef.current
99
+ if _condition == nil then
100
+ _condition = {}
101
+ end
102
+ if deepEquals(currentQuery, _condition) then
103
+ return nil
104
+ end
105
+ if not (currentText ~= "" and currentText) then
106
+ return setTextBounds(Vector2.zero)
107
+ end
108
+ local params = Instance.new("GetTextBoundsParams")
109
+ params.Text = currentText
110
+ params.Font = stateful(props.fontFace, currentState)
111
+ params.Size = stateful(props.textSize, currentState)
112
+ params.Width = stylesheet.sizing.textMeasurementWidth
113
+ setTextBounds(TextService:GetTextBoundsAsync(params))
114
+ previousQueryRef.current = currentQuery
115
+ end), { currentText, props.fontFace, props.textSize, currentState })
116
+ local imageSizeOff = stateful(props.imageSizeOffset, currentState)
117
+ local forceHeight = if location.type == "dropdown" then stylesheet.dropdown.forceHeight else nil
118
+ local _condition = stylesheet.sizing.iconHeight
119
+ if _condition == nil then
120
+ _condition = forceHeight
121
+ if _condition == nil then
122
+ _condition = inset.Height - 12
123
+ end
124
+ end
125
+ local iconHeight = _condition
126
+ local imageSize = iconHeight - stylesheet.sizing.imagePadding * 2 + imageSizeOff
127
+ local minLabelWidth = if location.type == "dropdown" then location.desiredIconWidth - stylesheet.sizing.minLabelWidthPadding else inset.Height - stylesheet.sizing.labelPadding * 2
128
+ local accumulatedLabelWidth = if currentImage ~= "" and currentImage then textBounds.X else math.max(textBounds.X, minLabelWidth)
129
+ local _exp = textBounds.X + stylesheet.sizing.labelPadding * 2
130
+ local _condition_1 = currentImage
131
+ if _condition_1 ~= "" and _condition_1 then
132
+ _condition_1 = textBounds.X ~= 0
133
+ end
134
+ local iconSize = Vector2.new(math.max(iconHeight, _exp + (if _condition_1 ~= "" and _condition_1 then imageSize + stylesheet.sizing.imageToTextSpacing else 0)), iconHeight)
135
+ local imagePos = stylesheet.sizing.imagePadding + imageSizeOff * -0.5
136
+ local textLabelPos = UDim2.new(0, if currentImage ~= "" and currentImage then imageSize + stylesheet.sizing.imagePadding * 2 else stylesheet.sizing.labelPadding, 0.5, 0)
137
+ useEffect(function()
138
+ if location.type ~= "dropdown" then
139
+ return nil
140
+ end
141
+ local includeContents = currentState == "selected" or dropdownAnimating
142
+ local _vector2 = Vector2.new(iconSize.X, iconSize.Y)
143
+ local _vector2_1 = Vector2.new(0, if includeContents then contentSize.Y else 0)
144
+ location.registerChild(id, _vector2 + _vector2_1)
145
+ end, { currentState, contentSize.Y, dropdownAnimating, iconSize })
146
+ useUnmountEffect(function()
147
+ if location.type ~= "dropdown" then
148
+ return nil
149
+ end
150
+ location.removeChild(id)
151
+ end)
152
+ local wrapSize = mapBinding(dropdownSize, function(t)
153
+ return UDim2.fromOffset(if location.type == "dropdown" then location.desiredIconWidth else iconSize.X, iconSize.Y + t.Y)
154
+ end)
155
+ return React.createElement(LocationContext.Provider, {
156
+ value = {
157
+ type = "icon",
158
+ isVisible = currentState == "selected",
159
+ isUnderDropdown = location.type == "dropdown",
160
+ width = if location.type == "dropdown" then location.desiredIconWidth else iconSize.X,
161
+ setDropdownSize = setDropdownSize,
162
+ setContentSize = setContentSize,
163
+ setAnimationState = setAnimationState,
164
+ },
165
+ }, React.createElement("frame", {
166
+ Size = wrapSize,
167
+ LayoutOrder = stateful(props.layoutOrder, currentState),
168
+ BackgroundTransparency = 1,
169
+ key = "IconWrapper",
170
+ }, React.createElement("textbutton", {
171
+ Size = UDim2.new(1, 0, 0, iconSize.Y),
172
+ Event = {
173
+ MouseButton1Click = function()
174
+ if stateful(props.toggleStateOnClick, currentState) then
175
+ setState(if currentState == "deselected" then "selected" else "deselected")
176
+ end
177
+ props.onClick()
178
+ local soundId = stateful(props.leftClickSound, currentState)
179
+ if not (soundId ~= "" and soundId) then
180
+ return nil
181
+ end
182
+ props.playSound(soundId)
183
+ end,
184
+ MouseButton2Click = function()
185
+ if props.onRightClick == noop then
186
+ return nil
187
+ end
188
+ props.onRightClick()
189
+ local soundId = stateful(props.rightClickSound, currentState)
190
+ if not (soundId ~= "" and soundId) then
191
+ return nil
192
+ end
193
+ props.playSound(soundId)
194
+ end,
195
+ MouseEnter = props.hover,
196
+ MouseLeave = props.unhover,
197
+ },
198
+ Text = "",
199
+ BackgroundTransparency = props.backgroundTransparency,
200
+ BackgroundColor3 = stateful(props.backgroundColor, currentState),
201
+ key = "IconButton",
202
+ }, children, currentImage ~= nil and currentImage ~= "" and (React.createElement("imagelabel", {
203
+ key = "IconImage",
204
+ Size = UDim2.fromOffset(imageSize, imageSize),
205
+ Position = UDim2.fromOffset(imagePos, imagePos),
206
+ Image = currentImage,
207
+ BackgroundTransparency = 1,
208
+ ImageColor3 = props.imageColor,
209
+ ImageTransparency = props.imageTransparency,
210
+ ImageRectOffset = stateful(props.imageRectOffset, currentState),
211
+ ImageRectSize = stateful(props.imageRectSize, currentState),
212
+ })), currentText ~= nil and currentText ~= "" and (React.createElement("textlabel", {
213
+ FontFace = stateful(props.fontFace, currentState),
214
+ TextSize = stateful(props.textSize, currentState),
215
+ TextColor3 = stateful(props.textColor, currentState),
216
+ TextWrapped = false,
217
+ AnchorPoint = Vector2.new(0, 0.5),
218
+ Size = UDim2.new(0, accumulatedLabelWidth, stylesheet.sizing.buttonLabelHeightFraction, 0),
219
+ Position = textLabelPos,
220
+ TextXAlignment = stateful(props.textAlignment, currentState),
221
+ RichText = stateful(props.richText, currentState),
222
+ BackgroundTransparency = 1,
223
+ Text = currentText,
224
+ key = "IconText",
225
+ }, React.createElement("uistroke", {
226
+ key = "UIStroke",
227
+ Thickness = stateful(props.strokeThickness, currentState),
228
+ Color = stateful(props.strokeColor, currentState),
229
+ Transparency = stateful(props.strokeTransparency, currentState),
230
+ }))), React.createElement("uicorner", {
231
+ key = "UICorner",
232
+ CornerRadius = if location.type == "dropdown" then stylesheet.dropdown.iconCornerRadius else stateful(props.cornerRadius, currentState),
233
+ }))))
234
+ end
235
+ return {
236
+ Icon = Icon,
237
+ }
@@ -0,0 +1,8 @@
1
+ import React from '@rbxts/react';
2
+ export type SelectionMode = 'Single' | 'Multiple';
3
+ interface ProviderProps extends React.PropsWithChildren {
4
+ selectionMode?: SelectionMode;
5
+ gameVoiceChatEnabled?: boolean;
6
+ }
7
+ export declare function TopbarProvider({ selectionMode, gameVoiceChatEnabled, children }: ProviderProps): React.JSX.Element;
8
+ export {};