@nrbx/topbar-components 1.0.12 → 1.1.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.
@@ -4,6 +4,7 @@ local deepEquals = TS.import(script, TS.getModule(script, "@rbxts", "object-util
4
4
  local _pretty_react_hooks = TS.import(script, TS.getModule(script, "@rbxts", "pretty-react-hooks").out)
5
5
  local mapBinding = _pretty_react_hooks.mapBinding
6
6
  local useAsyncEffect = _pretty_react_hooks.useAsyncEffect
7
+ local useMotion = _pretty_react_hooks.useMotion
7
8
  local useMountEffect = _pretty_react_hooks.useMountEffect
8
9
  local useUnmountEffect = _pretty_react_hooks.useUnmountEffect
9
10
  local useUpdateEffect = _pretty_react_hooks.useUpdateEffect
@@ -21,9 +22,38 @@ local useStylesheet = _context.useStylesheet
21
22
  local useAnimateableProps = TS.import(script, script.Parent.Parent, "hooks", "use-animateable-props").useAnimateableProps
22
23
  local useGuiInset = TS.import(script, script.Parent.Parent, "hooks", "use-gui-inset").useGuiInset
23
24
  local useId = TS.import(script, script.Parent.Parent, "hooks", "use-id").useId
25
+ local useToggleKey = TS.import(script, script.Parent.Parent, "hooks", "use-toggle-key").useToggleKey
24
26
  local noop = TS.import(script, script.Parent.Parent, "style").noop
25
27
  local stateful = TS.import(script, script.Parent.Parent, "utilities", "resolve-state-dependent").stateful
28
+ local Notification = TS.import(script, script.Parent, "notification").Notification
29
+ --[[
30
+ *
31
+ * Properties accepted by {@link Icon}.
32
+ *
33
+ * Most visual props accept a `StateDependent<T>` — either a single value for
34
+ * both states, or `{ deselected: T; selected: T }` to animate between them.
35
+
36
+ ]]
26
37
  local ANIMATEABLE = { "backgroundColor", "backgroundTransparency", "imageColor", "imageTransparency" }
38
+ --* Icon select/deselect state.
39
+ --[[
40
+ *
41
+ * A value that can change with icon state.
42
+ * Pass a single value for both states, or `{ deselected, selected }` for
43
+ * separate values that animate on state transitions.
44
+
45
+ ]]
46
+ --* Extracts the inner type from a `StateDependent`.
47
+ --* Unique numeric identifier for an icon instance.
48
+ --[[
49
+ *
50
+ * A single icon in the topbar.
51
+ *
52
+ * Supports an image, a text label, state-dependent styling, animated state
53
+ * transitions, hover lift, notification badges, toggle keys, and nested
54
+ * dropdown content.
55
+
56
+ ]]
27
57
  local function Icon(componentProps)
28
58
  local _binding = componentProps
29
59
  local children = _binding.children
@@ -32,6 +62,8 @@ local function Icon(componentProps)
32
62
  local location = useLocation()
33
63
  local id = useId()
34
64
  local currentState, setState = useState(componentProps.forcedState or "deselected")
65
+ local hovered, setHovered = useState(false)
66
+ local hoverLift, hoverLiftMotion = useMotion(0)
35
67
  local dropdownAnimating, setAnimationState = useState(false)
36
68
  local contentSize, setContentSize = useState(Vector2.new(0, 0))
37
69
  local dropdownSize, setDropdownSize = useBinding(Vector2.new(0, 0))
@@ -99,6 +131,21 @@ local function Icon(componentProps)
99
131
  setState("deselected")
100
132
  end
101
133
  end, { location.selectedIcons })
134
+ useEffect(function()
135
+ if stylesheet.animation.hoverEnabled then
136
+ hoverLiftMotion:spring(if hovered then 1 else 0, stylesheet.animation.stateSpring)
137
+ end
138
+ end, { hovered, stylesheet.animation.hoverEnabled })
139
+ if componentProps.toggleKey and componentProps.toggleKey ~= Enum.KeyCode.Unknown then
140
+ useToggleKey(componentProps.toggleKey, function()
141
+ if props.static then
142
+ return nil
143
+ end
144
+ if stateful(props.toggleStateOnClick, currentState) then
145
+ setState(if currentState == "deselected" then "selected" else "deselected")
146
+ end
147
+ end)
148
+ end
102
149
  local currentImage = stateful(props.imageId, currentState)
103
150
  local currentText = stateful(props.text, currentState)
104
151
  local previousQueryRef = useRef()
@@ -136,17 +183,37 @@ local function Icon(componentProps)
136
183
  end
137
184
  end
138
185
  local iconHeight = _condition
139
- local imageSize = iconHeight - sizing.imagePadding * 2 + imageSizeOff
186
+ local _condition_1 = props.contentPaddingX
187
+ if _condition_1 == nil then
188
+ _condition_1 = sizing.contentPaddingX
189
+ end
190
+ local contentPadX = _condition_1
191
+ local _condition_2 = props.contentPaddingY
192
+ if _condition_2 == nil then
193
+ _condition_2 = sizing.contentPaddingY
194
+ end
195
+ local contentPadY = _condition_2
196
+ local _condition_3 = props.imageToTextSpacing
197
+ if _condition_3 == nil then
198
+ _condition_3 = sizing.imageToTextSpacing
199
+ end
200
+ local imageToTextGap = _condition_3
201
+ local imageSize = iconHeight - contentPadY * 2 + imageSizeOff
140
202
  local minLabelWidth = if location.type == "dropdown" then location.desiredIconWidth - sizing.minLabelWidthPadding else inset.Height - sizing.iconHorizontalPadding * 2
141
203
  local accumulatedLabelWidth = if currentImage ~= "" and currentImage then textBounds.X else math.max(textBounds.X, minLabelWidth)
142
- local _exp = textBounds.X + sizing.labelPadding * 2
143
- local _condition_1 = currentImage
144
- if _condition_1 ~= "" and _condition_1 then
145
- _condition_1 = textBounds.X ~= 0
204
+ local contentWidth = if currentImage ~= "" and currentImage then imageSize + imageToTextGap + textBounds.X else textBounds.X
205
+ local autoWidth = contentWidth + contentPadX * 2
206
+ local _condition_4 = props.iconWidth
207
+ if not (_condition_4 ~= 0 and _condition_4 == _condition_4 and _condition_4) then
208
+ _condition_4 = sizing.iconWidth
209
+ if not (_condition_4 ~= 0 and _condition_4 == _condition_4 and _condition_4) then
210
+ _condition_4 = math.max(iconHeight, autoWidth)
211
+ end
146
212
  end
147
- local iconSize = Vector2.new(math.max(iconHeight, _exp + (if _condition_1 ~= "" and _condition_1 then imageSize + sizing.imageToTextSpacing else 0)), iconHeight)
148
- local imagePos = sizing.imagePadding + imageSizeOff * -0.5
149
- local textLabelPos = UDim2.new(0, if currentImage ~= "" and currentImage then imageSize + sizing.imagePadding * 2 else sizing.labelPadding, 0.5, 0)
213
+ local iconWidth = _condition_4
214
+ local iconSize = Vector2.new(iconWidth, iconHeight)
215
+ local imagePosY = (iconHeight - imageSize) / 2 + imageSizeOff * -0.5
216
+ local textLabelPos = UDim2.new(0, if currentImage ~= "" and currentImage then contentPadX + imageSize + imageToTextGap else contentPadX, 0.5, 0)
150
217
  useEffect(function()
151
218
  if props.static then
152
219
  return nil
@@ -184,12 +251,15 @@ local function Icon(componentProps)
184
251
  }, React.createElement("frame", {
185
252
  Size = wrapSize,
186
253
  LayoutOrder = stateful(props.layoutOrder, currentState),
254
+ Position = mapBinding(hoverLift, function(t)
255
+ return UDim2.new(0, 0, 0, -t * stylesheet.animation.hoverLift)
256
+ end),
187
257
  BackgroundTransparency = 1,
188
- key = "IconWrapper",
189
258
  }, React.createElement("textbutton", {
190
259
  Size = UDim2.new(1, 0, 0, iconSize.Y),
191
260
  Active = not props.static,
192
261
  Selectable = not props.static,
262
+ AutoButtonColor = not props.static,
193
263
  Event = {
194
264
  MouseButton1Click = function()
195
265
  if props.static then
@@ -219,21 +289,31 @@ local function Icon(componentProps)
219
289
  end
220
290
  props.playSound(soundId)
221
291
  end,
222
- MouseEnter = if props.static then noop else props.hover,
223
- MouseLeave = if props.static then noop else props.unhover,
292
+ MouseEnter = function()
293
+ if props.static then
294
+ return nil
295
+ end
296
+ setHovered(true)
297
+ props.hover()
298
+ end,
299
+ MouseLeave = function()
300
+ if props.static then
301
+ return nil
302
+ end
303
+ setHovered(false)
304
+ props.unhover()
305
+ end,
224
306
  },
225
307
  Text = "",
226
- BackgroundTransparency = props.backgroundTransparency,
308
+ BackgroundTransparency = stateful(props.backgroundTransparency, currentState),
227
309
  BackgroundColor3 = stateful(props.backgroundColor, currentState),
228
- key = "IconButton",
229
310
  }, children, currentImage ~= nil and currentImage ~= "" and (React.createElement("imagelabel", {
230
- key = "IconImage",
231
311
  Size = UDim2.fromOffset(imageSize, imageSize),
232
- Position = UDim2.fromOffset(imagePos, imagePos),
312
+ Position = UDim2.fromOffset(contentPadX, imagePosY),
233
313
  Image = currentImage,
234
314
  BackgroundTransparency = 1,
235
- ImageColor3 = props.imageColor,
236
- ImageTransparency = props.imageTransparency,
315
+ ImageColor3 = stateful(props.imageColor, currentState),
316
+ ImageTransparency = stateful(props.imageTransparency, currentState),
237
317
  ImageRectOffset = stateful(props.imageRectOffset, currentState),
238
318
  ImageRectSize = stateful(props.imageRectSize, currentState),
239
319
  })), currentText ~= nil and currentText ~= "" and (React.createElement("textlabel", {
@@ -248,22 +328,20 @@ local function Icon(componentProps)
248
328
  RichText = stateful(props.richText, currentState),
249
329
  BackgroundTransparency = 1,
250
330
  Text = currentText,
251
- key = "IconText",
252
331
  }, React.createElement("uistroke", {
253
- key = "UIStroke",
254
332
  Thickness = stateful(props.strokeThickness, currentState),
255
333
  Color = stateful(props.strokeColor, currentState),
256
334
  Transparency = stateful(props.strokeTransparency, currentState),
257
335
  }))), React.createElement("uicorner", {
258
- key = "UICorner",
259
336
  CornerRadius = if location.type == "dropdown" then stylesheet.dropdown.iconCornerRadius else stateful(props.cornerRadius, currentState),
260
337
  }), props.disabled and (React.createElement("frame", {
261
- key = "DisabledOverlay",
262
338
  Size = UDim2.fromScale(1, 1),
263
339
  BackgroundTransparency = sizing.disabledOverlayTransparency,
264
340
  BackgroundColor3 = sizing.disabledOverlayColor,
265
341
  BorderSizePixel = 0,
266
342
  ZIndex = 10,
343
+ })), componentProps.notificationCount ~= nil and (React.createElement(Notification, {
344
+ count = componentProps.notificationCount,
267
345
  })))))
