Last active
January 21, 2026 23:03
-
-
Save aloisdeniel/ee8376d7444afe1ba98a13727d4edeb8 to your computer and use it in GitHub Desktop.
Flutter Codegen plugin for Figma / Components
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/gestures.dart'; | |
| import 'package:flutter/material.dart'; | |
| abstract class Colors { | |
| static const white = Color(0xFFFFFFFF); | |
| static const blueLight = Color(0xFFECEFFF); | |
| static const blueMedium = Color(0xFF4463FF); | |
| static const blueMediumDark = Color(0xFF314DD5); | |
| } | |
| void main() { | |
| runApp(const MyApp()); | |
| } | |
| class MyApp extends StatelessWidget { | |
| const MyApp({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| debugShowCheckedModeBanner: false, | |
| home: Container( | |
| color: Colors.white, | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| crossAxisAlignment: CrossAxisAlignment.center, | |
| spacing: 32, | |
| children: [ | |
| Button(title: 'Continue', onPressed: () {}), | |
| // Widget preview instances | |
| Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| crossAxisAlignment: CrossAxisAlignment.center, | |
| spacing: 4, | |
| children: [ | |
| for (final style in ButtonStyleVariant.values) | |
| for (final state in ButtonStateVariant.values) | |
| ButtonProperties( | |
| data: ButtonData( | |
| title: 'Preview', | |
| style: style, | |
| state: state, | |
| ), | |
| child: const ButtonLayout(), | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| // This widget renders the visuals from the component properties | |
| class ButtonLayout extends StatelessWidget { | |
| const ButtonLayout({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| final properties = ButtonProperties.of(context); | |
| final decoration = switch (properties.style) { | |
| ButtonStyleVariant.filled => BoxDecoration( | |
| color: switch (properties.state) { | |
| ButtonStateVariant.hover => Colors.blueMediumDark, | |
| _ => Colors.blueMedium, | |
| }, | |
| borderRadius: BorderRadius.circular(100), | |
| ), | |
| ButtonStyleVariant.border => BoxDecoration( | |
| border: Border.all( | |
| color: switch (properties.state) { | |
| ButtonStateVariant.hover => Colors.blueMediumDark, | |
| _ => Colors.blueMedium, | |
| }, | |
| width: 2, | |
| ), | |
| borderRadius: BorderRadius.circular(100), | |
| ), | |
| ButtonStyleVariant.text => BoxDecoration( | |
| color: switch (properties.state) { | |
| ButtonStateVariant.hover => Colors.blueLight, | |
| _ => Colors.blueLight.withValues(alpha: 0), | |
| }, | |
| ), | |
| }; | |
| return AnimatedContainer( | |
| duration: const Duration(milliseconds: 250), | |
| padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), | |
| decoration: decoration, | |
| child: Text( | |
| properties.title, | |
| style: TextStyle( | |
| color: switch (properties.style) { | |
| ButtonStyleVariant.filled => Colors.blueLight, | |
| _ => Colors.blueMediumDark, | |
| }, | |
| decoration: TextDecoration.none, | |
| fontSize: 12, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| /// This widgets adds the behaviours to create to right properties | |
| /// for the layout. | |
| class Button extends StatefulWidget { | |
| const Button({ | |
| super.key, | |
| required this.title, | |
| this.style = ButtonStyleVariant.filled, | |
| this.onPressed, | |
| }); | |
| final String title; | |
| final ButtonStyleVariant style; | |
| final VoidCallback? onPressed; | |
| @override | |
| State<Button> createState() => _ButtonState(); | |
| } | |
| class _ButtonState extends State<Button> { | |
| var _hovering = false; | |
| var _pressed = false; | |
| void handleAnyTapDown(TapDownDetails details) { | |
| if (!_pressed) { | |
| setState(() { | |
| _pressed = true; | |
| }); | |
| } | |
| } | |
| void handleAnyTapUpOrCancel() { | |
| if (_pressed) { | |
| setState(() { | |
| _pressed = false; | |
| }); | |
| } | |
| } | |
| void handleMouseEnter(PointerEnterEvent event) { | |
| if (!_hovering) { | |
| setState(() { | |
| _hovering = true; | |
| }); | |
| } | |
| } | |
| void handleMouseExit(PointerExitEvent event) { | |
| if (_hovering) { | |
| setState(() { | |
| _hovering = false; | |
| }); | |
| } | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return MouseRegion( | |
| onEnter: handleMouseEnter, | |
| onExit: handleMouseExit, | |
| child: GestureDetector( | |
| onTapDown: handleAnyTapDown, | |
| onTapUp: (_) => handleAnyTapUpOrCancel(), | |
| onTapCancel: handleAnyTapUpOrCancel, | |
| onTap: widget.onPressed, | |
| child: ButtonProperties.merge( | |
| context, | |
| title: widget.title, | |
| style: widget.style, | |
| state: switch ((_pressed, _hovering)) { | |
| (false, false) => ButtonStateVariant.default_, | |
| _ => ButtonStateVariant.hover, | |
| }, | |
| child: ButtonLayout(), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| // The code below is generated with the Flutter Codegen plugin for Figma | |
| enum ButtonStateVariant { default_, hover } | |
| enum ButtonStyleVariant { filled, border, text } | |
| class ButtonData { | |
| const ButtonData({ | |
| this.title = 'Click me', | |
| this.state = ButtonStateVariant.default_, | |
| this.style = ButtonStyleVariant.filled, | |
| }); | |
| final String title; | |
| final ButtonStateVariant state; | |
| final ButtonStyleVariant style; | |
| @override | |
| int get hashCode { | |
| return Object.hashAll([title, state, style]); | |
| } | |
| @override | |
| bool operator ==(Object other) { | |
| if (identical(this, other)) return true; | |
| return other is ButtonData && | |
| other.title == title && | |
| other.state == state && | |
| other.style == style; | |
| } | |
| @override | |
| String toString() { | |
| return 'ButtonData(' | |
| 'title: $title, ' | |
| 'state: $state, ' | |
| 'style: $style' | |
| ')'; | |
| } | |
| ButtonData copyWith({ | |
| String? title, | |
| ButtonStateVariant? state, | |
| ButtonStyleVariant? style, | |
| }) { | |
| return ButtonData( | |
| title: title ?? this.title, | |
| state: state ?? this.state, | |
| style: style ?? this.style, | |
| ); | |
| } | |
| } | |
| class ButtonProperties extends InheritedWidget { | |
| const ButtonProperties({super.key, required this.data, required super.child}); | |
| factory ButtonProperties.merge( | |
| BuildContext context, { | |
| Key? key, | |
| required Widget child, | |
| String? title, | |
| ButtonStateVariant? state, | |
| ButtonStyleVariant? style, | |
| }) { | |
| var properties = ButtonProperties.maybeOf(context) ?? const ButtonData(); | |
| return ButtonProperties( | |
| key: key, | |
| data: ButtonData( | |
| title: title ?? properties.title, | |
| state: state ?? properties.state, | |
| style: style ?? properties.style, | |
| ), | |
| child: child, | |
| ); | |
| } | |
| final ButtonData data; | |
| static ButtonData? maybeOf(BuildContext context) { | |
| final result = context | |
| .dependOnInheritedWidgetOfExactType<ButtonProperties>(); | |
| return result?.data; | |
| } | |
| static ButtonData of(BuildContext context) { | |
| final result = maybeOf(context); | |
| assert(result != null, 'No ButtonProperties found in context'); | |
| return result!; | |
| } | |
| @override | |
| bool updateShouldNotify(ButtonProperties oldWidget) => data != oldWidget.data; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment