Skip to content

Commit b3b26c4

Browse files
authored
Merge pull request #1 from Nidoxs/collapsible
Collapsible
2 parents ceb9d45 + 7e28676 commit b3b26c4

11 files changed

Lines changed: 373 additions & 4 deletions

File tree

.moonwave/custom.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
td:nth-child(1) {
2-
background-color: rgb(46, 46, 46);
2+
background-color: rgb(60, 60, 61);
33
}
44

55
td:nth-child(2) {
27.8 KB
Loading
21.4 KB
Loading

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Changelog
22

3-
## Unreleased
3+
## 1.4.0
4+
- Components: `Collapsible` (for containing content in an expandable section)
5+
- Hooks: `useToggleState`
6+
7+
## 1.3.0
48
- Added an optional `DisplayTitle` prop to `TabContainer` children tabs to allow displaying custom text on tabs
59

610
## 1.2.0

moonwave.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ classes = ["Constants", "CommonProps"]
2222
[[classOrder]]
2323
section = "Components"
2424
collapsed = false
25-
classes = ["Background", "Button", "Checkbox", "ColorPicker", "DatePicker", "Dropdown", "DropShadowFrame", "Label", "LoadingDots", "MainButton", "NumberSequencePicker", "NumericInput", "PluginProvider", "ProgressBar", "RadioButton", "ScrollFrame", "Slider", "Splitter", "TabContainer", "TextInput"]
25+
classes = ["Background", "Button", "Checkbox", "Collapsible", "ColorPicker", "DatePicker", "Dropdown", "DropShadowFrame", "Label", "LoadingDots", "MainButton", "NumberSequencePicker", "NumericInput", "PluginProvider", "ProgressBar", "RadioButton", "ScrollFrame", "Slider", "Splitter", "TabContainer", "TextInput"]
2626

2727
[[classOrder]]
2828
section = "Hooks"
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
local CommonProps = require(script.Parent.Parent.Parent.CommonProps)
2+
local React = require("@pkg/@jsdotlua/react")
3+
4+
local e = React.createElement
5+
local useTheme = require("../../Hooks/useTheme")
6+
local useToggleState = require("../../Hooks/useToggleState")
7+
8+
local HEADER_HEIGHT = 24
9+
local ARROW_RIGHT = "rbxasset://textures/ui/MenuBar/arrow_right.png"
10+
local ARROW_DOWN = "rbxasset://textures/ui/MenuBar/arrow_down.png"
11+
12+
local function HeaderIcon(props: CommonProps.T & { Icon: { Image: string, UseThemeColor: boolean? } })
13+
local theme = useTheme()
14+
15+
return e("ImageLabel", {
16+
BackgroundTransparency = 1,
17+
BorderSizePixel = 0,
18+
LayoutOrder = props.LayoutOrder,
19+
Size = props.Size or UDim2.fromOffset(HEADER_HEIGHT, HEADER_HEIGHT),
20+
ImageColor3 = if props.Icon.UseThemeColor ~= true
21+
then Color3.fromRGB(255, 255, 255)
22+
else theme:GetColor(Enum.StudioStyleGuideColor.MainText),
23+
ImageTransparency = if props.Disabled then 0.6 else 0,
24+
Image = props.Icon.Image,
25+
})
26+
end
27+
28+
local function Header(props: CommonProps.T & {
29+
Selected: boolean?,
30+
Expanded: boolean?,
31+
IsBlockStyle: boolean?,
32+
OnActivated: () -> (),
33+
Text: string,
34+
Icon: {
35+
Image: string?,
36+
UseThemeColor: boolean?,
37+
},
38+
})
39+
local theme = useTheme()
40+
local hovering = useToggleState(false)
41+
42+
local modifier = Enum.StudioStyleGuideModifier.Default
43+
44+
return e("TextButton", {
45+
AutomaticSize = Enum.AutomaticSize.X,
46+
AutoButtonColor = false,
47+
BackgroundColor3 = if hovering.on
48+
then theme:GetColor(Enum.StudioStyleGuideColor.ViewPortBackground)
49+
elseif props.IsBlockStyle then theme:GetColor(Enum.StudioStyleGuideColor.Titlebar)
50+
else theme:GetColor(Enum.StudioStyleGuideColor.MainBackground),
51+
BackgroundTransparency = 0,
52+
BorderSizePixel = if props.IsBlockStyle then 1 else 0,
53+
BorderColor3 = theme:GetColor(Enum.StudioStyleGuideColor.Border, modifier),
54+
Size = UDim2.new(1, 0, 0, HEADER_HEIGHT),
55+
LayoutOrder = props.LayoutOrder,
56+
Text = "",
57+
ClipsDescendants = true,
58+
59+
[React.Event.MouseEnter] = if not props.Disabled then hovering.enable else nil,
60+
[React.Event.MouseLeave] = if not props.Disabled then hovering.disable else nil,
61+
[React.Event.Activated] = if not props.Disabled then props.OnActivated else nil,
62+
}, {
63+
Layout = e("UIListLayout", {
64+
FillDirection = Enum.FillDirection.Horizontal,
65+
Padding = UDim.new(0, 0),
66+
HorizontalAlignment = Enum.HorizontalAlignment.Left,
67+
VerticalAlignment = Enum.VerticalAlignment.Center,
68+
SortOrder = Enum.SortOrder.LayoutOrder,
69+
}),
70+
71+
UIPadding = e("UIPadding", {
72+
PaddingLeft = UDim.new(0, 5),
73+
}),
74+
75+
ArrowIconFrame = e(HeaderIcon, {
76+
Icon = {
77+
Image = if props.Expanded then ARROW_DOWN else ARROW_RIGHT,
78+
UseThemeColor = true,
79+
},
80+
Disabled = props.Disabled,
81+
LayoutOrder = 1,
82+
Size = UDim2.fromOffset(HEADER_HEIGHT * 0.75, HEADER_HEIGHT * 0.75),
83+
}),
84+
85+
IconFrame = props.Icon and e(HeaderIcon, {
86+
Icon = props.Icon,
87+
Disabled = props.Disabled,
88+
LayoutOrder = 2,
89+
}),
90+
91+
Title = e("TextLabel", {
92+
AutomaticSize = Enum.AutomaticSize.X,
93+
BackgroundTransparency = 1,
94+
BorderSizePixel = 0,
95+
LayoutOrder = 3,
96+
TextTransparency = if props.Disabled then 0.5 else 0,
97+
Size = UDim2.fromOffset(0, HEADER_HEIGHT),
98+
Text = props.Text,
99+
TextColor3 = theme:GetColor(Enum.StudioStyleGuideColor.MainText),
100+
TextXAlignment = Enum.TextXAlignment.Left,
101+
}, {
102+
UIPadding = e("UIPadding", {
103+
PaddingLeft = UDim.new(0, 4),
104+
}),
105+
}),
106+
})
107+
end
108+
109+
return Header
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
--[=[
2+
@class Collapsible
3+
A simple collapsible component that reveals content when clicked.
4+
5+
| Dark | Light |
6+
| - | - |
7+
| ![Dark](/StudioComponents/components/collapsible/dark.png) | ![Light](/StudioComponents/components/collapsible/light.png) |
8+
9+
```lua
10+
local function MySettingsPage()
11+
return React.createElement(StudioComponents.Background, {}, {
12+
General = e(StudioComponents.Collapsible, {
13+
Title = "General",
14+
Icon = {
15+
Image = "path.to.icon",
16+
UseThemeColor = false,
17+
},
18+
IsBlockStyle = true,
19+
LayoutOrder = 1
20+
}, {
21+
-- general settings here
22+
23+
-- collapsibles can also be nested to create tree structures
24+
AnotherCollapsible = e(StudioComponents.Collapsible, {
25+
Title = "Another Collapsible",
26+
IsBlockStyle = false, -- for this nested collapsible let's not use the block style
27+
}),
28+
}),
29+
30+
Graphics = e(StudioComponents.Collapsible, {
31+
Title = "Graphics",
32+
IsBlockStyle = true,
33+
LayoutOrder = 2
34+
}, {
35+
-- graphics settings here
36+
}),
37+
38+
Audio = e(StudioComponents.Collapsible, {
39+
Title = "Audio",
40+
IsBlockStyle = true,
41+
LayoutOrder = 3
42+
}, {
43+
-- audio settings here
44+
}),
45+
})
46+
end
47+
```
48+
]=]
49+
50+
local React = require("@pkg/@jsdotlua/react")
51+
52+
local e = React.createElement
53+
local CommonProps = require(script.Parent.Parent.CommonProps)
54+
local Header = require(script.Header)
55+
local useToggleState = require("../Hooks/useToggleState")
56+
57+
--[=[
58+
@within Collapsible
59+
@interface IconProps
60+
61+
@field Image string
62+
@field UseThemeColor boolean?
63+
]=]
64+
65+
--[=[
66+
@within Collapsible
67+
@interface Props
68+
@tag Component Props
69+
70+
@field ... CommonProps
71+
@field Title string
72+
@field IsBlockStyle boolean?
73+
@field KeepContentMounted boolean? -- if true, uses `Visible` based rendering instead of re-mounting
74+
@field Icon IconProps?
75+
@field Layout React.Element<UILayout>?
76+
@field ContentPadding React.Element<UIPadding>?
77+
]=]
78+
79+
local function Collapsible(props: CommonProps.T & {
80+
Title: string,
81+
IsBlockStyle: boolean?,
82+
KeepContentMounted: boolean?,
83+
Icon: {
84+
Image: string?,
85+
UseThemeColor: boolean?,
86+
},
87+
Layout: React.Element<UILayout>?,
88+
ContentPadding: React.Element<UIPadding>?,
89+
children: React.ReactNode,
90+
})
91+
local collapsible = useToggleState(false)
92+
93+
return e("Frame", {
94+
BackgroundTransparency = 1,
95+
Size = UDim2.fromScale(1, 0),
96+
AutomaticSize = Enum.AutomaticSize.Y,
97+
LayoutOrder = props.LayoutOrder,
98+
}, {
99+
UIListLayout = e("UIListLayout", {
100+
Padding = UDim.new(0, 0),
101+
SortOrder = Enum.SortOrder.LayoutOrder,
102+
}),
103+
104+
Header = e(Header, {
105+
OnActivated = collapsible.toggle,
106+
IsBlockStyle = props.IsBlockStyle or false,
107+
Expanded = collapsible.on,
108+
Disabled = props.Disabled,
109+
Icon = props.Icon,
110+
Text = props.Title,
111+
LayoutOrder = 1,
112+
}),
113+
114+
Content = if not props.KeepContentMounted and not (collapsible.on and props.Disabled ~= true)
115+
then nil
116+
else e("Frame", {
117+
BackgroundTransparency = 1,
118+
Position = UDim2.fromOffset(30, 0),
119+
AutomaticSize = Enum.AutomaticSize.Y,
120+
Size = UDim2.fromScale(1, 0),
121+
AnchorPoint = Vector2.new(0, 0),
122+
Visible = if props.KeepContentMounted then (collapsible.on and props.Disabled ~= true) else true,
123+
LayoutOrder = 2,
124+
}, {
125+
UIListLayout = props.Layout or e("UIListLayout", {
126+
Padding = UDim.new(0, 5),
127+
SortOrder = Enum.SortOrder.LayoutOrder,
128+
}),
129+
130+
UIPadding = props.ContentPadding or e("UIPadding", {
131+
PaddingLeft = UDim.new(0, 10),
132+
PaddingTop = UDim.new(0, 5),
133+
}),
134+
}, props.children),
135+
})
136+
end
137+
138+
return Collapsible

src/Hooks/useTheme.luau

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ local React = require("@pkg/@jsdotlua/react")
2626

2727
local ThemeContext = require("../Contexts/ThemeContext")
2828

29-
local function useTheme()
29+
local function useTheme(): StudioTheme
3030
local theme = React.useContext(ThemeContext)
3131
local studioTheme, setStudioTheme = React.useState(Studio.Theme)
3232

src/Hooks/useToggleState.luau

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
local React = require("@pkg/@jsdotlua/react")
2+
3+
local function useToggleState(default: boolean): {
4+
on: boolean,
5+
enable: () -> (),
6+
disable: () -> (),
7+
toggle: () -> (),
8+
}
9+
local toggled, setToggled = React.useState(default)
10+
11+
local enable = React.useCallback(function()
12+
setToggled(true)
13+
end, {})
14+
15+
local disable = React.useCallback(function()
16+
setToggled(false)
17+
end, {})
18+
19+
local toggle = React.useCallback(function()
20+
setToggled(function(currentToggled)
21+
return not currentToggled
22+
end)
23+
end, {})
24+
25+
return {
26+
on = toggled,
27+
enable = enable,
28+
disable = disable,
29+
toggle = toggle,
30+
}
31+
end
32+
33+
return useToggleState

0 commit comments

Comments
 (0)