Standardize UI to design system tokens

Refactor multiple UI components to use design system tokens and primitives. Added new UiIcons (coffee, wifi, xCircle, ban) and typography color getters (primary, accent). Replaced hardcoded paddings, spacings, radii, borderRadius, and icon imports (lucide_icons -> UiIcons) with UiConstants, UiColors, UiTypography and UiIcons, and switched to UiColors.withValues for opacity. Changes apply across authentication, availability, clock_in (and its widgets), commute tracker, lunch break modal, location map placeholder, attendance card, date selector, and related presentation files to improve visual consistency.
This commit is contained in:
Achintha Isuru
2026-02-10 17:17:56 -05:00
parent bcd973cf64
commit 4c38013c10
58 changed files with 1821 additions and 1832 deletions

View File

@@ -32,9 +32,7 @@ class AttirePage extends StatelessWidget {
),
title: Text(
t.staff_profile_attire.title,
style: UiTypography.headline3m.copyWith(
color: UiColors.textPrimary,
),
style: UiTypography.headline3m.textPrimary,
),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),

View File

@@ -11,8 +11,8 @@ class AttireInfoCard extends StatelessWidget {
return Container(
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.08),
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
color: UiColors.primary.withValues(alpha: 0.08),
borderRadius: UiConstants.radiusLg,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -26,16 +26,12 @@ class AttireInfoCard extends StatelessWidget {
children: <Widget>[
Text(
t.staff_profile_attire.info_card.title,
style: UiTypography.body2m.copyWith(
color: UiColors.textPrimary,
),
style: UiTypography.body2m.textPrimary,
),
const SizedBox(height: 2),
Text(
t.staff_profile_attire.info_card.description,
style: UiTypography.body2r.copyWith(
color: UiColors.textSecondary,
),
style: UiTypography.body2r.textSecondary,
),
],
),

View File

@@ -23,12 +23,12 @@ class EmergencyContactScreen extends StatelessWidget {
appBar: AppBar(
elevation: 0,
leading: IconButton(
icon: Icon(UiIcons.chevronLeft, color: UiColors.textSecondary),
icon: const Icon(UiIcons.chevronLeft, color: UiColors.textSecondary),
onPressed: () => Modular.to.pop(),
),
title: Text(
'Emergency Contact',
style: UiTypography.title1m.copyWith(color: UiColors.textPrimary),
style: UiTypography.title1m.textPrimary,
),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),
@@ -53,11 +53,11 @@ class EmergencyContactScreen extends StatelessWidget {
children: [
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.all(UiConstants.space6),
padding: const EdgeInsets.all(UiConstants.space6),
child: Column(
children: [
const EmergencyContactInfoBanner(),
SizedBox(height: UiConstants.space6),
const SizedBox(height: UiConstants.space6),
...state.contacts.asMap().entries.map(
(entry) => EmergencyContactFormItem(
index: entry.key,
@@ -66,7 +66,7 @@ class EmergencyContactScreen extends StatelessWidget {
),
),
const EmergencyContactAddButton(),
SizedBox(height: UiConstants.space16),
const SizedBox(height: UiConstants.space16),
],
),
),

View File

@@ -12,20 +12,20 @@ class EmergencyContactAddButton extends StatelessWidget {
child: TextButton.icon(
onPressed: () =>
context.read<EmergencyContactBloc>().add(EmergencyContactAdded()),
icon: Icon(UiIcons.add, size: 20.0),
icon: const Icon(UiIcons.add, size: 20.0),
label: Text(
'Add Another Contact',
style: UiTypography.title2b,
),
style: TextButton.styleFrom(
foregroundColor: UiColors.primary,
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space6,
vertical: UiConstants.space3,
),
shape: RoundedRectangleBorder(
borderRadius: UiConstants.radiusFull,
side: BorderSide(color: UiColors.primary),
side: const BorderSide(color: UiColors.primary),
),
),
),

View File

@@ -19,8 +19,8 @@ class EmergencyContactFormItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: UiConstants.space4),
padding: EdgeInsets.all(UiConstants.space4),
margin: const EdgeInsets.only(bottom: UiConstants.space4),
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: UiColors.bgPopup,
borderRadius: UiConstants.radiusLg,
@@ -30,33 +30,27 @@ class EmergencyContactFormItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(context),
SizedBox(height: UiConstants.space4),
const SizedBox(height: UiConstants.space4),
_buildLabel('Full Name'),
_buildTextField(
initialValue: contact.name,
hint: 'Contact name',
icon: UiIcons.user,
onChanged: (val) => context.read<EmergencyContactBloc>().add(
EmergencyContactUpdated(
index,
contact.copyWith(name: val),
),
),
EmergencyContactUpdated(index, contact.copyWith(name: val)),
),
),
SizedBox(height: UiConstants.space4),
const SizedBox(height: UiConstants.space4),
_buildLabel('Phone Number'),
_buildTextField(
initialValue: contact.phone,
hint: '+1 (555) 000-0000',
icon: UiIcons.phone,
onChanged: (val) => context.read<EmergencyContactBloc>().add(
EmergencyContactUpdated(
index,
contact.copyWith(phone: val),
),
),
EmergencyContactUpdated(index, contact.copyWith(phone: val)),
),
),
SizedBox(height: UiConstants.space4),
const SizedBox(height: UiConstants.space4),
_buildLabel('Relationship'),
_buildDropdown(
context,
@@ -65,11 +59,11 @@ class EmergencyContactFormItem extends StatelessWidget {
onChanged: (val) {
if (val != null) {
context.read<EmergencyContactBloc>().add(
EmergencyContactUpdated(
index,
contact.copyWith(relationship: val),
),
);
EmergencyContactUpdated(
index,
contact.copyWith(relationship: val),
),
);
}
},
),
@@ -85,7 +79,7 @@ class EmergencyContactFormItem extends StatelessWidget {
required ValueChanged<RelationshipType?> onChanged,
}) {
return Container(
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space2,
),
@@ -99,13 +93,13 @@ class EmergencyContactFormItem extends StatelessWidget {
value: value,
isExpanded: true,
dropdownColor: UiColors.bgPopup,
icon: Icon(UiIcons.chevronDown, color: UiColors.iconSecondary),
icon: const Icon(UiIcons.chevronDown, color: UiColors.iconSecondary),
items: items.map((type) {
return DropdownMenuItem<RelationshipType>(
value: type,
child: Text(
_formatRelationship(type),
style: UiTypography.body1r.copyWith(color: UiColors.textPrimary),
style: UiTypography.body1r.textPrimary,
),
);
}).toList(),
@@ -116,11 +110,15 @@ class EmergencyContactFormItem extends StatelessWidget {
}
String _formatRelationship(RelationshipType type) {
switch(type) {
case RelationshipType.family: return 'Family';
case RelationshipType.spouse: return 'Spouse';
case RelationshipType.friend: return 'Friend';
case RelationshipType.other: return 'Other';
switch (type) {
case RelationshipType.family:
return 'Family';
case RelationshipType.spouse:
return 'Spouse';
case RelationshipType.friend:
return 'Friend';
case RelationshipType.other:
return 'Other';
}
}
@@ -128,22 +126,17 @@ class EmergencyContactFormItem extends StatelessWidget {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Contact ${index + 1}',
style: UiTypography.title2m.copyWith(
color: UiColors.textPrimary,
),
),
Text('Contact ${index + 1}', style: UiTypography.title2m.textPrimary),
if (totalContacts > 1)
IconButton(
icon: Icon(
icon: const Icon(
UiIcons.delete,
color: UiColors.textError,
size: 20.0,
),
onPressed: () => context
.read<EmergencyContactBloc>()
.add(EmergencyContactRemoved(index)),
onPressed: () => context.read<EmergencyContactBloc>().add(
EmergencyContactRemoved(index),
),
),
],
);
@@ -151,13 +144,8 @@ class EmergencyContactFormItem extends StatelessWidget {
Widget _buildLabel(String label) {
return Padding(
padding: EdgeInsets.only(bottom: UiConstants.space2),
child: Text(
label,
style: UiTypography.body2m.copyWith(
color: UiColors.textSecondary,
),
),
padding: const EdgeInsets.only(bottom: UiConstants.space2),
child: Text(label, style: UiTypography.body2m.textSecondary),
);
}
@@ -169,16 +157,16 @@ class EmergencyContactFormItem extends StatelessWidget {
}) {
return TextFormField(
initialValue: initialValue,
style: UiTypography.body1r.copyWith(
color: UiColors.textPrimary,
),
style: UiTypography.body1r.textPrimary,
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: UiColors.textPlaceholder),
hintStyle: const TextStyle(color: UiColors.textPlaceholder),
prefixIcon: Icon(icon, color: UiColors.textSecondary, size: 20.0),
filled: true,
fillColor: UiColors.bgPopup,
contentPadding: EdgeInsets.symmetric(vertical: UiConstants.space4),
contentPadding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
border: OutlineInputBorder(
borderRadius: UiConstants.radiusLg,
borderSide: BorderSide(color: UiColors.border),
@@ -196,4 +184,3 @@ class EmergencyContactFormItem extends StatelessWidget {
);
}
}

View File

@@ -7,14 +7,14 @@ class EmergencyContactInfoBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(UiConstants.space4),
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: UiColors.accent.withOpacity(0.2),
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
color: UiColors.accent.withValues(alpha: 0.2),
borderRadius: UiConstants.radiusLg,
),
child: Text(
'Please provide at least one emergency contact. This information will only be used in case of an emergency during your shifts.',
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary),
style: UiTypography.body2r.textPrimary,
),
);
}

