kin_ui 1.2.0
kin_ui: ^1.2.0 copied to clipboard
A Native inspired Flutter design system. Tokens, primitives, components, adaptive shell, and templates out of the box.
example/main.dart
import 'package:flutter/material.dart';
import 'package:kin_ui/kin_ui.dart';
// ── Custom themes ────────────────────────────────────────────────
const _lightTheme = KinTheme(
primary: Color(0xFF6C63FF),
primaryContainer: Color(0xFFE8E6FF),
surface: Color(0xFFF5F5F0),
surfaceVariant: Color(0xFFFFFFFF),
onSurface: Color(0xFF1C1C1E),
onSurfaceVariant: Color(0xFF8E8E93),
outline: Color(0xFFE0E0E0),
destructive: Color(0xFFFF453A),
);
const _darkTheme = KinTheme(
primary: Color(0xFF6C63FF),
primaryContainer: Color(0xFF3D35A3),
surface: Color(0xFF1C1C1E),
surfaceVariant: Color(0xFF2C2C2E),
onSurface: Color(0xFFFFFFFF),
onSurfaceVariant: Color(0xFF8E8E93),
outline: Color(0xFF3A3A3C),
destructive: Color(0xFFFF453A),
);
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
brightness: Brightness.light,
scaffoldBackgroundColor: _lightTheme.surface,
colorScheme: ColorScheme.light(
primary: _lightTheme.primary,
surface: _lightTheme.surface,
onSurface: _lightTheme.onSurface,
error: _lightTheme.destructive,
outline: _lightTheme.outline,
),
extensions: const [_lightTheme],
),
darkTheme: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
scaffoldBackgroundColor: _darkTheme.surface,
colorScheme: ColorScheme.dark(
primary: _darkTheme.primary,
surface: _darkTheme.surface,
onSurface: _darkTheme.onSurface,
error: _darkTheme.destructive,
outline: _darkTheme.outline,
),
extensions: const [_darkTheme],
),
home: const KinApp(child: _Home()),
);
}
}
class _Home extends StatefulWidget {
const _Home();
@override
State<_Home> createState() => _HomeState();
}
class _HomeState extends State<_Home> {
Color _pickedColor = const Color(0xFF6C63FF);
int _rating = 0;
double _sliderValue = 0.5;
int _sortCol = 0;
KinSortDirection _sortDir = KinSortDirection.ascending;
KinAIOrbState _orbState = KinAIOrbState.idle;
// Chart data
final _barEntries = const [
KinChartEntry(label: 'Mon', value: 40),
KinChartEntry(label: 'Tue', value: 70),
KinChartEntry(label: 'Wed', value: 55),
KinChartEntry(label: 'Thu', value: 90),
KinChartEntry(label: 'Fri', value: 65),
KinChartEntry(label: 'Sat', value: 30),
];
final _lineEntries = const [
KinChartEntry(label: 'Jan', value: 20),
KinChartEntry(label: 'Feb', value: 45),
KinChartEntry(label: 'Mar', value: 35),
KinChartEntry(label: 'Apr', value: 80),
KinChartEntry(label: 'May', value: 60),
KinChartEntry(label: 'Jun', value: 95),
];
final _pieEntries = const [
KinChartEntry(label: 'Design', value: 35),
KinChartEntry(label: 'Dev', value: 45),
KinChartEntry(label: 'Testing', value: 20),
];
@override
Widget build(BuildContext context) {
final theme = KinTheme.of(context);
return Scaffold(
backgroundColor: theme.surface,
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(KinSpacing.md),
children: [
KinText.titleLarge(
'Kin UI Showcase',
color: theme.onSurface,
),
const SizedBox(height: KinSpacing.lg),
// ── AI Gradient Section ──
_sectionTitle(theme, 'AI Gradient'),
const SizedBox(height: KinSpacing.sm),
// Full-width card — touch to interact
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: SizedBox(
height: 200,
child: KinAIGradient(
mood: KinAIGradientMood.ember,
animation: KinAIGradientAnimation.orbit,
child: Center(
child: KinText.titleMedium(
'Touch & drag me',
color: const Color(0xCCFFFFFF),
),
),
),
),
),
const SizedBox(height: KinSpacing.sm),
// Row of different moods & form-factors
SizedBox(
height: 100,
child: Row(
spacing: KinSpacing.sm,
children: [
Expanded(
child: KinAIGradient(
mood: KinAIGradientMood.flora,
animation: KinAIGradientAnimation.wave,
borderRadius: BorderRadius.circular(12),
),
),
Expanded(
child: KinAIGradient(
mood: KinAIGradientMood.nebula,
animation: KinAIGradientAnimation.breathe,
borderRadius: BorderRadius.circular(12),
),
),
Expanded(
child: KinAIGradient(
mood: KinAIGradientMood.solar,
animation: KinAIGradientAnimation.pulse,
borderRadius: BorderRadius.circular(12),
),
),
],
),
),
const SizedBox(height: KinSpacing.sm),
// Circle + pill shapes
Row(
spacing: KinSpacing.md,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const KinAIGradient(
mood: KinAIGradientMood.iridescent,
animation: KinAIGradientAnimation.shimmer,
shape: BoxShape.circle,
size: Size(72, 72),
),
const KinAIGradient(
mood: KinAIGradientMood.mycelium,
animation: KinAIGradientAnimation.breathe,
shape: BoxShape.circle,
size: Size(72, 72),
speed: 1.5,
),
const KinAIGradient(
mood: KinAIGradientMood.frost,
animation: KinAIGradientAnimation.orbit,
shape: BoxShape.circle,
size: Size(72, 72),
),
KinAIGradient(
mood: KinAIGradientMood.abyss,
animation: KinAIGradientAnimation.wave,
borderRadius: BorderRadius.circular(20),
size: const Size(72, 72),
),
],
),
const SizedBox(height: KinSpacing.lg),
// ── AI Orb Section ──
_sectionTitle(theme, 'AI Orb'),
const SizedBox(height: KinSpacing.sm),
// Orb states showcase
Center(
child: Column(
children: [
KinAIOrb(
state: _orbState,
size: 96,
onTap: () {
const states = KinAIOrbState.values;
setState(() {
_orbState = states[
(states.indexOf(_orbState) + 1) % states.length];
});
},
label: _orbState.name[0].toUpperCase() +
_orbState.name.substring(1),
),
const SizedBox(height: KinSpacing.sm),
KinText.labelSmall(
'Tap orb to cycle states',
color: theme.onSurfaceVariant,
),
],
),
),
const SizedBox(height: KinSpacing.sm),
// Row of all states
Wrap(
spacing: KinSpacing.md,
runSpacing: KinSpacing.md,
alignment: WrapAlignment.center,
children: KinAIOrbState.values
.map((s) => KinAIOrb(state: s, size: 48, label: s.name))
.toList(),
),
const SizedBox(height: KinSpacing.lg),
// ── Rich Text Editor Section ──
_sectionTitle(theme, 'Rich Text Editor'),
const SizedBox(height: KinSpacing.sm),
KinRichEditor(
placeholder: 'Write something beautiful…',
minLines: 6,
),
const SizedBox(height: KinSpacing.lg),
// ── Charts Section ──
_sectionTitle(theme, 'Charts'),
const SizedBox(height: KinSpacing.sm),
KinCard(
child: Padding(
padding: const EdgeInsets.all(KinSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
KinText.titleMedium('Bar Chart', color: theme.onSurface),
const SizedBox(height: KinSpacing.sm),
KinChart.bar(
entries: _barEntries,
height: 160,
gridStyle: const KinChartGridStyle(dashed: true),
axisStyle: const KinChartAxisStyle(
showYAxisLabels: true,
),
),
],
),
),
),
const SizedBox(height: KinSpacing.md),
KinCard(
child: Padding(
padding: const EdgeInsets.all(KinSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
KinText.titleMedium(
'Line Chart (Smooth)', color: theme.onSurface),
const SizedBox(height: KinSpacing.sm),
KinChart.line(
entries: _lineEntries,
height: 160,
lineStyle: KinChartLineStyle.smooth,
axisStyle: const KinChartAxisStyle(
showYAxisLabels: true,
yAxisWidth: 36,
),
),
],
),
),
),
const SizedBox(height: KinSpacing.md),
KinCard(
child: Padding(
padding: const EdgeInsets.all(KinSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
KinText.titleMedium(
'Area Chart', color: theme.onSurface),
const SizedBox(height: KinSpacing.sm),
KinChart.area(
entries: _lineEntries,
height: 140,
lineStyle: KinChartLineStyle.smooth,
),
],
),
),
),
const SizedBox(height: KinSpacing.md),
KinCard(
child: Padding(
padding: const EdgeInsets.all(KinSpacing.md),
child: Column(
children: [
KinText.titleMedium('Donut Chart', color: theme.onSurface),
const SizedBox(height: KinSpacing.sm),
KinChart.donut(
entries: _pieEntries,
height: 140,
donutCenter: KinText.bodyMedium(
'100%',
color: theme.onSurface,
),
legendPosition: KinChartLegendPosition.bottom,
),
],
),
),
),
const SizedBox(height: KinSpacing.md),
KinCard(
child: Padding(
padding: const EdgeInsets.all(KinSpacing.md),
child: Column(
children: [
KinText.titleMedium('Pie Chart', color: theme.onSurface),
const SizedBox(height: KinSpacing.sm),
KinChart.pie(
entries: _pieEntries,
height: 140,
legendPosition: KinChartLegendPosition.bottom,
),
],
),
),
),
const SizedBox(height: KinSpacing.md),
KinCard(
child: Padding(
padding: const EdgeInsets.all(KinSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
KinText.titleMedium(
'Horizontal Bar', color: theme.onSurface),
const SizedBox(height: KinSpacing.sm),
KinChart.horizontalBar(
entries: const [
KinChartEntry(label: 'Flutter', value: 92),
KinChartEntry(label: 'React', value: 78),
KinChartEntry(label: 'Swift', value: 65),
KinChartEntry(label: 'Kotlin', value: 58),
],
height: 160,
),
],
),
),
),
const SizedBox(height: KinSpacing.lg),
// ── Dialogs Section ──
_sectionTitle(theme, 'Dialogs & Pickers'),
const SizedBox(height: KinSpacing.sm),
Wrap(
spacing: KinSpacing.sm,
runSpacing: KinSpacing.sm,
children: [
KinButton(
label: 'Color Picker',
variant: KinButtonVariant.secondary,
leading: Container(
width: 16,
height: 16,
decoration: BoxDecoration(
color: _pickedColor,
shape: BoxShape.circle,
),
),
onTap: () async {
final color = await KinColorPicker.show(
context: context,
initialColor: _pickedColor,
showOpacity: true,
);
if (color != null) {
setState(() => _pickedColor = color);
}
},
),
KinButton(
label: 'Date Picker',
variant: KinButtonVariant.secondary,
onTap: () async {
final date = await KinDatePicker.show(context: context);
if (date != null && mounted) {
KinSnackbar.show(
context,
message:
'Selected: ${date.day}/${date.month}/${date.year}',
);
}
},
),
KinButton(
label: 'Time Picker',
variant: KinButtonVariant.secondary,
onTap: () async {
final time = await KinTimePicker.show(context: context);
if (time != null && mounted) {
KinSnackbar.show(
context,
message: 'Selected: ${time.format(context)}',
);
}
},
),
KinButton(
label: 'Dialog',
variant: KinButtonVariant.secondary,
onTap: () {
KinDialog.show(
context: context,
title: 'Confirm Action',
message: 'Are you sure you want to proceed?',
confirmLabel: 'Yes',
cancelLabel: 'No',
);
},
),
KinButton(
label: 'Rate ⭐ $_rating',
variant: KinButtonVariant.secondary,
onTap: () async {
final result = await KinRatingPicker.show(
context: context,
title: 'How was your experience?',
initialRating: _rating,
);
if (result != null) {
setState(() => _rating = result);
}
},
),
],
),
const SizedBox(height: KinSpacing.lg),
// ── Slider Section ──
_sectionTitle(theme, 'Slider'),
const SizedBox(height: KinSpacing.sm),
KinCard(
child: Padding(
padding: const EdgeInsets.all(KinSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
KinText.bodyMedium('Volume',
color: theme.onSurface),
KinText.bodyMedium(
'${(_sliderValue * 100).round()}%',
color: theme.onSurfaceVariant,
),
],
),
const SizedBox(height: KinSpacing.sm),
KinSlider(
value: _sliderValue,
onChanged: (v) => setState(() => _sliderValue = v),
),
],
),
),
),
const SizedBox(height: KinSpacing.lg),
// ── Multi-series Chart ──
_sectionTitle(theme, 'Multi-Series Line Chart'),
const SizedBox(height: KinSpacing.sm),
KinCard(
child: Padding(
padding: const EdgeInsets.all(KinSpacing.md),
child: KinChart(
type: KinChartType.line,
series: [
KinChartSeries(
name: 'Revenue',
entries: const [
KinChartEntry(label: 'Q1', value: 30),
KinChartEntry(label: 'Q2', value: 55),
KinChartEntry(label: 'Q3', value: 45),
KinChartEntry(label: 'Q4', value: 80),
],
lineStyle: KinChartLineStyle.smooth,
showArea: true,
),
KinChartSeries(
name: 'Costs',
entries: const [
KinChartEntry(label: 'Q1', value: 20),
KinChartEntry(label: 'Q2', value: 35),
KinChartEntry(label: 'Q3', value: 40),
KinChartEntry(label: 'Q4', value: 50),
],
lineStyle: KinChartLineStyle.smooth,
showArea: true,
),
],
entries: const [
KinChartEntry(label: 'Q1', value: 80),
KinChartEntry(label: 'Q2', value: 55),
KinChartEntry(label: 'Q3', value: 45),
KinChartEntry(label: 'Q4', value: 80),
],
height: 180,
showLabels: true,
axisStyle: const KinChartAxisStyle(showYAxisLabels: true),
gridStyle:
const KinChartGridStyle(dashed: true),
),
),
),
const SizedBox(height: KinSpacing.lg),
// ── Search Bar Section ──
_sectionTitle(theme, 'Search Bar'),
const SizedBox(height: KinSpacing.sm),
KinSearchBar(
hint: 'Search anything…',
size: KinSearchBarSize.small,
onChanged: (v) {},
),
const SizedBox(height: KinSpacing.sm),
KinSearchBar(
hint: 'Medium search bar',
size: KinSearchBarSize.medium,
onChanged: (v) {},
),
const SizedBox(height: KinSpacing.sm),
KinSearchBar(
hint: 'Large default',
onChanged: (v) {},
),
const SizedBox(height: KinSpacing.lg),
// ── Data Table Section ──
_sectionTitle(theme, 'Data Table'),
const SizedBox(height: KinSpacing.sm),
KinDataTable(
sortColumnIndex: _sortCol,
sortDirection: _sortDir,
onSort: (i) {
setState(() {
if (_sortCol == i) {
_sortDir = _sortDir == KinSortDirection.ascending
? KinSortDirection.descending
: KinSortDirection.ascending;
} else {
_sortCol = i;
_sortDir = KinSortDirection.ascending;
}
});
},
columns: const [
KinDataColumn(label: 'Name', sortable: true, flex: 2),
KinDataColumn(label: 'Role', flex: 2),
KinDataColumn(label: 'Score', sortable: true, width: 70, alignment: Alignment.centerRight),
],
rows: const [
KinDataRow(cells: [KinDataCell.text('Alice'), KinDataCell.text('Designer'), KinDataCell.text('92')]),
KinDataRow(cells: [KinDataCell.text('Bob'), KinDataCell.text('Developer'), KinDataCell.text('87')]),
KinDataRow(cells: [KinDataCell.text('Charlie'), KinDataCell.text('PM'), KinDataCell.text('78')]),
KinDataRow(cells: [KinDataCell.text('Diana'), KinDataCell.text('QA Lead'), KinDataCell.text('95')]),
KinDataRow(cells: [KinDataCell.text('Eve'), KinDataCell.text('DevOps'), KinDataCell.text('83')]),
],
),
const SizedBox(height: KinSpacing.xl),
],
),
),
);
}
Widget _sectionTitle(KinTheme theme, String text) {
return KinText.titleMedium(text, color: theme.onSurface);
}
}