desktop_ui_kit 0.2.0
desktop_ui_kit: ^0.2.0 copied to clipboard
Platform-adaptive desktop widgets for Flutter with native OS conventions and keyboard navigation.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:desktop_ui_kit/desktop_ui_kit.dart';
void main() {
runApp(const MyDesktopApp());
}
class MyDesktopApp extends StatelessWidget {
const MyDesktopApp({super.key});
@override
Widget build(BuildContext context) {
return DesktopApp(
themeMode: ThemeMode.system,
home: const MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _tabIndex = 0;
@override
Widget build(BuildContext context) {
final theme = DesktopTheme.of(context);
final colors = theme.colors;
return Scaffold(
body: Column(
children: [
DesktopMenuBar(groups: [
DesktopMenuGroup('File', [
DesktopMenuEntry(label: 'New Project', shortcut: 'Ctrl+N', onPressed: () {}),
DesktopMenuEntry(label: 'Open...', shortcut: 'Ctrl+O', onPressed: () {}),
DesktopMenuEntry(label: 'Save', shortcut: 'Ctrl+S', onPressed: () {}),
DesktopMenuEntry.divider(),
DesktopMenuEntry(label: 'Exit', shortcut: 'Alt+F4', onPressed: () {}),
]),
DesktopMenuGroup('Edit', [
DesktopMenuEntry(label: 'Undo', shortcut: 'Ctrl+Z', onPressed: () {}),
DesktopMenuEntry(label: 'Redo', shortcut: 'Ctrl+Shift+Z', onPressed: () {}),
]),
DesktopMenuGroup('View', [
DesktopMenuEntry(label: 'Widgets', shortcut: 'Ctrl+1', onPressed: () => setState(() => _tabIndex = 0)),
DesktopMenuEntry(label: 'Forms', shortcut: 'Ctrl+2', onPressed: () => setState(() => _tabIndex = 1)),
]),
DesktopMenuGroup('Help', [
DesktopMenuEntry(label: 'About', onPressed: () {}),
]),
]),
Expanded(
child: _tabIndex == 0 ? const _WidgetsPage() : const _FormsPage(),
),
Container(
height: DesktopTokens.statusBarHeight,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: colors.surfaceContainer,
border: Border(top: BorderSide(color: colors.border)),
),
child: Row(
children: [
Icon(Icons.info_outline, size: 14, color: colors.textTertiary),
const SizedBox(width: 6),
Text('Ready', style: theme.typography.caption),
const Spacer(),
Text(_tabIndex == 0 ? 'Widgets' : 'Forms', style: theme.typography.caption),
const SizedBox(width: 16),
Text('desktop_ui_kit v0.2.0', style: theme.typography.caption),
],
),
),
],
),
);
}
}
class _WidgetsPage extends StatelessWidget {
const _WidgetsPage();
@override
Widget build(BuildContext context) {
final theme = DesktopTheme.of(context);
final colors = theme.colors;
final typography = theme.typography;
return Column(
children: [
Container(
padding: const EdgeInsets.all(DesktopTokens.spaceSm),
color: colors.surface,
child: Row(
children: [
Text('Widgets Demo', style: typography.label),
const Spacer(),
DesktopButton(
label: 'Open Dialog',
icon: Icons.open_in_new,
variant: DesktopButtonVariant.secondary,
onPressed: () {
DesktopDialog.show(
context,
DesktopDialog(
title: 'Example Dialog',
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('This is an example dialog'),
const SizedBox(height: DesktopTokens.spaceLg),
DesktopDataTable(
columns: [
DesktopColumn(
header: 'Name',
width: 120,
sortable: true,
cellBuilder: (v) => Text('$v'),
),
DesktopColumn(
header: 'Value',
width: 80,
sortable: true,
cellBuilder: (v) => Text('$v'),
),
],
rows: [
{'Name': 'Alpha', 'Value': 100},
{'Name': 'Beta', 'Value': 200},
{'Name': 'Gamma', 'Value': 300},
],
),
],
),
actions: [
DesktopButton(
label: 'Cancel',
variant: DesktopButtonVariant.secondary,
onPressed: () => Navigator.of(context).pop(),
),
DesktopButton(
label: 'Confirm',
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
},
),
],
),
),
const Divider(height: 1),
Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.widgets, size: 48, color: colors.textTertiary),
const SizedBox(height: DesktopTokens.spaceLg),
Text('desktop_ui_kit', style: typography.heading3),
const SizedBox(height: DesktopTokens.spaceSm),
Text(
'A Flutter desktop UI kit for Windows, macOS & Linux',
style: typography.body,
),
const SizedBox(height: DesktopTokens.spaceXxl),
Wrap(
spacing: DesktopTokens.spaceMd,
children: [
DesktopButton(
label: 'Primary',
icon: Icons.star,
onPressed: () {},
),
DesktopButton(
label: 'Secondary',
variant: DesktopButtonVariant.secondary,
onPressed: () {},
),
DesktopButton(
label: 'Ghost',
variant: DesktopButtonVariant.ghost,
onPressed: () {},
),
DesktopButton(
label: 'Danger',
variant: DesktopButtonVariant.danger,
onPressed: () {},
),
],
),
],
),
),
),
],
);
}
}
class _FormsPage extends StatefulWidget {
const _FormsPage();
@override
State<_FormsPage> createState() => _FormsPageState();
}
class _FormsPageState extends State<_FormsPage> {
String? _dropdownValue;
bool _checkbox1 = true;
bool _checkbox2 = false;
bool? _checkbox3;
String _radioValue = 'option1';
bool _switch1 = true;
bool _switch2 = false;
@override
Widget build(BuildContext context) {
final theme = DesktopTheme.of(context);
final typography = theme.typography;
return SingleChildScrollView(
padding: const EdgeInsets.all(DesktopTokens.spaceXl),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Form Controls', style: typography.heading2),
const SizedBox(height: DesktopTokens.spaceXxl),
Text('Text Fields', style: typography.heading4),
const SizedBox(height: DesktopTokens.spaceLg),
Wrap(
spacing: DesktopTokens.spaceXl,
runSpacing: DesktopTokens.spaceXl,
children: [
SizedBox(
width: 280,
child: DesktopTextField(
label: 'Email',
hint: 'you@example.com',
prefixIcon: Icons.email,
helper: 'We will never share your email',
),
),
SizedBox(
width: 280,
child: DesktopTextField(
label: 'Password',
hint: 'Enter password',
obscureText: true,
prefixIcon: Icons.lock,
suffixIcon: Icons.visibility,
),
),
SizedBox(
width: 280,
child: DesktopTextField(
label: 'With Error',
hint: 'Invalid input',
error: 'This field is required',
),
),
SizedBox(
width: 280,
child: DesktopTextField(
label: 'Disabled',
hint: 'Cannot edit',
disabled: true,
),
),
],
),
const SizedBox(height: DesktopTokens.spaceXxl),
Text('Dropdown', style: typography.heading4),
const SizedBox(height: DesktopTokens.spaceLg),
SizedBox(
width: 280,
child: DesktopDropdown<String>(
label: 'Country',
value: _dropdownValue,
hint: 'Select country',
options: const [
DropdownOption(value: 'us', label: 'United States', icon: Icons.flag),
DropdownOption(value: 'uk', label: 'United Kingdom', icon: Icons.flag),
DropdownOption(value: 'de', label: 'Germany', icon: Icons.flag),
DropdownOption(value: 'fr', label: 'France', icon: Icons.flag),
DropdownOption(value: 'jp', label: 'Japan', icon: Icons.flag),
],
onChanged: (v) => setState(() => _dropdownValue = v),
),
),
const SizedBox(height: DesktopTokens.spaceXxl),
Text('Checkboxes', style: typography.heading4),
const SizedBox(height: DesktopTokens.spaceLg),
Wrap(
spacing: DesktopTokens.spaceXl,
runSpacing: DesktopTokens.spaceLg,
children: [
DesktopCheckbox(
value: _checkbox1,
label: 'Enable notifications',
onChanged: (v) => setState(() => _checkbox1 = v ?? false),
),
DesktopCheckbox(
value: _checkbox2,
label: 'Subscribe to newsletter',
onChanged: (v) => setState(() => _checkbox2 = v ?? false),
),
DesktopCheckbox(
value: _checkbox3,
label: 'Indeterminate state',
onChanged: (v) => setState(() => _checkbox3 = v),
),
const DesktopCheckbox(
value: false,
label: 'Disabled',
disabled: true,
),
],
),
const SizedBox(height: DesktopTokens.spaceXxl),
Text('Radio Buttons', style: typography.heading4),
const SizedBox(height: DesktopTokens.spaceLg),
Wrap(
spacing: DesktopTokens.spaceXl,
runSpacing: DesktopTokens.spaceLg,
children: [
DesktopRadio<String>(
value: 'option1',
groupValue: _radioValue,
label: 'Option 1',
onChanged: (v) => setState(() => _radioValue = v ?? 'option1'),
),
DesktopRadio<String>(
value: 'option2',
groupValue: _radioValue,
label: 'Option 2',
onChanged: (v) => setState(() => _radioValue = v ?? 'option1'),
),
DesktopRadio<String>(
value: 'option3',
groupValue: _radioValue,
label: 'Option 3',
onChanged: (v) => setState(() => _radioValue = v ?? 'option1'),
),
const DesktopRadio<String>(
value: 'disabled',
groupValue: null,
label: 'Disabled',
disabled: true,
),
],
),
const SizedBox(height: DesktopTokens.spaceXxl),
Text('Switches', style: typography.heading4),
const SizedBox(height: DesktopTokens.spaceLg),
Wrap(
spacing: DesktopTokens.spaceXl,
runSpacing: DesktopTokens.spaceLg,
children: [
DesktopSwitch(
value: _switch1,
label: 'Dark mode',
onChanged: (v) => setState(() => _switch1 = v),
),
DesktopSwitch(
value: _switch2,
label: 'Enable analytics',
onChanged: (v) => setState(() => _switch2 = v),
),
const DesktopSwitch(
value: false,
label: 'Disabled',
disabled: true,
),
],
),
],
),
);
}
}