Demo application to show the contrast problem with warning messages using the Lumo theme of Vaadin.
Signed-off-by: Marcus Fihlon <marcus@fihlon.swiss>
This commit is contained in:
parent
6317f9e12f
commit
6679eada69
16 changed files with 105 additions and 458 deletions
87
README.md
87
README.md
|
@ -1,86 +1 @@
|
|||
# App README
|
||||
|
||||
- [ ] TODO Replace or update this README with instructions relevant to your application
|
||||
|
||||
## Project Structure
|
||||
|
||||
The sources of your App have the following structure:
|
||||
|
||||
```
|
||||
src
|
||||
├── main/frontend
|
||||
│ └── themes
|
||||
│ └── default
|
||||
│ ├── styles.css
|
||||
│ └── theme.json
|
||||
├── main/java
|
||||
│ └── [application package]
|
||||
│ ├── base
|
||||
│ │ └── ui
|
||||
│ │ ├── component
|
||||
│ │ │ └── ViewToolbar.java
|
||||
│ │ ├── MainErrorHandler.java
|
||||
│ │ └── MainLayout.java
|
||||
│ ├── examplefeature
|
||||
│ │ ├── ui
|
||||
│ │ │ └── TaskListView.java
|
||||
│ │ ├── Task.java
|
||||
│ │ ├── TaskRepository.java
|
||||
│ │ └── TaskService.java
|
||||
│ └── Application.java
|
||||
└── test/java
|
||||
└── [application package]
|
||||
└── examplefeature
|
||||
└── TaskServiceTest.java
|
||||
```
|
||||
|
||||
The main entry point into the application is `Application.java`. This class contains the `main()` method that start up
|
||||
the Spring Boot application.
|
||||
|
||||
The skeleton follows a *feature-based package structure*, organizing code by *functional units* rather than traditional
|
||||
architectural layers. It includes two feature packages: `base` and `examplefeature`.
|
||||
|
||||
* The `base` package contains classes meant for reuse across different features, either through composition or
|
||||
inheritance. You can use them as-is, tweak them to your needs, or remove them.
|
||||
* The `examplefeature` package is an example feature package that demonstrates the structure. It represents a
|
||||
*self-contained unit of functionality*, including UI components, business logic, data access, and an integration test.
|
||||
Once you create your own features, *you'll remove this package*.
|
||||
|
||||
The `src/main/frontend` directory contains an empty theme called `default`, based on the Lumo theme. It is activated in
|
||||
the `Application` class, using the `@Theme` annotation.
|
||||
|
||||
## Starting in Development Mode
|
||||
|
||||
To start the application in development mode, import it into your IDE and run the `Application` class.
|
||||
You can also start the application from the command line by running:
|
||||
|
||||
```bash
|
||||
./mvnw
|
||||
```
|
||||
|
||||
## Building for Production
|
||||
|
||||
To build the application in production mode, run:
|
||||
|
||||
```bash
|
||||
./mvnw -Pproduction package
|
||||
```
|
||||
|
||||
To build a Docker image, run:
|
||||
|
||||
```bash
|
||||
docker build -t my-application:latest .
|
||||
```
|
||||
|
||||
If you use commercial components, pass the license key as a build secret:
|
||||
|
||||
```bash
|
||||
docker build --secret id=proKey,src=$HOME/.vaadin/proKey .
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
The [Getting Started](https://vaadin.com/docs/latest/getting-started) guide will quickly familiarize you with your new
|
||||
App implementation. You'll learn how to set up your development environment, understand the project
|
||||
structure, and find resources to help you add muscles to your skeleton — transforming it into a fully-featured
|
||||
application.
|
||||
Demo application to show the contrast problem with warning messages using the Lumo theme of Vaadin.
|
||||
|
|
0
mvnw
vendored
Executable file → Normal file
0
mvnw
vendored
Executable file → Normal file
32
src/main/bundles/README.md
Normal file
32
src/main/bundles/README.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
This directory is automatically generated by Vaadin and contains the pre-compiled
|
||||
frontend files/resources for your project (frontend development bundle).
|
||||
|
||||
It should be added to Version Control System and committed, so that other developers
|
||||
do not have to compile it again.
|
||||
|
||||
Frontend development bundle is automatically updated when needed:
|
||||
- an npm/pnpm package is added with @NpmPackage or directly into package.json
|
||||
- CSS, JavaScript or TypeScript files are added with @CssImport, @JsModule or @JavaScript
|
||||
- Vaadin add-on with front-end customizations is added
|
||||
- Custom theme imports/assets added into 'theme.json' file
|
||||
- Exported web component is added.
|
||||
|
||||
If your project development needs a hot deployment of the frontend changes,
|
||||
you can switch Flow to use Vite development server (default in Vaadin 23.3 and earlier versions):
|
||||
- set `vaadin.frontend.hotdeploy=true` in `application.properties`
|
||||
- configure `vaadin-maven-plugin`:
|
||||
```
|
||||
<configuration>
|
||||
<frontendHotdeploy>true</frontendHotdeploy>
|
||||
</configuration>
|
||||
```
|
||||
- configure `jetty-maven-plugin`:
|
||||
```
|
||||
<configuration>
|
||||
<systemProperties>
|
||||
<vaadin.frontend.hotdeploy>true</vaadin.frontend.hotdeploy>
|
||||
</systemProperties>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
Read more [about Vaadin development mode](https://vaadin.com/docs/next/flow/configuration/development-mode#precompiled-bundle).
|
BIN
src/main/bundles/dev.bundle
Normal file
BIN
src/main/bundles/dev.bundle
Normal file
Binary file not shown.
23
src/main/frontend/index.html
Normal file
23
src/main/frontend/index.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
This file is auto-generated by Vaadin.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<style>
|
||||
html, body, #outlet {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<!-- index.ts is included here automatically (either by the dev server or during the build) -->
|
||||
</head>
|
||||
<body>
|
||||
<!-- This outlet div is where the views are rendered -->
|
||||
<div id="outlet"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -1 +1,16 @@
|
|||
/* Add your styles here */
|
||||
|
||||
.warning {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
background-color: var(--lumo-warning-color);
|
||||
color: var(--lumo-warning-text-color);
|
||||
box-shadow: var(--lumo-box-shadow-s);
|
||||
font-size: var(--lumo-font-size-xl);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--lumo-font-size-l);
|
||||
padding-top: var(--lumo-space-l);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
{
|
||||
"lumoImports": [
|
||||
"typography",
|
||||
"color",
|
||||
"spacing",
|
||||
"badge",
|
||||
"utility"
|
||||
]
|
||||
"lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ],
|
||||
"autoInjectComponents" : "false"
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package com.example.base.ui;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.notification.NotificationVariant;
|
||||
import com.vaadin.flow.server.VaadinServiceInitListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
class MainErrorHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MainErrorHandler.class);
|
||||
|
||||
@Bean
|
||||
public VaadinServiceInitListener errorHandlerInitializer() {
|
||||
return (event) -> event.getSource().addSessionInitListener(
|
||||
sessionInitEvent -> sessionInitEvent.getSession().setErrorHandler(errorEvent -> {
|
||||
log.error("An unexpected error occurred", errorEvent.getThrowable());
|
||||
errorEvent.getComponent().flatMap(Component::getUI).ifPresent(ui -> {
|
||||
var notification = new Notification(
|
||||
"An unexpected error has occurred. Please try again later.");
|
||||
notification.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
notification.setPosition(Notification.Position.TOP_CENTER);
|
||||
notification.setDuration(3000);
|
||||
ui.access(notification::open);
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package com.example.base.ui;
|
||||
|
||||
import com.vaadin.flow.component.applayout.AppLayout;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.Span;
|
||||
import com.vaadin.flow.component.icon.Icon;
|
||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.Scroller;
|
||||
import com.vaadin.flow.component.sidenav.SideNav;
|
||||
import com.vaadin.flow.component.sidenav.SideNavItem;
|
||||
import com.vaadin.flow.router.Layout;
|
||||
import com.vaadin.flow.server.menu.MenuConfiguration;
|
||||
import com.vaadin.flow.server.menu.MenuEntry;
|
||||
|
||||
import static com.vaadin.flow.theme.lumo.LumoUtility.*;
|
||||
|
||||
@Layout
|
||||
public final class MainLayout extends AppLayout {
|
||||
|
||||
MainLayout() {
|
||||
setPrimarySection(Section.DRAWER);
|
||||
addToDrawer(createHeader(), new Scroller(createSideNav()));
|
||||
}
|
||||
|
||||
private Div createHeader() {
|
||||
// TODO Replace with real application logo and name
|
||||
var appLogo = VaadinIcon.CUBES.create();
|
||||
appLogo.addClassNames(TextColor.PRIMARY, IconSize.LARGE);
|
||||
|
||||
var appName = new Span("App");
|
||||
appName.addClassNames(FontWeight.SEMIBOLD, FontSize.LARGE);
|
||||
|
||||
var header = new Div(appLogo, appName);
|
||||
header.addClassNames(Display.FLEX, Padding.MEDIUM, Gap.MEDIUM, AlignItems.CENTER);
|
||||
return header;
|
||||
}
|
||||
|
||||
private SideNav createSideNav() {
|
||||
var nav = new SideNav();
|
||||
nav.addClassNames(Margin.Horizontal.MEDIUM);
|
||||
MenuConfiguration.getMenuEntries().forEach(entry -> nav.addItem(createSideNavItem(entry)));
|
||||
return nav;
|
||||
}
|
||||
|
||||
private SideNavItem createSideNavItem(MenuEntry menuEntry) {
|
||||
if (menuEntry.icon() != null) {
|
||||
return new SideNavItem(menuEntry.title(), menuEntry.path(), new Icon(menuEntry.icon()));
|
||||
} else {
|
||||
return new SideNavItem(menuEntry.title(), menuEntry.path());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package com.example.base.ui.component;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.component.applayout.DrawerToggle;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.H1;
|
||||
import com.vaadin.flow.component.html.Header;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility.*;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@NullMarked
|
||||
public final class ViewToolbar extends Composite<Header> {
|
||||
|
||||
public ViewToolbar(@Nullable String viewTitle, Component... components) {
|
||||
addClassNames(Display.FLEX, FlexDirection.COLUMN, JustifyContent.BETWEEN, AlignItems.STRETCH, Gap.MEDIUM,
|
||||
FlexDirection.Breakpoint.Medium.ROW, AlignItems.Breakpoint.Medium.CENTER);
|
||||
|
||||
var drawerToggle = new DrawerToggle();
|
||||
drawerToggle.addClassNames(Margin.NONE);
|
||||
|
||||
var title = new H1(viewTitle);
|
||||
title.addClassNames(FontSize.XLARGE, Margin.NONE, FontWeight.LIGHT);
|
||||
|
||||
var toggleAndTitle = new Div(drawerToggle, title);
|
||||
toggleAndTitle.addClassNames(Display.FLEX, AlignItems.CENTER);
|
||||
getContent().add(toggleAndTitle);
|
||||
|
||||
if (components.length > 0) {
|
||||
var actions = new Div(components);
|
||||
actions.addClassNames(Display.FLEX, FlexDirection.COLUMN, JustifyContent.BETWEEN, Flex.GROW, Gap.SMALL,
|
||||
FlexDirection.Breakpoint.Medium.ROW);
|
||||
getContent().add(actions);
|
||||
}
|
||||
}
|
||||
|
||||
public static Component group(Component... components) {
|
||||
var group = new Div(components);
|
||||
group.addClassNames(Display.FLEX, FlexDirection.COLUMN, AlignItems.STRETCH, Gap.SMALL,
|
||||
FlexDirection.Breakpoint.Medium.ROW, AlignItems.Breakpoint.Medium.CENTER);
|
||||
return group;
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
package com.example.examplefeature;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity
|
||||
@Table(name = "task")
|
||||
public class Task {
|
||||
|
||||
public static final int DESCRIPTION_MAX_LENGTH = 300;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE)
|
||||
@Column(name = "task_id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "description", nullable = false, length = DESCRIPTION_MAX_LENGTH)
|
||||
private String description = "";
|
||||
|
||||
@Column(name = "creation_date", nullable = false)
|
||||
private Instant creationDate;
|
||||
|
||||
@Column(name = "due_date")
|
||||
@Nullable
|
||||
private LocalDate dueDate;
|
||||
|
||||
protected Task() { // To keep Hibernate happy
|
||||
}
|
||||
|
||||
public Task(String description, Instant creationDate) {
|
||||
setDescription(description);
|
||||
this.creationDate = creationDate;
|
||||
}
|
||||
|
||||
public @Nullable Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
if (description.length() > DESCRIPTION_MAX_LENGTH) {
|
||||
throw new IllegalArgumentException("Description length exceeds " + DESCRIPTION_MAX_LENGTH);
|
||||
}
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Instant getCreationDate() {
|
||||
return creationDate;
|
||||
}
|
||||
|
||||
public @Nullable LocalDate getDueDate() {
|
||||
return dueDate;
|
||||
}
|
||||
|
||||
public void setDueDate(@Nullable LocalDate dueDate) {
|
||||
this.dueDate = dueDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || !getClass().isAssignableFrom(obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Task other = (Task) obj;
|
||||
return getId() != null && getId().equals(other.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// Hashcode should never change during the lifetime of an object. Because of
|
||||
// this we can't use getId() to calculate the hashcode. Unless you have sets
|
||||
// with lots of entities in them, returning the same hashcode should not be a
|
||||
// problem.
|
||||
return getClass().hashCode();
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package com.example.examplefeature;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
interface TaskRepository extends JpaRepository<Task, Long>, JpaSpecificationExecutor<Task> {
|
||||
|
||||
// If you don't need a total row count, Slice is better than Page as it only performs a select query.
|
||||
// Page performs both a select and a count query.
|
||||
Slice<Task> findAllBy(Pageable pageable);
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package com.example.examplefeature;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class TaskService {
|
||||
|
||||
private final TaskRepository taskRepository;
|
||||
|
||||
TaskService(TaskRepository taskRepository) {
|
||||
this.taskRepository = taskRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void createTask(String description, @Nullable LocalDate dueDate) {
|
||||
if ("fail".equals(description)) {
|
||||
throw new RuntimeException("This is for testing the error handler");
|
||||
}
|
||||
var task = new Task(description, Instant.now());
|
||||
task.setDueDate(dueDate);
|
||||
taskRepository.saveAndFlush(task);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Task> list(Pageable pageable) {
|
||||
return taskRepository.findAllBy(pageable).toList();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
/**
|
||||
* This is a feature package for the Task Management sample feature. Its purpose is to demonstrate how you typically
|
||||
* structure Vaadin business applications, and how the different building blocks interact.
|
||||
* <p>
|
||||
* A feature package represents a self-contained unit of functionality, including UI components, business logic, and
|
||||
* data access. It could be a subdomain or bounded context (e.g., "Billing"), a specific use case (e.g., "User
|
||||
* Registration"), or even a complex UI view (e.g., "Dashboard").
|
||||
* </p>
|
||||
* <p>
|
||||
* If your application is very small, you may not need dedicated feature packages. In that case, move the subpackages
|
||||
* directly to the application package.
|
||||
* </p>
|
||||
*/
|
||||
@NullMarked
|
||||
package com.example.examplefeature;
|
||||
// TODO Remove this package once you have added real features
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
|
@ -1,83 +0,0 @@
|
|||
package com.example.examplefeature.ui;
|
||||
|
||||
import com.example.base.ui.component.ViewToolbar;
|
||||
import com.example.examplefeature.Task;
|
||||
import com.example.examplefeature.TaskService;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.datepicker.DatePicker;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.html.Main;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.notification.NotificationVariant;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.Menu;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.vaadin.flow.spring.data.VaadinSpringDataHelpers.toSpringPageRequest;
|
||||
|
||||
@Route("")
|
||||
@PageTitle("Task List")
|
||||
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Task List")
|
||||
class TaskListView extends Main {
|
||||
|
||||
private final TaskService taskService;
|
||||
|
||||
final TextField description;
|
||||
final DatePicker dueDate;
|
||||
final Button createBtn;
|
||||
final Grid<Task> taskGrid;
|
||||
|
||||
TaskListView(TaskService taskService) {
|
||||
this.taskService = taskService;
|
||||
|
||||
description = new TextField();
|
||||
description.setPlaceholder("What do you want to do?");
|
||||
description.setAriaLabel("Task description");
|
||||
description.setMaxLength(Task.DESCRIPTION_MAX_LENGTH);
|
||||
description.setMinWidth("20em");
|
||||
|
||||
dueDate = new DatePicker();
|
||||
dueDate.setPlaceholder("Due date");
|
||||
dueDate.setAriaLabel("Due date");
|
||||
|
||||
createBtn = new Button("Create", event -> createTask());
|
||||
createBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
var dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(getLocale())
|
||||
.withZone(ZoneId.systemDefault());
|
||||
var dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(getLocale());
|
||||
|
||||
taskGrid = new Grid<>();
|
||||
taskGrid.setItems(query -> taskService.list(toSpringPageRequest(query)).stream());
|
||||
taskGrid.addColumn(Task::getDescription).setHeader("Description");
|
||||
taskGrid.addColumn(task -> Optional.ofNullable(task.getDueDate()).map(dateFormatter::format).orElse("Never"))
|
||||
.setHeader("Due Date");
|
||||
taskGrid.addColumn(task -> dateTimeFormatter.format(task.getCreationDate())).setHeader("Creation Date");
|
||||
taskGrid.setSizeFull();
|
||||
|
||||
setSizeFull();
|
||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
||||
|
||||
add(new ViewToolbar("Task List", ViewToolbar.group(description, dueDate, createBtn)));
|
||||
add(taskGrid);
|
||||
}
|
||||
|
||||
private void createTask() {
|
||||
taskService.createTask(description.getValue(), dueDate.getValue());
|
||||
taskGrid.getDataProvider().refreshAll();
|
||||
description.clear();
|
||||
dueDate.clear();
|
||||
Notification.show("Task added", 3000, Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
}
|
||||
|
||||
}
|
31
src/main/java/com/example/views/TestView.java
Normal file
31
src/main/java/com/example/views/TestView.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
package com.example.views;
|
||||
|
||||
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.H1;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.router.RouterLayout;
|
||||
|
||||
@Route("")
|
||||
public class TestView extends Div implements RouterLayout {
|
||||
|
||||
public TestView() {
|
||||
|
||||
final var warning = new Div("This is a warning message.");
|
||||
warning.setClassName("warning");
|
||||
add(warning);
|
||||
|
||||
add(new H1("Lumo Theme Test"));
|
||||
|
||||
final var darkModeSwitcher = new Checkbox("Dark mode");
|
||||
add(darkModeSwitcher);
|
||||
darkModeSwitcher.addValueChangeListener(event -> {
|
||||
if (event.getValue()) {
|
||||
getElement().getThemeList().add("dark");
|
||||
} else {
|
||||
getElement().getThemeList().remove("dark");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue