blob: 757c2b4f611fc5be56fd3cc462e0c2e2bf540c7b [file]
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import '../../../utils/styles.dart';
class ProjectStatusView extends StatelessComponent {
const ProjectStatusView({required this.steps, required this.completedSteps});
final List<String> steps;
final int completedSteps;
@override
Component build(BuildContext context) {
return div(
classes: 'checklist-container',
[
div(classes: 'checklist-card', [
const h2(classes: 'checklist-title', [Component.text('Loading Project...')]),
div(classes: 'checklist-list', [
for (int i = 0; i < steps.length; i++)
_ChecklistItem(
label: steps[i],
state: i < completedSteps
? ChecklistItemState.done
: (i == completedSteps ? ChecklistItemState.loading : ChecklistItemState.pending),
),
]),
]),
],
);
}
@css
static List<StyleRule> get styles => [
css('.checklist-container').styles(
display: .flex,
width: 100.percent,
height: 100.percent,
justifyContent: .center,
alignItems: .center,
backgroundColor: colorSurface,
),
css('.checklist-card').styles(
display: .flex,
flexDirection: .column,
),
css('.checklist-title').styles(
margin: Margin.only(bottom: 24.px),
color: Colors.white,
textAlign: .center,
fontSize: 18.px,
fontWeight: .w600,
letterSpacing: 0.5.px,
),
css('.checklist-list').styles(
display: .flex,
flexDirection: .column,
gap: Gap.all(16.px),
),
css('.checklist-item').styles(
display: .flex,
alignItems: .center,
gap: Gap.all(12.px),
),
css('.checklist-icon-pending').styles(
width: 16.px,
height: 16.px,
border: Border.all(color: colorBorder, width: 2.px),
radius: .circular(99.px),
flex: const .shrink(0),
),
css('.checklist-icon-loading').styles(
width: 16.px,
height: 16.px,
border: Border.all(color: colorBorder, width: 2.px),
radius: .circular(99.px),
flex: const .shrink(0),
raw: {
'border-top-color': 'var(--color-primary)',
'animation': 'checklist-spin 0.8s linear infinite',
},
),
css('.checklist-icon-done').styles(
display: .flex,
width: 16.px,
height: 16.px,
radius: .circular(99.px),
justifyContent: .center,
alignItems: .center,
flex: const .shrink(0),
color: colorOnPrimary,
backgroundColor: colorPrimary,
),
css('.checklist-label-pending').styles(
color: colorOnSurfaceVariant,
fontSize: 14.px,
fontWeight: .w400,
),
css('.checklist-label-loading').styles(
color: colorPrimary,
fontSize: 14.px,
fontWeight: .w500,
),
css('.checklist-label-done').styles(
color: colorOnSurface,
fontSize: 14.px,
fontWeight: .w400,
),
css.keyframes('checklist-spin', {
'0%': const Styles(raw: {'transform': 'rotate(0deg)'}),
'100%': const Styles(raw: {'transform': 'rotate(360deg)'}),
}),
];
}
enum ChecklistItemState { pending, loading, done }
class _ChecklistItem extends StatelessComponent {
const _ChecklistItem({required this.label, required this.state});
final String label;
final ChecklistItemState state;
@override
Component build(BuildContext context) {
return div(classes: 'checklist-item', [
switch (state) {
ChecklistItemState.pending => const div(classes: 'checklist-icon-pending', []),
ChecklistItemState.loading => const div(classes: 'checklist-icon-loading', []),
ChecklistItemState.done => const div(classes: 'checklist-icon-done', [
svg(
attributes: {'width': '10', 'height': '8', 'viewBox': '0 0 10 8', 'fill': 'none'},
[
path(
attributes: {
'd': 'M1 4L3.5 6.5L9 1',
'stroke': 'currentColor',
'stroke-width': '2',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
},
[],
),
],
),
]),
},
span(
classes: switch (state) {
ChecklistItemState.pending => 'checklist-label-pending',
ChecklistItemState.loading => 'checklist-label-loading',
ChecklistItemState.done => 'checklist-label-done',
},
[Component.text(label)],
),
]);
}
}