Last active
January 19, 2026 17:34
-
-
Save aloisdeniel/0d7f5a4c1d1f9be775a0530a2c2b1ba1 to your computer and use it in GitHub Desktop.
Flutter Codegen plugin for Figma / Variables and Styles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| // The code below has been generated with the Flutter Codegen plugin for Figma. | |
| import 'dart:ui' as ui; | |
| import 'package:flutter/widgets.dart' as fl; | |
| import 'package:google_fonts/google_fonts.dart' as gf; | |
| typedef VariableMode = ({ColorsMode colors, AccentMode accent, FontsMode fonts}); | |
| extension VariableModeExtension on VariableMode { | |
| VariableMode copyWith({ | |
| ColorsMode? colors, | |
| AccentMode? accent, | |
| FontsMode? fonts, | |
| }) { | |
| return ( | |
| colors: colors ?? this.colors, | |
| accent: accent ?? this.accent, | |
| fonts: fonts ?? this.fonts, | |
| ); | |
| } | |
| } | |
| class VariableCollections { | |
| const VariableCollections._({required this.colors, required this.accent, required this.fonts, required this.spacing, required this.radii, required this.styles}); | |
| factory VariableCollections.fromModes(VariableMode mode) { | |
| final colors = ColorsData.fromMode(mode.colors); | |
| final accent = AccentData.fromMode(mode.accent); | |
| final fonts = FontsData.fromMode(mode.fonts); | |
| final spacing = SpacingData(); | |
| final radii = RadiiData(); | |
| final styles = StylesData(); | |
| // Resolving aliases | |
| colors.alias = (accent: accent); | |
| styles.alias = (fonts: fonts); | |
| return VariableCollections._(colors: colors, accent: accent, fonts: fonts, spacing: spacing, radii: radii, styles: styles); | |
| } | |
| final ColorsData colors; | |
| final AccentData accent; | |
| final FontsData fonts; | |
| final SpacingData spacing; | |
| final RadiiData radii; | |
| final StylesData styles; | |
| } | |
| class Variables extends fl.InheritedWidget { | |
| Variables({super.key, required super.child, required this.mode}) | |
| : data = VariableCollections.fromModes(mode); | |
| final VariableMode mode; | |
| final VariableCollections data; | |
| factory Variables.override( | |
| fl.BuildContext context, { | |
| fl.Key? key, | |
| required fl.Widget child, | |
| ColorsMode? colors, | |
| AccentMode? accent, | |
| FontsMode? fonts, | |
| }) { | |
| final mode = Variables.modeOf(context); | |
| return Variables( | |
| key: key, | |
| mode: mode!.copyWith( | |
| colors: colors, | |
| accent: accent, | |
| fonts: fonts, | |
| ), | |
| child: child, | |
| ); | |
| } | |
| @override | |
| bool updateShouldNotify(covariant Variables oldWidget) { | |
| return mode != oldWidget.mode; | |
| } | |
| static VariableCollections? maybeOf(fl.BuildContext context) { | |
| final instance = context.dependOnInheritedWidgetOfExactType<Variables>(); | |
| return instance?.data; | |
| } | |
| static VariableCollections of(fl.BuildContext context) { | |
| final data = maybeOf(context); | |
| assert(data != null, 'No Variables found in context'); | |
| return data!; | |
| } | |
| static VariableMode? modeOf(fl.BuildContext context) { | |
| final instance = context.dependOnInheritedWidgetOfExactType<Variables>(); | |
| return instance?.mode; | |
| } | |
| } | |
| enum ColorsMode { | |
| light, | |
| dark, | |
| } | |
| sealed class ColorsData { | |
| ColorsData(); | |
| factory ColorsData.fromMode(ColorsMode mode) { | |
| switch (mode) { | |
| case ColorsMode.light: | |
| return _Light(); | |
| case ColorsMode.dark: | |
| return _Dark(); | |
| } | |
| } | |
| late ({AccentData accent}) alias; | |
| fl.Color get backgroundMain; | |
| fl.Color get backgroundSecondary; | |
| fl.Color get foregroundMain; | |
| fl.Color get backgroundCover; | |
| fl.Color get foregroundSecondary; | |
| fl.Color get backgroundPrimary; | |
| fl.Color get foregroundPrimary; | |
| fl.Color get foregroundOnPrimary; | |
| } | |
| class _Light extends ColorsData { | |
| _Light(); | |
| @override | |
| final backgroundMain = const fl.Color.from(red: 1, green: 1, blue: 1, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final backgroundSecondary = const fl.Color.from(red: 0.9657426476478577, green: 0.9651665091514587, blue: 0.9709284901618958, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final foregroundMain = const fl.Color.from(red: 0.0941176488995552, green: 0.0941176488995552, blue: 0.10196078568696976, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final backgroundCover = const fl.Color.from(red: 0.0941176488995552, green: 0.0941176488995552, blue: 0.10196078568696976, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final foregroundSecondary = const fl.Color.from(red: 0.2168998122215271, green: 0.2168998122215271, blue: 0.286865234375, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| get backgroundPrimary => alias.accent.medium; | |
| @override | |
| get foregroundPrimary => alias.accent.dark; | |
| @override | |
| get foregroundOnPrimary => alias.accent.light; | |
| } | |
| class _Dark extends ColorsData { | |
| _Dark(); | |
| @override | |
| final backgroundMain = const fl.Color.from(red: 0.1282019019126892, green: 0.1282019019126892, blue: 0.14614632725715637, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final backgroundSecondary = const fl.Color.from(red: 0.20936547219753265, green: 0.20936547219753265, blue: 0.23069411516189575, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final foregroundMain = const fl.Color.from(red: 1, green: 1, blue: 1, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final backgroundCover = const fl.Color.from(red: 0.0941176488995552, green: 0.0941176488995552, blue: 0.10196078568696976, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final foregroundSecondary = const fl.Color.from(red: 0.915771484375, green: 0.8869644403457642, blue: 0.8869644403457642, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| get backgroundPrimary => alias.accent.medium; | |
| @override | |
| get foregroundPrimary => alias.accent.dark; | |
| @override | |
| get foregroundOnPrimary => alias.accent.light; | |
| } | |
| enum AccentMode { | |
| blue, | |
| green, | |
| } | |
| sealed class AccentData { | |
| AccentData(); | |
| factory AccentData.fromMode(AccentMode mode) { | |
| switch (mode) { | |
| case AccentMode.blue: | |
| return _Blue(); | |
| case AccentMode.green: | |
| return _Green(); | |
| } | |
| } | |
| fl.Color get light; | |
| fl.Color get medium; | |
| fl.Color get dark; | |
| } | |
| class _Blue extends AccentData { | |
| _Blue(); | |
| @override | |
| final light = const fl.Color.from(red: 0.9249361753463745, green: 0.9374467730522156, blue: 1, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final medium = const fl.Color.from(red: 0.26528695225715637, green: 0.3877391517162323, blue: 1, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final dark = const fl.Color.from(red: 0.16449296474456787, green: 0.2027168571949005, blue: 0.3938363790512085, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| } | |
| class _Green extends AccentData { | |
| _Green(); | |
| @override | |
| final light = const fl.Color.from(red: 0.9699666500091553, green: 1, blue: 0.836181640625, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final medium = const fl.Color.from(red: 0.1932700127363205, green: 0.608229398727417, blue: 0.4353296458721161, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| @override | |
| final dark = const fl.Color.from(red: 0.19369162619113922, green: 0.283203125, blue: 0.23397180438041687, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB); | |
| } | |
| enum FontsMode { | |
| regular, | |
| monospace, | |
| } | |
| sealed class FontsData { | |
| FontsData(); | |
| factory FontsData.fromMode(FontsMode mode) { | |
| switch (mode) { | |
| case FontsMode.regular: | |
| return _Regular(); | |
| case FontsMode.monospace: | |
| return _Monospace(); | |
| } | |
| } | |
| String get familyMain; | |
| double get fontSizeM; | |
| double get fontSizeL; | |
| double get fontSizeXl; | |
| double get weightNormal; | |
| double get weightBold; | |
| } | |
| class _Regular extends FontsData { | |
| _Regular(); | |
| @override | |
| final familyMain = 'Inter'; | |
| @override | |
| final fontSizeM = 11.0; | |
| @override | |
| final fontSizeL = 12.0; | |
| @override | |
| final fontSizeXl = 24.0; | |
| @override | |
| final weightNormal = 400.0; | |
| @override | |
| final weightBold = 600.0; | |
| } | |
| class _Monospace extends FontsData { | |
| _Monospace(); | |
| @override | |
| final familyMain = 'Fira Code'; | |
| @override | |
| final fontSizeM = 11.0; | |
| @override | |
| final fontSizeL = 11.0; | |
| @override | |
| final fontSizeXl = 22.0; | |
| @override | |
| final weightNormal = 400.0; | |
| @override | |
| final weightBold = 700.0; | |
| } | |
| class SpacingData { | |
| SpacingData(); | |
| final xxs = 1.0; | |
| final xs = 2.0; | |
| final s = 4.0; | |
| final m = 8.0; | |
| final l = 16.0; | |
| final xl = 24.0; | |
| final xxl = 32.0; | |
| } | |
| class RadiiData { | |
| RadiiData(); | |
| final xxs = 1.0; | |
| final xs = 2.0; | |
| final s = 4.0; | |
| final m = 8.0; | |
| final l = 12.0; | |
| final xl = 24.0; | |
| final xxl = 48.0; | |
| } | |
| class StylesData { | |
| StylesData(); | |
| late ({FontsData fonts}) alias; | |
| late final title = gf.GoogleFonts.getFont(alias.fonts.familyMain, textStyle: fl.TextStyle(fontSize: alias.fonts.fontSizeXl, fontWeight: fl.FontWeight.values.firstWhere( | |
| (e) => alias.fonts.weightBold <= e.value.toInt(), | |
| orElse: () => fl.FontWeight.w400, | |
| ))); | |
| late final body = gf.GoogleFonts.getFont(alias.fonts.familyMain, textStyle: fl.TextStyle(fontSize: alias.fonts.fontSizeM, fontWeight: fl.FontWeight.values.firstWhere( | |
| (e) => alias.fonts.weightNormal <= e.value.toInt(), | |
| orElse: () => fl.FontWeight.w400, | |
| ))); | |
| late final callout = gf.GoogleFonts.getFont(alias.fonts.familyMain, textStyle: fl.TextStyle(fontSize: alias.fonts.fontSizeL, fontWeight: fl.FontWeight.values.firstWhere( | |
| (e) => alias.fonts.weightBold <= e.value.toInt(), | |
| orElse: () => fl.FontWeight.w400, | |
| ))); | |
| } | |
| // The code below is a usage example corresponding to the original | |
| // component in Figma. | |
| void main() { | |
| runApp(const App()); | |
| } | |
| class App extends StatelessWidget { | |
| const App({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| // We provide root default modes. | |
| builder: (context, child) => Variables( | |
| mode: ( | |
| colors: switch (MediaQuery.platformBrightnessOf(context)) { | |
| Brightness.light => ColorsMode.light, | |
| Brightness.dark => ColorsMode.dark, | |
| }, | |
| accent: AccentMode.blue, | |
| fonts: FontsMode.regular, | |
| ), | |
| child: DefaultTextStyle( | |
| style: TextStyle(decoration: TextDecoration.none), | |
| child: child!, | |
| ), | |
| ), | |
| home: const Home(), | |
| ); | |
| } | |
| } | |
| class Home extends StatefulWidget { | |
| const Home({super.key}); | |
| @override | |
| State<Home> createState() => _HomeState(); | |
| } | |
| class _HomeState extends State<Home> { | |
| VariableMode? _modeOverride; | |
| @override | |
| Widget build(BuildContext context) { | |
| final mode = _modeOverride ?? Variables.modeOf(context)!; | |
| return Variables.override( | |
| context, | |
| colors: _modeOverride?.colors, | |
| fonts: _modeOverride?.fonts, | |
| accent: _modeOverride?.accent, | |
| child: Column( | |
| children: [ | |
| ModesBar( | |
| mode: mode, | |
| onModeChange: (newMode) { | |
| setState(() { | |
| _modeOverride = newMode; | |
| }); | |
| }, | |
| ), | |
| Expanded(child: const Example()), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class ModesBar extends StatelessWidget { | |
| const ModesBar({super.key, required this.mode, required this.onModeChange}); | |
| final VariableMode mode; | |
| final ValueChanged<VariableMode> onModeChange; | |
| @override | |
| Widget build(BuildContext context) { | |
| final mode = Variables.modeOf(context)!; | |
| final vars = Variables.of(context); | |
| return Row( | |
| mainAxisSize: MainAxisSize.min, | |
| children: [ | |
| Button( | |
| label: '${mode.colors.name}', | |
| onPressed: () { | |
| final newColors = switch (mode.colors) { | |
| ColorsMode.light => ColorsMode.dark, | |
| ColorsMode.dark => ColorsMode.light, | |
| }; | |
| onModeChange(mode.copyWith(colors: newColors)); | |
| }, | |
| ), | |
| const SizedBox(width: 16.0), | |
| Button( | |
| label: '${mode.accent.name}', | |
| onPressed: () { | |
| final newAccent = AccentMode | |
| .values[(mode.accent.index + 1) % AccentMode.values.length]; | |
| onModeChange(mode.copyWith(accent: newAccent)); | |
| }, | |
| ), | |
| const SizedBox(width: 16.0), | |
| Button( | |
| label: '${mode.fonts.name}', | |
| onPressed: () { | |
| final newFonts = FontsMode | |
| .values[(mode.fonts.index + 1) % FontsMode.values.length]; | |
| onModeChange(mode.copyWith(fonts: newFonts)); | |
| }, | |
| ), | |
| ], | |
| ); | |
| } | |
| } | |
| class Example extends StatelessWidget { | |
| const Example({Key? key}) : super(key: key); | |
| @override | |
| Widget build(BuildContext context) { | |
| final vars = Variables.of(context); | |
| return Container( | |
| color: vars.colors.backgroundCover, | |
| child: Center( | |
| child: Container( | |
| padding: EdgeInsets.all(vars.spacing.xl), | |
| decoration: BoxDecoration( | |
| color: vars.colors.backgroundMain, | |
| borderRadius: BorderRadius.circular(vars.radii.l), | |
| ), | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| children: [ | |
| Text( | |
| 'Hello, World!', | |
| style: vars.styles.title.copyWith( | |
| color: vars.colors.foregroundMain, | |
| ), | |
| ), | |
| SizedBox(height: vars.spacing.m), | |
| Text( | |
| 'This is an example of using generated design tokens in Flutter.', | |
| style: vars.styles.body.copyWith( | |
| color: vars.colors.foregroundMain, | |
| ), | |
| ), | |
| SizedBox(height: vars.spacing.l), | |
| IntrinsicWidth( | |
| child: Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| children: [ | |
| Button( | |
| label: 'Cancel', | |
| style: ButtonStyleVariant.secondary, | |
| onPressed: () {}, | |
| ), | |
| Button(label: 'Continue', onPressed: () {}), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| enum ButtonStyleVariant { primary, secondary } | |
| class Button extends StatelessWidget { | |
| const Button({ | |
| super.key, | |
| required this.label, | |
| required this.onPressed, | |
| this.style = ButtonStyleVariant.primary, | |
| }); | |
| final String label; | |
| final ButtonStyleVariant style; | |
| final VoidCallback onPressed; | |
| @override | |
| Widget build(BuildContext context) { | |
| final vars = Variables.of(context); | |
| final background = switch (style) { | |
| ButtonStyleVariant.primary => vars.colors.backgroundPrimary, | |
| ButtonStyleVariant.secondary => vars.colors.backgroundSecondary, | |
| }; | |
| final foreground = switch (style) { | |
| ButtonStyleVariant.primary => vars.colors.foregroundOnPrimary, | |
| ButtonStyleVariant.secondary => vars.colors.foregroundSecondary, | |
| }; | |
| return GestureDetector( | |
| onTap: onPressed, | |
| child: Container( | |
| padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), | |
| decoration: BoxDecoration( | |
| color: background, | |
| borderRadius: BorderRadius.circular(100.0), | |
| ), | |
| child: Text( | |
| label, | |
| style: vars.styles.callout.copyWith(color: foreground), | |
| ), | |
| ), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment