nova_ui 1.0.2
nova_ui: ^1.0.2 copied to clipboard
A modern Flutter UI framework with reusable widgets, design system components, and beautiful developer-friendly APIs.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:nova_ui/nova_ui.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: NovaTheme.light(),
darkTheme: NovaTheme.dark(),
themeMode: ThemeMode.system,
title: 'Nova UI Demo',
debugShowCheckedModeBanner: false,
home: const LoginScreen(),
);
}
}
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _loading = false;
Future<void> _onLogin() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _loading = true);
await Future.delayed(const Duration(seconds: 2));
setState(() => _loading = false);
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final primary = NovaColors.indigo;
final isLight = !context.isDark;
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isLight
? [
NovaColors.slate[50]!,
NovaColors.indigo[50]!,
NovaColors.slate[100]!,
]
: [
NovaColors.slate[900]!,
const Color(0xFF1E1B4B),
NovaColors.slate[900]!,
],
),
),
child: SafeArea(
child: SingleChildScrollView(
padding: NovaSpacing.paddingPage.copyWith(top: NovaSpacing.xxxl),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Logo
NovaContainer(
width: 52,
height: 52,
borderRadius: NovaRadius.lg,
gradient: LinearGradient(
colors: [primary[500]!, primary[400]!],
),
child: const Center(
child: Icon(
Icons.blur_circular_rounded,
color: Colors.white,
size: 26,
),
),
),
NovaSpacing.gapXl,
// Tag
NovaContainer(
padding: NovaSpacing.paddingHV(10, 4),
borderRadius: NovaRadius.sm,
color: primary[500]!.withValues(alpha: 0.15),
child: Text(
'NOVA UI',
style: TextStyle(
color: primary[300],
fontSize: 11,
fontWeight: FontWeight.w700,
letterSpacing: 1,
),
),
),
NovaSpacing.gapSm,
Text(
'Welcome back',
style: TextStyle(
color: context.novaTextPrimary,
fontSize: 28,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
),
),
const SizedBox(height: 6),
Text(
'Sign in to continue',
style: TextStyle(
color: context.novaTextSecondary,
fontSize: 15,
),
),
NovaSpacing.gapXl,
// Email
NovaTextField(
controller: _emailController,
label: 'Email',
hintText: 'you@example.com',
prefixIcon: const Icon(Icons.mail_outline_rounded),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
validator: (v) =>
v == null || !v.contains('@')
? 'Valid email required'
: null,
),
NovaSpacing.gapMd,
// Password
NovaTextField(
controller: _passwordController,
label: 'Password',
prefixIcon: const Icon(Icons.lock_outline_rounded),
obscureText: true,
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => _onLogin(),
validator: (v) =>
v == null || v.length < 6
? 'Min 6 characters'
: null,
),
// Forgot password
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {},
child: Text(
'Forgot password?',
style: TextStyle(color: context.novaPrimary),
),
),
),
// Login button
NovaButton(
text: 'Login',
loading: _loading,
onPressed: _onLogin,
backgroundColor: primary[500],
),
NovaSpacing.gapLg,
// Divider
Row(
children: [
const Expanded(
child: Divider(color: Color(0x1FFFFFFF)),
),
Padding(
padding: NovaSpacing.paddingH(12),
child: Text(
'or continue with',
style: TextStyle(
color: context.novaTextSecondary
.withValues(alpha: 0.5),
fontSize: 13,
),
),
),
const Expanded(
child: Divider(color: Color(0x1FFFFFFF)),
),
],
),
NovaSpacing.gapMd,
// Google button
NovaButton(
text: 'Continue with Google',
variant: NovaButtonVariant.outlined,
icon: const Icon(Icons.g_mobiledata_rounded, size: 22),
onPressed: () {},
foregroundColor: context.novaTextSecondary,
),
NovaSpacing.gapXl,
// Sign up link
Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Don't have an account? ",
style: TextStyle(color: context.novaTextSecondary),
),
GestureDetector(
onTap: () {},
child: Text(
'Sign up',
style: TextStyle(
color: context.novaPrimary,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
NovaSpacing.gapXl,
// ── Widget Showcase ───────────────────────────────
Divider(
color: context.novaTextSecondary.withValues(alpha: 0.15),
),
NovaSpacing.gapMd,
Text(
'Widget Showcase',
style: TextStyle(
color: context.novaTextSecondary,
fontSize: 11,
fontWeight: FontWeight.w600,
letterSpacing: 1,
),
),
NovaSpacing.gapMd,
// ── NovaLoader ────────────────────────────────────
NovaCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'NovaLoader',
style: TextStyle(
color: context.novaTextPrimary,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
NovaSpacing.gapMd,
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
// Circular
Column(
children: [
NovaLoader(color: primary[500]),
NovaSpacing.gapSm,
Text(
'circular',
style: TextStyle(
color: context.novaTextSecondary,
fontSize: 11,
),
),
],
),
// Dots
Column(
children: [
NovaLoader(
type: NovaLoaderType.dots,
color: primary[500],
size: 28,
),
NovaSpacing.gapSm,
Text(
'dots',
style: TextStyle(
color: context.novaTextSecondary,
fontSize: 11,
),
),
],
),
// Linear
Column(
children: [
NovaLoader(
type: NovaLoaderType.linear,
color: primary[500],
width: 80,
),
NovaSpacing.gapSm,
Text(
'linear',
style: TextStyle(
color: context.novaTextSecondary,
fontSize: 11,
),
),
],
),
],
),
],
),
),
NovaSpacing.gapMd,
// ── NovaDialog ────────────────────────────────────
NovaCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'NovaDialog',
style: TextStyle(
color: context.novaTextPrimary,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
NovaSpacing.gapMd,
Row(
children: [
Expanded(
child: NovaButton(
text: 'Success',
height: 40,
backgroundColor: const Color(0xFF22C55E),
onPressed: () => NovaDialog.show(
context: context,
title: 'Profile Saved',
message:
'Your changes have been saved successfully.',
type: NovaDialogType.success,
),
),
),
NovaSpacing.gapSmH,
Expanded(
child: NovaButton(
text: 'Warning',
height: 40,
backgroundColor: const Color(0xFFF59E0B),
onPressed: () => NovaDialog.show(
context: context,
title: 'Log Out?',
message:
'This will log you out of all devices.',
type: NovaDialogType.warning,
confirmText: 'Logout',
cancelText: 'Cancel',
),
),
),
NovaSpacing.gapSmH,
Expanded(
child: NovaButton(
text: 'Danger',
height: 40,
backgroundColor:
Theme.of(context).colorScheme.error,
onPressed: () => NovaDialog.show(
context: context,
title: 'Delete Account?',
message:
'This action cannot be undone.',
type: NovaDialogType.danger,
confirmText: 'Delete',
cancelText: 'Cancel',
barrierDismissible: false,
),
),
),
],
),
],
),
),
NovaSpacing.gapMd,
// ── NovaBadge ─────────────────────────────────────
NovaCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'NovaBadge',
style: TextStyle(
color: context.novaTextPrimary,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
NovaSpacing.gapMd,
// Status labels
Wrap(
spacing: 8,
runSpacing: 8,
children: [
NovaBadge(
label: 'Active',
color: NovaBadgeColor.success,
),
NovaBadge(
label: 'Pending',
color: NovaBadgeColor.warning,
variant: NovaBadgeVariant.soft,
),
NovaBadge(
label: 'Failed',
color: NovaBadgeColor.danger,
variant: NovaBadgeVariant.outlined,
),
NovaBadge(
label: 'Draft',
color: NovaBadgeColor.neutral,
variant: NovaBadgeVariant.soft,
),
NovaBadge(
label: 'New',
color: NovaBadgeColor.primary,
),
],
),
NovaSpacing.gapMd,
// Dot + Count + Overlay
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
// Dot
Column(
children: [
NovaBadge(
isDot: true,
color: NovaBadgeColor.success,
),
NovaSpacing.gapSm,
Text(
'dot',
style: TextStyle(
color: context.novaTextSecondary,
fontSize: 11,
),
),
],
),
// Count
Column(
children: [
NovaBadge(
count: 5,
color: NovaBadgeColor.danger,
),
NovaSpacing.gapSm,
Text(
'count',
style: TextStyle(
color: context.novaTextSecondary,
fontSize: 11,
),
),
],
),
// Overlay on icon
Column(
children: [
NovaBadge(
count: 12,
color: NovaBadgeColor.danger,
child: Icon(
Icons.notifications_outlined,
size: 28,
color: context.novaTextPrimary,
),
),
NovaSpacing.gapSm,
Text(
'overlay',
style: TextStyle(
color: context.novaTextSecondary,
fontSize: 11,
),
),
],
),
// 99+ overflow
Column(
children: [
NovaBadge(
count: 150,
color: NovaBadgeColor.primary,
),
NovaSpacing.gapSm,
Text(
'99+',
style: TextStyle(
color: context.novaTextSecondary,
fontSize: 11,
),
),
],
),
],
),
],
),
),
NovaSpacing.gapXl,
],
),
),
),
),
),
);
}
}