Skip to content

Commit 492d375

Browse files
authored
Introduce stepper dialog (#934)
A stepper dialog (also known as wizard), contains at least two steps that are displayed consecutively to the user and ask them for input. They are usually applied when the user input is more complex, is conditionally chained, or can be separated contextually into logical steps. This contribution introduces a stepper dialog component, that is easy to build and extend, and simplifies the stepper dialog creation.
1 parent e70a389 commit 492d375

File tree

16 files changed

+879
-32
lines changed

16 files changed

+879
-32
lines changed

user-interface/front-end-components.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Frontend components
22
Some visual aid of our custom view components structure.
33

4-
## Dialog window
4+
## App dialog
55

66
```mermaid
77
---
@@ -71,3 +71,70 @@ classDiagram
7171
}
7272
7373
```
74+
75+
## Stepper dialog
76+
77+
```mermaid
78+
79+
classDiagram
80+
81+
StepperDialogFooter ..|> NavigationListener
82+
StepperDialogFooter --> StepperDialog
83+
StepperDialog --> NavigationListener
84+
DialogStep ..|> Step
85+
StepperDialog --> AppDialog
86+
StepperDialog --> Step
87+
StepDisplay ..|> NavigationListener
88+
StepDisplay --> StepperDialog
89+
90+
91+
class Step {
92+
<<interface>>
93+
+ name() String
94+
+ content() Component
95+
+ userInput() UserInput
96+
}
97+
98+
class AppDialog {
99+
100+
}
101+
102+
class DialogStep {
103+
104+
}
105+
106+
class StepperDialog {
107+
AppDialog dialog
108+
Step[] steps
109+
+ registerCancelAction(Action action)
110+
+ registerConfirmAction(Action action)
111+
+ registerNavigationListener(NavigationListener listener)
112+
+ setFooter(Component component)
113+
+ setHeader(Component component)
114+
+ setStepDisplay(Component component)
115+
+ cancel()
116+
+ confirm()
117+
+ next()
118+
+ previous()
119+
120+
}
121+
122+
class NavigationListener {
123+
<<interface>>
124+
+ onNavigationUpdate(NavigationInfo info)
125+
}
126+
127+
class StepDisplay {
128+
StepperDialog dialog
129+
}
130+
131+
class StepperDialogFooter {
132+
DialogFooter currentState
133+
StepperDialog dialog
134+
135+
}
136+
137+
```
138+
139+
140+

user-interface/frontend/themes/datamanager/components/all.css

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,16 @@ First some general property definitions
2525
--font-weight-medium: 500;
2626
--font-weight-semi-bold: 600;
2727
--font-weight-bold: 700;
28+
--icon-color-primary: var(--lumo-primary-color);
2829
--icon-color-info: rgba(22, 118, 243, 1);
2930
--icon-color-error: rgba(255, 66, 56, 1);
3031
--icon-color-success: rgba(21, 193, 93, 1);
3132
--icon-color-warning: rgba(254, 201, 1, 1);
32-
--icon-size-m: var(--lumo-icon-size-m);
33+
--icon-color-default: rgba(28, 46, 69, 0.6);
34+
--icon-size-xs: 1rem;
35+
--icon-size-s: 1.25rem;
36+
--icon-size-m: 1.5rem;
37+
--icon-size-l: 2rem;
3338
--spacing-01: 0.125rem;
3439
--spacing-02: 0.25rem;
3540
--spacing-03: 0.5rem;
@@ -147,6 +152,16 @@ Body text
147152
Padding & gaps
148153
***************************/
149154

155+
.padding-left-right-02 {
156+
padding-left: var(--spacing-02);
157+
padding-right: var(--spacing-02);
158+
}
159+
160+
.padding-left-right-03 {
161+
padding-left: var(--spacing-03);
162+
padding-right: var(--spacing-03);
163+
}
164+
150165
.padding-left-right-04 {
151166
padding-left: var(--spacing-04);
152167
padding-right: var(--spacing-04);
@@ -187,6 +202,14 @@ Padding & gaps
187202
padding-bottom: var(--spacing-07);
188203
}
189204

205+
.gap-02 {
206+
gap: var(--spacing-02);
207+
}
208+
209+
.gap-03 {
210+
gap: var(--spacing-03);
211+
}
212+
190213
.gap-04 {
191214
gap: var(--spacing-04);
192215
}
@@ -275,6 +298,35 @@ Dialog flavours
275298
color: var(--lumo-header-text-color);
276299
}
277300

