Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .claude/rules/coding-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,51 @@ public abstract class ConfigBase<T> : ScriptableObject where T : ConfigBase<T>
- Handle Unity domain reloads properly (state resets on recompile)
- Use `SessionState` or `EditorPrefs` for persistent editor state
- Clean up resources in `EditorApplication.quitting`

## Unit Testing

**IMPORTANT**: When adding or modifying features in non-core packages (JEngine.UI, JEngine.Util), you MUST add unit tests.

### Test Location

Tests go in `Packages/com.jasonxudeveloper.jengine.<package>/Tests/Editor/`:
- Components: `Tests/Editor/Components/<Category>/<ComponentName>Tests.cs`
- Theming: `Tests/Editor/Theming/<ClassName>Tests.cs`
- Utilities: `Tests/Editor/Utilities/<ClassName>Tests.cs`

### Test Pattern

Follow the existing test structure:
```csharp
using NUnit.Framework;
using JEngine.UI.Editor.Components.Layout;

namespace JEngine.UI.Tests.Editor.Components.Layout
{
[TestFixture]
public class JContainerTests
{
private JContainer _container;

[SetUp]
public void SetUp()
{
_container = new JContainer();
}

[Test]
public void Constructor_Default_AddsBaseClass()
{
Assert.IsTrue(_container.ClassListContains("j-container"));
}
}
}
```

### Test Coverage Requirements

- Constructor behavior (default and parameterized)
- All public methods and properties
- Fluent API chaining
- Edge cases and error conditions
- Inherited behavior from base classes
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Document all public APIs with XML comments.
- Run tests via Unity Test Runner
- Use `[UnityTest]` with `UniTask.ToCoroutine()` for async tests
- Editor code should check `TestRunnerCallbacks.IsRunningTests`
- **IMPORTANT**: Non-core packages (JEngine.UI, JEngine.Util) require unit tests for new/modified features

## Code Review Checklist

Expand All @@ -89,3 +90,4 @@ Document all public APIs with XML comments.
- [ ] Uses UniTask for async (not Task)
- [ ] Thread-safe where needed
- [ ] Proper resource cleanup
- [ ] Unit tests added for non-core package changes
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// JContainer.cs
//
// Author:
// JasonXuDeveloper <jason@xgamedev.net>
//
// Copyright (c) 2025 JEngine
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using JEngine.UI.Editor.Theming;
using UnityEngine.UIElements;

