prf_design 1.0.2 copy "prf_design: ^1.0.2" to clipboard
prf_design: ^1.0.2 copied to clipboard

PRF Design System - Shared widgets, theme system, and essential utilities.

PRF Design #

pub package License: MIT CI

A reusable Flutter package containing shared widgets, theme system, and essential utilities for PRF applications.

Maintenance & Support #

  • This project is maintained & it's development sponsored by FROG Technologies in conjunction with Parkroad Fellowship.
  • Fellowship website: Parkroad Fellowship.
  • To get hosting & setup assistance, email engineering@parkroadfellowship.org or open an issue on GitHub.

Installation #

Add this package to your pubspec.yaml:

dependencies:
  prf_design: ^latest

Or run:

flutter pub add prf_design

For local development in a monorepo, use a path dependency instead:

dependencies:
  prf_design:
    path: packages/prf_design

Quick Start #

import 'package:prf_design/prf_design.dart';

MaterialApp(
  theme: PRFTheme.light(scaleFactor: DeviceHelper.getScaleFactor(context: context)),
  darkTheme: PRFTheme.dark(scaleFactor: DeviceHelper.getScaleFactor(context: context)),
  home: const MyHomePage(),
)

Imports #

Full Import #

import 'package:prf_design/prf_design.dart';

Granular Imports #

// Theme only
import 'package:prf_design/exports/theme.dart';

// Widgets only
import 'package:prf_design/exports/widgets.dart';

// Utilities only
import 'package:prf_design/exports/utils.dart';

// Enums & error models only
import 'package:prf_design/exports/enums.dart';

Theme System #

PRFTheme #

Provides complete Material 3 theme configurations for light and dark modes.

// Static factory methods
static ThemeData light({
  required double scaleFactor,
  ColorScheme? colorScheme,
  PRFBaseColors? colors,
})
static ThemeData dark({
  required double scaleFactor,
  ColorScheme? colorScheme,
  PRFBaseColors? colors,
})

Both methods configure: color scheme, text theme, app bar, buttons, inputs, cards, dividers, tab bar, data table, snackbar, icon, dialog, list tile, chip, and dropdown menu themes.

Omitting both parameters uses the hand-authored PRF palette. Apps with their own brand have two ways to theme the design system:

  1. Base colours — pass PRFBaseColors (primary/secondary/tertiary/error). The theme derives on-colours and container roles from Material 3 tonal palettes while keeping PRF's neutral surfaces:

    PRFTheme.light(
      scaleFactor: DeviceHelper.getScaleFactor(context: context),
      colors: const PRFBaseColors(
        primary: myBrandColor,
        secondary: myAccentColor,
      ),
    )
    
  2. Full scheme — pass any Material 3 ColorScheme for complete control. Every component theme is derived from the supplied scheme (its brightness selects light or dark typography). This takes precedence over colors:

    PRFTheme.light(
      scaleFactor: DeviceHelper.getScaleFactor(context: context),
      colorScheme: myColorScheme,
    )
    

PRFColors #

Core brand colors and semantic color constants.

Brand Colors:

Color Hex Usage
Navy Blue #1A2253 Primary brand color
Lime Green #9DE35D Secondary brand color

Neutral Colors: gray50 through gray900 (10 shades from light to dark)

Status Colors: success, successLight, successDark, warning, warningLight, warningDark, error, errorLight, errorDark, info, infoLight, infoDark