View File

@@ -30,19 +30,18 @@ class EmergencyContactSaveButton extends StatelessWidget {
builder: (context, state) {
final isLoading = state.status == EmergencyContactStatus.saving;
return Container(
padding: EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
padding: const EdgeInsets.all(UiConstants.space4),
decoration: const BoxDecoration(
color: UiColors.bgPopup,
border: Border(top: BorderSide(color: UiColors.border)),
),
child: SafeArea(
child: UiButton.primary(
fullWidth: true,
onPressed: state.isValid && !isLoading
? () => _onSave(context)
: null,
onPressed:
state.isValid && !isLoading ? () => _onSave(context) : null,
child: isLoading
? SizedBox(
? const SizedBox(
height: 20.0,
width: 20.0,
child: CircularProgressIndicator(

View File

@@ -73,16 +73,16 @@ class ExperiencePage extends StatelessWidget {
children: [
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.all(UiConstants.space5),
padding: const EdgeInsets.all(UiConstants.space5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ExperienceSectionTitle(title: i18n.industries_title),
Text(
i18n.industries_subtitle,
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary),
style: UiTypography.body2m.textSecondary,
),
SizedBox(height: UiConstants.space3),
const SizedBox(height: UiConstants.space3),
Wrap(
spacing: UiConstants.space2,
runSpacing: UiConstants.space2,
@@ -90,23 +90,26 @@ class ExperiencePage extends StatelessWidget {
.map(
(i) => UiChip(
label: _getIndustryLabel(i18n.industries, i),
isSelected: state.selectedIndustries.contains(i),
onTap: () => BlocProvider.of<ExperienceBloc>(context)
.add(ExperienceIndustryToggled(i)),
variant: state.selectedIndustries.contains(i)
? UiChipVariant.primary
: UiChipVariant.secondary,
isSelected:
state.selectedIndustries.contains(i),
onTap: () =>
BlocProvider.of<ExperienceBloc>(context)
.add(ExperienceIndustryToggled(i)),
variant:
state.selectedIndustries.contains(i)
? UiChipVariant.primary
: UiChipVariant.secondary,
),
)
.toList(),
),
SizedBox(height: UiConstants.space6),
const SizedBox(height: UiConstants.space6),
ExperienceSectionTitle(title: i18n.skills_title),
Text(
i18n.skills_subtitle,
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary),
style: UiTypography.body2m.textSecondary,
),
SizedBox(height: UiConstants.space3),
const SizedBox(height: UiConstants.space3),
Wrap(
spacing: UiConstants.space2,
runSpacing: UiConstants.space2,
@@ -114,19 +117,22 @@ class ExperiencePage extends StatelessWidget {
.map(
(s) => UiChip(
label: _getSkillLabel(i18n.skills, s),
isSelected: state.selectedSkills.contains(s.value),
onTap: () => BlocProvider.of<ExperienceBloc>(context)
.add(ExperienceSkillToggled(s.value)),
variant: state.selectedSkills.contains(s.value)
? UiChipVariant.primary
: UiChipVariant.secondary,
isSelected:
state.selectedSkills.contains(s.value),
onTap: () =>
BlocProvider.of<ExperienceBloc>(context)
.add(ExperienceSkillToggled(s.value)),
variant:
state.selectedSkills.contains(s.value)
? UiChipVariant.primary
: UiChipVariant.secondary,
),
)
.toList(),
),
],
),
),
),
),
_buildSaveButton(context, state, i18n),
],
@@ -148,9 +154,9 @@ class ExperiencePage extends StatelessWidget {
children: [
Text(
i18n.custom_skills_title,
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary),
style: UiTypography.body2m.textSecondary,
),
SizedBox(height: UiConstants.space2),
const SizedBox(height: UiConstants.space2),
Wrap(
spacing: UiConstants.space2,
runSpacing: UiConstants.space2,
@@ -165,10 +171,14 @@ class ExperiencePage extends StatelessWidget {
);
}
Widget _buildSaveButton(BuildContext context, ExperienceState state, dynamic i18n) {
Widget _buildSaveButton(
BuildContext context,
ExperienceState state,
dynamic i18n,
) {
return Container(
padding: EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
padding: const EdgeInsets.all(UiConstants.space4),
decoration: const BoxDecoration(
color: UiColors.bgPopup,
border: Border(top: BorderSide(color: UiColors.border)),
),
@@ -176,16 +186,21 @@ class ExperiencePage extends StatelessWidget {
child: UiButton.primary(
onPressed: state.status == ExperienceStatus.loading
? null
: () => BlocProvider.of<ExperienceBloc>(context).add(ExperienceSubmitted()),
: () => BlocProvider.of<ExperienceBloc>(context)
.add(ExperienceSubmitted()),
fullWidth: true,
text: state.status == ExperienceStatus.loading ? null : i18n.save_button,
text: state.status == ExperienceStatus.loading
? null
: i18n.save_button,
child: state.status == ExperienceStatus.loading
? SizedBox(
? const SizedBox(
height: 20.0,
width: 20.0,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(UiColors.white), // UiColors.primaryForeground is white mostly
valueColor: AlwaysStoppedAnimation<Color>(
UiColors.white,
), // UiColors.primaryForeground is white mostly
),
)
: null,

View File

@@ -1,4 +1,3 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
@@ -52,13 +51,16 @@ class PersonalInfoPage extends StatelessWidget {
backgroundColor: UiColors.bgPopup,
elevation: 0,
leading: IconButton(
icon: const Icon(UiIcons.chevronLeft, color: UiColors.textSecondary),
icon: const Icon(
UiIcons.chevronLeft,
color: UiColors.textSecondary,
),
onPressed: () => Modular.to.pop(),
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
),
title: Text(
i18n.title,
style: UiTypography.title1m.copyWith(color: UiColors.textPrimary),
style: UiTypography.title1m.textPrimary,
),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),
@@ -82,9 +84,7 @@ class PersonalInfoPage extends StatelessWidget {
return Center(
child: Text(
'Failed to load personal information',
style: UiTypography.body1r.copyWith(
color: UiColors.textSecondary,
),
style: UiTypography.body1r.textSecondary,
),
);
}

View File

@@ -93,7 +93,7 @@ class _FieldLabel extends StatelessWidget {
Widget build(BuildContext context) {
return Text(
text,
style: UiTypography.body2m.copyWith(color: UiColors.textPrimary),
style: UiTypography.body2m.textPrimary,
);
}
}
@@ -101,7 +101,6 @@ class _FieldLabel extends StatelessWidget {
/// A read-only field widget for displaying non-editable information.
/// A read-only field widget for displaying non-editable information.
class _ReadOnlyField extends StatelessWidget {
const _ReadOnlyField({required this.value});
final String value;
@@ -120,7 +119,7 @@ class _ReadOnlyField extends StatelessWidget {
),
child: Text(
value,
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary),
style: UiTypography.body2r.textPrimary,
),
);
}
@@ -129,7 +128,6 @@ class _ReadOnlyField extends StatelessWidget {
/// An editable text field widget.
/// An editable text field widget.
class _EditableField extends StatelessWidget {
const _EditableField({
required this.controller,
required this.hint,
@@ -150,10 +148,10 @@ class _EditableField extends StatelessWidget {
enabled: enabled,
keyboardType: keyboardType,
autofillHints: autofillHints,
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary),
style: UiTypography.body2r.textPrimary,
decoration: InputDecoration(
hintText: hint,
hintStyle: UiTypography.body2r.copyWith(color: UiColors.textSecondary),
hintStyle: UiTypography.body2r.textSecondary,
contentPadding: const EdgeInsets.symmetric(
horizontal: UiConstants.space3,
vertical: UiConstants.space3,

View File

@@ -28,7 +28,8 @@ class ProfilePhotoWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TranslationsStaffOnboardingPersonalInfoEn i18n = t.staff.onboarding.personal_info;
final TranslationsStaffOnboardingPersonalInfoEn i18n =
t.staff.onboarding.personal_info;
return Column(
children: <Widget>[
@@ -41,7 +42,7 @@ class ProfilePhotoWidget extends StatelessWidget {
height: 96,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: UiColors.primary.withOpacity(0.1),
color: UiColors.primary.withValues(alpha: 0.1),
),
child: photoUrl != null
? ClipOval(
@@ -53,9 +54,7 @@ class ProfilePhotoWidget extends StatelessWidget {
: Center(
child: Text(
fullName.isNotEmpty ? fullName[0].toUpperCase() : '?',
style: UiTypography.displayL.copyWith(
color: UiColors.primary,
),
style: UiTypography.displayL.primary,
),
),
),
@@ -71,7 +70,7 @@ class ProfilePhotoWidget extends StatelessWidget {
border: Border.all(color: UiColors.border),
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.textPrimary.withOpacity(0.1),
color: UiColors.textPrimary.withValues(alpha: 0.1),
blurRadius: UiConstants.space1,
offset: const Offset(0, 2),
),
@@ -92,7 +91,7 @@ class ProfilePhotoWidget extends StatelessWidget {
const SizedBox(height: UiConstants.space3),
Text(
i18n.change_photo_hint,
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary),
style: UiTypography.body2r.textSecondary,
),
],
);