@rbxts/react-clean-ui 1.0.19 → 1.0.38

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.
Files changed (99) hide show
  1. package/out/Components/Chart/BarChart.d.ts +47 -0
  2. package/out/Components/Chart/BarChart.luau +880 -0
  3. package/out/Components/Chart/Pie.luau +12 -12
  4. package/out/Components/Chart/index.d.ts +1 -0
  5. package/out/Components/Chart/init.luau +3 -0
  6. package/out/Components/Input/Button.d.ts +9 -0
  7. package/out/Components/Input/Button.luau +155 -46
  8. package/out/Components/Input/Checkbox.luau +21 -5
  9. package/out/Components/Input/Increment.d.ts +11 -0
  10. package/out/Components/Input/Increment.luau +107 -0
  11. package/out/Components/Input/Increment.step.d.ts +1 -0
  12. package/out/Components/Input/Increment.step.luau +25 -0
  13. package/out/Components/Input/Input.d.ts +4 -3
  14. package/out/Components/Input/Input.luau +124 -17
  15. package/out/Components/Input/Input.validation.d.ts +3 -0
  16. package/out/Components/Input/Input.validation.luau +21 -0
  17. package/out/Components/Input/Select.d.ts +4 -3
  18. package/out/Components/Input/Select.luau +23 -18
  19. package/out/Components/Input/Slider.d.ts +1 -1
  20. package/out/Components/Input/Slider.luau +68 -34
  21. package/out/Components/Input/index.d.ts +1 -0
  22. package/out/Components/Input/init.luau +3 -0
  23. package/out/Components/Interaction/Toast.d.ts +1 -1
  24. package/out/Components/Interaction/Toast.luau +4 -4
  25. package/out/Components/Interaction/Tooltip.d.ts +1 -1
  26. package/out/Components/Interaction/Tooltip.luau +8 -1
  27. package/out/Components/Layout/Accordion.d.ts +34 -0
  28. package/out/Components/Layout/Accordion.luau +431 -0
  29. package/out/Components/Layout/Container.d.ts +4 -2
  30. package/out/Components/Layout/Container.luau +32 -7
  31. package/out/Components/Layout/Draggable.luau +1 -1
  32. package/out/Components/Layout/Fieldset.d.ts +1 -1
  33. package/out/Components/Layout/FlexItem.d.ts +2 -1
  34. package/out/Components/Layout/FlexItem.luau +1 -0
  35. package/out/Components/Layout/HStack.d.ts +2 -1
  36. package/out/Components/Layout/HStack.luau +1 -2
  37. package/out/Components/Layout/Scroller.d.ts +1 -0
  38. package/out/Components/Layout/Scroller.luau +1 -1
  39. package/out/Components/Layout/Tabs.d.ts +1 -1
  40. package/out/Components/Layout/Tabs.luau +37 -7
  41. package/out/Components/Layout/VStack.d.ts +4 -1
  42. package/out/Components/Layout/VStack.luau +2 -2
  43. package/out/Components/Layout/index.d.ts +1 -0
  44. package/out/Components/Layout/init.luau +3 -0
  45. package/out/Components/Navigation/Menu.d.ts +1 -1
  46. package/out/Components/Surface/Box.d.ts +5 -3
  47. package/out/Components/Surface/Box.luau +12 -3
  48. package/out/Components/Surface/Card.d.ts +8 -6
  49. package/out/Components/Surface/Card.luau +430 -26
  50. package/out/Components/Surface/Icon.d.ts +1 -1
  51. package/out/Components/Surface/Icon.luau +43 -4
  52. package/out/Components/Typography/Text.d.ts +1 -0
  53. package/out/Components/Typography/Text.luau +135 -13
  54. package/out/Contexts/row.context.d.ts +1 -1
  55. package/out/Contexts/theme.context.d.ts +2 -2
  56. package/out/Helpers/color.helper.d.ts +5 -3
  57. package/out/Helpers/color.helper.luau +62 -34
  58. package/out/Helpers/create-ui-story.d.ts +8 -6
  59. package/out/Helpers/create-ui-story.luau +48 -5
  60. package/out/Helpers/css.helper.d.ts +28 -2
  61. package/out/Helpers/css.helper.luau +208 -0
  62. package/out/Helpers/index.d.ts +1 -0
  63. package/out/Helpers/init.luau +3 -0
  64. package/out/Helpers/size.helper.d.ts +3 -1
  65. package/out/Helpers/size.helper.luau +10 -9
  66. package/out/Helpers/spacing.helper.d.ts +3 -3
  67. package/out/Helpers/typography.helper.d.ts +2 -2
  68. package/out/Interfaces/css.types.d.ts +28 -0
  69. package/out/Interfaces/css.types.luau +17 -0
  70. package/out/Interfaces/element.props.d.ts +61 -0
  71. package/out/Interfaces/element.props.luau +1 -0
  72. package/out/Interfaces/index.d.ts +4 -2
  73. package/out/Interfaces/init.luau +9 -3
  74. package/out/Interfaces/responsive.types.d.ts +26 -0
  75. package/out/Interfaces/semantics.d.ts +3 -0
  76. package/out/Interfaces/semantics.luau +2 -0
  77. package/out/Providers/app.provider.d.ts +2 -2
  78. package/out/Providers/theme.provider.d.ts +2 -2
  79. package/out/Theme/index.d.ts +1 -0
  80. package/out/Theme/theme.factory.d.ts +3 -3
  81. package/out/Theme/theme.style.d.ts +48 -0
  82. package/out/Theme/theme.style.luau +1 -0
  83. package/out/Theme/theme.template.d.ts +90 -36
  84. package/out/Theme/themes/dark.theme.d.ts +1 -1
  85. package/out/Theme/themes/dark.theme.luau +73 -1
  86. package/out/Theme/themes/default.theme.d.ts +2 -2
  87. package/out/Theme/themes/default.theme.luau +122 -12
  88. package/out/Theme/themes/index.d.ts +1 -0
  89. package/out/Theme/themes/init.luau +3 -0
  90. package/out/Theme/themes/sandstone.theme.d.ts +1 -1
  91. package/out/Theme/themes/sandstone.theme.luau +81 -10
  92. package/out/Theme/themes/wooden.theme.d.ts +1 -0
  93. package/out/Theme/themes/wooden.theme.luau +443 -0
  94. package/out/index.d.ts +8 -8
  95. package/package.json +20 -6
  96. package/out/Interfaces/clean.element.props.d.ts +0 -114
  97. /package/out/{Interfaces/userinput.d.ts → Helpers/input.helper.d.ts} +0 -0
  98. /package/out/{Interfaces/userinput.luau → Helpers/input.helper.luau} +0 -0
  99. /package/out/Interfaces/{clean.element.props.luau → responsive.types.luau} +0 -0
@@ -9,34 +9,196 @@ local VStack = _Layout.VStack
9
9
  local _Helpers = TS.import(script, script.Parent.Parent.Parent, "Helpers")
10
10
  local ColorHelper = _Helpers.ColorHelper
11
11
  local CssHelper = _Helpers.CssHelper
12
+ local SizeHelper = _Helpers.SizeHelper
12
13
  local SpacingHelper = _Helpers.SpacingHelper
13
14
  local CleanThemeContext = TS.import(script, script.Parent.Parent.Parent, "Contexts").CleanThemeContext
14
15
  local Padding = TS.import(script, script.Parent.Parent, "Decorator").Padding
15
16
  local CardContext = React.createContext({})
17
+ -- Resolves a theme-driven CssPosition into raw Position/AnchorPoint offsets.
18
+ -- This is a one-off resolver rather than a SizeHelper addition because
19
+ -- CssPosition's `center` is axis-aware (`"x"` / `"y"`) so it can express
20
+ -- "center horizontally, anchor to the top with an offset" — something
21
+ -- SizeHelper.GetPosition/GetAnchor can't do since they apply `center`
22
+ -- identically to both axes, and that symmetric behavior is relied on
23
+ -- elsewhere in the codebase so isn't something to widen here.
24
+ local function resolveCssPositionOffset(position)
25
+ local centerX = position.center == true or position.center == "x"
26
+ local centerY = position.center == true or position.center == "y"
27
+ local anchorX = if centerX then 0.5 else (if position.left == nil and position.right ~= nil then 1 else 0)
28
+ local anchorY = if centerY then 0.5 else (if position.top == nil and position.bottom ~= nil then 1 else 0)
29
+ local posX = if centerX then UDim.new(0.5, 0) elseif position.left ~= nil then SizeHelper:toUDim(position.left) elseif position.right ~= nil then UDim.new(1 - SizeHelper:toUDim(position.right).Scale, -SizeHelper:toUDim(position.right).Offset) else UDim.new(0, 0)
30
+ local posY = if centerY then UDim.new(0.5, 0) elseif position.top ~= nil then SizeHelper:toUDim(position.top) elseif position.bottom ~= nil then UDim.new(1 - SizeHelper:toUDim(position.bottom).Scale, -SizeHelper:toUDim(position.bottom).Offset) else UDim.new(0, 0)
31
+ -- A theme-driven fixed/relative width (only meaningful in overlay mode,
32
+ -- see CssPosition.width) forces a fixed-width/auto-height Size, taking
33
+ -- over the caller's default Size={fromScale(0,0)}/AutomaticSize={XY} —
34
+ -- otherwise Size/AutomaticSize are left undefined so the caller's own
35
+ -- default applies unchanged. Parsed via CssHelper.parseCssSize (not
36
+ -- SizeHelper.toUDim, used for top/left/right/bottom above) since it's
37
+ -- the one that understands the "<percent>% - <px>px" calc() shape a
38
+ -- width value may use (e.g. "100% - 50px").
39
+ --
40
+ -- When `width` isn't explicit but both `left` and `right` are set, imply
41
+ -- a width from them the same way CSS does for an absolutely-positioned
42
+ -- element: width = 100% - left - right. Explicit `width` always wins if
43
+ -- somehow both are set.
44
+ local impliedWidth = if position.width == nil and position.left ~= nil and position.right ~= nil then (function()
45
+ local leftUDim = SizeHelper:toUDim(position.left)
46
+ local rightUDim = SizeHelper:toUDim(position.right)
47
+ return UDim.new(1 - leftUDim.Scale - rightUDim.Scale, -leftUDim.Offset - rightUDim.Offset)
48
+ end)() else nil
49
+ local sizeOverride = if position.width ~= nil then {
50
+ Size = UDim2.new(CssHelper:parseCssSize(position.width), UDim.new(0, 0)),
51
+ AutomaticSize = Enum.AutomaticSize.Y,
52
+ } elseif impliedWidth ~= nil then {
53
+ Size = UDim2.new(impliedWidth, UDim.new(0, 0)),
54
+ AutomaticSize = Enum.AutomaticSize.Y,
55
+ } else {}
56
+ local _object = {
57
+ Position = UDim2.new(posX, posY),
58
+ AnchorPoint = Vector2.new(anchorX, anchorY),
59
+ ZIndex = position.zIndex,
60
+ }
61
+ for _k, _v in sizeOverride do
62
+ _object[_k] = _v
63
+ end
64
+ return _object
65
+ end
66
+ -- Header/footer overlay mode escapes the card's VStack (a UIListLayout
67
+ -- forcibly repositions every sibling it lays out, so there's no way to keep
68
+ -- a custom Position/AnchorPoint on a child that stays inside it) — Card
69
+ -- pulls an overlay-requesting Header/Footer out of the flow and renders it
70
+ -- as an absolutely-positioned sibling of the Box instead. Overlay mode is
71
+ -- requested via the instance `overlay` prop or `theme.components.card.header
72
+ -- /footer.position.position === "absolute"`; when the instance sets any of
73
+ -- its own Position/AnchorPoint/top/left/right/bottom/center fields, those
74
+ -- win wholesale over the theme's CssPosition (mixing partial fields across
75
+ -- the two differently-shaped position vocabularies isn't attempted), exactly
76
+ -- mirroring the precedence the previous PositionElementProps-based
77
+ -- implementation gave instance props.
78
+ local function resolveOverlayPosition(props, themePosition)
79
+ local _condition = props.overlay
80
+ if _condition == nil then
81
+ local _result = themePosition
82
+ if _result ~= nil then
83
+ _result = _result.position
84
+ end
85
+ _condition = (_result == "absolute")
86
+ end
87
+ local overlay = _condition
88
+ if not overlay then
89
+ return {
90
+ overlay = overlay,
91
+ positionProps = {},
92
+ }
93
+ end
94
+ local hasInstancePosition = props.Position ~= nil or props.AnchorPoint ~= nil or props.top ~= nil or props.left ~= nil or props.right ~= nil or props.bottom ~= nil or props.center ~= nil
95
+ if hasInstancePosition then
96
+ local _object = {
97
+ overlay = overlay,
98
+ }
99
+ local _left = "positionProps"
100
+ local _object_1 = {
101
+ Position = SizeHelper:GetPosition(props),
102
+ AnchorPoint = SizeHelper:GetAnchor(props),
103
+ }
104
+ local _left_1 = "ZIndex"
105
+ local _condition_1 = props.ZIndex
106
+ if _condition_1 == nil then
107
+ local _result = themePosition
108
+ if _result ~= nil then
109
+ _result = _result.zIndex
110
+ end
111
+ _condition_1 = _result
112
+ if _condition_1 == nil then
113
+ _condition_1 = 2
114
+ end
115
+ end
116
+ _object_1[_left_1] = _condition_1
117
+ _object[_left] = _object_1
118
+ return _object
119
+ end
120
+ if themePosition ~= nil then
121
+ local resolved = resolveCssPositionOffset(themePosition)
122
+ local _object = {
123
+ overlay = overlay,
124
+ }
125
+ local _left = "positionProps"
126
+ local _object_1 = {
127
+ Position = resolved.Position,
128
+ AnchorPoint = resolved.AnchorPoint,
129
+ }
130
+ local _left_1 = "ZIndex"
131
+ local _condition_1 = props.ZIndex
132
+ if _condition_1 == nil then
133
+ _condition_1 = resolved.ZIndex
134
+ if _condition_1 == nil then
135
+ _condition_1 = 2
136
+ end
137
+ end
138
+ _object_1[_left_1] = _condition_1
139
+ _object_1.Size = resolved.Size
140
+ _object_1.AutomaticSize = resolved.AutomaticSize
141
+ _object[_left] = _object_1
142
+ return _object
143
+ end
144
+ local _object = {
145
+ overlay = overlay,
146
+ }
147
+ local _left = "positionProps"
148
+ local _object_1 = {}
149
+ local _left_1 = "ZIndex"
150
+ local _condition_1 = props.ZIndex
151
+ if _condition_1 == nil then
152
+ _condition_1 = 2
153
+ end
154
+ _object_1[_left_1] = _condition_1
155
+ _object[_left] = _object_1
156
+ return _object
157
+ end
16
158
  local CardHeader = React.forwardRef(function(props, ref)
17
159
  local theme = React.useContext(CleanThemeContext)
18
160
  local card = React.useContext(CardContext)
19
161
  local intent = ColorHelper:getIntentColors(theme, props.intent or card.intent or "primary", "default", theme.components.card.header.intents)
20
162
  local padding = SpacingHelper:ResolveNumberPadding(SpacingHelper:GetPadding(theme, "md", theme.components.card.header.spacing))
21
163
  local corners = CssHelper:parseCssSize(theme.components.card.cornerRadius)
164
+ local _binding = resolveOverlayPosition(props, theme.components.card.header.position)
165
+ local overlay = _binding.overlay
166
+ local positionProps = _binding.positionProps
22
167
  local _attributes = {
23
168
  ref = ref,
24
169
  }
25
170
  for _k, _v in props do
26
171
  _attributes[_k] = _v
27
172
  end
28
- _attributes.Size = UDim2.fromScale(0, 0)
29
- _attributes.AutomaticSize = Enum.AutomaticSize.XY
173
+ for _k, _v in positionProps do
174
+ _attributes[_k] = _v
175
+ end
176
+ _attributes.Size = positionProps.Size or UDim2.fromScale(0, 0)
177
+ _attributes.AutomaticSize = positionProps.AutomaticSize or Enum.AutomaticSize.XY
30
178
  _attributes.BackgroundTransparency = intent.backgroundTransparency
31
179
  _attributes.BackgroundColor3 = intent.backgroundColor
32
180
  _attributes.BorderSizePixel = 0
33
- return React.createElement(Container, _attributes, React.createElement(Padding, {
181
+ _attributes.backgroundImage = intent.backgroundImage
182
+ _attributes.Change = if overlay and card.reportOverlayHeaderRect ~= nil then {
183
+ AbsolutePosition = function(instance)
184
+ return card.reportOverlayHeaderRect(instance.AbsolutePosition, instance.AbsoluteSize)
185
+ end,
186
+ AbsoluteSize = function(instance)
187
+ return card.reportOverlayHeaderRect(instance.AbsolutePosition, instance.AbsoluteSize)
188
+ end,
189
+ } else nil
190
+ local _exp = React.createElement(Padding, {
34
191
  resolvedPadding = padding,
35
- }), React.createElement("uistroke", {
36
- Thickness = 1,
37
- Color = intent.borderColor,
38
- BorderStrokePosition = Enum.BorderStrokePosition.Inner,
39
- }), React.createElement("uicorner", {
192
+ })
193
+ local _attributes_1 = {}
194
+ local _condition = theme.components.card.header.borderThickness
195
+ if _condition == nil then
196
+ _condition = theme.components.card.borderThickness
197
+ end
198
+ _attributes_1.Thickness = _condition
199
+ _attributes_1.Color = intent.borderColor
200
+ _attributes_1.BorderStrokePosition = Enum.BorderStrokePosition.Inner
201
+ return React.createElement(Container, _attributes, _exp, React.createElement("uistroke", _attributes_1), React.createElement("uicorner", {
40
202
  TopLeftRadius = corners,
41
203
  TopRightRadius = corners,
42
204
  BottomLeftRadius = UDim.new(0, 0),
@@ -45,18 +207,50 @@ local CardHeader = React.forwardRef(function(props, ref)
45
207
  end)
46
208
  local CardBody = React.forwardRef(function(props, ref)
47
209
  local theme = React.useContext(CleanThemeContext)
48
- local padding = SpacingHelper:ResolveNumberPadding(SpacingHelper:GetPadding(theme, "md", theme.components.card.header.spacing))
210
+ -- Mirrors Box.tsx's own padding resolution (CardBody wraps its
211
+ -- children in a Container, not a Box, so it doesn't get Box's
212
+ -- automatic `box.padding` application for free) — apply the card
213
+ -- body's own theme padding only when the caller hasn't set explicit
214
+ -- padding props. `card.body.padding`, when set, is an intentional
215
+ -- full-override escape hatch and wins wholesale over everything
216
+ -- below. Absent that, top/bottom fall back to the same generic
217
+ -- md-scale default Header/Footer use for their own padding (NOT
218
+ -- `box.padding`'s top/bottom) — CardBody's bottom padding stacks
219
+ -- directly against an in-flow CardFooter's own top padding (the
220
+ -- VStack's spacing is "None"), so inheriting `box.padding`'s
221
+ -- vertical values here would double them up with Footer's/Header's
222
+ -- own padding. Left/right still inherit `box.padding` since nothing
223
+ -- else contributes horizontal padding at that seam.
224
+ local paddingSourceProps = props
225
+ local hasCustomPadding = paddingSourceProps.top ~= nil or paddingSourceProps.bottom ~= nil or paddingSourceProps.left ~= nil or paddingSourceProps.right ~= nil or paddingSourceProps.spacing ~= nil or paddingSourceProps.padding ~= nil or paddingSourceProps.resolvedPadding ~= nil
226
+ local _bodyThemePadding = theme.components.card.body
227
+ if _bodyThemePadding ~= nil then
228
+ _bodyThemePadding = _bodyThemePadding.padding
229
+ end
230
+ local bodyThemePadding = _bodyThemePadding
231
+ local boxThemePadding = theme.components.box.padding
232
+ local mdPadding = SpacingHelper:GetPadding(theme, "md")
233
+ local paddingProps = if hasCustomPadding then paddingSourceProps elseif bodyThemePadding ~= nil then {
234
+ resolvedPadding = CssHelper:parseCssQuad(bodyThemePadding),
235
+ } else {
236
+ resolvedPadding = {
237
+ top = mdPadding,
238
+ bottom = mdPadding,
239
+ left = if boxThemePadding ~= nil then CssHelper:parseCssQuad(boxThemePadding).left else mdPadding,
240
+ right = if boxThemePadding ~= nil then CssHelper:parseCssQuad(boxThemePadding).right else mdPadding,
241
+ },
242
+ }
49
243
  local _attributes = {
50
244
  ref = ref,
245
+ Size = SizeHelper:GetSize(props, UDim2.fromScale(0, 0)),
51
246
  }
52
247
  for _k, _v in props do
53
248
  _attributes[_k] = _v
54
249
  end
55
- _attributes.Size = UDim2.fromScale(0, 0)
56
250
  _attributes.AutomaticSize = Enum.AutomaticSize.XY
57
- return React.createElement(FlexItem, nil, React.createElement(Container, _attributes, React.createElement(Padding, {
58
- resolvedPadding = padding,
59
- }), props.children))
251
+ local _attributes_1 = table.clone(paddingProps)
252
+ setmetatable(_attributes_1, nil)
253
+ return React.createElement(FlexItem, nil, React.createElement(Container, _attributes, React.createElement(Padding, _attributes_1), props.children))
60
254
  end)
61
255
  local CardFooter = React.forwardRef(function(props, ref)
62
256
  local theme = React.useContext(CleanThemeContext)
@@ -64,24 +258,44 @@ local CardFooter = React.forwardRef(function(props, ref)
64
258
  local intent = ColorHelper:getIntentColors(theme, props.intent or card.intent or "primary", "default", theme.components.card.footer.intents)
65
259
  local padding = SpacingHelper:ResolveNumberPadding(SpacingHelper:GetPadding(theme, "md", theme.components.card.footer.spacing))
66
260
  local corners = CssHelper:parseCssSize(theme.components.card.cornerRadius)
261
+ local _binding = resolveOverlayPosition(props, theme.components.card.footer.position)
262
+ local overlay = _binding.overlay
263
+ local positionProps = _binding.positionProps
67
264
  local _attributes = {
68
265
  ref = ref,
69
266
  }
70
267
  for _k, _v in props do
71
268
  _attributes[_k] = _v
72
269
  end
73
- _attributes.Size = UDim2.fromScale(0, 0)
74
- _attributes.AutomaticSize = Enum.AutomaticSize.XY
270
+ for _k, _v in positionProps do
271
+ _attributes[_k] = _v
272
+ end
273
+ _attributes.Size = positionProps.Size or UDim2.fromScale(0, 0)
274
+ _attributes.AutomaticSize = positionProps.AutomaticSize or Enum.AutomaticSize.XY
75
275
  _attributes.BackgroundTransparency = intent.backgroundTransparency
76
276
  _attributes.BackgroundColor3 = intent.backgroundColor
77
277
  _attributes.BorderSizePixel = 0
78
- return React.createElement(Container, _attributes, React.createElement(Padding, {
278
+ _attributes.backgroundImage = intent.backgroundImage
279
+ _attributes.Change = if overlay and card.reportOverlayFooterRect ~= nil then {
280
+ AbsolutePosition = function(instance)
281
+ return card.reportOverlayFooterRect(instance.AbsolutePosition, instance.AbsoluteSize)
282
+ end,
283
+ AbsoluteSize = function(instance)
284
+ return card.reportOverlayFooterRect(instance.AbsolutePosition, instance.AbsoluteSize)
285
+ end,
286
+ } else nil
287
+ local _exp = React.createElement(Padding, {
79
288
  resolvedPadding = padding,
80
- }), React.createElement("uistroke", {
81
- Thickness = 1,
82
- Color = intent.borderColor,
83
- BorderStrokePosition = Enum.BorderStrokePosition.Inner,
84
- }), React.createElement("uicorner", {
289
+ })
290
+ local _attributes_1 = {}
291
+ local _condition = theme.components.card.footer.borderThickness
292
+ if _condition == nil then
293
+ _condition = theme.components.card.borderThickness
294
+ end
295
+ _attributes_1.Thickness = _condition
296
+ _attributes_1.Color = intent.borderColor
297
+ _attributes_1.BorderStrokePosition = Enum.BorderStrokePosition.Inner
298
+ return React.createElement(Container, _attributes, _exp, React.createElement("uistroke", _attributes_1), React.createElement("uicorner", {
85
299
  TopLeftRadius = UDim.new(0, 0),
86
300
  TopRightRadius = UDim.new(0, 0),
87
301
  BottomLeftRadius = corners,
@@ -90,22 +304,212 @@ local CardFooter = React.forwardRef(function(props, ref)
90
304
  end)
91
305
  local Card = React.forwardRef(function(props, ref)
92
306
  local theme = React.useContext(CleanThemeContext)
307
+ -- Overlay clearance: an overlay header (see resolveOverlayPosition)
308
+ -- is pulled out of the VStack entirely, so nothing reserves space
309
+ -- for it — its rendered box typically extends downward past its own
310
+ -- anchor point and overlaps into the top of the Box. These two
311
+ -- pieces of state capture the header's reported rect (via
312
+ -- CardContext.reportOverlayHeaderRect, set on CardHeader below) and
313
+ -- the overlay wrapper frame's own AbsolutePosition, so the amount of
314
+ -- that overlap can be measured and reserved as extra top padding on
315
+ -- the Box (see headerClearance/boxProps below). Both are undefined
316
+ -- until their first Change event fires after mount, which is the
317
+ -- same measure-then-settle characteristic Accordion.tsx accepts for
318
+ -- its own AbsoluteSize-driven measurements.
319
+ local headerRect, setHeaderRect = React.useState()
320
+ local footerRect, setFooterRect = React.useState()
321
+ local wrapperAbsoluteY, setWrapperAbsoluteY = React.useState()
322
+ local wrapperAbsoluteSize, setWrapperAbsoluteSize = React.useState()
323
+ local overlayWrapperRef = React.useRef()
324
+ local reportOverlayHeaderRect = React.useCallback(function(position, size)
325
+ setHeaderRect({
326
+ position = position,
327
+ size = size,
328
+ })
329
+ end, {})
330
+ local reportOverlayFooterRect = React.useCallback(function(position, size)
331
+ setFooterRect({
332
+ position = position,
333
+ size = size,
334
+ })
335
+ end, {})
93
336
  local contextValue = React.useMemo(function()
94
337
  return {
95
338
  intent = props.intent,
339
+ reportOverlayHeaderRect = reportOverlayHeaderRect,
340
+ reportOverlayFooterRect = reportOverlayFooterRect,
96
341
  }
97
- end, { props.intent })
342
+ end, { props.intent, reportOverlayHeaderRect, reportOverlayFooterRect })
98
343
  local intent = ColorHelper:getIntentColors(theme, props.intent or "primary", "default", theme.components.card.header.intents)
99
- local _attributes = table.clone(props)
344
+ -- Pull out a Header/Footer that wants overlay placement so it can be
345
+ -- rendered outside the VStack below (see resolveOverlayPosition above
346
+ -- for why it can't stay inside the VStack and still be positioned).
347
+ -- Everything else renders in place, in its original order.
348
+ local _binding = React.useMemo(function()
349
+ local flow = {}
350
+ local header
351
+ local footer
352
+ React.Children.forEach(props.children, function(child)
353
+ if child == nil then
354
+ return nil
355
+ end
356
+ if React.isValidElement(child) and child.type == CardHeader and header == nil then
357
+ local headerElement = child
358
+ local _condition = headerElement.props.overlay
359
+ if _condition == nil then
360
+ local _result = theme.components.card.header.position
361
+ if _result ~= nil then
362
+ _result = _result.position
363
+ end
364
+ _condition = (_result == "absolute")
365
+ end
366
+ if _condition then
367
+ header = headerElement
368
+ return nil
369
+ end
370
+ end
371
+ if React.isValidElement(child) and child.type == CardFooter and footer == nil then
372
+ local footerElement = child
373
+ local _condition = footerElement.props.overlay
374
+ if _condition == nil then
375
+ local _result = theme.components.card.footer.position
376
+ if _result ~= nil then
377
+ _result = _result.position
378
+ end
379
+ _condition = (_result == "absolute")
380
+ end
381
+ if _condition then
382
+ footer = footerElement
383
+ return nil
384
+ end
385
+ end
386
+ local _child = child
387
+ table.insert(flow, _child)
388
+ end)
389
+ return {
390
+ flowChildren = flow,
391
+ overlayHeader = header,
392
+ overlayFooter = footer,
393
+ }
394
+ end, { props.children, theme })
395
+ local flowChildren = _binding.flowChildren
396
+ local overlayHeader = _binding.overlayHeader
397
+ local overlayFooter = _binding.overlayFooter
398
+ local hasOverlay = overlayHeader ~= nil or overlayFooter ~= nil
399
+ -- How far the overlay header's bottom edge extends past the
400
+ -- wrapper's (i.e. the Box's) top edge — 0 when there's no overlap
401
+ -- (e.g. a small/no theme offset) or nothing has been measured yet.
402
+ local headerClearance = if overlayHeader ~= nil and headerRect ~= nil and wrapperAbsoluteY ~= nil then math.max(0, (headerRect.position.Y + headerRect.size.Y) - wrapperAbsoluteY) else 0
403
+ -- Same idea, mirrored for the footer: the wrapper's bottom edge
404
+ -- (its top plus its own height) minus the footer's reported top
405
+ -- edge (AbsolutePosition is always the top-left corner regardless
406
+ -- of AnchorPoint) is how far the footer's top edge sits above the
407
+ -- Box's bottom edge, i.e. how much it overlaps upward into it.
408
+ local wrapperBottomY = if wrapperAbsoluteY ~= nil and wrapperAbsoluteSize ~= nil then wrapperAbsoluteY + wrapperAbsoluteSize.Y else nil
409
+ local footerClearance = if overlayFooter ~= nil and footerRect ~= nil and wrapperBottomY ~= nil then math.max(0, wrapperBottomY - footerRect.position.Y) else 0
410
+ -- The Box below now sits inside an extra positioning frame (see
411
+ -- boxWithOverlay), so its own Position/AnchorPoint/ZIndex/LayoutOrder
412
+ -- are cleared here to avoid double-applying them — the wrapper frame
413
+ -- owns those instead.
414
+ local _result
415
+ if hasOverlay then
416
+ local _object = table.clone(props)
417
+ setmetatable(_object, nil)
418
+ _object.Position = nil
419
+ _object.AnchorPoint = nil
420
+ _object.top = nil
421
+ _object.left = nil
422
+ _object.right = nil
423
+ _object.bottom = nil
424
+ _object.ZIndex = nil
425
+ _object.LayoutOrder = nil
426
+ _result = _object
427
+ else
428
+ _result = props
429
+ end
430
+ local boxProps = _result
431
+ -- When an overlay header and/or footer measurably overlaps into the
432
+ -- Box (headerClearance/footerClearance > 0), reserve that much extra
433
+ -- top/bottom padding via resolvedPadding — this takes over Box's own
434
+ -- outer padding, which is harmless since Card always forces
435
+ -- spacing="None" on Box below anyway (so it was always 0/0/0/0 until
436
+ -- now).
437
+ local _result_1
438
+ if headerClearance > 0 or footerClearance > 0 then
439
+ local _object = table.clone(boxProps)
440
+ setmetatable(_object, nil)
441
+ _object.resolvedPadding = {
442
+ top = headerClearance,
443
+ bottom = footerClearance,
444
+ left = 0,
445
+ right = 0,
446
+ }
447
+ _result_1 = _object
448
+ else
449
+ _result_1 = boxProps
450
+ end
451
+ local boxPropsWithOverlayClearance = _result_1
452
+ local _attributes = table.clone(boxPropsWithOverlayClearance)
100
453
  setmetatable(_attributes, nil)
101
454
  _attributes.ref = ref
455
+ _attributes.center = nil
102
456
  _attributes.spacing = "None"
103
457
  _attributes["border-color"] = intent.borderColor
458
+ local cardBox = (React.createElement(Box, _attributes, React.createElement(VStack, {
459
+ spacing = "None",
460
+ }, flowChildren)))
461
+ -- ClipsDescendants is off so an overlay header/footer can protrude
462
+ -- past the Box's own edges (e.g. a plaque mounted above the top
463
+ -- border). This frame takes over the Position/AnchorPoint the Box
464
+ -- would otherwise have used, and auto-sizes to hug the Box (plus
465
+ -- whatever the overlay siblings protrude by).
466
+ --
467
+ -- `center` is stripped before deriving Position/AnchorPoint here,
468
+ -- the same way `cardBox`'s Box always gets `center={undefined}`:
469
+ -- when `center` is set, centering is handled by the outer
470
+ -- `isCentered` wrapper below (a UIListLayout that centers this
471
+ -- whole frame), not by this frame's own Position/AnchorPoint. If
472
+ -- both applied `center` at once, the UIListLayout would place the
473
+ -- frame using an (0,0)-anchor assumption while the frame itself
474
+ -- reported a (0.5,0.5) AnchorPoint, offsetting it by half its own
475
+ -- size and letting its (ClipsDescendants=false) contents spill out.
476
+ -- Explicit top/left/right/bottom/Position/AnchorPoint overrides are
477
+ -- unaffected and still apply directly to this frame.
478
+ local _object = table.clone(props)
479
+ setmetatable(_object, nil)
480
+ _object.center = nil
481
+ local wrapperPositionProps = _object
482
+ local boxWithOverlay = if hasOverlay then (React.createElement("frame", {
483
+ ref = overlayWrapperRef,
484
+ BackgroundTransparency = 1,
485
+ ClipsDescendants = false,
486
+ Size = SizeHelper:GetSize(props, UDim2.fromScale(1, 1)),
487
+ AutomaticSize = SizeHelper:GetAutoSize(props),
488
+ Position = SizeHelper:GetPosition(wrapperPositionProps),
489
+ AnchorPoint = SizeHelper:GetAnchor(wrapperPositionProps),
490
+ ZIndex = props.ZIndex,
491
+ LayoutOrder = props.LayoutOrder,
492
+ Change = {
493
+ AbsolutePosition = function(instance)
494
+ return setWrapperAbsoluteY(instance.AbsolutePosition.Y)
495
+ end,
496
+ AbsoluteSize = function(instance)
497
+ return setWrapperAbsoluteSize(instance.AbsoluteSize)
498
+ end,
499
+ },
500
+ }, cardBox, overlayHeader, overlayFooter)) else cardBox
501
+ local isCentered = props.center == true and props.Position == nil and props.AnchorPoint == nil
104
502
  return React.createElement(CardContext.Provider, {
105
503
  value = contextValue,
106
- }, React.createElement(Box, _attributes, React.createElement(VStack, {
107
- spacing = "None",
108
- }, props.children)))
504
+ }, if isCentered then (React.createElement(Container, {
505
+ Size = UDim2.fromScale(1, 1),
506
+ ZIndex = props.ZIndex,
507
+ LayoutOrder = props.LayoutOrder,
508
+ }, React.createElement("uilistlayout", {
509
+ FillDirection = Enum.FillDirection.Horizontal,
510
+ HorizontalAlignment = Enum.HorizontalAlignment.Center,
511
+ VerticalAlignment = Enum.VerticalAlignment.Center,
512
+ }), boxWithOverlay)) else boxWithOverlay)
109
513
  end)
110
514
  Card.Header = CardHeader
111
515
  Card.Footer = CardFooter
@@ -1,6 +1,6 @@
1
1
  import React, { Binding } from "@rbxts/react";
2
2
  import { IconElementProps, ScalableElementProps } from "../../Interfaces/";
3
- export interface IconProps extends IconElementProps, ScalableElementProps {
3
+ export interface IconProps extends IconElementProps, ScalableElementProps, React.InstanceProps<ImageLabel> {
4
4
  color?: Color3;
5
5
  Size?: UDim2;
6
6
  spinning?: boolean;
@@ -51,13 +51,52 @@ function Icon(props)
51
51
  setmetatable(_attributes, nil)
52
52
  return React.createElement(SpinningIcon, _attributes)
53
53
  end
54
- return React.createElement("imagelabel", {
54
+ local _attributes = {
55
+ AnchorPoint = props.AnchorPoint,
55
56
  Rotation = props.Rotation,
56
57
  Size = props.Size or UDim2.fromOffset(size, size),
58
+ Position = props.Position,
59
+ LayoutOrder = props.LayoutOrder,
57
60
  Image = if iconId == nil then nil else `rbxassetid://{iconId}`,
58
- BackgroundTransparency = 1,
59
- ImageColor3 = props.color or theme.colors.intents.primary.default.textColor,
60
- })
61
+ }
62
+ local _condition = props.BackgroundTransparency
63
+ if _condition == nil then
64
+ _condition = 1
65
+ end
66
+ _attributes.BackgroundTransparency = _condition
67
+ _attributes.ImageColor3 = props.color or theme.colors.intents.primary.default.textColor
68
+ _attributes.ZIndex = props.ZIndex
69
+ _attributes.Visible = props.Visible
70
+ _attributes.Active = props.Active
71
+ _attributes.Archivable = props.Archivable
72
+ _attributes.BackgroundColor3 = props.BackgroundColor3
73
+ _attributes.BorderColor3 = props.BorderColor3
74
+ _attributes.BorderMode = props.BorderMode
75
+ _attributes.BorderSizePixel = props.BorderSizePixel
76
+ _attributes.ClipsDescendants = props.ClipsDescendants
77
+ _attributes.Selectable = props.Selectable
78
+ _attributes.SelectionImageObject = props.SelectionImageObject
79
+ _attributes.SizeConstraint = props.SizeConstraint
80
+ _attributes.Tag = props.Tag
81
+ _attributes.AutoLocalize = props.AutoLocalize
82
+ _attributes.RootLocalizationTable = props.RootLocalizationTable
83
+ _attributes.NextSelectionDown = props.NextSelectionDown
84
+ _attributes.NextSelectionLeft = props.NextSelectionLeft
85
+ _attributes.NextSelectionRight = props.NextSelectionRight
86
+ _attributes.NextSelectionUp = props.NextSelectionUp
87
+ _attributes.SelectionGroup = props.SelectionGroup
88
+ _attributes.SelectionOrder = props.SelectionOrder
89
+ _attributes.ImageRectOffset = props.ImageRectOffset
90
+ _attributes.ImageRectSize = props.ImageRectSize
91
+ _attributes.ImageTransparency = props.ImageTransparency
92
+ _attributes.ResampleMode = props.ResampleMode
93
+ _attributes.ScaleType = props.ScaleType
94
+ _attributes.SliceCenter = props.SliceCenter
95
+ _attributes.SliceScale = props.SliceScale
96
+ _attributes.TileSize = props.TileSize
97
+ _attributes.Change = props.Change
98
+ _attributes.Event = props.Event
99
+ return React.createElement("imagelabel", _attributes)
61
100
  end
62
101
  return {
63
102
  Icon = Icon,
@@ -8,6 +8,7 @@ interface TextProps extends React.InstanceProps<TextLabel> {
8
8
  weight?: Enum.FontWeight | "bold";
9
9
  align?: "Left" | "Right" | "Center";
10
10
  TextWrap?: boolean;
11
+ letterSpacing?: number;
11
12
  }
12
13
  export declare const Text: React.ForwardRefExoticComponent<Omit<TextProps, "ref"> & React.RefAttributes<TextLabel>>;
13
14
  export {};