@nrbx/topbar-components 1.2.3 → 1.3.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 CHANGED
@@ -1,6 +1,5 @@
1
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"/>
2
+ <img src="https://r2.nrbx.nn140.uk/img/NRBX-Banner.png" alt="NRBX logo" width="1000"/>
4
3
  <br />
5
4
  <br />
6
5
  <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">
@@ -93,13 +92,27 @@ Dock containers (`<LeftDock>`, `<CenterDock>`, `<RightDock>`) are still availabl
93
92
 
94
93
  The disabled overlay transparency and color are configurable via the stylesheet `sizing` section.
95
94
 
95
+ #### Click behaviour and the `toggle` prop
96
+
97
+ By default an icon is a **single-click button**: clicking it briefly lights up the background (and darkens the label) and then animates back, firing `onClick` once. It does not change its selection state.
98
+
99
+ ```tsx
100
+ <Icon text="Home" onClick={() => goHome()} /> {/* standard click button */}
101
+ ```
102
+
103
+ To make an icon a persistent toggle, pass the `toggle` prop. Clicking it switches between selected and deselected: `selected` fires when toggled on (button stays lit) and `deselected` fires when toggled off.
104
+
105
+ ```tsx
106
+ <Icon text="Mute" toggle selected={() => mute()} deselected={() => unmute()} />
107
+ ```
108
+
96
109
  #### Dropdowns
97
110
 
98
111
  You can add a dropdown to an icon by mounting `<Dropdown />` component as it's child.
99
112
  Dropdowns & TopbarProvider have a property called `selectionMode`, which lets you specify how many icons can be selected at once.
100
113
 
101
114
  ```tsx
102
- <Icon text="Skins">
115
+ <Icon text="Skins" toggle>
103
116
  <Dropdown selectionMode="Single">
104
117
  <Icon text="yellow" selected={() => chooseSkin("yellow")} />
105
118
  <Icon text="red" selected={() => chooseSkin("red")} />
@@ -107,7 +120,7 @@ Dropdowns & TopbarProvider have a property called `selectionMode`, which lets yo
107
120
  </Icon>
108
121
  ```
109
122
 
110
- Dropdowns **can be nested.**
123
+ Because a dropdown stays open while its parent icon is selected, the parent must opt in to toggle behaviour with the `toggle` prop. Dropdowns **can be nested.**
111
124
 
112
125
  #### Hover animations
113
126
 
@@ -142,10 +155,10 @@ Badge styling (colors, position, corner radius, border) is controlled via `style
142
155
 
143
156
  #### Toggle keys
144
157
 
145
- Bind a keyboard key to toggle an icon on/off with the `toggleKey` prop:
158
+ Bind a keyboard key to toggle an icon on/off with the `toggleKey` prop (requires the `toggle` prop):
146
159
 
147
160
  ```tsx
148
- <Icon text="Menu" toggleKey={Enum.KeyCode.M} toggleStateOnClick />
161
+ <Icon text="Menu" toggleKey={Enum.KeyCode.M} toggle />
149
162
  ```
150
163
 
151
164
  Any `Enum.KeyCode` value works. The key binding only activates while the icon is mounted.
@@ -173,7 +186,7 @@ Tooltip appearance (colors, font, padding, border) is controlled via `stylesheet
173
186
  Use `<CustomDropdown>` when you need to show rich, arbitrary content in a dropdown panel - not just child `<Icon>` elements:
174
187
 