268
346
  end
269
347
  return {
@@ -0,0 +1,10 @@
1
+ import React from '@rbxts/react';
2
+ /**
3
+ * A red notification badge that overlays an icon.
4
+ *
5
+ * Displays a count number. Values over 99 show "99+".
6
+ * Hides automatically when `count` is 0 or less.
7
+ */
8
+ export declare function Notification({ count }: {
9
+ count: number;
10
+ }): React.JSX.Element;
@@ -0,0 +1,52 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local React = TS.import(script, TS.getModule(script, "@rbxts", "react"))
4
+ local useStylesheet = TS.import(script, script.Parent.Parent, "context").useStylesheet
5
+ --[[
6
+ *
7
+ * A red notification badge that overlays an icon.
8
+ *
9
+ * Displays a count number. Values over 99 show "99+".
10
+ * Hides automatically when `count` is 0 or less.
11
+
12
+ ]]
13
+ local function Notification(_param)
14
+ local count = _param.count
15
+ local stylesheet = useStylesheet().notification
16
+ if count <= 0 then
17
+ return React.createElement(React.Fragment)
18
+ end
19
+ local exceeded99 = count > 99
20
+ local displayText = if exceeded99 then "99+" else tostring(count)
21
+ return React.createElement("frame", {
22
+ Size = stylesheet.size,
23
+ Position = stylesheet.position,
24
+ AnchorPoint = Vector2.new(1, 0),
25
+ ZIndex = 25,
26
+ AutomaticSize = if exceeded99 then Enum.AutomaticSize.X else nil,
27
+ BackgroundColor3 = stylesheet.backgroundColor,
28
+ BackgroundTransparency = stylesheet.backgroundTransparency,
29
+ BorderSizePixel = 0,
30
+ }, React.createElement("uicorner", {
31
+ CornerRadius = stylesheet.cornerRadius,
32
+ }), React.createElement("uistroke", {
33
+ Color = stylesheet.borderColor,
34
+ Transparency = stylesheet.borderTransparency,
35
+ Thickness = 1,
36
+ }), React.createElement("textlabel", {
37
+ Size = UDim2.new(1, 0, 1, 0),
38
+ AnchorPoint = Vector2.new(0.5, 0.5),
39
+ Position = UDim2.new(0.5, 0, 0.515, 0),
40
+ AutomaticSize = if exceeded99 then Enum.AutomaticSize.X else nil,
41
+ BackgroundTransparency = 1,
42
+ FontFace = stylesheet.fontFace,
43
+ Text = displayText,
44
+ TextColor3 = stylesheet.textColor,
45
+ TextSize = stylesheet.textSize,
46
+ TextWrapped = true,
47
+ ZIndex = 26,
48
+ }))
49
+ end
50
+ return {
51
+ Notification = Notification,
52
+ }
@@ -0,0 +1,10 @@
1
+ import React from '@rbxts/react';
2
+ /**
3
+ * Collects child icons and renders a "more" button (⋯) when they overflow
4
+ * the available space. Overflowing icons appear in a dropdown.
5
+ *
6
+ * Simplified from TopbarPlus v3 overflow handler.
7
+ */
8
+ export declare function Overflow({ children }: {
9
+ children: React.ReactNode;
10
+ }): React.JSX.Element;
@@ -0,0 +1,62 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _react = TS.import(script, TS.getModule(script, "@rbxts", "react"))
4
+ local React = _react
5
+ local useCallback = _react.useCallback
6
+ local useMemo = _react.useMemo
7
+ local useState = _react.useState
8
+ local useStylesheet = TS.import(script, script.Parent.Parent, "context").useStylesheet
9
+ local Icon = TS.import(script, script.Parent, "icon").Icon
10
+ --[[
11
+ *
12
+ * Collects child icons and renders a "more" button (⋯) when they overflow
13
+ * the available space. Overflowing icons appear in a dropdown.
14
+ *
15
+ * Simplified from TopbarPlus v3 overflow handler.
16
+
17
+ ]]
18
+ local function Overflow(_param)
19
+ local children = _param.children
20
+ local stylesheet = useStylesheet()
21
+ local expanded, setExpanded = useState(false)
22
+ local childArray = useMemo(function()
23
+ local arr = React.Children.toArray(children)
24
+ return arr
25
+ end, { children })
26
+ local overflowCount = #childArray
27
+ local handleToggle = useCallback(function()
28
+ setExpanded(function(prev)
29
+ return not prev
30
+ end)
31
+ end, {})
32
+ if overflowCount == 0 then
33
+ return React.createElement(React.Fragment)
34
+ end
35
+ return React.createElement(Icon, {
36
+ imageId = "rbxassetid://6069276526",
37
+ static = false,
38
+ toggleStateOnClick = true,
39
+ contentPaddingX = 4,
40
+ contentPaddingY = 4,
41
+ }, expanded and (React.createElement("frame", {
42
+ Size = UDim2.new(0, 0, 0, 0),
43
+ Position = stylesheet.customDropdown.position or stylesheet.dropdownTheme.position,
44
+ BackgroundColor3 = stylesheet.dropdownTheme.backgroundColor,
45
+ BackgroundTransparency = stylesheet.dropdownTheme.backgroundTransparency,
46
+ BorderSizePixel = stylesheet.dropdownTheme.borderSize,
47
+ BorderColor3 = stylesheet.dropdownTheme.borderColor,
48
+ ZIndex = 50,
49
+ }, React.createElement("uicorner", {
50
+ CornerRadius = stylesheet.dropdownTheme.cornerRadius,
51
+ }), React.createElement("uistroke", {
52
+ Color = stylesheet.dropdownTheme.borderColor,
53
+ Transparency = stylesheet.dropdownTheme.borderTransparency,
54
+ Thickness = stylesheet.dropdownTheme.borderSize,
55
+ }), React.createElement("uilistlayout", {
56
+ SortOrder = Enum.SortOrder.LayoutOrder,
57
+ Padding = UDim.new(0, 2),
58
+ }), childArray)))
59
+ end
60
+ return {
61
+ Overflow = Overflow,
62
+ }
@@ -1,11 +1,26 @@
1
1
  import React from '@rbxts/react';
2
2
  export type SelectionMode = 'Single' | 'Multiple';
3
+ /** Docks icons to the left side of the topbar. */
3
4
  export declare function LeftDock({ children }: React.PropsWithChildren): React.JSX.Element;
5
+ /** Docks icons to the center of the topbar. */
4
6
  export declare function CenterDock({ children }: React.PropsWithChildren): React.JSX.Element;
7
+ /** Docks icons to the right side of the topbar. */
5
8
  export declare function RightDock({ children }: React.PropsWithChildren): React.JSX.Element;
6
9
  interface ProviderProps extends React.PropsWithChildren {
10
+ /**
11
+ * How icon selection works across the topbar.
12
+ * `Single` deselects all other icons when one is selected;
13
+ * `Multiple` allows any number to be selected at once.
14
+ */
7
15
  selectionMode?: SelectionMode;
16
+ /** When `true`, shows a "Beta" label if voice chat is enabled on the client. */
8
17
  gameVoiceChatEnabled?: boolean;
9
18
  }
10
- export declare function TopbarProvider({ selectionMode, gameVoiceChatEnabled, children, }: ProviderProps): React.JSX.Element;
19
+ /**
20
+ * Root provider for the topbar system.
21
+ *
22
+ * Must wrap all topbar components. Manages global selection state,
23
+ * gui inset tracking, and the topbar frame bounds.
24
+ */
25
+ export declare function TopbarProvider({ selectionMode, gameVoiceChatEnabled, children }: ProviderProps): React.JSX.Element;
11
26
  export {};
@@ -9,7 +9,12 @@ local useStylesheet = _context.useStylesheet
9
9
  local useGuiInset = TS.import(script, script.Parent.Parent, "hooks", "use-gui-inset").useGuiInset
10
10
  local useVoicechatEnabled = TS.import(script, script.Parent.Parent, "hooks", "use-voicechat-enabled").useVoicechatEnabled
11
11
  local debugLog = TS.import(script, script.Parent.Parent, "utilities", "debug").debugLog
12
- -- ── Dock container components ───────────────────────────────────────────────
12
+ --[[
13
+ *
14
+ * Anchors children to a side of the topbar.
15
+ * Uses a horizontal list layout with icon spacing from the stylesheet.
16
+
17
+ ]]
13
18
  local function DockFrame(props)
14
19
  local stylesheet = useStylesheet().provider
15
20
  return React.createElement("frame", {
@@ -28,6 +33,7 @@ local function DockFrame(props)
28
33
  VerticalAlignment = Enum.VerticalAlignment.Center,
29
34
  }), props.children)