301+
.dialog-step-name-text {
302+
font-size: var(--lumo-font-size-m);
303+
font-weight: var(--font-weight-medium);
304+
line-height: var(--lumo-line-height-xs);
305+
}
306+
307+
.dialog-step-icon-arrow {
308+
font-size: 12px;
309+
}
310+
311+
.footer {
312+
justify-content: flex-end;
313+
}
314+
315+
.footer-intermediate {
316+
justify-content: space-between;
317+
width: 100%;
318+
}
319+
320+
.full-width {
321+
width: 100%;
322+
}
323+
324+
.border-bottom-solid {
325+
border-bottom-style: solid;
326+
border-bottom-color: rgba(26, 56, 96, 0.1);
327+
border-bottom-width: 1px;
328+
}
329+
278330
/***************************
279331
Buttons
280332
**************************/
@@ -301,10 +353,68 @@ Icons
301353
color: var(--icon-color-warning)
302354
}
303355

356+
.icon-size-xs {
357+
height: var(--icon-size-xs);
358+
width: var(--icon-size-xs);
359+
}
360+
361+
.icon-size-s {
362+
height: var(--icon-size-s);
363+
width: var(--icon-size-s);
364+
}
365+
304366
.icon-size-m {
305367
height: var(--icon-size-m);
368+
width: var(--icon-size-m);
369+
}
370+
371+
.icon-size-l {
372+
height: var(--icon-size-l);
373+
width: var(--icon-size-l);
374+
}
375+
376+
.round {
377+
border-radius: 50%;
378+
}
379+
380+
.icon-background-color-default {
381+
background-color: var(--icon-color-default);
306382
}
307383

384+
.icon-background-color-primary {
385+
background-color: var(--icon-color-primary);
386+
}
387+
388+
.icon-color-default {
389+
color: var(--icon-color-default);
390+
}
391+
392+
.icon-text-white {
393+
color: white;
394+
}
395+
396+
.icon-text-inner {
397+
font-size: 0.875rem;
398+
}
399+
400+
.icon-label-text {
401+
font-size: 0.875rem;
402+
}
403+
404+
.icon-label-text-color-default {
405+
color: var(--icon-color-default);
406+
}
407+
408+
.icon-label-text-color-primary {
409+
color: var(--icon-color-primary);
410+
}
411+
412+
.icon-content-center {
413+
display: inline-flex;
414+
justify-content: center;
415+
align-items: center;
416+
line-height: 1;
417+
}
308418

309419

