@rbxts/react-clean-ui 1.0.36 → 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.
- package/out/Components/Decorator/Corners.luau +5 -1
- package/out/Components/Decorator/Padding.luau +1 -1
- package/out/Components/Input/Button.d.ts +9 -0
- package/out/Components/Input/Button.luau +155 -46
- package/out/Components/Input/Checkbox.luau +22 -6
- package/out/Components/Input/Increment.d.ts +11 -0
- package/out/Components/Input/Increment.luau +107 -0
- package/out/Components/Input/Increment.step.d.ts +1 -0
- package/out/Components/Input/Increment.step.luau +25 -0
- package/out/Components/Input/Input.d.ts +4 -3
- package/out/Components/Input/Input.luau +115 -18
- package/out/Components/Input/Input.validation.d.ts +4 -0
- package/out/Components/Input/Input.validation.luau +42 -0
- package/out/Components/Input/Select.d.ts +4 -3
- package/out/Components/Input/Select.luau +23 -18
- package/out/Components/Input/Slider.d.ts +1 -1
- package/out/Components/Input/Slider.luau +68 -34
- package/out/Components/Input/index.d.ts +1 -0
- package/out/Components/Input/init.luau +3 -0
- package/out/Components/Layout/Accordion.d.ts +4 -4
- package/out/Components/Layout/Accordion.luau +75 -23
- package/out/Components/Layout/Container.d.ts +4 -2
- package/out/Components/Layout/Container.luau +12 -3
- package/out/Components/Layout/Draggable.luau +1 -1
- package/out/Components/Layout/Fieldset.d.ts +1 -1
- package/out/Components/Layout/Fieldset.luau +40 -6
- package/out/Components/Layout/FlexItem.d.ts +2 -1
- package/out/Components/Layout/FlexItem.luau +1 -0
- package/out/Components/Layout/Group.luau +30 -8
- package/out/Components/Layout/HStack.luau +1 -0
- package/out/Components/Layout/Row.luau +43 -9
- package/out/Components/Layout/Scroller.d.ts +1 -0
- package/out/Components/Layout/Scroller.luau +20 -5
- package/out/Components/Layout/Tabs.d.ts +5 -3
- package/out/Components/Layout/Tabs.luau +48 -16
- package/out/Components/Navigation/Menu.d.ts +1 -1
- package/out/Components/Navigation/Menu.luau +2 -0
- package/out/Components/Surface/Box.d.ts +4 -2
- package/out/Components/Surface/Box.luau +25 -22
- package/out/Components/Surface/Card.d.ts +12 -6
- package/out/Components/Surface/Card.luau +456 -26
- package/out/Components/Surface/Icon.luau +42 -4
- package/out/Components/Typography/Text.d.ts +1 -0
- package/out/Components/Typography/Text.luau +119 -10
- package/out/Contexts/row.context.d.ts +1 -1
- package/out/Contexts/theme.context.d.ts +2 -2
- package/out/Helpers/color.helper.d.ts +5 -3
- package/out/Helpers/color.helper.luau +62 -34
- package/out/Helpers/create-ui-story.d.ts +2 -2
- package/out/Helpers/create-ui-story.luau +33 -1
- package/out/Helpers/css.helper.d.ts +28 -2
- package/out/Helpers/css.helper.luau +208 -0
- package/out/Helpers/index.d.ts +1 -0
- package/out/Helpers/init.luau +3 -0
- package/out/Helpers/size.helper.d.ts +3 -1
- package/out/Helpers/size.helper.luau +10 -9
- package/out/Helpers/spacing.helper.d.ts +5 -4
- package/out/Helpers/spacing.helper.luau +102 -18
- package/out/Helpers/typography.helper.d.ts +2 -2
- package/out/Interfaces/css.types.d.ts +30 -0
- package/out/Interfaces/css.types.luau +21 -0
- package/out/Interfaces/element.props.d.ts +61 -0
- package/out/Interfaces/element.props.luau +1 -0
- package/out/Interfaces/index.d.ts +5 -3
- package/out/Interfaces/init.luau +1 -7
- package/out/Interfaces/responsive.types.d.ts +26 -0
- package/out/Interfaces/semantics.d.ts +3 -0
- package/out/Interfaces/semantics.luau +2 -0
- package/out/Providers/app.provider.d.ts +2 -2
- package/out/Providers/theme.provider.d.ts +2 -2
- package/out/Theme/index.d.ts +1 -0
- package/out/Theme/theme.factory.d.ts +3 -3
- package/out/Theme/theme.style.d.ts +48 -0
- package/out/Theme/theme.style.luau +1 -0
- package/out/Theme/theme.template.d.ts +34 -26
- package/out/Theme/themes/dark.theme.d.ts +1 -1
- package/out/Theme/themes/dark.theme.luau +28 -0
- package/out/Theme/themes/default.theme.d.ts +2 -2
- package/out/Theme/themes/default.theme.luau +11 -1
- package/out/Theme/themes/index.d.ts +1 -0
- package/out/Theme/themes/init.luau +3 -0
- package/out/Theme/themes/sandstone.theme.d.ts +1 -1
- package/out/Theme/themes/sandstone.theme.luau +25 -0
- package/out/Theme/themes/wooden.theme.d.ts +1 -0
- package/out/Theme/themes/wooden.theme.luau +554 -0
- package/out/index.d.ts +8 -8
- package/package.json +19 -7
- package/out/Interfaces/clean.element.props.d.ts +0 -114
- /package/out/{Interfaces/userinput.d.ts → Helpers/input.helper.d.ts} +0 -0
- /package/out/{Interfaces/userinput.luau → Helpers/input.helper.luau} +0 -0
- /package/out/Interfaces/{clean.element.props.luau → responsive.types.luau} +0 -0
|
@@ -14,30 +14,191 @@ local SpacingHelper = _Helpers.SpacingHelper
|
|
|
14
14
|
local CleanThemeContext = TS.import(script, script.Parent.Parent.Parent, "Contexts").CleanThemeContext
|
|
15
15
|
local Padding = TS.import(script, script.Parent.Parent, "Decorator").Padding
|
|
16
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
|
|
17
158
|
local CardHeader = React.forwardRef(function(props, ref)
|
|
18
159
|
local theme = React.useContext(CleanThemeContext)
|
|
19
160
|
local card = React.useContext(CardContext)
|
|
20
161
|
local intent = ColorHelper:getIntentColors(theme, props.intent or card.intent or "primary", "default", theme.components.card.header.intents)
|
|
21
|
-
local padding = SpacingHelper:
|
|
162
|
+
local padding = SpacingHelper:GetResolvedPadding(theme, props, theme.components.card.header.spacing, theme.components.card.header.padding, "md")
|
|
22
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
|
|
23
167
|
local _attributes = {
|
|
24
168
|
ref = ref,
|
|
25
169
|
}
|
|
26
170
|
for _k, _v in props do
|
|
27
171
|
_attributes[_k] = _v
|
|
28
172
|
end
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
31
178
|
_attributes.BackgroundTransparency = intent.backgroundTransparency
|
|
32
179
|
_attributes.BackgroundColor3 = intent.backgroundColor
|
|
33
180
|
_attributes.BorderSizePixel = 0
|
|
34
|
-
|
|
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, {
|
|
35
191
|
resolvedPadding = padding,
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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", {
|
|
41
202
|
TopLeftRadius = corners,
|
|
42
203
|
TopRightRadius = corners,
|
|
43
204
|
BottomLeftRadius = UDim.new(0, 0),
|
|
@@ -46,7 +207,15 @@ local CardHeader = React.forwardRef(function(props, ref)
|
|
|
46
207
|
end)
|
|
47
208
|
local CardBody = React.forwardRef(function(props, ref)
|
|
48
209
|
local theme = React.useContext(CleanThemeContext)
|
|
49
|
-
|
|
210
|
+
-- Unified 4-tier padding resolution (see SpacingHelper.GetResolvedPadding):
|
|
211
|
+
-- `card.body.spacing` (tier 2) overrides the global spacing map per
|
|
212
|
+
-- scale key, falling back to it for any key it doesn't define, and
|
|
213
|
+
-- `card.body.padding` (tier 3) overrides the scale-based tiers
|
|
214
|
+
-- entirely at the active key. CardBody wraps its children in a
|
|
215
|
+
-- Container, not a Box, so it doesn't get Box's automatic
|
|
216
|
+
-- `box.padding` application for free — this resolves independently
|
|
217
|
+
-- against `theme.components.card.body`'s own tiers.
|
|
218
|
+
local paddingSourceProps = props
|
|
50
219
|
local _attributes = {
|
|
51
220
|
ref = ref,
|
|
52
221
|
Size = SizeHelper:GetSize(props, UDim2.fromScale(0, 0)),
|
|
@@ -55,34 +224,62 @@ local CardBody = React.forwardRef(function(props, ref)
|
|
|
55
224
|
_attributes[_k] = _v
|
|
56
225
|
end
|
|
57
226
|
_attributes.AutomaticSize = Enum.AutomaticSize.XY
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
227
|
+
local _attributes_1 = {}
|
|
228
|
+
local _result = theme.components.card.body
|
|
229
|
+
if _result ~= nil then
|
|
230
|
+
_result = _result.spacing
|
|
231
|
+
end
|
|
232
|
+
local _result_1 = theme.components.card.body
|
|
233
|
+
if _result_1 ~= nil then
|
|
234
|
+
_result_1 = _result_1.padding
|
|
235
|
+
end
|
|
236
|
+
_attributes_1.resolvedPadding = SpacingHelper:GetResolvedPadding(theme, paddingSourceProps, _result, _result_1)
|
|
237
|
+
return React.createElement(FlexItem, nil, React.createElement(Container, _attributes, React.createElement(Padding, _attributes_1), props.children))
|
|
61
238
|
end)
|
|
62
239
|
local CardFooter = React.forwardRef(function(props, ref)
|
|
63
240
|
local theme = React.useContext(CleanThemeContext)
|
|
64
241
|
local card = React.useContext(CardContext)
|
|
65
242
|
local intent = ColorHelper:getIntentColors(theme, props.intent or card.intent or "primary", "default", theme.components.card.footer.intents)
|
|
66
|
-
local padding = SpacingHelper:
|
|
243
|
+
local padding = SpacingHelper:GetResolvedPadding(theme, props, theme.components.card.footer.spacing, theme.components.card.footer.padding, "md")
|
|
67
244
|
local corners = CssHelper:parseCssSize(theme.components.card.cornerRadius)
|
|
245
|
+
local _binding = resolveOverlayPosition(props, theme.components.card.footer.position)
|
|
246
|
+
local overlay = _binding.overlay
|
|
247
|
+
local positionProps = _binding.positionProps
|
|
68
248
|
local _attributes = {
|
|
69
249
|
ref = ref,
|
|
70
250
|
}
|
|
71
251
|
for _k, _v in props do
|
|
72
252
|
_attributes[_k] = _v
|
|
73
253
|
end
|
|
74
|
-
|
|
75
|
-
|
|
254
|
+
for _k, _v in positionProps do
|
|
255
|
+
_attributes[_k] = _v
|
|
256
|
+
end
|
|
257
|
+
_attributes.Size = positionProps.Size or UDim2.fromScale(0, 0)
|
|
258
|
+
_attributes.AutomaticSize = positionProps.AutomaticSize or Enum.AutomaticSize.XY
|
|
76
259
|
_attributes.BackgroundTransparency = intent.backgroundTransparency
|
|
77
260
|
_attributes.BackgroundColor3 = intent.backgroundColor
|
|
78
261
|
_attributes.BorderSizePixel = 0
|
|
79
|
-
|
|
262
|
+
_attributes.backgroundImage = intent.backgroundImage
|
|
263
|
+
_attributes.Change = if overlay and card.reportOverlayFooterRect ~= nil then {
|
|
264
|
+
AbsolutePosition = function(instance)
|
|
265
|
+
return card.reportOverlayFooterRect(instance.AbsolutePosition, instance.AbsoluteSize)
|
|
266
|
+
end,
|
|
267
|
+
AbsoluteSize = function(instance)
|
|
268
|
+
return card.reportOverlayFooterRect(instance.AbsolutePosition, instance.AbsoluteSize)
|
|
269
|
+
end,
|
|
270
|
+
} else nil
|
|
271
|
+
local _exp = React.createElement(Padding, {
|
|
80
272
|
resolvedPadding = padding,
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
273
|
+
})
|
|
274
|
+
local _attributes_1 = {}
|
|
275
|
+
local _condition = theme.components.card.footer.borderThickness
|
|
276
|
+
if _condition == nil then
|
|
277
|
+
_condition = theme.components.card.borderThickness
|
|
278
|
+
end
|
|
279
|
+
_attributes_1.Thickness = _condition
|
|
280
|
+
_attributes_1.Color = intent.borderColor
|
|
281
|
+
_attributes_1.BorderStrokePosition = Enum.BorderStrokePosition.Inner
|
|
282
|
+
return React.createElement(Container, _attributes, _exp, React.createElement("uistroke", _attributes_1), React.createElement("uicorner", {
|
|
86
283
|
TopLeftRadius = UDim.new(0, 0),
|
|
87
284
|
TopRightRadius = UDim.new(0, 0),
|
|
88
285
|
BottomLeftRadius = corners,
|
|
@@ -91,13 +288,214 @@ local CardFooter = React.forwardRef(function(props, ref)
|
|
|
91
288
|
end)
|
|
92
289
|
local Card = React.forwardRef(function(props, ref)
|
|
93
290
|
local theme = React.useContext(CleanThemeContext)
|
|
291
|
+
-- Overlay clearance: an overlay header (see resolveOverlayPosition)
|
|
292
|
+
-- is pulled out of the VStack entirely, so nothing reserves space
|
|
293
|
+
-- for it — its rendered box typically extends downward past its own
|
|
294
|
+
-- anchor point and overlaps into the top of the Box. These two
|
|
295
|
+
-- pieces of state capture the header's reported rect (via
|
|
296
|
+
-- CardContext.reportOverlayHeaderRect, set on CardHeader below) and
|
|
297
|
+
-- the overlay wrapper frame's own AbsolutePosition, so the amount of
|
|
298
|
+
-- that overlap can be measured and reserved as extra top padding on
|
|
299
|
+
-- the Box (see headerClearance/boxProps below). Both are undefined
|
|
300
|
+
-- until their first Change event fires after mount, which is the
|
|
301
|
+
-- same measure-then-settle characteristic Accordion.tsx accepts for
|
|
302
|
+
-- its own AbsoluteSize-driven measurements.
|
|
303
|
+
local headerRect, setHeaderRect = React.useState()
|
|
304
|
+
local footerRect, setFooterRect = React.useState()
|
|
305
|
+
local wrapperAbsoluteY, setWrapperAbsoluteY = React.useState()
|
|
306
|
+
local wrapperAbsoluteSize, setWrapperAbsoluteSize = React.useState()
|
|
307
|
+
local overlayWrapperRef = React.useRef()
|
|
308
|
+
local reportOverlayHeaderRect = React.useCallback(function(position, size)
|
|
309
|
+
setHeaderRect({
|
|
310
|
+
position = position,
|
|
311
|
+
size = size,
|
|
312
|
+
})
|
|
313
|
+
end, {})
|
|
314
|
+
local reportOverlayFooterRect = React.useCallback(function(position, size)
|
|
315
|
+
setFooterRect({
|
|
316
|
+
position = position,
|
|
317
|
+
size = size,
|
|
318
|
+
})
|
|
319
|
+
end, {})
|
|
94
320
|
local contextValue = React.useMemo(function()
|
|
95
321
|
return {
|
|
96
322
|
intent = props.intent,
|
|
323
|
+
reportOverlayHeaderRect = reportOverlayHeaderRect,
|
|
324
|
+
reportOverlayFooterRect = reportOverlayFooterRect,
|
|
97
325
|
}
|
|
98
|
-
end, { props.intent })
|
|
326
|
+
end, { props.intent, reportOverlayHeaderRect, reportOverlayFooterRect })
|
|
99
327
|
local intent = ColorHelper:getIntentColors(theme, props.intent or "primary", "default", theme.components.card.header.intents)
|
|
100
|
-
|
|
328
|
+
-- Pull out a Header/Footer that wants overlay placement so it can be
|
|
329
|
+
-- rendered outside the VStack below (see resolveOverlayPosition above
|
|
330
|
+
-- for why it can't stay inside the VStack and still be positioned).
|
|
331
|
+
-- Everything else renders in place, in its original order.
|
|
332
|
+
local _binding = React.useMemo(function()
|
|
333
|
+
local flow = {}
|
|
334
|
+
local header
|
|
335
|
+
local footer
|
|
336
|
+
React.Children.forEach(props.children, function(child)
|
|
337
|
+
if child == nil then
|
|
338
|
+
return nil
|
|
339
|
+
end
|
|
340
|
+
if React.isValidElement(child) and child.type == CardHeader and header == nil then
|
|
341
|
+
local headerElement = child
|
|
342
|
+
local _condition = headerElement.props.overlay
|
|
343
|
+
if _condition == nil then
|
|
344
|
+
local _result = theme.components.card.header.position
|
|
345
|
+
if _result ~= nil then
|
|
346
|
+
_result = _result.position
|
|
347
|
+
end
|
|
348
|
+
_condition = (_result == "absolute")
|
|
349
|
+
end
|
|
350
|
+
if _condition then
|
|
351
|
+
header = headerElement
|
|
352
|
+
return nil
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
if React.isValidElement(child) and child.type == CardFooter and footer == nil then
|
|
356
|
+
local footerElement = child
|
|
357
|
+
local _condition = footerElement.props.overlay
|
|
358
|
+
if _condition == nil then
|
|
359
|
+
local _result = theme.components.card.footer.position
|
|
360
|
+
if _result ~= nil then
|
|
361
|
+
_result = _result.position
|
|
362
|
+
end
|
|
363
|
+
_condition = (_result == "absolute")
|
|
364
|
+
end
|
|
365
|
+
if _condition then
|
|
366
|
+
footer = footerElement
|
|
367
|
+
return nil
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
local _child = child
|
|
371
|
+
table.insert(flow, _child)
|
|
372
|
+
end)
|
|
373
|
+
return {
|
|
374
|
+
flowChildren = flow,
|
|
375
|
+
overlayHeader = header,
|
|
376
|
+
overlayFooter = footer,
|
|
377
|
+
}
|
|
378
|
+
end, { props.children, theme })
|
|
379
|
+
local flowChildren = _binding.flowChildren
|
|
380
|
+
local overlayHeader = _binding.overlayHeader
|
|
381
|
+
local overlayFooter = _binding.overlayFooter
|
|
382
|
+
local hasOverlay = overlayHeader ~= nil or overlayFooter ~= nil
|
|
383
|
+
React.useEffect(function()
|
|
384
|
+
if not hasOverlay then
|
|
385
|
+
return nil
|
|
386
|
+
end
|
|
387
|
+
local frame = overlayWrapperRef.current
|
|
388
|
+
if not frame then
|
|
389
|
+
return nil
|
|
390
|
+
end
|
|
391
|
+
-- Some renderers (e.g. the browser-based Loom scene preview) don't
|
|
392
|
+
-- back this ref with an instance that supports property-changed
|
|
393
|
+
-- signals, so guard the subscription instead of throwing and
|
|
394
|
+
-- losing overlay clearance tracking.
|
|
395
|
+
local positionConn
|
|
396
|
+
local sizeConn
|
|
397
|
+
pcall(function()
|
|
398
|
+
positionConn = frame:GetPropertyChangedSignal("AbsolutePosition"):Connect(function()
|
|
399
|
+
return setWrapperAbsoluteY(frame.AbsolutePosition.Y)
|
|
400
|
+
end)
|
|
401
|
+
end)
|
|
402
|
+
pcall(function()
|
|
403
|
+
sizeConn = frame:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
|
|
404
|
+
return setWrapperAbsoluteSize(frame.AbsoluteSize)
|
|
405
|
+
end)
|
|
406
|
+
end)
|
|
407
|
+
return function()
|
|
408
|
+
local _result = positionConn
|
|
409
|
+
if _result ~= nil then
|
|
410
|
+
_result:Disconnect()
|
|
411
|
+
end
|
|
412
|
+
local _result_1 = sizeConn
|
|
413
|
+
if _result_1 ~= nil then
|
|
414
|
+
_result_1:Disconnect()
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end, { hasOverlay })
|
|
418
|
+
-- How far the overlay header's bottom edge extends past the
|
|
419
|
+
-- wrapper's (i.e. the Box's) top edge — 0 when there's no overlap
|
|
420
|
+
-- (e.g. a small/no theme offset) or nothing has been measured yet.
|
|
421
|
+
local headerClearance = if overlayHeader ~= nil and headerRect ~= nil and wrapperAbsoluteY ~= nil then math.max(0, (headerRect.position.Y + headerRect.size.Y) - wrapperAbsoluteY) else 0
|
|
422
|
+
-- Same idea, mirrored for the footer: the wrapper's bottom edge
|
|
423
|
+
-- (its top plus its own height) minus the footer's reported top
|
|
424
|
+
-- edge (AbsolutePosition is always the top-left corner regardless
|
|
425
|
+
-- of AnchorPoint) is how far the footer's top edge sits above the
|
|
426
|
+
-- Box's bottom edge, i.e. how much it overlaps upward into it.
|
|
427
|
+
local wrapperBottomY = if wrapperAbsoluteY ~= nil and wrapperAbsoluteSize ~= nil then wrapperAbsoluteY + wrapperAbsoluteSize.Y else nil
|
|
428
|
+
local footerClearance = if overlayFooter ~= nil and footerRect ~= nil and wrapperBottomY ~= nil then math.max(0, wrapperBottomY - footerRect.position.Y) else 0
|
|
429
|
+
-- The Box below now sits inside an extra positioning frame (see
|
|
430
|
+
-- boxWithOverlay), so its own Position/AnchorPoint/ZIndex/LayoutOrder
|
|
431
|
+
-- are cleared here to avoid double-applying them — the wrapper frame
|
|
432
|
+
-- owns those instead.
|
|
433
|
+
local _result
|
|
434
|
+
if hasOverlay then
|
|
435
|
+
local _object = table.clone(props)
|
|
436
|
+
setmetatable(_object, nil)
|
|
437
|
+
_object.Position = nil
|
|
438
|
+
_object.AnchorPoint = nil
|
|
439
|
+
_object.top = nil
|
|
440
|
+
_object.left = nil
|
|
441
|
+
_object.right = nil
|
|
442
|
+
_object.bottom = nil
|
|
443
|
+
_object.ZIndex = nil
|
|
444
|
+
_object.LayoutOrder = nil
|
|
445
|
+
_result = _object
|
|
446
|
+
else
|
|
447
|
+
_result = props
|
|
448
|
+
end
|
|
449
|
+
local boxProps = _result
|
|
450
|
+
-- When there's no footer at all, footerClearance is always 0 (there's
|
|
451
|
+
-- nothing to measure an overlap against), so fall back to the Box's
|
|
452
|
+
-- own explicit theme.components.box.padding bottom inset — if the
|
|
453
|
+
-- theme sets one — as a floor instead. This keeps content clear of a
|
|
454
|
+
-- themed Box's decorative border image (e.g. wooden's wood-frame art,
|
|
455
|
+
-- which needs a real bottom inset) even when there's no footer to
|
|
456
|
+
-- reserve that space via measured overlap. Themes that don't set an
|
|
457
|
+
-- explicit box padding (GetExplicitPadding returns undefined) resolve
|
|
458
|
+
-- this floor to 0, so behavior there is unchanged. When a footer IS
|
|
459
|
+
-- present, footerClearance (its real measured overlap) is used as-is
|
|
460
|
+
-- exactly as before — this floor never applies then, since the
|
|
461
|
+
-- footer's own overlap already reserves whatever space it needs.
|
|
462
|
+
local _result_1
|
|
463
|
+
if overlayFooter == nil then
|
|
464
|
+
local _result_2 = SpacingHelper:GetExplicitPadding(theme.components.box.padding, theme.default.spacing)
|
|
465
|
+
if _result_2 ~= nil then
|
|
466
|
+
_result_2 = _result_2.bottom
|
|
467
|
+
end
|
|
468
|
+
local _condition = _result_2
|
|
469
|
+
if _condition == nil then
|
|
470
|
+
_condition = 0
|
|
471
|
+
end
|
|
472
|
+
_result_1 = _condition
|
|
473
|
+
else
|
|
474
|
+
_result_1 = 0
|
|
475
|
+
end
|
|
476
|
+
local explicitBoxBottomFloor = _result_1
|
|
477
|
+
-- When an overlay header and/or footer measurably overlaps into the
|
|
478
|
+
-- Box (headerClearance/footerClearance > 0), or the footer-absent
|
|
479
|
+
-- floor above is nonzero, reserve that much extra top/bottom padding
|
|
480
|
+
-- via resolvedPadding — this takes over Box's own outer padding,
|
|
481
|
+
-- which is otherwise 0/0/0/0 since Card always forces spacing="None"
|
|
482
|
+
-- on Box below.
|
|
483
|
+
local _result_2
|
|
484
|
+
if headerClearance > 0 or footerClearance > 0 or explicitBoxBottomFloor > 0 then
|
|
485
|
+
local _object = table.clone(boxProps)
|
|
486
|
+
setmetatable(_object, nil)
|
|
487
|
+
_object.resolvedPadding = {
|
|
488
|
+
top = headerClearance,
|
|
489
|
+
bottom = math.max(footerClearance, explicitBoxBottomFloor),
|
|
490
|
+
left = 0,
|
|
491
|
+
right = 0,
|
|
492
|
+
}
|
|
493
|
+
_result_2 = _object
|
|
494
|
+
else
|
|
495
|
+
_result_2 = boxProps
|
|
496
|
+
end
|
|
497
|
+
local boxPropsWithOverlayClearance = _result_2
|
|
498
|
+
local _attributes = table.clone(boxPropsWithOverlayClearance)
|
|
101
499
|
setmetatable(_attributes, nil)
|
|
102
500
|
_attributes.ref = ref
|
|
103
501
|
_attributes.center = nil
|
|
@@ -105,7 +503,39 @@ local Card = React.forwardRef(function(props, ref)
|
|
|
105
503
|
_attributes["border-color"] = intent.borderColor
|
|
106
504
|
local cardBox = (React.createElement(Box, _attributes, React.createElement(VStack, {
|
|
107
505
|
spacing = "None",
|
|
108
|
-
},
|
|
506
|
+
}, flowChildren)))
|
|
507
|
+
-- ClipsDescendants is off so an overlay header/footer can protrude
|
|
508
|
+
-- past the Box's own edges (e.g. a plaque mounted above the top
|
|
509
|
+
-- border). This frame takes over the Position/AnchorPoint the Box
|
|
510
|
+
-- would otherwise have used, and auto-sizes to hug the Box (plus
|
|
511
|
+
-- whatever the overlay siblings protrude by).
|
|
512
|
+
--
|
|
513
|
+
-- `center` is stripped before deriving Position/AnchorPoint here,
|
|
514
|
+
-- the same way `cardBox`'s Box always gets `center={undefined}`:
|
|
515
|
+
-- when `center` is set, centering is handled by the outer
|
|
516
|
+
-- `isCentered` wrapper below (a UIListLayout that centers this
|
|
517
|
+
-- whole frame), not by this frame's own Position/AnchorPoint. If
|
|
518
|
+
-- both applied `center` at once, the UIListLayout would place the
|
|
519
|
+
-- frame using an (0,0)-anchor assumption while the frame itself
|
|
520
|
+
-- reported a (0.5,0.5) AnchorPoint, offsetting it by half its own
|
|
521
|
+
-- size and letting its (ClipsDescendants=false) contents spill out.
|
|
522
|
+
-- Explicit top/left/right/bottom/Position/AnchorPoint overrides are
|
|
523
|
+
-- unaffected and still apply directly to this frame.
|
|
524
|
+
local _object = table.clone(props)
|
|
525
|
+
setmetatable(_object, nil)
|
|
526
|
+
_object.center = nil
|
|
527
|
+
local wrapperPositionProps = _object
|
|
528
|
+
local boxWithOverlay = if hasOverlay then (React.createElement("frame", {
|
|
529
|
+
ref = overlayWrapperRef,
|
|
530
|
+
BackgroundTransparency = 1,
|
|
531
|
+
ClipsDescendants = false,
|
|
532
|
+
Size = SizeHelper:GetSize(props, UDim2.fromScale(1, 1)),
|
|
533
|
+
AutomaticSize = SizeHelper:GetAutoSize(props),
|
|
534
|
+
Position = SizeHelper:GetPosition(wrapperPositionProps),
|
|
535
|
+
AnchorPoint = SizeHelper:GetAnchor(wrapperPositionProps),
|
|
536
|
+
ZIndex = props.ZIndex,
|
|
537
|
+
LayoutOrder = props.LayoutOrder,
|
|
538
|
+
}, cardBox, overlayHeader, overlayFooter)) else cardBox
|
|
109
539
|
local isCentered = props.center == true and props.Position == nil and props.AnchorPoint == nil
|
|
110
540
|
return React.createElement(CardContext.Provider, {
|
|
111
541
|
value = contextValue,
|
|
@@ -117,7 +547,7 @@ local Card = React.forwardRef(function(props, ref)
|
|
|
117
547
|
FillDirection = Enum.FillDirection.Horizontal,
|
|
118
548
|
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
|
119
549
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
120
|
-
}),
|
|
550
|
+
}), boxWithOverlay)) else boxWithOverlay)
|
|
121
551
|
end)
|
|
122
552
|
Card.Header = CardHeader
|
|
123
553
|
Card.Footer = CardFooter
|
|
@@ -51,14 +51,52 @@ function Icon(props)
|
|
|
51
51
|
setmetatable(_attributes, nil)
|
|
52
52
|
return React.createElement(SpinningIcon, _attributes)
|
|
53
53
|
end
|
|
54
|
-
|
|
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,
|
|
57
59
|
LayoutOrder = props.LayoutOrder,
|
|
58
60
|
Image = if iconId == nil then nil else `rbxassetid://{iconId}`,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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)
|
|
62
100
|
end
|
|
63
101
|
return {
|
|
64
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 {};
|