30
35
  end
36
+ --* Docks icons to the left side of the topbar.
31
37
  local function LeftDock(_param)
32
38
  local children = _param.children
33
39
  return React.createElement(DockFrame, {
@@ -36,6 +42,7 @@ local function LeftDock(_param)
36
42
  children = children,
37
43
  })
38
44
  end
45
+ --* Docks icons to the center of the topbar.
39
46
  local function CenterDock(_param)
40
47
  local children = _param.children
41
48
  local stylesheet = useStylesheet().provider
@@ -47,6 +54,7 @@ local function CenterDock(_param)
47
54
  children = children,
48
55
  })
49
56
  end
57
+ --* Docks icons to the right side of the topbar.
50
58
  local function RightDock(_param)
51
59
  local children = _param.children
52
60
  local stylesheet = useStylesheet().provider
@@ -57,7 +65,14 @@ local function RightDock(_param)
57
65
  children = children,
58
66
  })
59
67
  end
60
- -- ── Provider ────────────────────────────────────────────────────────────────
68
+ --[[
69
+ *
70
+ * Root provider for the topbar system.
71
+ *
72
+ * Must wrap all topbar components. Manages global selection state,
73
+ * gui inset tracking, and the topbar frame bounds.
74
+
75
+ ]]
61
76
  local function TopbarProvider(_param)
