diff --git a/clj/src/cljd/flutter.cljd b/clj/src/cljd/flutter.cljd
index 8e2f8535..d631af42 100644
--- a/clj/src/cljd/flutter.cljd
+++ b/clj/src/cljd/flutter.cljd
@@ -1279,7 +1279,7 @@
:managed [*hud-enabled (atom nil) :dispose nil
_ (println "[* RDY)_") :dispose nil]
(widgets/Stack
- .alignment widgets/AlignmentGeometry.topLeft
+ .alignment widgets/Alignment.topLeft
.fit widgets/StackFit.expand
.children
[(widget
diff --git a/samples/bottom_navigation_bar/src/sample/bottom_navigation_bar.cljd b/samples/bottom_navigation_bar/src/sample/bottom_navigation_bar.cljd
index ec857784..e9b5d2de 100644
--- a/samples/bottom_navigation_bar/src/sample/bottom_navigation_bar.cljd
+++ b/samples/bottom_navigation_bar/src/sample/bottom_navigation_bar.cljd
@@ -4,36 +4,28 @@
["package:flutter/material.dart" :as m]
[cljd.flutter :as f]))
-(defn main
+(defn bottom-nav-widget
+ "Reusable bottom navigation bar widget that can be embedded in any app."
[]
- (let
- [title "Bottom Navigation Bar Demo"
- pages [(m/Icon. m/Icons.directions_car)
- (m/Icon. m/Icons.directions_transit)
- (m/Icon. m/Icons.directions_bike)]
- selected-index (atom 0)]
- (f/run
- (m/MaterialApp .title title)
+ (let [pages [(m/Icon. m/Icons.directions_car .size 64)
+ (m/Icon. m/Icons.directions_transit .size 64)
+ (m/Icon. m/Icons.directions_bike .size 64)]
+ selected-index (atom 0)]
+ (f/widget
+ :watch [idx selected-index]
+ (m/Scaffold
+ .appBar (m/AppBar .title (m/Text "Bottom Navigation"))
+ .body (m/Center .child (get pages idx))
+ .bottomNavigationBar
+ (m/BottomNavigationBar
+ .currentIndex idx
+ .onTap #(reset! selected-index %)
+ .items
+ [(m/BottomNavigationBarItem .icon (m/Icon m/Icons.directions_car) .label "Car")
+ (m/BottomNavigationBarItem .icon (m/Icon m/Icons.directions_transit) .label "Transit")
+ (m/BottomNavigationBarItem .icon (m/Icon m/Icons.directions_bike) .label "Bike")])))))
- .home
- (m/Scaffold
-
- .appBar
- (m/AppBar
- .title
- (m/Text "BottomNavigationBar"))
-
- .body
- (m/Center
- .child
- (f/widget :watch [current-index selected-index] (get pages current-index)))
-
- .bottomNavigationBar
- (f/widget
- :watch [current-index selected-index]
- (m/BottomNavigationBar
- .items [(m/BottomNavigationBarItem .icon (m/Icon. m/Icons.directions_car) .label "directions_car")
- (m/BottomNavigationBarItem .icon (m/Icon. m/Icons.directions_transit) .label "directions_transit")
- (m/BottomNavigationBarItem .icon (m/Icon. m/Icons.directions_bike) .label "directions_bike")]
- .currentIndex current-index
- .onTap (fn [index] (reset! selected-index index))))))))
+(defn main []
+ (f/run
+ (m/MaterialApp .title "Bottom Navigation Bar Demo")
+ .home (bottom-nav-widget)))
diff --git a/samples/counter/src/sample/counter.cljd b/samples/counter/src/sample/counter.cljd
index fa824060..d197f527 100644
--- a/samples/counter/src/sample/counter.cljd
+++ b/samples/counter/src/sample/counter.cljd
@@ -4,27 +4,32 @@
["package:flutter/material.dart" :as m]
[cljd.flutter :as f]))
-(defn main []
+(defn counter-widget
+ "Reusable counter widget that can be embedded in any app."
+ []
(let [counter (atom 0)]
- (f/run
- (m/MaterialApp
- .title "Cljd Demo"
- .theme (m/ThemeData .primarySwatch m/Colors.blue))
- .home
- (m/Scaffold
- .appBar (m/AppBar .title (m/Text "ClojureDart Home Page"))
- .floatingActionButton
- (f/widget
- (m/FloatingActionButton
- .onPressed #(swap! counter inc)
- .tooltip "Increment")
- (m/Icon m/Icons.add)))
- .body
- m/Center
- (m/Column .mainAxisAlignment m/MainAxisAlignment.center)
- .children
- [(m/Text "You have pushed the button this many times:")
- (f/widget
- :get {{{:flds [displayLarge]} .-textTheme} m/Theme}
- :watch [N counter]
- (m/Text (str N) .style displayLarge))])))
+ (f/widget
+ (m/Scaffold
+ .appBar (m/AppBar .title (m/Text "Counter Demo"))
+ .floatingActionButton
+ (f/widget
+ (m/FloatingActionButton
+ .onPressed #(swap! counter inc)
+ .tooltip "Increment")
+ (m/Icon m/Icons.add)))
+ .body
+ m/Center
+ (m/Column .mainAxisAlignment m/MainAxisAlignment.center)
+ .children
+ [(m/Text "You have pushed the button this many times:")
+ (f/widget
+ :get {{{:flds [displayLarge]} .-textTheme} m/Theme}
+ :watch [N counter]
+ (m/Text (str N) .style displayLarge))])))
+
+(defn main []
+ (f/run
+ (m/MaterialApp
+ .title "Cljd Demo"
+ .theme (m/ThemeData .primarySwatch m/Colors.blue))
+ .home (counter-widget)))
diff --git a/samples/fetch-data/src/sample/fetch_data.cljd b/samples/fetch-data/src/sample/fetch_data.cljd
index 6f02b1ce..484c6287 100644
--- a/samples/fetch-data/src/sample/fetch_data.cljd
+++ b/samples/fetch-data/src/sample/fetch_data.cljd
@@ -6,18 +6,22 @@
["package:http/http.dart" :as http]
[cljd.flutter :as f]))
+(def fetch-data-widget
+ "Reusable fetch data widget that can be embedded in any app."
+ (f/widget
+ (m/Scaffold .appBar (m/AppBar .title (m/Text "Fetch Data Example")))
+ .body
+ m/Center
+ :watch [response (http/get (Uri/parse "https://jsonplaceholder.typicode.com/albums/1"))]
+ (if-some [{sc .-statusCode body .-body} ^http/Response response]
+ (case sc
+ 200 (m/Text (get (c/json.decode body) "title"))
+ (m/Text (str "Something wrong happened, status code: " sc)))
+ (m/CircularProgressIndicator))))
+
(defn main []
(f/run
- (m/MaterialApp
- .title "Fetch Data Example"
- .theme (m/ThemeData .primarySwatch m/Colors.blue))
- .home
- (m/Scaffold .appBar (m/AppBar .title (m/Text "Fetch Data Example")))
- .body
- m/Center
- :watch [response (http/get (Uri/parse "https://jsonplaceholder.typicode.com/albums/1"))]
- (if-some [{sc .-statusCode body .-body} ^http/Response response] ;; type hint is not necessary here, it removes compiler warnings
- (case sc
- 200 (m/Text (get (c/json.decode body) "title"))
- (m/Text (str "Something wrong happened, status code: " sc)))
- (m/CircularProgressIndicator))))
+ (m/MaterialApp
+ .title "Fetch Data Example"
+ .theme (m/ThemeData .primarySwatch m/Colors.blue))
+ .home fetch-data-widget))
diff --git a/samples/fizzbuzz/src/sample/fizzbuzz.cljd b/samples/fizzbuzz/src/sample/fizzbuzz.cljd
index 85d07fc9..bbab681a 100644
--- a/samples/fizzbuzz/src/sample/fizzbuzz.cljd
+++ b/samples/fizzbuzz/src/sample/fizzbuzz.cljd
@@ -5,26 +5,30 @@
(def ^m/TextStyle text-style (m/TextStyle .fontWeight m/FontWeight.w700 .fontSize 32))
+(def fizzbuzz-widget
+ "Reusable fizzbuzz widget that can be embedded in any app."
+ (f/widget
+ (m/Scaffold .appBar (m/AppBar .title (m/Text "Fizz Buzz Demo")))
+ .body
+ :let [s1 (-> (Stream/periodic (Duration .seconds 1) identity) .asBroadcastStream)
+ s3 (Stream/periodic (Duration .seconds 3) #(* 3 (inc %)))
+ s5 (Stream/periodic (Duration .seconds 5) #(* 5 (inc %)))]
+ (m/Column .mainAxisAlignment m/MainAxisAlignment.center .crossAxisAlignment m/CrossAxisAlignment.stretch)
+ .children
+ [(f/widget
+ :watch [n s1
+ :default 0]
+ (m/Text (str n) .textAlign m/TextAlign.center .style text-style))
+ (f/widget
+ :watch [n (f/sub [s1 s3] (fn [[n n3]] (if (= n n3) "Fizz" " ")))
+ :default " "]
+ (m/Text n .textAlign m/TextAlign.center .style (.apply text-style .color m/Colors.red.shade200)))
+ (f/widget
+ :watch [n (f/sub [s1 s5] (fn [[n n5]] (if (= n n5) "Buzz" " ")))
+ :default " "]
+ (m/Text n .textAlign m/TextAlign.center .style (.apply text-style .color m/Colors.green.shade200)))]))
+
(defn main []
(f/run
- (m/MaterialApp .title "Fizz buzz Demo")
- .home
- (m/Scaffold .appBar (m/AppBar .title (m/Text "Fizz buzz Demo")))
- .body
- :let [s1 (-> (Stream/periodic (Duration .seconds 1) identity) .asBroadcastStream)
- s3 (Stream/periodic (Duration .seconds 3) #(* 3 (inc %)))
- s5 (Stream/periodic (Duration .seconds 5) #(* 5 (inc %)))]
- (m/Column .mainAxisAlignment m/MainAxisAlignment.center .crossAxisAlignment m/CrossAxisAlignment.stretch)
- .children
- [(f/widget
- :watch [n s1
- :default 0]
- (m/Text (str n) .textAlign m/TextAlign.center .style text-style))
- (f/widget
- :watch [n (f/sub [s1 s3] (fn [[n n3]] (if (= n n3) "Fizz" " ")))
- :default " "]
- (m/Text n .textAlign m/TextAlign.center .style (.apply text-style .color m/Colors.red.shade200)))
- (f/widget
- :watch [n (f/sub [s1 s5] (fn [[n n5]] (if (= n n5) "Buzz" " ")))
- :default " "]
- (m/Text n .textAlign m/TextAlign.center .style (.apply text-style .color m/Colors.green.shade200)))]))
+ (m/MaterialApp .title "Fizz Buzz Demo")
+ .home fizzbuzz-widget))
diff --git a/samples/form_handle_change_textfield/src/sample/form_handle_change.cljd b/samples/form_handle_change_textfield/src/sample/form_handle_change.cljd
new file mode 100644
index 00000000..0f368880
--- /dev/null
+++ b/samples/form_handle_change_textfield/src/sample/form_handle_change.cljd
@@ -0,0 +1,28 @@
+(ns sample.form-handle-change
+ "Faithful port of https://docs.flutter.dev/cookbook/forms/text-field-changes"
+ (:require ["package:flutter/material.dart" :as m]
+ [cljd.flutter :as f]))
+
+(def form-handle-change-widget
+ "Reusable form handle change widget that can be embedded in any app."
+ (f/widget
+ (m/Scaffold .appBar (m/AppBar .title (m/Text "Handle Text Changes")))
+ .body
+ (m/Padding .padding (m/EdgeInsets.all 16.0))
+ :managed [text-controller (m/TextEditingController)]
+ :bg-watcher ([^m/TextEditingValue {second-input-text .-text} text-controller]
+ (dart:core/print (str "Second text field: " second-input-text)))
+ m/Column
+ .children
+ [(m/TextField
+ .decoration (m/InputDecoration .labelText "First field (onChanged)")
+ .onChanged (fn [text] (dart:core/print (str "First text field: " text))))
+ (m/SizedBox .height 16)
+ (m/TextField
+ .decoration (m/InputDecoration .labelText "Second field (controller)")
+ .controller text-controller)]))
+
+(defn main []
+ (f/run
+ (m/MaterialApp .title "Handle Text Changes")
+ .home form-handle-change-widget))
diff --git a/samples/form_retrieve_input/src/sample/form_retrieve_input.cljd b/samples/form_retrieve_input/src/sample/form_retrieve_input.cljd
new file mode 100644
index 00000000..cbacea37
--- /dev/null
+++ b/samples/form_retrieve_input/src/sample/form_retrieve_input.cljd
@@ -0,0 +1,32 @@
+(ns sample.form-retrieve-input
+ "Faithful port of https://docs.flutter.dev/cookbook/forms/retrieve-input"
+ (:require ["package:flutter/material.dart" :as m]
+ [cljd.flutter :as f]))
+
+(defn form-retrieve-input-widget
+ "Reusable form retrieve input widget that can be embedded in any app."
+ []
+ (f/widget
+ :context ctx
+ :managed [tc (m/TextEditingController)]
+ (m/Scaffold
+ .appBar (m/AppBar .title (m/Text "Retrieve Text Input"))
+ .body
+ (m/Padding. .padding (m/EdgeInsets.all 16.0)
+ .child (m/TextField .controller tc))
+ .floatingActionButton
+ (m/FloatingActionButton
+ .onPressed (fn []
+ (m/showDialog
+ .context ctx
+ .builder
+ (f/build
+ :let [{:flds [text]} tc]
+ (m/AlertDialog .content (m/Text text)))) nil)
+ .tooltip "Show me the value!"
+ .child (m/Icon m/Icons.text_fields)))))
+
+(defn main []
+ (f/run
+ (m/MaterialApp .title "Retrieve text input")
+ .home (form-retrieve-input-widget)))
diff --git a/samples/form_validate/src/sample/form_validate.cljd b/samples/form_validate/src/sample/form_validate.cljd
new file mode 100644
index 00000000..becdbb33
--- /dev/null
+++ b/samples/form_validate/src/sample/form_validate.cljd
@@ -0,0 +1,32 @@
+(ns sample.form-validate
+ "Faithful port of https://docs.flutter.dev/cookbook/forms/validation"
+ (:require ["package:flutter/material.dart" :as m]
+ [cljd.flutter :as f]
+ [cljd.string :as str]))
+
+(defn form-validate-widget
+ "Reusable form validation widget that can be embedded in any app."
+ []
+ (let [form-key (#/(m/GlobalKey m/FormState))]
+ (f/widget
+ :get [m/ScaffoldMessenger]
+ (m/Scaffold .appBar (m/AppBar .title (m/Text "Form Validation")))
+ .body
+ (m/Form .key form-key)
+ (m/Padding .padding (m/EdgeInsets.all 16))
+ (m/Column .crossAxisAlignment m/CrossAxisAlignment.start)
+ .children
+ [(m/TextFormField
+ .decoration (m/InputDecoration .labelText "Enter some text")
+ .validator (fn [value] (when (str/blank? value) "Please enter some text")))
+ (m/SizedBox .height 16)
+ (m/ElevatedButton
+ .onPressed #(when (.validate (.-currentState form-key))
+ (.showSnackBar scaffold-messenger (m/SnackBar .content (m/Text "Processing Data")))
+ nil)
+ .child (m/Text "Submit"))])))
+
+(defn main []
+ (f/run
+ (m/MaterialApp .title "Form Validation Demo")
+ .home (form-validate-widget)))
diff --git a/samples/gridlist/src/sample/gridlist.cljd b/samples/gridlist/src/sample/gridlist.cljd
index 703c2340..1e0991aa 100644
--- a/samples/gridlist/src/sample/gridlist.cljd
+++ b/samples/gridlist/src/sample/gridlist.cljd
@@ -4,16 +4,19 @@
["package:flutter/material.dart" :as m]
[cljd.flutter :as f]))
+(def gridlist-widget
+ "Reusable grid list widget that can be embedded in any app."
+ (f/widget
+ (m/Scaffold .appBar (m/AppBar .title (m/Text "Grid List")))
+ .body
+ :get {{{:flds [headlineMedium]} .-textTheme} m/Theme}
+ (m/GridView.count .crossAxisCount 2)
+ .children
+ (for [i (range 100)]
+ (m/Center .child (m/Text (str "Item " i) .style headlineMedium)))))
+
(defn main []
- (let [title "Grid List"]
- (m/runApp
- (f/widget
- (m/MaterialApp .title title)
- .home
- (m/Scaffold .appBar (m/AppBar .title (m/Text title)))
- .body
- :get {{{:flds [headline3]} .-textTheme} m/Theme}
- (m/GridView.count .crossAxisCount 2)
- .children
- (for [i (range 100)]
- (m/Center .child (m/Text (str "Item " i) .style headline3)))))))
+ (m/runApp
+ (f/widget
+ (m/MaterialApp .title "Grid List")
+ .home gridlist-widget)))
diff --git a/samples/keep_alive/src/sample/keep_alive.cljd b/samples/keep_alive/src/sample/keep_alive.cljd
index 65598089..028dba17 100644
--- a/samples/keep_alive/src/sample/keep_alive.cljd
+++ b/samples/keep_alive/src/sample/keep_alive.cljd
@@ -2,36 +2,34 @@
(:require ["package:flutter/material.dart" :as m]
[cljd.flutter :as f]))
+(def keep-alive-widget
+ "Reusable keep-alive demo widget that can be embedded in any app."
+ (m/Scaffold
+ .appBar (m/AppBar .title (m/Text "Keep Alive Demo"))
+ .body
+ (m/ListView.builder
+ .itemCount 100
+ .itemBuilder
+ (f/build [idx]
+ :key idx
+ :height 80
+ :let [kept-alive (odd? idx)]
+ :color (cond
+ (zero? idx) m/Colors.white
+ kept-alive m/Colors.red.shade200
+ :else m/Colors.blue.shade200)
+ :keep-alive kept-alive
+ :let [now (DateTime.now)]
+ m/Center
+ (m/Text
+ (if (zero? idx)
+ "Scroll back and forth to see kept-alive vs ephemeral items"
+ (print-str idx (if kept-alive "KEPT ALIVE" "EPHEMERAL")
+ " at: " (.-hour now) "h" (.-minute now) "m" (.-second now) "s"))
+ .style (m/TextStyle .fontSize 16 .fontWeight m/FontWeight.w700)
+ .textAlign m/TextAlign.center)))))
+
(defn main []
(f/run
- m/MaterialApp
- .home
- m/Scaffold
- .body
- (m/ListView.builder
- .itemCount 100
- .itemBuilder
- (f/build [idx]
- :key idx
- :height 80
- :let [kept-alive (odd? idx)]
- :color (cond
- (zero? idx) m/Colors.white
- kept-alive m/Colors.red.shade200
- :else m/Colors.blue.shade200)
- :keep-alive kept-alive
- :let [now (DateTime.now)]
- m/Center
- (m/Text
- (if (zero? idx)
- "Scroll back and forth and you should see ephemeral items being rebuilt while kept-alive items are reused."
- (print-str idx
- (if kept-alive
- "KEPT ALIVE"
- "EPHEMERAL")
- " rebuilt at: "
- (.-hour now) "h"
- (.-minute now) "m"
- (.-second now) "s"))
- .style (m/TextStyle .fontSize 18 .fontWeight m/FontWeight.w700)
- .textAlign m/TextAlign.center)))))
+ m/MaterialApp
+ .home keep-alive-widget))
diff --git a/samples/scoped_watch/src/sample/scoped_watch.cljd b/samples/scoped_watch/src/sample/scoped_watch.cljd
index b0f6e6d7..b06dccd1 100644
--- a/samples/scoped_watch/src/sample/scoped_watch.cljd
+++ b/samples/scoped_watch/src/sample/scoped_watch.cljd
@@ -4,36 +4,37 @@
["package:flutter/material.dart" :as m]
[cljd.flutter :as f]))
-(defn main []
+(defn scoped-watch-widget
+ "Reusable scoped watch demo widget that can be embedded in any app."
+ []
(let [flip (atom false)]
- (f/run
- (m/MaterialApp
- .title "Cljd Demo"
- .theme (m/ThemeData .primarySwatch m/Colors.blue))
- .home
- (m/Scaffold
- .appBar (m/AppBar .title (m/Text "ClojureDart Home Page"))
- .floatingActionButton
- (f/widget
- (m/FloatingActionButton
- .onPressed #(swap! flip not)
- .tooltip "Flip color")
- (m/Icon m/Icons.add)))
- .body
- m/Row
- .children
- [(f/widget
- m/Expanded
- :watch [is-flipped flip]
- :color (if is-flipped m/Colors.blue m/Colors.green)
- m/Center
- :let [_ (prn 'flat)]
- (m/Text "I shouldn't rebuild when flip changes"))
+ (f/widget
+ (m/Scaffold
+ .appBar (m/AppBar .title (m/Text "Scoped Watch Demo"))
+ .floatingActionButton
+ (f/widget
+ (m/FloatingActionButton .onPressed #(swap! flip not) .tooltip "Flip color")
+ (m/Icon m/Icons.swap_horiz)))
+ .body
+ m/Row
+ .children
+ [(f/widget
+ m/Expanded
+ :watch [is-flipped flip]
+ :color (if is-flipped m/Colors.blue m/Colors.green)
+ m/Center
+ (m/Text "Flat watch\n(rebuilds container)" .textAlign m/TextAlign.center))
+ (f/widget
+ m/Expanded
(f/widget
- m/Expanded
- (f/widget
- :watch [is-flipped flip]
- :color (if is-flipped m/Colors.blue m/Colors.green))
- m/Center
- :let [_ (prn 'scoped)]
- (m/Text "I shouldn't rebuild when flip changes"))])))
+ :watch [is-flipped flip]
+ :color (if is-flipped m/Colors.blue m/Colors.green))
+ m/Center
+ (m/Text "Scoped watch\n(only color rebuilds)" .textAlign m/TextAlign.center))])))
+
+(defn main []
+ (f/run
+ (m/MaterialApp
+ .title "Scoped Watch Demo"
+ .theme (m/ThemeData .primarySwatch m/Colors.blue))
+ .home (scoped-watch-widget)))
diff --git a/samples/tabs/src/sample/tabs.cljd b/samples/tabs/src/sample/tabs.cljd
index 4305bd4f..a69a8037 100644
--- a/samples/tabs/src/sample/tabs.cljd
+++ b/samples/tabs/src/sample/tabs.cljd
@@ -4,23 +4,26 @@
["package:flutter/material.dart" :as m]
[cljd.flutter :as f]))
-(defn main
- []
- (let [title "Tabs Demo"]
- (f/run
- (m/MaterialApp .title title)
- .home
- (m/DefaultTabController .length 3)
- (m/Scaffold
- .appBar
- (m/AppBar
- .bottom
- (m/TabBar
- .tabs [(m/Tab .icon (m/Icon. m/Icons.directions_car)),
- (m/Tab .icon (m/Icon. m/Icons.directions_transit)),
- (m/Tab .icon (m/Icon. m/Icons.directions_bike))])))
- .body
- (m/TabBarView
- .children [(m/Tab .icon (m/Icon. m/Icons.directions_car)),
- (m/Tab .icon (m/Icon. m/Icons.directions_transit)),
- (m/Tab .icon (m/Icon. m/Icons.directions_bike))]))))
+(def tabs-widget
+ "Reusable tabs widget that can be embedded in any app."
+ (f/widget
+ (m/DefaultTabController .length 3)
+ (m/Scaffold
+ .appBar
+ (m/AppBar
+ .title (m/Text "Tabs Demo")
+ .bottom
+ (m/TabBar
+ .tabs [(m/Tab .icon (m/Icon. m/Icons.directions_car))
+ (m/Tab .icon (m/Icon. m/Icons.directions_transit))
+ (m/Tab .icon (m/Icon. m/Icons.directions_bike))])))
+ .body
+ (m/TabBarView
+ .children [(m/Center .child (m/Icon m/Icons.directions_car .size 64))
+ (m/Center .child (m/Icon m/Icons.directions_transit .size 64))
+ (m/Center .child (m/Icon m/Icons.directions_bike .size 64))])))
+
+(defn main []
+ (f/run
+ (m/MaterialApp .title "Tabs Demo")
+ .home tabs-widget))
diff --git a/samples/widgetbook/README.md b/samples/widgetbook/README.md
new file mode 100644
index 00000000..afd8da50
--- /dev/null
+++ b/samples/widgetbook/README.md
@@ -0,0 +1,27 @@
+# ClojureDart Widgetbook
+
+A [Widgetbook](https://www.widgetbook.io/) catalog showcasing all ClojureDart sample widgets in one place.
+
+## Running
+
+```bash
+cd samples/widgetbook
+clj -M:cljd init
+clj -M:cljd flutter
+```
+
+## What's Included
+
+This widgetbook imports widgets from all the sample projects:
+
+- **Animations**: AnimatedContainer, AnimatedString, FadeWidget, PhysicsSimulation, HeroAnimations
+- **Basic Widgets**: Counter, TwoCounters, Snackbar, WidgetTest
+- **Navigation**: Tabs, BottomNavigationBar, Drawer, BasicNavigation, NamedRoutes
+- **Data Display**: DataTable, GridList, FetchData
+- **Forms**: FormValidation, FormRetrieveInput, FormHandleChange
+- **Gestures & Custom Paint**: GestureDetector
+- **State Management**: ScopedWatch, KeepAlive
+- **Advanced Components**: ExpandableFAB, Shopper, Isolates
+- **Streams**: FizzBuzz
+- **Media**: VideoPlayer
+- **Tutorial Apps**: FirstFlutterApp
\ No newline at end of file
diff --git a/samples/widgetbook/analysis_options.yaml b/samples/widgetbook/analysis_options.yaml
new file mode 100644
index 00000000..0d290213
--- /dev/null
+++ b/samples/widgetbook/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/samples/widgetbook/deps.edn b/samples/widgetbook/deps.edn
new file mode 100644
index 00000000..b11e9bfe
--- /dev/null
+++ b/samples/widgetbook/deps.edn
@@ -0,0 +1,36 @@
+{:paths ["src"
+ ;; All sample sources
+ "../animated_container/src"
+ "../animated_string/src"
+ "../bottom_navigation_bar/src"
+ "../counter/src"
+ "../datatable/src"
+ "../drawer/src"
+ "../drawer_navigate_named/src"
+ "../fab/src"
+ "../fade_widget/src"
+ "../fetch-data/src"
+ "../first_flutter_app_codelabs/src"
+ "../fizzbuzz/src"
+ "../form_validate/src"
+ "../form_retrieve_input/src"
+ "../form_handle_change_textfield/src"
+ "../gesture_detector/src"
+ "../gridlist/src"
+ "../hero_animations/src"
+ "../isolates/src"
+ "../keep_alive/src"
+ "../navigate_named_routes/src"
+ "../navigation/src"
+ "../physics_simulation/src"
+ "../scoped_watch/src"
+ "../shopper/src"
+ "../snackbar/src"
+ "../tabs/src"
+ "../twocounters/src"
+ "../video_player/src"
+ "../widget_tests/src"]
+ :deps {tensegritics/clojuredart {:local/root "../../"}}
+ :aliases {:cljd {:main-opts ["-m" "cljd.build"]}}
+ :cljd/opts {:main sample.widgetbook
+ :kind :flutter}}
diff --git a/samples/widgetbook/lib/main.dart b/samples/widgetbook/lib/main.dart
new file mode 100644
index 00000000..8d4884a4
--- /dev/null
+++ b/samples/widgetbook/lib/main.dart
@@ -0,0 +1 @@
+export "cljd-out/sample/widgetbook.dart" show main;
diff --git a/samples/widgetbook/macos/Runner/DebugProfile.entitlements b/samples/widgetbook/macos/Runner/DebugProfile.entitlements
new file mode 100644
index 00000000..3ba6c126
--- /dev/null
+++ b/samples/widgetbook/macos/Runner/DebugProfile.entitlements
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.network.client
+
+ com.apple.security.network.server
+
+
+
diff --git a/samples/widgetbook/macos/Runner/Release.entitlements b/samples/widgetbook/macos/Runner/Release.entitlements
new file mode 100644
index 00000000..ee95ab7e
--- /dev/null
+++ b/samples/widgetbook/macos/Runner/Release.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.network.client
+
+
+
diff --git a/samples/widgetbook/pubspec.yaml b/samples/widgetbook/pubspec.yaml
new file mode 100644
index 00000000..687f6329
--- /dev/null
+++ b/samples/widgetbook/pubspec.yaml
@@ -0,0 +1,93 @@
+name: cljd_widgetbook
+description: "A new Flutter project."
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+# In Windows, build-name is used as the major, minor, and patch parts
+# of the product and file versions while build-number is used as the build suffix.
+version: 1.0.0+1
+
+environment:
+ sdk: ^3.8.1
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+ flutter:
+ sdk: flutter
+
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^1.0.8
+ widgetbook: ^3.20.2
+ http: ^1.2.0
+ english_words: ^4.0.0
+ video_player: ^2.8.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+ # The "flutter_lints" package below contains a set of recommended lints to
+ # encourage good coding practices. The lint set provided by the package is
+ # activated in the `analysis_options.yaml` file located at the root of your
+ # package. See that file for information about deactivating specific lint
+ # rules and activating additional ones.
+ flutter_lints: ^5.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
+
+ # To add assets to your application, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/to/resolution-aware-images
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/to/asset-from-package
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/to/font-from-package
diff --git a/samples/widgetbook/src/sample/widgetbook.cljd b/samples/widgetbook/src/sample/widgetbook.cljd
new file mode 100644
index 00000000..44177386
--- /dev/null
+++ b/samples/widgetbook/src/sample/widgetbook.cljd
@@ -0,0 +1,227 @@
+(ns sample.widgetbook
+ "Widgetbook catalog importing all ClojureDart sample widgets"
+ (:require
+ ["package:flutter/material.dart" :as m]
+ ["package:widgetbook/widgetbook.dart" :as wb]
+ [cljd.flutter :as f]
+ [sample.animated-container :as animated-container]
+ [sample.animated-string :as animated-string]
+ [sample.bottom-navigation-bar :as bottom-nav]
+ [sample.counter :as counter]
+ [sample.datatable :as datatable]
+ [sample.drawer :as drawer]
+ [sample.fab :as fab]
+ [sample.fade-widget :as fade-widget]
+ [sample.fetch-data :as fetch-data]
+ [sample.first-flutter-app-codelabs :as codelabs]
+ [sample.fizzbuzz :as fizzbuzz]
+ [sample.form-validate :as form-validate]
+ [sample.form-retrieve-input :as form-retrieve]
+ [sample.form-handle-change :as form-handle]
+ [sample.gesture-detector :as gesture]
+ [sample.gridlist :as gridlist]
+ [sample.hero-animations :as hero]
+ [sample.isolates :as isolates]
+ [sample.keep-alive :as keep-alive]
+ [sample.navigate-named-routes :as named-routes]
+ [sample.navigation :as navigation]
+ [sample.physics-sim :as physics]
+ [sample.scoped-watch :as scoped-watch]
+ [sample.shopper :as shopper]
+ [sample.snackbar :as snackbar]
+ [sample.tabs :as tabs]
+ [sample.two-counters :as two-counters]
+ [sample.video-player :as video-player]
+ [samples.widget :as widget-test]))
+
+;; ============================================================================
+;; Generic Widgetbook Component Helper
+;; ============================================================================
+
+(defn make-component
+ "Creates a WidgetbookComponent from a map with :name and :use-cases.
+ Each use-case is a map with :name and :widget (or :builder fn)."
+ [{:keys [name use-cases]}]
+ (wb/WidgetbookComponent
+ .name name
+ .useCases (mapv (fn [{uc-name :name widget :widget builder :builder}]
+ (wb/WidgetbookUseCase
+ .name uc-name
+ .builder (or builder (fn [_] widget))))
+ use-cases)))
+
+(defn make-category
+ "Creates a WidgetbookCategory from a map with :name and :components."
+ [{:keys [name components]}]
+ (wb/WidgetbookCategory
+ .name name
+ .children (mapv make-component components)))
+
+;; ============================================================================
+;; Component Definitions (Data-driven)
+;; ============================================================================
+
+(def widget-catalog
+ [{:name "Animations"
+ :components
+ [{:name "AnimatedContainer"
+ :use-cases [{:name "Default"
+ :widget animated-container/animated-container}]}
+ {:name "AnimatedString"
+ :use-cases [{:name "Default"
+ :widget animated-string/animated-text}]}
+ {:name "FadeWidget"
+ :use-cases [{:name "Default"
+ :builder (fn [_] (fade-widget/my-home-page "Opacity Demo"))}]}
+ {:name "PhysicsSimulation"
+ :use-cases [{:name "DraggableCard"
+ :widget physics/physics-card-demo}]}
+ {:name "HeroAnimations"
+ :use-cases [{:name "Default"
+ :widget hero/main-screen}]}]}
+
+ {:name "Basic Widgets"
+ :components
+ [{:name "Counter"
+ :use-cases [{:name "Default"
+ :builder (fn [_] (counter/counter-widget))}]}
+ {:name "TwoCounters"
+ :use-cases [{:name "Default"
+ :builder (fn [_]
+ (let [st {:left 0
+ :right 0}]
+ (f/widget
+ :bind {:counters (atom st)}
+ two-counters/home)))}]}
+ {:name "Snackbar"
+ :use-cases [{:name "Default"
+ :builder (fn [_]
+ (m/Scaffold
+ .appBar (m/AppBar
+ .title (m/Text "Snackbar Demo"))
+ .body snackbar/snackbar-demo))}]}
+ {:name "WidgetTest"
+ :use-cases [{:name "Default"
+ :builder (fn [_] (widget-test/my-widget "Widget Test" "Hello!"))}]}]}
+ {:name "Navigation"
+ :components
+ [{:name "Tabs"
+ :use-cases [{:name "Default"
+ :widget tabs/tabs-widget}]}
+ {:name "BottomNavigationBar"
+ :use-cases [{:name "Default"
+ :builder (fn [_]
+ (bottom-nav/bottom-nav-widget))}]}
+ {:name "Drawer"
+ :use-cases [{:name "Default"
+ :builder (fn [_]
+ (drawer/my-home-page "Drawer Demo"))}]}
+ {:name "BasicNavigation"
+ :use-cases [{:name "Default"
+ :widget navigation/first-route}]}
+ {:name "NamedRoutes"
+ :use-cases [{:name "FirstScreen"
+ :builder (fn [ctx]
+ (named-routes/first-screen ctx))}
+ {:name "SecondScreen"
+ :builder (fn [ctx]
+ (named-routes/second-screen ctx))}]}]}
+ {:name "Data Display"
+ :components
+ [{:name "DataTable"
+ :use-cases [{:name "Default"
+ :widget datatable/demo}]}
+ {:name "GridList"
+ :use-cases [{:name "Default"
+ :widget gridlist/gridlist-widget}]}
+ {:name "FetchData"
+ :use-cases [{:name "Default"
+ :widget fetch-data/fetch-data-widget}]}]}
+ {:name "Forms"
+ :components
+ [{:name "FormValidation"
+ :use-cases [{:name "Default"
+ :builder (fn [_]
+ (form-validate/form-validate-widget))}]}
+ {:name "FormRetrieveInput"
+ :use-cases [{:name "Default"
+ :builder (fn [_]
+ (form-retrieve/form-retrieve-input-widget))}]}
+ {:name "FormHandleChange"
+ :use-cases [{:name "Default"
+ :widget form-handle/form-handle-change-widget}]}]}
+
+ {:name "Gestures & Custom Paint"
+ :components
+ [{:name "GestureDetector"
+ :use-cases [{:name "Parallelogram"
+ :builder (fn [_]
+ (m/Scaffold
+ .appBar (m/AppBar .title (m/Text "Gesture Detector"))
+ .body gesture/gesture-parallelogram))}]}]}
+
+ {:name "State Management"
+ :components
+ [{:name "ScopedWatch"
+ :use-cases [{:name "Default"
+ :builder (fn [_]
+ (scoped-watch/scoped-watch-widget))}]}
+ {:name "KeepAlive"
+ :use-cases [{:name "Default"
+ :widget keep-alive/keep-alive-widget}]}]}
+
+ {:name "Advanced Components"
+ :components
+ [{:name "ExpandableFAB"
+ :use-cases [{:name "Default"
+ :widget fab/example-expandable-fab}]}
+ {:name "Shopper"
+ :use-cases [{:name "Catalog"
+ :widget shopper/catalog}
+ {:name "Cart"
+ :widget shopper/my-cart}]}
+ {:name "Isolates"
+ :use-cases [{:name "Default"
+ :builder (fn [_]
+ (m/Scaffold
+ .appBar (m/AppBar .title (m/Text "Isolates Demo"))
+ .body isolates/isolate-ui))}]}]}
+
+ {:name "Streams"
+ :components
+ [{:name "FizzBuzz"
+ :use-cases [{:name "Default"
+ :widget fizzbuzz/fizzbuzz-widget}]}]}
+
+ {:name "Media"
+ :components
+ [{:name "VideoPlayer"
+ :use-cases [{:name "Default"
+ :widget video-player/video-player-widget}]}]}
+
+ {:name "Tutorial Apps"
+ :components
+ [{:name "FirstFlutterApp"
+ :use-cases [{:name "GeneratorPage"
+ :widget codelabs/generator-page}
+ {:name "FavoritesPage"
+ :widget codelabs/favorites-page}]}]}])
+
+;; ============================================================================
+;; Widgetbook Main
+;; ============================================================================
+(defn main []
+ (m/runApp
+ (wb/Widgetbook.material
+ .directories (mapv make-category widget-catalog)
+ .addons
+ [(wb/MaterialThemeAddon
+ .themes
+ [(#/(wb/WidgetbookTheme m/ThemeData) .name "Light" .data (m/ThemeData.light))
+ (#/(wb/WidgetbookTheme m/ThemeData) .name "Dark" .data (m/ThemeData.dark))
+ (#/(wb/WidgetbookTheme m/ThemeData) .name "Shopper" .data shopper/theme)])
+ (wb/DeviceFrameAddon
+ .devices
+ [wb/Devices.ios.iPhone13
+ wb/Devices.ios.iPad
+ wb/Devices.android.samsungGalaxyS20])])))
diff --git a/samples/widgetbook/test/widget_test.dart b/samples/widgetbook/test/widget_test.dart
new file mode 100644
index 00000000..3b6f93a2
--- /dev/null
+++ b/samples/widgetbook/test/widget_test.dart
@@ -0,0 +1,30 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility in the flutter_test package. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:cljd_widgetbook/main.dart';
+
+void main() {
+ testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+
+ // Verify that our counter starts at 0.
+ expect(find.text('0'), findsOneWidget);
+ expect(find.text('1'), findsNothing);
+
+ // Tap the '+' icon and trigger a frame.
+ await tester.tap(find.byIcon(Icons.add));
+ await tester.pump();
+
+ // Verify that our counter has incremented.
+ expect(find.text('0'), findsNothing);
+ expect(find.text('1'), findsOneWidget);
+ });
+}