From d0c1a76d4ec4e9845a82fcd31facd29d0d9e532e Mon Sep 17 00:00:00 2001
From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com>
Date: Thu, 29 Jan 2026 13:14:44 -0300
Subject: [PATCH 1/3] build: set version to 5.2.0-SNAPSHOT
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 64cc853..b20126d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.flowingcode.vaadin.addons.demo
commons-demo
- 5.1.1-SNAPSHOT
+ 5.2.0-SNAPSHOT
Commons Demo
Common classes for add-ons demo
From 710b1e862edde2cba96ccb7f8e7af34378222000 Mon Sep 17 00:00:00 2001
From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com>
Date: Thu, 29 Jan 2026 13:38:07 -0300
Subject: [PATCH 2/3] feat: deprecate get/applyThemeAttribute in favor of
get/setColorScheme
---
.../vaadin/addons/demo/ColorScheme.java | 59 +++++++++++++++
.../vaadin/addons/demo/TabbedDemo.java | 74 +++++++++++++++----
2 files changed, 118 insertions(+), 15 deletions(-)
create mode 100644 src/main/java/com/flowingcode/vaadin/addons/demo/ColorScheme.java
diff --git a/src/main/java/com/flowingcode/vaadin/addons/demo/ColorScheme.java b/src/main/java/com/flowingcode/vaadin/addons/demo/ColorScheme.java
new file mode 100644
index 0000000..b8d67c6
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/demo/ColorScheme.java
@@ -0,0 +1,59 @@
+/*-
+ * #%L
+ * Commons Demo
+ * %%
+ * Copyright (C) 2020 - 2026 Flowing Code
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.demo;
+
+import lombok.Getter;
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Enumeration of supported color scheme values.
+ *
+ * These values correspond to the CSS color-scheme property values and control how the browser
+ * renders UI elements and how the application responds to system color scheme preferences.
+ */
+@RequiredArgsConstructor
+public enum ColorScheme {
+ /**
+ * Light color scheme. The application will use a light theme regardless of system preferences.
+ */
+ LIGHT("light"),
+
+ /**
+ * Dark color scheme. The application will use a dark theme regardless of system preferences.
+ */
+ DARK("dark");
+
+ @Getter
+ private final String value;
+
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java b/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java
index 311bd32..97ce88e 100644
--- a/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java
+++ b/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java
@@ -94,8 +94,7 @@ public TabbedDemo() {
themeCB.addClassName("smallcheckbox");
themeCB.addValueChangeListener(cb -> {
boolean useDarkTheme = themeCB.getValue();
- String theme = useDarkTheme ? "dark" : "";
- applyThemeAttribute(getElement(), theme);
+ setColorScheme(this, useDarkTheme ? ColorScheme.DARK : ColorScheme.LIGHT);
});
footer = new HorizontalLayout();
footer.setWidthFull();
@@ -201,11 +200,11 @@ public void showRouterLayoutContent(HasElement content) {
demo.getElement().getStyle().set("height", "100%");
setupDemoHelperButton(content.getClass());
}
-
+
updateFooterButtonsVisibility();
getElement().insertChild(1, content.getElement());
- applyThemeAttribute(getElement(), getThemeAttribute());
+ setColorScheme(this, getColorScheme());
}
private static SourceUrlResolver resolver = null;
@@ -241,11 +240,11 @@ private Optional createSourceCodeTab(Class> annotatedClass, Dem
SourceCodeTab.SourceCodeTabBuilder builder = SourceCodeTab.builder();
builder.url(url);
-
+
if (!annotation.caption().equals(DemoSource.DEFAULT_VALUE)) {
builder.caption(annotation.caption());
}
-
+
if (!annotation.language().equals(DemoSource.DEFAULT_VALUE)) {
builder.language(annotation.language());
}
@@ -255,10 +254,10 @@ private Optional createSourceCodeTab(Class> annotatedClass, Dem
}
builder.sourcePosition(annotation.sourcePosition());
-
+
return Optional.of(builder.build());
}
-
+
public static String lookupGithubBranch(Class extends TabbedDemo> clazz) {
GithubBranch branch = clazz.getAnnotation(GithubBranch.class);
if (branch == null) {
@@ -323,16 +322,62 @@ public void setOrientation(Orientation orientation) {
orientationCB.setValue(Orientation.HORIZONTAL.equals(orientation));
}
- private static final String THEME_NAME = TabbedDemo.class.getName() + "#THEME_NAME";
-
+ /**
+ * Returns the theme attribute value.
+ *
+ * The "theme attribute" is either an empty string (light) or "dark".
+ *
+ *
+ * @deprecated Use {@link #getColorScheme()}
+ * @return the theme attribute value
+ */
+ @Deprecated
public static String getThemeAttribute() {
- return (String) Optional.ofNullable(VaadinSession.getCurrent().getAttribute(THEME_NAME))
- .orElse("");
+ ColorScheme scheme = getColorScheme();
+ return scheme == ColorScheme.LIGHT ? "" : scheme.getValue();
}
+ /**
+ * Returns the current color scheme.
+ *
+ * @return the current color scheme
+ */
+ public static ColorScheme getColorScheme() {
+ return Optional.ofNullable(VaadinSession.getCurrent().getAttribute(ColorScheme.class))
+ .orElse(ColorScheme.LIGHT);
+ }
+
+ /**
+ * Applies the theme attribute to the given element.
+ *
+ * The "theme attribute" is either an empty string (light) or "dark".
+ *
+ *
+ * @param element the element to apply the theme to
+ * @param theme the theme attribute value
+ * @deprecated Use {@link #setColorScheme(Component, ColorScheme)}
+ */
+ @Deprecated
public static void applyThemeAttribute(Element element, String theme) {
- VaadinSession.getCurrent().setAttribute(THEME_NAME, theme);
+ Component c = element.getComponent().get();
+ if (theme.isEmpty()) {
+ setColorScheme(c, ColorScheme.LIGHT);
+ } else if (theme.equals("dark")) {
+ setColorScheme(c, ColorScheme.DARK);
+ }
+ }
+
+ /**
+ * Sets the color scheme for the given component.
+ *
+ * @param component the component to apply the color scheme to
+ * @param colorScheme the color scheme to apply
+ */
+ public static void setColorScheme(Component component, @NonNull ColorScheme colorScheme) {
+ VaadinSession.getCurrent().setAttribute(ColorScheme.class, colorScheme);
+ String theme = colorScheme.getValue();
+ Element element = component.getElement();
String script;
if (element.getTag().equalsIgnoreCase("iframe")) {
script = "let e = this.contentWindow.document.documentElement;";
@@ -347,8 +392,7 @@ public static void applyThemeAttribute(Element element, String theme) {
element.executeJs(script, theme);
- Component c = element.getComponent().get();
- collectThemeChangeObservers(c).forEach(observer -> observer.onThemeChange(theme));
+ collectThemeChangeObservers(component).forEach(observer -> observer.onThemeChange(theme));
}
private static Stream collectThemeChangeObservers(Component c) {
From 5e84c0e231a1dc3090b61fd9d14071079989a78d Mon Sep 17 00:00:00 2001
From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com>
Date: Thu, 29 Jan 2026 14:55:31 -0300
Subject: [PATCH 3/3] docs: improve TabbedDemo javadoc
---
.../vaadin/addons/demo/TabbedDemo.java | 57 +++++++++++++++++--
1 file changed, 53 insertions(+), 4 deletions(-)
diff --git a/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java b/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java
index 97ce88e..1deeae7 100644
--- a/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java
+++ b/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java
@@ -48,6 +48,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/**
+ * A layout for displaying a tabbed demo with source code integration.
+ *
+ * This layout consists of a set of tabs for navigating between different demos,
+ * a content area for displaying the current demo, and a footer with controls
+ * for
+ * toggling the source code visibility, orientation, and theme.
+ *
+ */
@StyleSheet("context://frontend/styles/commons-demo/shared-styles.css")
@SuppressWarnings("serial")
public class TabbedDemo extends VerticalLayout implements RouterLayout {
@@ -67,6 +76,9 @@ public class TabbedDemo extends VerticalLayout implements RouterLayout {
private Button helperButton;
private DemoHelperViewer demoHelperViewer;
+ /**
+ * Constructs a new TabbedDemo instance.
+ */
public TabbedDemo() {
demoHelperViewer = new DialogDemoHelperViewer();
@@ -124,6 +136,7 @@ public TabbedDemo() {
*
* @param clazz the class of routed demo view component
* @param label the demo name (tab label)
+ * @throws IllegalArgumentException if the class is not annotated with {@link Route}
*/
public void addDemo(Class extends Component> clazz, String label) {
if (!clazz.isAnnotationPresent(Route.class)) {
@@ -142,6 +155,8 @@ private void updateVisibility() {
/**
* Sets the autovisibility mode. When autovisibility is enabled, the tabs component is hidden
* unless it contains two or more tabs.
+ *
+ * @param autoVisibility {@code true} to enable autovisibility, {@code false} to disable it
*/
public void setAutoVisibility(boolean autoVisibility) {
this.autoVisibility = autoVisibility;
@@ -215,7 +230,7 @@ public void showRouterLayoutContent(HasElement content) {
*
* @param resolver The {@code SourceUrlResolver} to be used. Must not be {@code null}.
* @throws IllegalStateException if a resolver has already been set.
- * @throws NullPointerException if the provided {@code resolver} is {@code null}.
+ * @throws NullPointerException if the provided {@code resolver} is {@code null}.
*/
public static void configureSourceUrlResolver(@NonNull SourceUrlResolver resolver) {
if (TabbedDemo.resolver != null) {
@@ -234,7 +249,7 @@ private static SourceUrlResolver getResolver() {
private Optional createSourceCodeTab(Class> annotatedClass, DemoSource annotation) {
String url = getResolver().resolveURL(this, annotatedClass, annotation).orElse(null);
- if (url==null) {
+ if (url == null) {
return Optional.empty();
}
@@ -258,11 +273,17 @@ private Optional createSourceCodeTab(Class> annotatedClass, Dem
return Optional.of(builder.build());
}
+ /**
+ * Looks up the GitHub branch name associated with the given TabbedDemo class.
+ *
+ * @param clazz the TabbedDemo class to inspect
+ * @return the GitHub branch name, or "master" if the annotation is not found
+ */
public static String lookupGithubBranch(Class extends TabbedDemo> clazz) {
GithubBranch branch = clazz.getAnnotation(GithubBranch.class);
if (branch == null) {
Package pkg = clazz.getPackage();
- if (pkg!=null) {
+ if (pkg != null) {
branch = pkg.getAnnotation(GithubBranch.class);
}
}
@@ -286,11 +307,19 @@ private void updateSplitterPosition() {
}
}
+ /**
+ * Sets the visibility of the source code.
+ *
+ * @param visible {@code true} to make the source code visible, {@code false} otherwise
+ */
public void setSourceVisible(boolean visible) {
codeCB.setValue(visible);
codePositionCB.setVisible(visible);
}
+ /**
+ * Toggles the position of the source code relative to the demo content.
+ */
public void toggleSourcePosition() {
if (currentLayout != null) {
currentLayout.toggleSourcePosition();
@@ -309,10 +338,20 @@ private void toggleSplitterOrientation() {
setOrientation(splitOrientation);
}
+ /**
+ * Returns the current orientation of the split layout.
+ *
+ * @return the current orientation
+ */
public Orientation getOrientation() {
return currentLayout.getOrientation();
}
+ /**
+ * Sets the orientation of the split layout.
+ *
+ * @param orientation the new orientation
+ */
public void setOrientation(Orientation orientation) {
splitOrientation = orientation;
if (currentLayout != null) {
@@ -409,6 +448,11 @@ private void updateFooterButtonsVisibility() {
codePositionCB.setVisible(hasSourceCode);
}
+ /**
+ * Adds a listener for {@link TabbedDemoSourceEvent}.
+ *
+ * @param listener the listener to add
+ */
public void addTabbedDemoSourceListener(ComponentEventListener listener) {
ComponentUtil.addListener(this, TabbedDemoSourceEvent.class, listener);
listener.onComponentEvent(new TabbedDemoSourceEvent(this, currentLayout != null));
@@ -437,6 +481,11 @@ private void adjustSplitOrientation(boolean portraitOrientation) {
setOrientation(splitOrientation);
}
+ /**
+ * Sets the {@link DemoHelperViewer} to be used for displaying demo helpers.
+ *
+ * @param demoHelperViewer the viewer to set
+ */
public void setDemoHelperViewer(DemoHelperViewer demoHelperViewer) {
this.demoHelperViewer =
Objects.requireNonNull(demoHelperViewer, "Demo helper viewer cannot be null");
@@ -455,7 +504,7 @@ private void setupDemoHelperButton(Class> helperClass) {
.addClickListener(e -> demoHelperViewer.show(demoHelperRenderer.helperContent()));
add(helperButton);
} catch (Exception e) {
- logger.error("Error creating an instance",e);
+ logger.error("Error creating an instance", e);
}
}
}