62
77
  local selectionMode = _param.selectionMode
63
78
  if selectionMode == nil then
@@ -120,14 +135,12 @@ local function TopbarProvider(_param)
120
135
  end,
121
136
  },
122
137
  }, React.createElement("frame", {
123
- key = "TopbarProvider",
124
138
  BackgroundTransparency = stylesheet.backgroundTransparency,
125
139
  BackgroundColor3 = stylesheet.backgroundColor,
126
140
  Size = UDim2.fromOffset(inset.Width * stylesheet.sizeScale.X, frameHeight * stylesheet.sizeScale.Y),
127
141
  AnchorPoint = stylesheet.anchorPoint,
128
142
  Position = stylesheet.position,
129
143
  }, React.createElement("uipadding", {
130
- key = "UIPadding",
131
144
  PaddingLeft = UDim.new(0, leftPadding),
132
145
  PaddingRight = UDim.new(0, stylesheet.paddingRight),
133
146
  PaddingTop = UDim.new(0, stylesheet.paddingTop),
@@ -2,9 +2,16 @@ import React from '@rbxts/react';
2
2
  import { DefaultStylesheet, type Stylesheet as StylesheetType } from '../style';
3
3
  import type { DeepPartial } from '../utilities/types';
4
4
  interface Props extends React.PropsWithChildren {
5
+ /** A partial stylesheet that is deep-merged over the defaults. */
5
6
  stylesheet: PartialStylesheet;
6
7
  }
7
8
  type PartialStylesheet = DeepPartial<StylesheetType>;
8
9
  export { DefaultStylesheet };
9
10
  export type { StylesheetType };
11
+ /**
12
+ * Applies a custom stylesheet to all descendant topbar components.
13
+ *
14
+ * Only the properties you provide will override the defaults — missing
15
+ * keys fall back to {@link DefaultStylesheet}.
16
+ */
10
17
  export declare function Stylesheet({ stylesheet, children }: Props): React.JSX.Element;
@@ -3,7 +3,15 @@ local TS = _G[script]
3
3
  local React = TS.import(script, TS.getModule(script, "@rbxts", "react"))
4
4
  local StylesheetContext = TS.import(script, script.Parent.Parent, "context").StylesheetContext
5
5
  local DefaultStylesheet = TS.import(script, script.Parent.Parent, "style").DefaultStylesheet
6
- local reconcile = TS.import(script, script.Parent.Parent, "utilities", "merge")
6
+ local reconcile = TS.import(script, script.Parent.Parent, "utilities", "merge").default
7
+ --[[
8
+ *
9
+ * Applies a custom stylesheet to all descendant topbar components.
10
+ *
11
+ * Only the properties you provide will override the defaults — missing
12
+ * keys fall back to {@link DefaultStylesheet}.
13
+
14
+ ]]
7
15
  local function Stylesheet(_param)
8
16
  local stylesheet = _param.stylesheet
9
17
  local children = _param.children
@@ -0,0 +1,14 @@
1
+ import React from '@rbxts/react';
2
+ type TooltipSide = 'top' | 'bottom' | 'left' | 'right';
3
+ interface TooltipProps {
4
+ /** Text displayed inside the tooltip. */
5
+ content: string;
6
+ /** The element that triggers the tooltip on hover. */
7
+ children: React.ReactNode;
8
+ /** Which side of the child the tooltip appears on. */
9
+ side?: TooltipSide;
10
+ /** Delay in milliseconds before the tooltip becomes visible. */
11
+ delayMs?: number;
12
+ }
13
+ export declare function Tooltip({ content, children, side, delayMs }: TooltipProps): React.JSX.Element;
14
+ export {};
@@ -0,0 +1,99 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _react = TS.import(script, TS.getModule(script, "@rbxts", "react"))
4
+ local React = _react
5
+ local useState = _react.useState
6
+ local useStylesheet = TS.import(script, script.Parent.Parent, "context").useStylesheet
7
+ local positionMap = {
8
+ top = UDim2.new(0.5, 0, 0, -4),
9
+ bottom = UDim2.new(0.5, 0, 1, 4),
10
+ left = UDim2.new(0, -4, 0.5, 0),
11
+ right = UDim2.new(1, 4, 0.5, 0),
12
+ }
13
+ local anchorMap = {
14
+ top = Vector2.new(0.5, 1),
15
+ bottom = Vector2.new(0.5, 0),
16
+ left = Vector2.new(1, 0.5),
17
+ right = Vector2.new(0, 0.5),
18
+ }
19
+ local function Tooltip(_param)
20
+ local content = _param.content
21
+ local children = _param.children
22
+ local side = _param.side
23
+ if side == nil then
24
+ side = "top"
25
+ end
26
+ local delayMs = _param.delayMs
27
+ local stylesheet = useStylesheet().tooltip
28
+ local visible, setVisible = useState(false)
29
+ local showTimer, setShowTimer = useState(nil)
30
+ local _condition = delayMs
31
+ if _condition == nil then
32
+ _condition = stylesheet.delayMs
33
+ end
34
+ local delay = _condition
35
+ local handleMouseEnter = function()
36
+ local timer = task.delay(delay / 1000, function()
37
+ setVisible(true)
38
+ end)
39
+ setShowTimer(timer)
40
+ end
41
+ local handleMouseLeave = function()
42
+ if showTimer then
43
+ task.cancel(showTimer)
44
+ end
45
+ setVisible(false)
46
+ end
47
+ return React.createElement("frame", {
48
+ Size = UDim2.new(0, 0, 0, 0),
49
+ AutomaticSize = Enum.AutomaticSize.XY,
50
+ BackgroundTransparency = 1,
51
+ ClipsDescendants = true,
52
+ }, React.createElement("textbutton", {
53
+ Size = UDim2.new(0, 0, 0, 0),
54
+ AutomaticSize = Enum.AutomaticSize.XY,
55
+ AutoButtonColor = false,
56
+ Text = "",
57
+ BackgroundTransparency = 1,
58
+ BorderSizePixel = 0,
59
+ Event = {
60
+ MouseEnter = handleMouseEnter,
61
+ MouseLeave = handleMouseLeave,
62
+ },
63
+ }, children), visible and (React.createElement("frame", {
64
+ Position = positionMap[side],
65
+ AnchorPoint = anchorMap[side],
66
+ Size = UDim2.new(0, 0, 0, 0),
67
+ AutomaticSize = Enum.AutomaticSize.XY,
68
+ BackgroundColor3 = stylesheet.backgroundColor,
69
+ BackgroundTransparency = stylesheet.backgroundTransparency,
70
+ BorderSizePixel = 0,
71
+ ZIndex = 100,
72
+ ClipsDescendants = true,
73
+ }, React.createElement("uicorner", {
74
+ CornerRadius = stylesheet.cornerRadius,
75
+ }), React.createElement("uistroke", {
76
+ Color = stylesheet.borderColor,
77
+ Transparency = stylesheet.borderTransparency,
78
+ Thickness = stylesheet.borderSize,
79
+ }), React.createElement("uipadding", {
80
+ PaddingTop = UDim.new(0, stylesheet.paddingY),
81
+ PaddingBottom = UDim.new(0, stylesheet.paddingY),
82
+ PaddingLeft = UDim.new(0, stylesheet.paddingX),
83
+ PaddingRight = UDim.new(0, stylesheet.paddingX),
84
+ }), React.createElement("textlabel", {
85
+ Size = UDim2.new(0, 0, 0, 0),
86
+ AutomaticSize = Enum.AutomaticSize.XY,
87
+ BackgroundTransparency = 1,
88
+ BorderSizePixel = 0,
89
+ FontFace = stylesheet.fontFace,
90
+ Text = content,
91
+ TextColor3 = stylesheet.textColor,
92
+ TextSize = stylesheet.textSize,
93
+ TextXAlignment = Enum.TextXAlignment.Center,
94
+ TextYAlignment = Enum.TextYAlignment.Center,
95
+ }))))
96
+ end
97
+ return {
98
+ Tooltip = Tooltip,
99
+ }
@@ -1,8 +1,7 @@
1
1
  import { type Binding } from '@rbxts/react';
2
- import type { MotionGoal } from '@rbxts/ripple';
3
2
  import type { FromStateDependent, IconState, StateDependent } from '../components/icon';
4
- type Result<T extends Record<string, StateDependent<MotionGoal>>, K extends keyof T> = ExcludeMembers<{
3
+ type Result<T extends Record<string, StateDependent<unknown>>, K extends keyof T> = ExcludeMembers<{
5
4
  [P in keyof T]: P extends K ? Binding<NonNullable<FromStateDependent<T[P]>>> : undefined;
6
5
  }, undefined>;
7
- export declare function useAnimateableProps<T extends Record<string, StateDependent<MotionGoal>>, K extends keyof T>(state: IconState, props: T, ...keys: K[]): Result<T, K>;
6
+ export declare function useAnimateableProps<T extends Record<string, StateDependent<unknown>>, K extends keyof T>(state: IconState, props: T, ...keys: K[]): Result<T, K>;
8
7
  export {};
@@ -1,6 +1,6 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
- local Object = TS.import(script, TS.getModule(script, "@rbxts", "object-utils"))
3
+ local object = TS.import(script, TS.getModule(script, "@rbxts", "object-utils"))
4
4
  local _pretty_react_hooks = TS.import(script, TS.getModule(script, "@rbxts", "pretty-react-hooks").out)
5
5
  local useMotion = _pretty_react_hooks.useMotion
6
6
  local usePrevious = _pretty_react_hooks.usePrevious
@@ -12,8 +12,7 @@ local function useAnimateableProps(state, props, ...)
12
12
  local previousProps = usePrevious(props)
13
13
  local motions = {}
14
14
  for _, key in keys do
15
- local binding, motion = useMotion(stateful(props[key], state))
16
- local _arg0 = { binding, motion }
15
+ local _arg0 = useMotion(stateful(props[key], state))
17
16
  table.insert(motions, _arg0)
18
17
  end
19
18
  useEffect(function()
@@ -23,7 +22,7 @@ local function useAnimateableProps(state, props, ...)
23
22
  if value == previousValue then
24
23
  continue
25
24
  end
26
- motions[table.find(keys, key) or 0][2]:spring(value, springs.responsive)
25
+ (motions[table.find(keys, key) or 0][2]).spring(value, springs.responsive)
27
26
  end
28
27
  end, { props })
29
28
  -- ▼ ReadonlyArray.map ▼
@@ -39,7 +38,7 @@ local function useAnimateableProps(state, props, ...)
39
38
  _newValue[_k] = _callback(_v, _k - 1, keys)
40
39
  end
41
40
  -- ▲ ReadonlyArray.map ▲
42
- return Object.fromEntries(_newValue)
41
+ return object.fromEntries(_newValue)
43
42
  end
44
43
  return {
45
44
  useAnimateableProps = useAnimateableProps,
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Calls `callback` whenever the given `keycode` is pressed.
3
+ *
4
+ * Listens on `UserInputService.InputBegan` and ignores game-processed input.
5
+ */
6
+ export declare function useToggleKey(keycode: Enum.KeyCode, callback: () => void): void;
@@ -0,0 +1,29 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local useEffect = TS.import(script, TS.getModule(script, "@rbxts", "react")).useEffect
4
+ local UserInputService = TS.import(script, TS.getModule(script, "@rbxts", "services")).UserInputService
5
+ --[[
6
+ *
7
+ * Calls `callback` whenever the given `keycode` is pressed.
8
+ *
9
+ * Listens on `UserInputService.InputBegan` and ignores game-processed input.
10
+
11
+ ]]
12
+ local function useToggleKey(keycode, callback)
13
+ useEffect(function()
14
+ local connection = UserInputService.InputBegan:Connect(function(input, gameProcessed)
15
+ if gameProcessed then
16
+ return nil
17
+ end
18
+ if input.KeyCode == keycode then
19
+ callback()
20
+ end
21
+ end)
22
+ return function()
23
+ return connection:Disconnect()
24
+ end
25
+ end, { keycode })
26
+ end
27
+ return {
28
+ useToggleKey = useToggleKey,
29
+ }