175
188
  ```tsx
176
- <Icon text="Info" toggleStateOnClick>
189
+ <Icon text="Info" toggle>
177
190
  <CustomDropdown width={250} maxHeight={180}>
178
191
  <textlabel Text="Custom content here!" />
179
192
  <textbutton Text="A button" />
@@ -237,7 +250,7 @@ import { Stylesheet } from "@nrbx/topbar-components";
237
250
  }}>
238
251
  <TopbarProvider>
239
252
  <LeftDock>
240
- <Icon text="Skins">
253
+ <Icon text="Skins" toggle>
241
254
  <Dropdown selectionMode="Single">
242
255
  <Icon text="yellow" selected={() => chooseSkin("yellow")} />
243
256
  <Icon text="red" selected={() => chooseSkin("red")} />
@@ -382,6 +395,5 @@ Package is licensed under the MIT License.
382
395
  <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">
383
396
  <br />
384
397
  <br />
385
- <img src="https://github.com/nn140/Branding/blob/main/LogoBlack-Full.png?raw=true" alt="NN140.UK logo" width="800"/>
386
- <img src="https://github.com/nn140/Branding/blob/main/LogoWhite-Full.png?raw=true" alt="NN140.UK logo" width="800"/>
398
+ <img src="https://r2.nrbx.nn140.uk/img/NRBX-Banner.png" alt="NRBX logo" width="1000"/>
387
399
  </div>
@@ -55,8 +55,12 @@ export interface IconProps extends React.PropsWithChildren {
55
55
  textAlignment?: StateDependent<Enum.TextXAlignment>;
56
56
  /** Whether the label supports rich text markup. */
57
57
  richText?: StateDependent<boolean>;
58
- /** When `true`, clicking the icon toggles between selected and deselected. */
59
- toggleStateOnClick?: boolean;
58
+ /**
59
+ * When `true`, clicking the icon toggles between selected and deselected.
60
+ * When `false` (the default), clicking briefly lights the icon up and fires
61
+ * {@link onClick} once without changing the selection state.
62
+ */
63
+ toggle?: boolean;
60
64
  /** When `true`, the icon ignores all clicks, hover, and state changes. */
61
65
  static?: boolean;
62
66
  /** When `true`, the icon is dimmed and non-interactive. */
@@ -85,7 +89,7 @@ export interface IconProps extends React.PropsWithChildren {
85
89
  unhover?: () => void;
86
90
  /** Callback fired whenever the state changes (receives the new state). */
87
91
  stateChanged?: (state: IconState) => void;
88
- /** Callback fired on left click (in addition to the built-in toggle). */
92
+ /** Callback fired on left click. */
89
93
  onClick?: () => void;
90
94
  /** Callback fired on right click. */
91
95
  onRightClick?: () => void;
@@ -33,7 +33,8 @@ local Notification = TS.import(script, script.Parent, "notification").Notificati
33
33
  * both states, or `{ deselected: T; selected: T }` to animate between them.
34
34
 
35
35
  ]]
36
- local ANIMATEABLE = { "backgroundColor", "backgroundTransparency", "imageColor", "imageTransparency", "textTransparency" }
36
+ local ANIMATEABLE = { "backgroundColor", "backgroundTransparency", "imageColor", "imageTransparency", "textColor", "textTransparency" }
37
+ local FLASH_DURATION = 0.35
37
38
  --* Icon select/deselect state.
38
39
  --[[
39
40
  *
@@ -61,6 +62,9 @@ local function Icon(componentProps)
61
62
  local location = useLocation()
62
63
  local id = useId()
63
64
  local currentState, setState = useState(componentProps.forcedState or "deselected")
65
+ --* Transiently lights the icon up on a single click when not in toggle mode.
66
+ local flash, setFlash = useState(false)
67
+ local flashTimer = useRef()
64
68
  local hovered, setHovered = useState(false)
65
69
  local hoverLift, hoverLiftMotion = useMotion(0)
66
70
  local dropdownAnimating, setAnimationState = useState(false)
@@ -76,12 +80,13 @@ local function Icon(componentProps)
76
80
  _object[_k] = _v
77
81
  end
78
82
  local sizing = _object
83
+ local animateState = if flash then "selected" else currentState
79
84
  local _object_1 = table.clone(stylesheet.icon)
80
85
  setmetatable(_object_1, nil)
81
86
  for _k, _v in componentProps do
82
87
  _object_1[_k] = _v
83
88
  end
84
- local animatedProps = useAnimateableProps(currentState, _object_1, unpack(ANIMATEABLE))
89
+ local animatedProps = useAnimateableProps(animateState, _object_1, unpack(ANIMATEABLE))
85
90
  local _object_2 = table.clone(stylesheet.icon)
86
91
  setmetatable(_object_2, nil)
87
92
  for _k, _v in (style or {}) do
@@ -140,7 +145,7 @@ local function Icon(componentProps)
140
145
  if props.static then
141
146
  return nil
142
147
  end
143
- if stateful(props.toggleStateOnClick, currentState) then
148
+ if props.toggle then
144
149
  setState(if currentState == "deselected" then "selected" else "deselected")
145
150
  end
146
151
  end)
@@ -227,6 +232,11 @@ local function Icon(componentProps)
227
232
  local _vector2_1 = Vector2.new(0, if includeContents then contentSize.Y else 0)
228
233
  location.registerChild(id, _vector2 + _vector2_1)
229
234
  end, { currentState, contentSize.Y, dropdownAnimating, iconSize })
235
+ useUnmountEffect(function()
236
+ if flashTimer.current then
237
+ task.cancel(flashTimer.current)
238
+ end
239
+ end)
230
240
  useUnmountEffect(function()
231
241
  if props.static then
232
242
  return nil
@@ -269,8 +279,17 @@ local function Icon(componentProps)
269
279
  if props.static then
270
280
  return nil
271
281
  end
272
- if stateful(props.toggleStateOnClick, currentState) then
282
+ if props.toggle then
273
283
  setState(if currentState == "deselected" then "selected" else "deselected")
284
+ else
285
+ if flashTimer.current then
286
+ task.cancel(flashTimer.current)
287
+ end
288
+ setFlash(true)
289
+ flashTimer.current = task.delay(FLASH_DURATION, function()
290
+ flashTimer.current = nil
291
+ setFlash(false)
292
+ end)
274
293
  end
275
294
  props.onClick()
276
295
  local soundId = stateful(props.leftClickSound, currentState)
@@ -333,7 +352,7 @@ local function Icon(componentProps)
333
352
  })), currentText ~= nil and currentText ~= "" and (React.createElement("textlabel", {
334
353
  FontFace = stateful(props.fontFace, currentState),
335
354
  TextSize = stateful(props.textSize, currentState),
336
- TextColor3 = stateful(props.textColor, currentState),
355
+ TextColor3 = animatedProps.textColor,
337
356
  TextWrapped = false,
338
357
  Size = UDim2.fromOffset(textBounds.X, 0),
339
358
  AutomaticSize = Enum.AutomaticSize.Y,
@@ -32,7 +32,7 @@ local function Overflow(_param)
32
32
  end
33
33
  return React.createElement(Icon, {
34
34
  imageId = "rbxassetid://6069276526",
35
- toggleStateOnClick = true,
35
+ toggle = true,
36
36
  defaultState = "deselected",
37
37
  stateChanged = handleToggle,
38
38
  contentPaddingX = 4,
package/out/style.luau CHANGED
@@ -49,7 +49,7 @@ local DefaultStylesheet = {
49
49
  text = "",
50
50
  defaultState = "deselected",
51
51
  forcedState = "deselected",
52
- toggleStateOnClick = true,
52
+ toggle = false,
53
53
  static = false,
54
54
  disabled = false,
55
55
  toggleKey = Enum.KeyCode.None,
@@ -87,7 +87,7 @@ local DefaultStylesheet = {
87
87
  provider = {
88
88
  paddingLeft = 8,
89
89
  paddingRight = 12,
90
- paddingTop = 11,
90
+ paddingTop = 12,
91
91
  paddingBottom = 0,
92
92
  iconSpacing = 12,
93
93
  backgroundTransparency = 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nrbx/topbar-components",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "main": "out/init.luau",
5
5
  "scripts": {
6
6
  "build": "rbxtsc",
@@ -26,16 +26,16 @@
26
26
  ],
27
27
  "repository": {
28
28
  "type": "git",
29
- "url": "git+https://github.com/ninjaninja140/rbxts-topbar-components.git"
29
+ "url": "git+https://github.com/nrbx-ts/rbxts-topbar-components.git"
30
30
  },
31
31
  "publishConfig": {
32
32
  "access": "public"
33
33
  },
34
34
  "devDependencies": {
35
- "@biomejs/biome": "^2.5.7",
35
+ "@biomejs/biome": "^2.5.8",
36
36
  "@rbxts/compiler-types": "^3.0.0-types.0",
37
37
  "@rbxts/react": "^17.3.7-ts.2",
38
- "@rbxts/types": "^1.0.942",
38
+ "@rbxts/types": "^1.0.943",
39
39
  "roblox-ts": "^3.0.0",
40
40
  "typescript": "^6.0.3"
41
41
  },