Accent Colors: purple (#6E4CEB), blue (#296DFF), orange (#EB8B2D), emerald (#12B886)

Common Colors: white, black, transparent

PRFColorPalette #

Tints and shades for brand colors.

  • Navy palette: navy50 through navy900 (10 shades)
  • Lime palette: lime50 through lime900 (10 shades)
  • Convenience getters: primary, primaryLight, primaryContainer, primaryDark, secondary, secondaryLight, secondaryContainer, secondaryDark

PRFColorsExtension #

Theme extension for accessing brand colors through the theme system. Includes all palette colors, grays, and accent colors with light/dark variants.

// Access via context
final colors = context.prfColors;
Container(color: colors.navyBlue)

PRFStatusExtension #

Theme extension providing semantic status colors via StatusColorSet.

StatusColorSet:

StatusColorSet({
  required Color main,       // Primary status color
  required Color background, // Light background for containers
  required Color onColor,    // Text/icon color on main
})

Standard statuses: success, warning, error, info

Mission statuses: pending (orange), initiated (blue), scheduled (orange), inProgress (emerald), completed (success), failed (error), ignored (gray)

Active indicator: active, activeGlow

final status = context.statusColors;
Container(
  color: status.success.background,
  child: Text('Done', style: TextStyle(color: status.success.main)),
)

Context Extensions #

Convenience getters on BuildContext:

context.prfColors    // PRFColorsExtension
context.statusColors // PRFStatusExtension
context.colorScheme  // ColorScheme
context.textTheme    // TextTheme
context.theme        // ThemeData

PRFTextTheme #

Typography system using the bundled Manrope family. Fonts ship offline with the package (declared as Flutter font assets), so no runtime network fetching occurs.

static TextTheme getLightTheme({required double scaleFactor})
static TextTheme getDarkTheme({required double scaleFactor})

Utility text styles:

PRFTextTheme.getButtonTextStyle(scaleFactor: 1)

PRFAdaptive #

The adaptive layer replaces flutter_adaptive_ui. Every widget routes to a device-specific implementation via PRFAdaptive, with breakpoints from PRFBreakpoints.

PRFAdaptive(
  handset: (_) => PRFMyWidgetHandset(...),
  tablet: (_) => PRFMyWidgetTablet(...),
  builder: (_, _) => PRFMyWidgetTablet(...), // fallback
)
PRFBreakpoints.of(context)              // PRFBreakpoint.handset | tablet | desktop
PRFBreakpoints.fromWidth(400)           // handset (<600), tablet (<1024), desktop (>=1024)
PRFBreakpoints.isTabletOrWider(context) // true on tablet/desktop

Widgets #

All widgets live under lib/src/widgets/ and are exported through package:prf_design/exports/widgets.dart (or the main barrel). Every component reads colours/typography from the current ThemeData and respects adaptive breakpoints via PRFAdaptive.

Buttons #

Unified adaptive buttons with a single implementation across device sizes. Full-width on handset, width-constrained on tablet, press-scale feedback, and a spinner replacing the label while loading.

PRFButton

The single button API. Configure behaviour through variant, disabled and isLoading.

PRFButton({
  required VoidCallback onPressed,
  required String title,
  PRFButtonVariant variant = PRFButtonVariant.primary,
  bool disabled = false,
  bool isLoading = false,
})

PRFButtonVariant is primary | secondary | destructive | google.

PRFButton(
  onPressed: _continue,
  title: 'Continue',
  variant: PRFButtonVariant.primary,
  disabled: false,
  isLoading: _submitting,
)

Inputs #

All inputs are adaptive. The unified PRFTextField is the only input API; the type parameter selects the behaviour of each variant.

PRFTextField

Single adaptive text input implementation for every PRF input variant.

PRFTextField({
  required String hintText,
  required TextEditingController controller,
  PRFTextFieldType type = PRFTextFieldType.text,
  bool enabled = true,
  bool readOnly = false,
  ValueChanged<String>? onChanged,
  String? labelText,
  String? errorText,
  String? helperText,
  TextInputType? keyboardType,
  TextInputAction textInputAction = TextInputAction.next,
  bool normalizeLeadingCapitalization = true,
  bool isLoading = false,
  String? prefixText,
  int? minLines,
  int? maxLines,
  bool? autocorrect,
  ValueNotifier<bool>? obscureNotifier,
  bool initiallyHidden = true,
})

PRFTextFieldType is text | email | name | number | password | textArea. Number inputs accept an optional prefixText (e.g. 'KES'); password inputs obscure the value and expose a visibility toggle driven by obscureNotifier.

PRFTextField(
  hintText: 'Enter amount',
  controller: _amountController,
  type: PRFTextFieldType.number,
  prefixText: 'KES',
  errorText: _amountError,
)

PRFPhoneInput

International phone input backed by phone_form_field, with a draggable country selector. Validates as a required, valid mobile number.

PRFPhoneInput({
  required String hintText,
  required PhoneController controller,
  bool enabled = true,
  ValueChanged<PhoneNumber>? onChanged,
  List<IsoCode> countries = const [IsoCode.KE],
})

PRFFormFieldLabel

Label with an optional required marker.

PRFFormFieldLabel({
  required String label,
  bool? isRequired,
  Color? color,
  bool? isBold,
})

PRFFormSection

Groups a form field with an icon, title and optional subtitle.

PRFFormSection({
  required IconData icon,
  required String title,
  required Widget child,
  bool isRequired = false,
  String? subtitle,
  EdgeInsets? margin,
})
PRFFormSection(
  icon: Icons.person_outline,
  title: 'Contact details',
  subtitle: 'How can the team reach you?',
  child: PRFTextField(hintText: 'Full name', controller: _nameController),
)

PRFSearchableList<T>

Searchable picker with single- and multi-select modes. Works inline on normal pages (dynamic shrink-wrapped height up to maxResultHeight) or modally in bottom sheets via showModal. Pass selection for single-select or selections for multi-select.

PRFSearchableList<T>({
  required List<PRFSearchableListEntry<T>> entries,
  required ValueChanged<T?> onSelected,
  T? selection,
  List<T>? selections,
  String hintText = 'Search',
  String emptyText = 'No results found',
  double maxResultHeight = 240,
  bool isExpanded = false,
  bool autoFocus = false,
})
// Inline on normal page
PRFSearchableList<Team>(
  entries: teams
      .map((t) => PRFSearchableListEntry(value: t, label: t.name))
      .toList(),
  onSelected: (team) => setState(() => _team = team),
  selection: _team,
)

// Modal bottom sheet picker
final selectedTeam = await PRFSearchableList.showModal<Team>(
  context,
  title: 'Select team',
  entries: teams
      .map((t) => PRFSearchableListEntry(value: t, label: t.name))
      .toList(),
  selection: _team,
);

PRFSearchableListEntry<T> holds a stable value and a searchable label.

PRFReplyComposer

Fixed message-composing bar with a send button, for pinning at the bottom of a chat screen. The send button activates once isComposing is true.

PRFReplyComposer({
  required TextEditingController controller,
  required String hintText,
  required bool isComposing,
  required bool isLoading,
  required VoidCallback onSend,
  bool enabled = true,
  bool hasFocus = false,
  double bottomInset = 0,
  int minLines = 1,
  int maxLines = 4,
})

State Displays #

PRFEmptyView

Full-screen empty state with a centred icon, message and optional action.

PRFEmptyView({
  required String label,
  required String description,
  IconData? icon,
  Widget? action,
  String? actionLabel,
  VoidCallback? onActionPressed,
  String? navBarTitle,
  VoidCallback? onBackPressed,
  bool showBackButton = true,
  List<Widget>? navBarActions,
  Color? navBarBackgroundColor,
  Color? navBarForegroundColor,
})
PRFEmptyView(
  label: 'No missions yet',
  description: 'Tap below to start your first mission.',
  icon: Icons.rocket_launch_outlined,
  actionLabel: 'Create mission',
  onActionPressed: _createMission,
  navBarTitle: 'Missions',
)

PRFErrorView

Error state with an optional retry button. compact renders a smaller inline variant for lists/cards.

PRFErrorView({
  required PRFFailure failure,
  VoidCallback? onRetry,
  bool compact = false,
})

// Factories:
PRFErrorView.fromMessage({required String message, VoidCallback? onRetry, bool compact})
PRFErrorView.fromFailure({required PRFFailure failure, VoidCallback? onRetry, bool compact})
PRFErrorView(failure: PRFFailure.noConnection(), onRetry: _reload)

PRFCategoryChips<T>

Horizontally scrollable category chip selector with an optional "ALL" chip.

PRFCategoryChips<T>({
  required List<T> categories,
  required void Function(T?) onCategorySelected,
  required String Function(T) labelBuilder,
  T? selectedCategory,
  bool isLoading = false,
  bool showAllOption = true,
  String allLabel = 'ALL',
})

ReplyStatusView

Read/unread reply-status toggle.

ReplyStatusView({
  required void Function({required bool status}) onStatusSelected,
  required String unreadLabel,
  required String repliedLabel,
  bool reversed = false,
  bool defaultStatus = false,
})

PRFChatView<T>

Full chat screen scaffold: nav bar, scrollable message list rendered via messageBuilder, and a fixed composer (e.g. PRFReplyComposer).

PRFChatView<T>({
  required String title,
  required List<T> messages,
  required Widget Function(BuildContext context, T message, int index) messageBuilder,
  required Widget composer,
  VoidCallback? onBack,
  Color? navBarBackgroundColor,
  ScrollController? scrollController,
  bool loading = false,
  String? emptyLabel,
  String? emptyDescription,
  Widget bottomSpacing = const SizedBox(height: PRFSpacingTokens.lg),
})

PRFSnackbar

Static typed snackbar helpers. Replaces the old PRFErrorSnackbar.

PRFSnackbar.error(context, 'Something went wrong', onRetry: () => _retry());
PRFSnackbar.success(context, 'Saved successfully');
PRFSnackbar.info(context, 'New update available');
PRFSnackbar.warning(context, 'Low storage space');

Each method accepts an optional Duration duration parameter (default 4s).

Cards #

PRFActionCard

Tappable card with an image and title, adaptive to device size.

PRFActionCard({
  required String title,
  required Widget image,
  VoidCallback? onTap,
})

PRFDetailActionCard

Tappable detail card with title, subtitle and optional leading/trailing/footer slots.

PRFDetailActionCard({
  required String title,
  required String subtitle,
  VoidCallback? onTap,
  Widget? leading,
  Widget? trailing,
  Widget? footer,
  EdgeInsetsGeometry? margin,
  EdgeInsetsGeometry? padding,
  Color? backgroundColor,
})

PRFInfoCard

Read-only label/value card with a tinted icon tile.

PRFInfoCard({
  required IconData icon,
  required String label,
  required String value,
})

PRFMessageBubble

Chat message bubble with timestamp and optional delivery status.

PRFMessageBubble({
  required String message,
  required String timestamp,
  required bool isIncoming,
  bool showStatusIndicator = false,
  EdgeInsetsGeometry? margin,
  double? maxWidth,
})

PRFNavigationTile

Tappable card with an SVG illustration and a chevron affordance.

PRFNavigationTile({
  required String title,
  required String assetPath,
  required VoidCallback onTap,
  double assetHeight = 56,
  bool isNeutralCard = false,
})

PRFSchoolCard

School list tile with icon, name, address, optional mission count and chevron.

PRFSchoolCard({
  required String schoolName,
  required String address,
  required VoidCallback onTap,
  int? missionCount,
})

PRFStatusBadge

Small rounded status pill on a solid colour.

PRFStatusBadge({
  required String label,
  required Color color,
  EdgeInsetsGeometry? padding,
  BorderRadius? borderRadius,
  TextStyle? textStyle,
  List<BoxShadow>? boxShadow,
})

PRFTimelineDateBadge

Timeline date badge with an optional connector line; supports single- and multi-day ranges.

PRFTimelineDateBadge({
  required DateTime startDate,
  required Color statusColor,
  DateTime? endDate,
  bool isLast = false,
  double connectorHeight = 60,
  double badgeWidth = 50,
})

PRFTimelineMissionCard

Model-agnostic mission timeline card. All display values are passed as constructor parameters, so no mission model is required.

PRFTimelineMissionCard({
  required bool isLast,
  required DateTime startDate,
  required Color statusColor,
  required String statusText,
  required String schoolName,
  required String missionTypeName,
  required String durationLabel,
  required String durationValue,
  required String capacityLabel,
  required String capacityValue,
  required String datePrimaryText,
  required String actionLabel,
  DateTime? endDate,
  String? dateSecondaryText,
  bool showActiveIndicator = false,
  Color? activeIndicatorColor,
  VoidCallback? onTap,
})

Dialogs #

PRFBottomSheet

Rounded modal bottom sheet with a handle, title, close button and scrollable body. Stays above the keyboard.

// Push it:
final result = await PRFBottomSheet.show<Report>(
  context,
  title: 'Generate report',
  heightFactor: 0.7,
  child: ReportForm(onSubmit: (r) => Navigator.pop(context, r)),
);

show accepts heightFactor (default 0.94), useSafeArea, isScrollControlled, isDismissible, enableDrag and showHandle.

PRFConfirmationDialog

Confirmation dialog with an icon header and confirm/cancel actions. Resolves true when confirmed, false when cancelled.

final confirmed = await PRFConfirmationDialog.show<bool>(
  context,
  title: 'Delete mission?',
  message: 'This action cannot be undone.',
  confirmLabel: 'Delete',
  isDestructive: true,
  onConfirm: _deleteMission,
);

Media #

PRFMediaCarousel

Full-screen media carousel with swipe navigation, pinch-zoom, and optional delete/save actions and video pages.

// Push it:
await PRFMediaCarousel.show(
  context,
  items: [
    PRFCarouselItem(url: photo.url, isVideo: false),
    PRFCarouselItem(url: video.url, isVideo: true),
  ],
  initialIndex: 0,
  onSave: (item) => _download(item.url),
  onDelete: (index) async => await _confirmDelete(),
  videoBuilder: (context, item) => VideoPlayerPage(url: item.url),
);

PRFCarouselItem({required String url, required bool isVideo, String? id}).

PRFMediaGrid

Masonry grid of media tiles (2 columns on handset, 3 on tablet) with an optional leading "Add Media" tile.

PRFMediaGrid({
  required int itemCount,
  required Widget Function(BuildContext context, int index) itemBuilder,
  VoidCallback? onAdd,
  String addLabel = 'Add Media',
  IconData addIcon = Icons.add_a_photo_outlined,
})

PRFMediaTile

Single media tile handling image loading states, video overlays and taps.

PRFMediaTile({
  required String url,
  bool isVideo = false,
  VoidCallback? onTap,
  Widget Function(BuildContext context, String url)? imageBuilder,
  Widget Function(BuildContext context, String url)? videoThumbnailBuilder,
  double? height,
})

PRFNavBar

Adaptive top bar with back button, title and actions. Defaults to a sliver (for CustomScrollView); pass isSliver: false for a plain bar.

PRFNavBar({
  required String title,
  VoidCallback? onBack,
  List<Widget>? actions,
  IconData? backIcon,
  Color? backgroundColor,
  bool centerTitle = true,
  bool isSliver = true,
})

PRFAppBar

Non-sliver variant of PRFNavBar for use as Scaffold.appBar.

Scaffold(
  appBar: PRFAppBar(title: 'Profile', onBack: _close),
  body: /* ... */,
)

PRFBrandedNavBar

Branded app bar with a filled primary background and large title.

PRFBrandedNavBar({
  required String title,
  VoidCallback? onBack,
  List<Widget>? actions,
  bool showBackButton = true,
  Color? backgroundColor,
  Color? foregroundColor,
})

PRFDomainTabSection

Titled TabBar + TabBarView section that fills the remaining vertical space.

PRFDomainTabSection({
  required String title,
  required String subtitle,
  required List<Widget> tabs,
  required List<Widget> children,
})

PRFSectionHeader

Bold section heading with an optional subtitle line.

PRFSectionHeader({
  required String title,
  String? subtitle,
  EdgeInsetsGeometry? padding,
  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.start,
})

Progress Indicators #

PRFCircularProgressIndicator

Themed circular spinner, centred in its parent. value (0–1) makes it determinate; null is indeterminate.

PRFCircularProgressIndicator({
  Color? color,
  double? value,
  double size = 24,
  double strokeWidth = 2,
})

PRFLinearProgressIndicator

Themed linear progress bar, centred in its parent.

PRFLinearProgressIndicator({
  Color? color,
  double? value,
  double height = PRFSizeTokens.progressBarHeight,
  double? borderRadius,
  Color? backgroundColor,
})

Indicators #

WrappedPageIndicator

Animated pill-style page dots (active dot becomes a wide bar).

WrappedPageIndicator({
  required int currentPage,
  required int pageCount,
})

Viewers #

PRFPdfViewer

Full-screen PDF viewer. The package performs no network I/O — decode the file and pass the bytes in. Shows a spinner while opening and an error state with retry on failure.

PRFPdfViewer({
  required Uint8List bytes,
  required String title,
  String errorText = 'Failed to open PDF document.',
})

// Push it full-screen:
await PRFPdfViewer.show(context, bytes: bytes, title: 'Document');

Utilities #

DeviceHelper #

Device type detection and responsive scaling.

// Get scale factor for responsive sizing
double scale = DeviceHelper.getScaleFactor(
  context: context,
  customBaseWidth: 375,
  minScale: 0.8,
  maxScale: 1.4,
);

// Detect device type
DeviceType type = DeviceHelper.getDeviceType(context: context);
// DeviceType.phone | DeviceType.tablet | DeviceType.desktop

// Check orientation
bool landscape = DeviceHelper.isLandscape(context: context);

Debouncer #

final debouncer = Debouncer(milliseconds: 300);

debouncer.run(() => search(query));
debouncer.cancel();
debouncer.flush();     // Execute pending action immediately
debouncer.isPending;   // Check if action is pending
debouncer.dispose();

DateFormatter #

Timezone-aware date/time formatting.

DateFormatter.formatDateTime(dateTime, 'Africa/Nairobi')       // "Jan 15, 2025, 2:30 PM"
DateFormatter.formatMissionDate(dateTime, 'Africa/Nairobi')    // Mission-specific format
DateFormatter.formatDate(dateTime, 'Africa/Nairobi')           // "Jan 15, 2025"
DateFormatter.timestamp(dateTime, 'Africa/Nairobi')            // Timestamp format
DateFormatter.formatTime('14:30', 'Africa/Nairobi')            // "2:30 PM"
DateFormatter.formatTimeFromDateTime(dateTime, 'Africa/Nairobi')
DateFormatter.getRelativeTime(dateTime)                         // "2 hours ago"
DateFormatter.getMonthAbbreviation(1)                           // "Jan"

All methods accept an optional locale parameter.

NumberFormatter #

NumberFormatter.formatCash(1500.50, locale: 'en_KE', symbol: '', decimalDigits: 0, customSymbol: 'KES')
NumberFormatter.truncateToDecimalPlaces(3.14159, 2)  // 3.14
NumberFormatter.roundToDecimalPlaces(3.14159, 2)     // 3.14
NumberFormatter.formatFileSize(1572864)               // "1.5 MB"

StringFormatter #

StringFormatter.getUserNameInitials('John Doe', maxInitials: 2)  // "JD"
StringFormatter.getFileName('/path/to/file.pdf')                  // "file.pdf"
StringFormatter.generateRandomString(16, includeNumbers: true, includeSymbols: false)

Error Handling #

PRFFailure #

Comprehensive error model for structured error handling.

PRFFailure({
  required String message,          // User-friendly message
  int? statusCode,                  // HTTP status code
  PRFErrorType type,                // Error category (default: unknown)
  PRFErrorSeverity severity,        // Severity level (default: medium)
  String? technicalMessage,         // Debug details (not shown to users)
  bool isRecoverable,               // Whether user can retry (default: true)
  StackTrace? stackTrace,
  Map<String, dynamic> context,     // Additional context data
})

Factory constructors:

PRFFailure.fromStatusCode(404, 'Not found')
PRFFailure.fromException(error, stackTrace)
PRFFailure.noConnection()
PRFFailure.timeout()
PRFFailure.authentication(message: 'Session expired')
PRFFailure.authorization(message: 'Insufficient permissions')

Methods:

failure.copyWith(message: 'Updated message', severity: PRFErrorSeverity.high)

PRFErrorType #

network | authentication | authorization | validation | notFound | server | timeout | cancelled | unknown

PRFErrorSeverity #

low | medium | high | critical

PRFSnackbarType #

error | success | info | warning

Example: Error Creation & Display #

// Create a failure
final failure = PRFFailure.noConnection();

// Display as error view
PRFErrorView(
  failure: failure,
  onRetry: () => _reload(),
)

// Display as snackbar
PRFSnackbar.error(context, failure.message, onRetry: () => _retry());

Package Structure #

prf_design/
├── lib/
│   ├── prf_design.dart                # Full library barrel export
│   ├── tokens.dart                    # Standalone design tokens (no widget deps)
│   ├── exports/                       # Granular entry points
│   │   ├── theme.dart                 #   Theme system exports
│   │   ├── widgets.dart               #   Widget exports
│   │   ├── utils.dart                 #   Utility exports
│   │   └── enums.dart                 #   Enum & model exports
│   └── src/
│       ├── enums/                     # Error model
│       │   ├── prf_failure.dart
│       │   ├── prf_error_type.dart
│       │   ├── prf_error_severity.dart
│       │   └── prf_snackbar_type.dart
│       ├── theme/                     # Theming system
│       │   ├── prf_theme.dart         #   ThemeData factories (light/dark)
│       │   ├── text_theme.dart        #   Typography (Manrope/Lato)
│       │   ├── adaptive/              #   PRFAdaptive + PRFBreakpoints
│       │   ├── colors/                #   PRFColors, palette, color utils
│       │   ├── extensions/            #   Theme extension data classes
│       │   └── tokens/                #   Spacing/size/radius/motion/status tokens
│       ├── utils/                     # DeviceHelper, debouncer, formatters
│       └── widgets/                   # Adaptive widgets, one folder per widget
│           ├── buttons/               #   PRFButton (single implementation)
│           ├── inputs/                #   PRFTextField, PRFPhoneInput, searchable list
│           ├── cards/                 #   Action/detail cards, tiles, badges
│           ├── states/                #   Empty/error/reply status views, snackbar
│           ├── media/                 #   Carousel, grid, tiles
│           ├── navigation/            #   Nav bars, tabs, section headers
│           ├── progress/              #   Circular/linear progress indicators
│           ├── indicators/            #   Wrapped page indicator
│           ├── dialogs/               #   Confirmation dialog, bottom sheet
│           └── viewers/               #   PDF viewer

Each adaptive widget folder follows the shell + variants pattern:

<widget>/
├── <widget>.dart      # Public API shell (PRFAdaptive routing only)
├── _shared.dart       # Pure builder functions + optional shared state
├── _handset.dart      # Handset layout
└── _tablet.dart       # Tablet layout

Consolidated widgets (buttons, inputs, PDF viewer) live in a single file that shares one _base implementation instead of the folder split.

Requirements #

Requirement Version
Dart SDK >=3.12.2 <4.0.0
Flutter >=3.38.5

Dependencies #

Package Description
flutter_animate Declarative animations
flutter_staggered_grid_view Staggered and masonry grid layouts
flutter_svg SVG rendering support
intl Internationalization and formatting
pdfx PDF rendering
phone_form_field International phone number input
timezone Timezone-aware date/time handling

The package ships offline: the Manrope typeface is bundled as Flutter font assets, and removed runtime dependencies (google_fonts, http, flutter_adaptive_ui) are gone.

Contributing #

Development #

# Format code
make fmt

# Run analysis
flutter analyze

# Run tests
flutter test

CI #

All pull requests are checked for:

  • Code formatting
  • Static analysis
  • Tests

Publishing to pub.dev is a manual GitHub Actions workflow (release.yaml, triggered via Run workflow in the Actions tab) — it publishes the version declared in pubspec.yaml, skipping if the tag already exists.

Open Source Contribution Standards #

This repository welcomes contributions. Please review:

  • Contributing Guide
  • Code of Conduct
  • Security Policy
  • Support

License #

This project is licensed under Parkroad Fellowship Public Ministry License 1.0.

Allowed #

  • Use, adapt, and deploy for noncommercial ministry or nonprofit use cases.
  • Use by charitable, educational and public-interest organizations.
  • Share improvements under the same noncommercial licensing constraints.

Not allowed #

  • Selling this software or offering it as a paid commercial product.
  • Commercial hosting or commercialization of the software.
  • Any use that violates the terms in the LICENSE.

For setup and hosting assistance, contact engineering@parkroadfellowship.org.

0
likes
0
points
408
downloads

Publisher

verified publisherparkroadfellowship.org

Weekly Downloads

PRF Design System - Shared widgets, theme system, and essential utilities.

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_animate, flutter_staggered_grid_view, flutter_svg, intl, pdfx, phone_form_field, timezone

More

Packages that depend on prf_design