310420
/****************************
@@ -315,11 +425,20 @@ Layout
315425
align-items: center;
316426
}
317427

428+
.flex-align-items-bottom {
429+
align-items: self-end;
430+
}
431+
318432
.flex-horizontal {
319433
display: flex;
320434
flex-direction: row;
321435
}
322436

437+
.flex-vertical {
438+
display: flex;
439+
flex-direction: column;
440+
}
441+
323442

324443

325444
/****************************
596 Bytes
Binary file not shown.
889 KB
Binary file not shown.

user-interface/src/main/java/life/qbic/datamanager/views/demo/ComponentDemo.java

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import com.vaadin.flow.router.Route;
1010
import com.vaadin.flow.server.auth.AnonymousAllowed;
1111
import com.vaadin.flow.spring.annotation.UIScope;
12+
import java.util.ArrayList;
1213
import java.util.Arrays;
14+
import java.util.List;
1315
import java.util.Objects;
1416
import life.qbic.datamanager.views.general.dialog.AppDialog;
1517
import life.qbic.datamanager.views.general.dialog.DialogBody;
@@ -18,6 +20,10 @@
1820
import life.qbic.datamanager.views.general.dialog.DialogSection;
1921
import life.qbic.datamanager.views.general.dialog.InputValidation;
2022
import life.qbic.datamanager.views.general.dialog.UserInput;
23+
import life.qbic.datamanager.views.general.dialog.stepper.Step;
24+
import life.qbic.datamanager.views.general.dialog.stepper.StepperDisplay;
25+
import life.qbic.datamanager.views.general.dialog.stepper.StepperDialog;
26+
import life.qbic.datamanager.views.general.dialog.stepper.StepperDialogFooter;
2127
import life.qbic.datamanager.views.general.icon.IconFactory;
2228
import org.springframework.context.annotation.Profile;
2329
import org.springframework.lang.NonNull;
@@ -38,6 +44,8 @@
3844
public class ComponentDemo extends Div {
3945

4046
public static final String HEADING_2 = "heading-2";
47+
public static final String GAP_04 = "gap-04";
48+
public static final String FLEX_VERTICAL = "flex-vertical";
4149
Div title = new Div("Data Manager - Component Demo");
4250

4351
public ComponentDemo() {
@@ -50,6 +58,7 @@ public ComponentDemo() {
5058
add(dialogShowCase(AppDialog.medium(), "Medium Dialog Type"));
5159
add(dialogShowCase(AppDialog.large(), "Large Dialog Type"));
5260
add(dialogSectionShowCase());
61+
add(stepperDialogShowCase(threeSteps(), "Three steps example"));
5362
}
5463

5564
private static Div dialogSectionShowCase() {
@@ -88,7 +97,7 @@ private static Div fontsShowCase() {
8897
Div header = new Div("Body Font Styles");
8998
header.addClassName(HEADING_2);
9099
container.add(header);
91-
container.addClassNames("flex-vertical", "gap-04");
100+
container.addClassNames(FLEX_VERTICAL, GAP_04);
92101

93102
Arrays.stream(BodyFontStyles.fontStyles).forEach(fontStyle -> {
94103
Div styleHeader = new Div();
@@ -109,6 +118,69 @@ private static Div fontsShowCase() {
109118
return container;
110119
}
111120

121+
private static Div stepperDialogShowCase(List<Step> steps, String dialogTitle) {
122+
Div content = new Div();
123+
Div title = new Div("Stepper Dialog");
124+
title.addClassName(HEADING_2);
125+
Button showDialog = new Button("Show Stepper");
126+
AppDialog dialog = AppDialog.medium();
127+
128+
DialogHeader.with(dialog, dialogTitle);
129+
StepperDialog stepperDialog = StepperDialog.create(dialog, steps);
130+
StepperDialogFooter.with(stepperDialog);
131+
132+
StepperDisplay.with(stepperDialog, steps.stream().map(Step::name).toList());
133+
134+
showDialog.addClickListener(listener -> stepperDialog.open());
135+
136+
content.add(title);
137+
content.add(showDialog);
138+
content.addClassNames(FLEX_VERTICAL, GAP_04);
139+
140+
Div confirmBox = new Div("Click the button and press 'Cancel' or 'Save'");
141+
dialog.registerConfirmAction(() -> {
142+
confirmBox.setText("Stepper dialog has been confirmed");
143+
dialog.close();
144+
});
145+
146+
dialog.registerCancelAction(() -> {
147+
confirmBox.setText("Stepper dialog has been cancelled");
148+
dialog.close();
149+
});
150+
151+
content.add(confirmBox);
152+
153+
return content;
154+
}
155+
156+
private static List<Step> threeSteps() {
157+
List<Step> steps = new ArrayList<>();
158+
for (int step= 0; step < 3; step++) {
159+
int stepNumber = step + 1;
160+
steps.add(new Step() {
161+
162+
final ExampleUserInput userInput = new ExampleUserInput("example step " + stepNumber );
163+
164+
165+
@Override
166+
public String name() {
167+
return "Step " + stepNumber;
168+
}
169+
170+
@Override
171+
public com.vaadin.flow.component.Component component() {
172+
return userInput;
173+
}
174+
175+
@Override
176+
public UserInput userInput() {
177+
return userInput;
178+
}
179+
});
180+
}
181+
return steps;
182+
}
183+
112184
private static Div dialogShowCase(AppDialog dialog, String dialogType) {
113185
Div content = new Div();
114186
Div title = new Div();
@@ -142,7 +214,7 @@ private static Div dialogShowCase(AppDialog dialog, String dialogType) {
142214
});
143215

144216
content.add(showDialog, confirmBox);
145-
content.addClassNames("flex-vertical", "gap-04");
217+
content.addClassNames(FLEX_VERTICAL, GAP_04);
146218
return content;
147219
}
148220

0 commit comments

Comments
 (0)