namespace JEngine.UI.Editor.Components.Layout
{
/// <summary>
/// Responsive container component with max-width constraints.
/// Centers content horizontally with configurable maximum width.
/// Based on Tailwind CSS container patterns.
/// </summary>
public class JContainer : JComponent
{
private ContainerSize _size;

/// <summary>
/// Creates a new container with the default large (1024px) size.
/// </summary>
public JContainer() : this(ContainerSize.Lg)
{
}

/// <summary>
/// Creates a new container with the specified size.
/// </summary>
/// <param name="size">The container size (max-width constraint).</param>
public JContainer(ContainerSize size) : base("j-container")
{
// Set width to 100% and center with auto margins
style.width = Length.Percent(100);
style.marginLeft = StyleKeyword.Auto;
style.marginRight = StyleKeyword.Auto;

SetSize(size);
}

/// <summary>
/// Gets or sets the container size.
/// </summary>
public ContainerSize Size
{
get => _size;
set => SetSize(value);
}

/// <summary>
/// Sets the container size.
/// </summary>
/// <param name="size">The container size.</param>
/// <returns>This container for chaining.</returns>
public JContainer WithSize(ContainerSize size)
{
SetSize(size);
return this;
}

/// <summary>
/// Sets horizontal padding on the container.
/// </summary>
/// <param name="padding">The padding value in pixels.</param>
/// <returns>This container for chaining.</returns>
public JContainer WithHorizontalPadding(float padding)
{
style.paddingLeft = padding;
style.paddingRight = padding;
return this;
}

/// <summary>
/// Applies responsive horizontal padding based on container size.
/// Larger containers get more padding.
/// </summary>
/// <returns>This container for chaining.</returns>
public JContainer WithResponsivePadding()
{
var padding = _size switch
{
ContainerSize.Xs => Tokens.Spacing.MD,
ContainerSize.Sm => Tokens.Spacing.Lg,
ContainerSize.Md => Tokens.Spacing.Xl,
ContainerSize.Lg => Tokens.Spacing.Xxl,
ContainerSize.Xl => Tokens.Spacing.Xxl,
ContainerSize.Full => Tokens.Spacing.Lg,
_ => Tokens.Spacing.Lg
};

style.paddingLeft = padding;
style.paddingRight = padding;
return this;
}

/// <summary>
/// Sets the container to fluid mode (no max-width constraint).
/// </summary>
/// <returns>This container for chaining.</returns>
public JContainer Fluid()
{
SetSize(ContainerSize.Full);
return this;
}

/// <summary>
/// Adds child elements to this container.
/// </summary>
/// <param name="children">The elements to add.</param>
/// <returns>This container for chaining.</returns>
public new JContainer Add(params VisualElement[] children)
{
base.Add(children);
return this;
}

private void SetSize(ContainerSize size)
{
_size = size;

// Remove existing size classes
RemoveFromClassList("j-container--xs");
RemoveFromClassList("j-container--sm");
RemoveFromClassList("j-container--md");
RemoveFromClassList("j-container--lg");
RemoveFromClassList("j-container--xl");
RemoveFromClassList("j-container--full");

// Add new size class
var sizeClass = size switch
{
ContainerSize.Xs => "j-container--xs",
ContainerSize.Sm => "j-container--sm",
ContainerSize.Md => "j-container--md",
ContainerSize.Lg => "j-container--lg",
ContainerSize.Xl => "j-container--xl",
ContainerSize.Full => "j-container--full",
_ => "j-container--lg"
};
AddToClassList(sizeClass);

// Also set max-width style directly for immediate effect
if (size == ContainerSize.Full)
{
style.maxWidth = StyleKeyword.None;
}
else
{
var maxWidth = size switch
{
ContainerSize.Xs => Tokens.Container.Xs,
ContainerSize.Sm => Tokens.Container.Sm,
ContainerSize.Md => Tokens.Container.Md,
ContainerSize.Lg => Tokens.Container.Lg,
ContainerSize.Xl => Tokens.Container.Xl,
_ => Tokens.Container.Lg
};
style.maxWidth = maxWidth;
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ public static VisualElement CreateInspector(SerializedObject serializedObject, B
root.style.paddingRight = Tokens.Spacing.MD;
root.style.paddingBottom = Tokens.Spacing.MD;

// Centered container for compact inspector layout
var container = new JContainer(ContainerSize.Xs);

var content = new JStack(GapSize.Sm);

// Header
Expand All @@ -98,7 +101,8 @@ public static VisualElement CreateInspector(SerializedObject serializedObject, B
// UI Settings
content.Add(CreateUISettingsSection());

root.Add(content);
container.Add(content);
root.Add(container);

// Register undo/redo callback
Undo.undoRedoPerformed += OnUndoRedo;
Expand Down Expand Up @@ -131,6 +135,9 @@ private static void OnUndoRedo()
// Rebuild the entire UI
_currentRoot.Clear();

// Centered container for compact inspector layout
var container = new JContainer(ContainerSize.Xs);

// Recreate content
var content = new JStack(GapSize.Sm);

Expand All @@ -144,7 +151,8 @@ private static void OnUndoRedo()
content.Add(CreateSecuritySettingsSection());
content.Add(CreateUISettingsSection());

_currentRoot.Add(content);
container.Add(content);
_currentRoot.Add(container);
}

private static VisualElement CreateHeader()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ public static VisualElement CreateContent(Panel panel, BuildManager buildManager
var scrollView = new ScrollView(ScrollViewMode.Vertical);
scrollView.style.flexGrow = 1;

// Centered container for compact panel layout
var container = new JContainer(ContainerSize.Xs);

var content = new JStack(GapSize.Sm);

// Header
Expand All @@ -103,7 +106,8 @@ public static VisualElement CreateContent(Panel panel, BuildManager buildManager
// Status Section
content.Add(CreateStatusSection());

scrollView.Add(content);
container.Add(content);
scrollView.Add(container);
root.Add(scrollView);

return root;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* Base reset and common styles for JEngine Editor UI
*/

/* Base container */
.j-container {
/* Base panel styling */
.j-panel-base {
background-color: var(--j-bg-base);
color: var(--j-text-primary);
padding: var(--j-spacing-lg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,41 @@
* Component-specific styles for JEngine Editor UI
*/

/* ===========================
CONTAINER COMPONENT
=========================== */

/* JContainer - responsive max-width container with horizontal centering */
.j-container {
width: 100%;
margin-left: auto;
margin-right: auto;
}

.j-container--xs {
max-width: 480px;
}

.j-container--sm {
max-width: 640px;
}

.j-container--md {
max-width: 768px;
}

.j-container--lg {
max-width: 1024px;
}

.j-container--xl {
max-width: 1280px;
}

.j-container--full {
max-width: none;
}

/* ===========================
LAYOUT COMPONENTS
=========================== */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,4 +411,24 @@ public enum StatusType
Warning,
Error
}

/// <summary>
/// Container size options for max-width constraints.
/// Based on Tailwind CSS breakpoints.
/// </summary>
public enum ContainerSize
{
/// <summary>480px - Mobile-like, narrow inspectors.</summary>
Xs,
/// <summary>640px - Compact panels, side docks.</summary>
Sm,
/// <summary>768px - Standard inspector width.</summary>
Md,
/// <summary>1024px - Wide panels (default).</summary>
Lg,
/// <summary>1280px - Full-width editor windows.</summary>
Xl,
/// <summary>No max-width constraint (fluid).</summary>
Full
}
}
Loading
Loading