File size: 83,144 Bytes
d467ea7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_franken.ipynb.

# %% auto 0
__all__ = ['franken_class_map', 'TextT', 'TextPresets', 'CodeSpan', 'CodeBlock', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Subtitle',
           'Q', 'Em', 'Strong', 'I', 'Small', 'Mark', 'Del', 'Ins', 'Sub', 'Sup', 'Blockquote', 'Caption', 'Cite',
           'Time', 'Address', 'Abbr', 'Dfn', 'Kbd', 'Samp', 'Var', 'Figure', 'Details', 'Summary', 'Data', 'Meter', 'S',
           'U', 'Output', 'PicSumImg', 'AccordionItem', 'Accordion', 'ButtonT', 'Button', 'ContainerT', 'BackgroundT',
           'Container', 'Titled', 'DividerT', 'Divider', 'DividerSplit', 'DividerLine', 'Article', 'ArticleTitle',
           'ArticleMeta', 'SectionT', 'Section', 'Form', 'Fieldset', 'Legend', 'Input', 'Radio', 'CheckboxX', 'Range',
           'TextArea', 'Switch', 'Upload', 'UploadZone', 'FormLabel', 'LabelT', 'Label', 'UkFormSection',
           'GenericLabelInput', 'LabelInput', 'LabelTextArea', 'LabelSwitch', 'LabelRadio', 'LabelCheckboxX', 'Options',
           'Select', 'LabelSelect', 'LabelRange', 'AT', 'ListT', 'ModalContainer', 'ModalDialog', 'ModalHeader',
           'ModalBody', 'ModalFooter', 'ModalTitle', 'ModalCloseButton', 'Modal', 'Placeholder', 'Progress', 'UkIcon',
           'UkIconLink', 'DiceBearAvatar', 'Center', 'FlexT', 'Grid', 'DivFullySpaced', 'DivCentered', 'DivLAligned',
           'DivRAligned', 'DivVStacked', 'DivHStacked', 'NavT', 'NavContainer', 'NavParentLi', 'NavDividerLi',
           'NavHeaderLi', 'NavSubtitle', 'NavCloseLi', 'ScrollspyT', 'NavBar', 'SliderContainer', 'SliderItems',
           'SliderNav', 'Slider', 'DropDownNavContainer', 'TabContainer', 'CardT', 'CardTitle', 'CardHeader',
           'CardBody', 'CardFooter', 'CardContainer', 'Card', 'TableT', 'Table', 'Td', 'Th', 'Tbody', 'TableFromLists',
           'TableFromDicts', 'apply_classes', 'FrankenRenderer', 'render_md', 'ThemePicker', 'LightboxContainer',
           'LightboxItem', 'ApexChart']

# %% ../nbs/02_franken.ipynb
import fasthtml.common as fh
from .foundations import *
from fasthtml.common import Div, P, Span, FT
from enum import Enum, auto
from fasthtml.components import Uk_select,Uk_input_tag,Uk_icon,Uk_input_range, Uk_chart
from functools import partial
from itertools import zip_longest
from typing import Union, Tuple, Optional, Sequence
from fastcore.all import *
import copy, re, httpx, os
import pathlib
from mistletoe.html_renderer import HTMLRenderer
from mistletoe.span_token import Image
import mistletoe
from lxml import html, etree
import fasthtml.components as fh_comp
import json

# %% ../nbs/02_franken.ipynb
class TextT(VEnum):
    'Text Styles from https://franken-ui.dev/docs/text'
    def _generate_next_value_(name, start, count, last_values):
        return str2ukcls('text', name)
    
    paragraph = "uk-paragraph"
    # Text Style
    lead,meta, gray, italic= auto(), auto(), 'text-gray-500 dark:text-gray-200', 'italic'
    # Text Size
    xs, sm, lg, xl = 'text-xs', 'text-sm', 'text-lg', 'text-xl'
    # Text Weight
    light, normal, medium, bold, extrabold = 'font-light','font-normal','font-medium','font-bold','font-extrabold'
    # Text Color
    muted,primary,secondary = 'text-gray-500 dark:text-gray-200', 'text-primary', 'text-secondary'
    success,warning, error, info =  'text-success', 'text-warning', 'text-error', 'text-info'
    # Text Alignment
    left, right,center = "text-left","text-right","text-center"
    justify, start, end = "text-justify","text-start","text-end"
    # Vertical Alignment
    top,middle,bottom = 'align-top','align-middle','align-bottom'
    # Text Wrapping
    truncate,break_,nowrap = 'uk-text-truncate','uk-text-break', 'uk-text-nowrap' 
    # other
    underline = 'underline'
    highlight = 'bg-yellow-200 dark:bg-yellow-800 text-black'
class TextPresets(VEnum):
    'Common Typography Presets'
    muted_sm = TextT.muted+TextT.sm
    muted_lg = TextT.muted+TextT.lg
    
    bold_sm = TextT.bold+TextT.sm
    bold_lg = TextT.bold+TextT.lg
    
    md_weight_sm = stringify((TextT.sm, TextT.medium))
    md_weight_muted = stringify((TextT.medium, TextT.muted))

# %% ../nbs/02_franken.ipynb
def CodeSpan(*c, # Contents of CodeSpan tag (inline text code snippets)
             cls=(), # Classes in addition to CodeSpan styling
             **kwargs # Additional args for CodeSpan tag
             )->FT: # Code(..., cls='uk-codespan')
    "A CodeSpan with Styling"
    return fh.Code(*c, cls=('uk-codespan', stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def CodeBlock(*c: str, # Contents of Code tag (often text)
              cls: Enum | str | tuple = (), # Classes for the outer container
              code_cls: Enum | str | tuple = (), # Classes for the code tag
              **kwargs # Additional args for Code tag
              ) -> FT: # Div(Pre(Code(..., cls='uk-codeblock), cls='multiple tailwind styles'), cls='uk-block')
    "CodeBlock with Styling"
    return Div(
        Pre(Code(*c, cls=('uk-codeblock', stringify(code_cls)), **kwargs),
            cls=(f'bg-gray-100 dark:bg-gray-800 {TextT.gray} p-0.4 rounded text-sm font-mono')),
#             cls=('bg-gray-100 dark:bg-gray-800 dark:text-gray-200 p-0.4 rounded text-sm font-mono')),
        cls=('uk-block', stringify(cls)))

# %% ../nbs/02_franken.ipynb
def H1(*c:FT|str, # Contents of H1 tag (often text)
       cls:Enum|str|tuple=(), # Classes in addition to H1 styling
       **kwargs # Additional args for H1 tag
       )->FT: # H1(..., cls='uk-h1')
    "H1 with styling and appropriate size"
    return fh.H1(*c, cls=('uk-h1',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def H2(*c:FT|str, # Contents of H2 tag (often text)
       cls:Enum|str|tuple=(), # Classes in addition to H2 styling
       **kwargs # Additional args for H2 tag
       )->FT: # H2(..., cls='uk-h2')
    "H2 with styling and appropriate size"
    return fh.H2(*c, cls=('uk-h2',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def H3(*c:FT|str, # Contents of H3 tag (often text)
       cls:Enum|str|tuple=(), # Classes in addition to H3 styling
       **kwargs # Additional args for H3 tag
       )->FT: # H3(..., cls='uk-h3')
    "H3 with styling and appropriate size"
    return fh.H3(*c, cls=('uk-h3',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def H4(*c:FT|str, # Contents of H4 tag (often text)
       cls:Enum|str|tuple=(), # Classes in addition to H4 styling
       **kwargs # Additional args for H4 tag
       )->FT: # H4(..., cls='uk-h4')
    "H4 with styling and appropriate size"
    return fh.H4(*c, cls=('uk-h4',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def H5(*c:FT|str, # Contents of H5 tag (often text)
       cls:Enum|str|tuple=(), # Classes in addition to H5 styling
       **kwargs # Additional args for H5 tag
       )->FT: # H5(..., cls='text-lg font-semibold')
    "H5 with styling and appropriate size"
    return fh.H5(*c, cls=('text-lg font-semibold',stringify(cls)), **kwargs)

def H6(*c:FT|str, # Contents of H6 tag (often text)
       cls:Enum|str|tuple=(), # Classes in addition to H6 styling
       **kwargs # Additional args for H6 tag
       )->FT: # H6(..., cls='text-base font-semibold')
    "H6 with styling and appropriate size"
    return fh.H6(*c, cls=('text-base font-semibold',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Subtitle(*c:FT|str, # Contents of P tag (often text)
         cls:Enum|str|tuple='mt-1.5', # Additional classes
         **kwargs # Additional args for P tag
         )->FT:
    "Styled muted_sm text designed to go under Headings and Titles"
    return fh.P(*c, cls=(TextPresets.muted_sm, stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Q(*c:FT|str, # Contents of Q tag (quote)
      cls:Enum|str|tuple=(), # Additional classes
      **kwargs # Additional args for Q tag
      )->FT:
    "Styled quotation mark"
    return fh.Q(*c, cls=(TextT.italic,TextT.lg, stringify(cls)), **kwargs)

def Em(*c:FT|str, # Contents of Em tag (emphasis)
       cls:Enum|str|tuple=(), # Additional classes 
       **kwargs # Additional args for Em tag
       )->FT:
    "Styled emphasis text"
    return fh.Em(*c, cls=(TextT.medium, stringify(cls)), **kwargs)

def Strong(*c:FT|str, # Contents of Strong tag
          cls:Enum|str|tuple=(), # Additional classes
          **kwargs # Additional args for Strong tag
          )->FT:
    "Styled strong text" 
    return fh.Strong(*c, cls=(TextT.bold, stringify(cls)), **kwargs)

def I(*c:FT|str, # Contents of I tag (italics)
      cls:Enum|str|tuple=(), # Additional classes
      **kwargs # Additional args for I tag
      )->FT:
    "Styled italic text"
    return fh.I(*c, cls=(TextT.italic, stringify(cls)), **kwargs)

def Small(*c:FT|str, # Contents of Small tag
         cls:Enum|str|tuple=(), # Additional classes
         **kwargs # Additional args for Small tag
         )->FT:
    "Styled small text"
    return fh.Small(*c, cls=(TextT.sm, stringify(cls)), **kwargs)

def Mark(*c:FT|str, # Contents of Mark tag (highlighted text)
        cls:Enum|str|tuple=(), # Additional classes
        **kwargs # Additional args for Mark tag
        )->FT:
    "Styled highlighted text"
    return fh.Mark(*c, cls=(TextT.highlight, stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Del(*c:FT|str, # Contents of Del tag (deleted text)
       cls:Enum|str|tuple=(), # Additional classes
       **kwargs # Additional args for Del tag
       )->FT:
    "Styled deleted text"
    return fh.Del(*c, cls=('line-through', TextT.gray, stringify(cls)), **kwargs)

def Ins(*c:FT|str, # Contents of Ins tag (inserted text)
        cls:Enum|str|tuple=(), # Additional classes
        **kwargs # Additional args for Ins tag
        )->FT:
    "Styled inserted text"
    return fh.Ins(*c, cls=(TextT.underline+' text-green-600', stringify(cls)), **kwargs)

def Sub(*c:FT|str, # Contents of Sub tag (subscript)
       cls:Enum|str|tuple=(), # Additional classes
       **kwargs # Additional args for Sub tag
       )->FT:
    "Styled subscript text"
    return fh.Sub(*c, cls=(TextT.sm+' -bottom-1 relative', stringify(cls)), **kwargs)

def Sup(*c:FT|str, # Contents of Sup tag (superscript) 
        cls:Enum|str|tuple=(), # Additional classes
        **kwargs # Additional args for Sup tag
        )->FT:
    "Styled superscript text"
    return fh.Sup(*c, cls=(TextT.sm+' -top-1 relative', stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Blockquote(*c:FT|str, # Contents of Blockquote tag (often text)
               cls:Enum|str|tuple=(), # Classes in addition to Blockquote styling
               **kwargs # Additional args for Blockquote tag
               )->FT: # Blockquote(..., cls='uk-blockquote')
    "Blockquote with Styling"
    return fh.Blockquote(*c, cls=('uk-blockquote',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Caption(*c:FT|str, cls:Enum|str|tuple=(), **kwargs)->FT:
    "Styled caption text"
    return fh.Caption(
        Span(*c, cls=(TextT.gray, TextT.sm, stringify(cls))),
        **kwargs)

# %% ../nbs/02_franken.ipynb
def Cite(*c:FT|str, # Contents of Cite tag
         cls:Enum|str|tuple=(), # Additional classes
         **kwargs # Additional args for Cite tag
         )->FT:
    "Styled citation text"
    return fh.Cite(*c, cls=(TextT.italic, TextT.gray, stringify(cls)), **kwargs)

def Time(*c:FT|str, # Contents of Time tag
         cls:Enum|str|tuple=(), # Additional classes
         datetime:str=None, # datetime attribute
         **kwargs # Additional args for Time tag
         )->FT:
    "Styled time element"
    if datetime: kwargs['datetime'] = datetime
    return fh.Time(*c, cls=(TextT.gray, stringify(cls)), **kwargs)

def Address(*c:FT|str, # Contents of Address tag
           cls:Enum|str|tuple=(), # Additional classes
           **kwargs # Additional args for Address tag
           )->FT:
    "Styled address element"
    return fh.Address(*c, cls=(TextT.italic, stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Abbr(*c:FT|str, # Contents of Abbr tag
         cls:Enum|str|tuple=(), # Additional classes
         title:str=None, # Title attribute for abbreviation
         **kwargs # Additional args for Abbr tag
         )->FT:
    "Styled abbreviation with dotted underline"
    if title: kwargs['title'] = title
    return fh.Abbr(*c, cls=('border-b border-dotted border-secondary hover:cursor-help', stringify(cls)), **kwargs)

def Dfn(*c:FT|str, # Contents of Dfn tag (definition)
        cls:Enum|str|tuple=(), # Additional classes
        **kwargs # Additional args for Dfn tag
        )->FT:
    "Styled definition term with italic and medium weight"
    return fh.Dfn(*c, cls=(TextT.medium + TextT.italic + TextT.gray, stringify(cls)), **kwargs)

def Kbd(*c:FT|str, # Contents of Kbd tag (keyboard input)
        cls:Enum|str|tuple=(), # Additional classes
        **kwargs # Additional args for Kbd tag
        )->FT:
    "Styled keyboard input with subtle background"
    return fh.Kbd(*c, cls=('font-mono px-1.5 py-0.5 text-sm bg-secondary border border-gray-300 dark:border-gray-600 rounded shadow-sm', stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Samp(*c:FT|str, # Contents of Samp tag (sample output)
         cls:Enum|str|tuple=(), # Additional classes
         **kwargs # Additional args for Samp tag
         )->FT:
    "Styled sample output with subtle background"
    return fh.Samp(*c, cls=('font-mono bg-secondary px-1 rounded', TextT.gray, stringify(cls)), **kwargs)

def Var(*c:FT|str, # Contents of Var tag (variable)
        cls:Enum|str|tuple=(), # Additional classes
        **kwargs # Additional args for Var tag
        )->FT:
    "Styled variable with italic monospace"
    return fh.Var(*c, cls=('font-mono',TextT.italic + TextT.gray, stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Figure(*c:FT|str, # Contents of Figure tag
          cls:Enum|str|tuple=(), # Additional classes 
          **kwargs # Additional args for Figure tag
          )->FT:
    "Styled figure container with card-like appearance"
    return fh.Figure(*c, cls=('p-4 my-4 border border-gray-200 dark:border-gray-800 rounded-lg shadow-sm bg-card', stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Details(*c:FT|str, # Contents of Details tag
           cls:Enum|str|tuple=(), # Additional classes
           **kwargs # Additional args for Details tag
           )->FT:
    "Styled details element"
    return fh.Details(*c, cls=('border border-secondary rounded-lg', stringify(cls)), **kwargs)

def Summary(*c:FT|str, # Contents of Summary tag
           cls:Enum|str|tuple=(), # Additional classes
           **kwargs # Additional args for Summary tag
           )->FT:
    "Styled summary element"
    return fh.Summary(*c, cls=(TextT.medium + ' p-3 hover:bg-secondary cursor-pointer', stringify(cls)), **kwargs)

def Data(*c:FT|str, # Contents of Data tag
         value:str=None, # Value attribute
         cls:Enum|str|tuple=(), # Additional classes
         **kwargs # Additional args for Data tag
         )->FT:
    "Styled data element"
    if value: kwargs['value'] = value
    return fh.Data(*c, cls=('font-mono text-sm bg-secondary px-1 rounded', stringify(cls)), **kwargs)

def Meter(*c:FT|str, # Contents of Meter tag
          value:float=None, # Current value
          min:float=None, # Minimum value
          max:float=None, # Maximum value
          cls:Enum|str|tuple=(), # Additional classes
          **kwargs # Additional args for Meter tag
          )->FT:
    "Styled meter element"
    if value is not None: kwargs['value'] = value
    if min is not None: kwargs['min'] = min
    if max is not None: kwargs['max'] = max
    return fh.Meter(*c, cls=('w-full h-2 bg-secondary rounded', stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def S(*c:FT|str, # Contents of S tag (strikethrough)
      cls:Enum|str|tuple=(), # Additional classes
      **kwargs # Additional args for S tag
      )->FT:
    "Styled strikethrough text (different semantic meaning from Del)"
    return fh.S(*c, cls=('line-through', TextT.gray, stringify(cls)), **kwargs)

def U(*c:FT|str, # Contents of U tag (unarticulated annotation)
      cls:Enum|str|tuple=(), # Additional classes
      **kwargs # Additional args for U tag
      )->FT:
    "Styled underline (for proper names in Chinese, proper spelling etc)"
    return fh.U(*c, cls=(TextT.underline, stringify(cls)), **kwargs)

def Output(*c:FT|str, # Contents of Output tag
          form:str=None, # ID of form this output belongs to
          for_:str=None, # IDs of elements this output is for
          cls:Enum|str|tuple=(), # Additional classes
          **kwargs # Additional args for Output tag
          )->FT:
    "Styled output element for form results"
    if form: kwargs['form'] = form
    if for_: kwargs['for'] = for_  # Note: 'for' is reserved in Python
    return fh.Output(*c, cls=('font-mono bg-secondary px-2 py-1 rounded', 
                             stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def PicSumImg(h:int=200,           # Height in pixels
              w:int=200,           # Width in pixels
              id:int=None,        # Optional specific image ID to use
              grayscale:bool=False, # Whether to return grayscale version
              blur:int=None,       # Optional blur amount (1-10)
              **kwargs             # Additional args for Img tag
              )->FT:              # Img tag with picsum image
    "Creates a placeholder image using https://picsum.photos/"
    url = f"https://picsum.photos"
    if id is not None: url = f"{url}/id/{id}"
    url = f"{url}/{w}/{h}"
    if grayscale: url = f"{url}?grayscale"
    if blur is not None: 
        url = f"{url}{'?' if not grayscale else '&'}blur={max(1,min(10,blur))}"
    return fh.Img(src=url, loading="lazy", **kwargs)

# %% ../nbs/02_franken.ipynb
def AccordionItem(title: Union[str, FT], # Content for the accordion item title
                  *c: FT,                # Content to display when the item is open
                  cls: Union[str, Enum, tuple] = (), # Additional classes for the outer `Li` container
                  title_cls: Union[str, Enum, tuple] = ('flex justify-between items-center w-full',), # Additional classes for the title `A` tag
                  content_cls: Union[str, Enum, tuple] = (), # Additional classes for the content `Div`
                  open: bool = False,          # Whether this item should be open by default
                  li_kwargs: Optional[Dict] = None, # Additional attributes for the outer `Li` tag
                  a_kwargs: Optional[Dict] = None,  # Additional attributes for the title `A` tag
                  div_kwargs: Optional[Dict] = None # Additional attributes for the content `Div` tag
                  ) -> FT: # Li(A(title, Span(Icon, Icon)), Div(content))
    "Creates a single item for use within an Accordion component, handling title, content, and open state."
    li_attrs, a_attrs, div_attrs = li_kwargs or {}, a_kwargs or {}, div_kwargs or {}
    li_classes = ['group', stringify(cls)]
    if open: li_classes.append('uk-open')
    final_li_cls = stringify(li_classes)
    combined_title_cls = stringify(('uk-accordion-title', stringify(title_cls)))
    content_classes = stringify(('uk-accordion-content', stringify(content_cls)))
    if 'href' not in a_attrs: a_attrs['href'] = '#'
    icon_container = Span(
        UkIcon("chevron-down", cls="block group-[.uk-open]:hidden h-5 w-5"),
        UkIcon("chevron-up", cls="hidden group-[.uk-open]:block h-5 w-5")
    )
    return fh.Li(
        fh.A(title, icon_container, cls=combined_title_cls, **a_attrs),
        fh.Div(*c, cls=content_classes, **div_attrs),
        cls=final_li_cls,
        **li_attrs
    )

# %% ../nbs/02_franken.ipynb
def Accordion(*c: 'AccordionItem', # One or more `AccordionItem` components
              cls: Union[str, Enum, tuple] = (), # Additional classes for the container (`Ul` or `Div`)
              multiple: Optional[bool] = None,    # Allow multiple items to be open simultaneously (UIkit option)
              collapsible: Optional[bool] = None, # Allow all items to be closed (UIkit option, default True)
              animation: Optional[bool] = None,   # Enable/disable animation (UIkit option, default True)
              duration: Optional[int] = None,     # Animation duration in ms (UIkit option, default 200)
              active: Optional[int] = None,       # Index (0-based) of the item to be open by default (UIkit option)
              transition: Optional[str] = None,   # Animation transition timing function (UIkit option, e.g., 'ease-out')
              tag: str = 'ul',                    # HTML tag for the container ('ul' or 'div')
              **kwargs                            # Additional attributes for the container tag (e.g., id)
              ) -> FT: # Ul(*items...) or Div(*items...)
    """
    Creates a styled Accordion container using accordion component.
    """
    opts = []
    if multiple is not None: opts.append(f"multiple: {str(multiple).lower()}")
    if collapsible is not None: opts.append(f"collapsible: {str(collapsible).lower()}")
    if animation is not None: opts.append(f"animation: {str(animation).lower()}")
    if duration is not None: opts.append(f"duration: {duration}")
    if active is not None: opts.append(f"active: {active}")
    if transition is not None: opts.append(f"transition: {transition}")
    uk_attr_val = "; ".join(opts) if opts else ""
    kwargs['uk-accordion'] = uk_attr_val
    container_tag = fh.Ul if tag.lower() == 'ul' else fh.Div
    final_cls = stringify(cls)
    return container_tag(*c, cls=final_cls, **kwargs)

# %% ../nbs/02_franken.ipynb
class ButtonT(VEnum):
    "Options for styling Buttons"
    def _generate_next_value_(name, start, count, last_values): return str2ukcls('btn', name)
    default, ghost, primary = auto(),auto(),auto()
    secondary, destructive = auto(), auto()
    text, link = auto(), auto()
    xs, sm, lg, xl = auto(), auto(), auto(), auto()
    icon = auto()

# %% ../nbs/02_franken.ipynb
def Button(*c: Union[str, FT], # Contents of `Button` tag (often text)
           cls: Union[str, Enum]=ButtonT.default, # Classes in addition to `Button` styling (use `ButtonT` for built in styles)
           submit=True, # Whether the button should submit a form
           **kwargs # Additional args for `Button` tag
           ) -> FT: # Button(..., cls='uk-btn')
    "Button with Styling (defaults to `submit` for form submission)"
    if 'type' not in kwargs: kwargs['type'] = 'submit' if submit else 'button'
    return fh.Button(*c, cls=('uk-btn', stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
class ContainerT(VEnum):
    'Max width container sizes from https://franken-ui.dev/docs/container'
    def _generate_next_value_(name, start, count, last_values): return str2ukcls('container', name)
    xs = auto()
    sm = auto()
    lg = auto()
    xl = auto()
    expand = auto()

# %% ../nbs/02_franken.ipynb
class BackgroundT(VEnum):
    def _generate_next_value_(name, start, count, last_values): return str2ukcls('background', name)
    muted = auto()
    primary = auto()
    secondary = auto()
    default = auto()

# %% ../nbs/02_franken.ipynb
def Container(*c, # Contents of Container tag (often other FT Components)
              cls=('mt-5', ContainerT.xl), # Classes in addition to Container styling
              **kwargs # Additional args for Container (`Div` tag)
              )->FT: # Container(..., cls='uk-container')
    "Div to be used as a container that often wraps large sections or a page of content"
    return Div(*c, cls=('uk-container',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Titled(title:str="FastHTML app", # Title of the page
           *c, # Contents of the page (often other tags)
           cls=ContainerT.xl, # Classes in addition to Container styling
           **kwargs # Additional args for Container (`Div` tag)
           )->FT: # Title, Main(Container(H1(title), content))
    "Creates a standard page structure for titled page.  Main(Container(title, content))"
    return fh.Title(title), fh.Main(Container(H1(title), *c, cls=cls, **kwargs))

# %% ../nbs/02_franken.ipynb
class DividerT(VEnum):
    "Divider Styles from https://franken-ui.dev/docs/divider"
    def _generate_next_value_(name, start, count, last_values): return str2ukcls('divider', name)
    icon=auto()
    sm=auto()
    vertical=auto()

# %% ../nbs/02_franken.ipynb
def Divider(*c, # contents of Divider tag (often nothing)
            cls=('my-4', DividerT.icon), # Classes in addition to Divider styling
            **kwargs # Additional args for Divider tag
            )->FT: #  Hr(..., cls='uk-divider-icon') or Div(..., cls='uk-divider-vertical')
    "Divider with default styling and margin"
    cls = stringify(cls)
    container = Div if 'uk-divider-vertical' in cls else Hr
    return container(*c, cls=cls, **kwargs)

# %% ../nbs/02_franken.ipynb
def DividerSplit(*c, cls=(), line_cls=(), text_cls=()):
    "Creates a simple horizontal line divider with configurable thickness and vertical spacing"
    cls, line_cls, text_cls = map(stringify,(cls, line_cls, text_cls))
    return Div(cls='relative ' + cls)(
        Div(cls="absolute inset-0 flex items-center " + line_cls)(Span(cls="w-full border-t border-border")),
        Div(cls="relative flex justify-center " + text_cls)(Span(cls="bg-background px-2 ")(*c)))

# %% ../nbs/02_franken.ipynb
def DividerLine(lwidth=2, y_space=4): return Hr(cls=f"my-{y_space} h-[{lwidth}px] w-full bg-secondary")

# %% ../nbs/02_franken.ipynb
def Article(*c, # contents of Article tag (often other tags)
            cls=(), # Classes in addition to Article styling
            **kwargs # Additional args for Article tag
            )->FT: # Article(..., cls='uk-article')
    "A styled article container for blog posts or similar content"
    return fh.Article(*c, cls=('uk-article',stringify(cls)), **kwargs)

def ArticleTitle(*c, # contents of ArticleTitle tag (often other tags)
                 cls=(), # Classes in addition to ArticleTitle styling
                 **kwargs # Additional args for ArticleTitle tag
                 )->FT: # H1(..., cls='uk-article-title')
    "A title component for use within an Article"
    return H1(*c, cls=('uk-article-title',stringify(cls)), **kwargs)

def ArticleMeta(*c, # contents of ArticleMeta tag (often other tags)
                cls=(), # Classes in addition to ArticleMeta styling
                **kwargs # Additional args for ArticleMeta tag
                )->FT: # P(..., cls='uk-article-meta')
    "A metadata component for use within an Article showing things like date, author etc"
    return P(*c, cls=('uk-article-meta',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
class SectionT(VEnum):
    'Section styles from https://franken-ui.dev/docs/section'
    def _generate_next_value_(name, start, count, last_values): return str2ukcls('section', name)
    default = auto()
    muted = auto()
    primary = auto()
    secondary = auto()
    xs = 'uk-section-xsmall'
    sm = 'uk-section-small'
    lg = 'uk-section-large'
    xl = 'uk-section-xlarge'
    remove_vertical = auto()

# %% ../nbs/02_franken.ipynb
def Section(*c, # contents of Section tag (often other tags)
            cls=(), # Classes in addition to Section styling
            **kwargs # Additional args for Section tag
            )->FT: # Div(..., cls='uk-section')
    "Section with styling and margins"
    return fh.Div(*c, cls=('uk-section',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Form(*c, # contents of Form tag (often Buttons, FormLabels, and LabelInputs)
          cls='space-y-3', # Classes in addition to Form styling (default is 'space-y-3' to prevent scrunched up form elements)
          **kwargs # Additional args for Form tag
          )->FT: # Form(..., cls='space-y-3')
    "A Form with default spacing between form elements"
    return fh.Form(*c, cls=stringify(cls), **kwargs)

# %% ../nbs/02_franken.ipynb
def Fieldset(*c, # contents of Fieldset tag (often other tags)
             cls='flex', # Classes in addition to Fieldset styling
             **kwargs # Additional args for Fieldset tag
             )->FT: # Fieldset(..., cls='uk-fieldset')
    "A Fieldset with default styling"
    return fh.Fieldset(*c, cls=('uk-fieldset',stringify(cls)), **kwargs)

def Legend(*c, # contents of Legend tag (often other tags)
           cls=(), # Classes in addition to Legend styling
           **kwargs # Additional args for Legend tag
           )->FT: # Legend(..., cls='uk-legend')
    "A Legend with default styling"
    return fh.Legend(*c, cls=('uk-legend',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Input(*c, # contents of Input tag (often nothing)
          cls=(), # Classes in addition to Input styling
          **kwargs # Additional args for Input tag
          )->FT: # Input(..., cls='uk-input')
    "An Input with default styling"
    return fh.Input(*c, cls=('uk-input',stringify(cls)), **kwargs)

def Radio(*c, # contents of Radio tag (often nothing)
           cls=(), # Classes in addition to Radio styling
           **kwargs # Additional args for Radio tag
           )->FT: # Input(..., cls='uk-radio', type='radio')
    "A Radio with default styling"
    return fh.Input(*c, cls=('uk-radio',stringify(cls)), type='radio', **kwargs)
def CheckboxX(*c, # contents of CheckboxX tag (often nothing)
               cls=(), # Classes in addition to CheckboxX styling
               **kwargs # Additional args for CheckboxX tag
               )->FT: # Input(..., cls='uk-checkbox', type='checkbox')
    "A Checkbox with default styling"
    return fh.Input(*c, cls=('uk-checkbox',stringify(cls)), type='checkbox', **kwargs)

# %% ../nbs/02_franken.ipynb
def Range(*c, # contents of Range tag (often nothing)
          value='',
          label=True,
          min=None,
          max=None,
          step=None,
           cls=(), # Classes in addition to Range styling
           **kwargs # Additional args for Range tag
           )->FT: # Input(..., cls='uk-range', type='range')
    "A Range with default styling"
    return Uk_input_range(*c, min=min, label=label, max=max, value=value, step=step, multiple=len(value.split(','))>1, cls=('uk-range',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def TextArea(*c, # contents of TextArea tag (often text)
             cls=(), # Classes in addition to TextArea styling
             **kwargs # Additional args for TextArea tag
             )->FT: # TextArea(..., cls='uk-textarea')
    "A Textarea with default styling"
    return fh.Textarea(*c, cls=('uk-textarea',stringify(cls)), **kwargs)
def Switch(*c, # contents of Switch tag (often nothing)
           cls=(), # Classes in addition to Switch styling
           **kwargs # Additional args for Switch tag
           )->FT: # Input(..., cls='uk-toggle-switch uk-toggle-switch-primary min-w-9', type='checkbox')
    "A Switch with default styling"
    return fh.Input(*c, cls=('uk-toggle-switch uk-toggle-switch-primary min-w-9',stringify(cls)), type='checkbox', **kwargs)

# %% ../nbs/02_franken.ipynb
def Upload(*c, # Contents of Upload tag button (often text)
          cls=(), # Classes in addition to Upload styling
          multiple=False, # Whether to allow multiple file selection
          accept=None, # File types to accept (e.g. 'image/*')
          button_cls=ButtonT.default, # Classes for the button
          id=None, # ID for the file input
          name=None, # Name for the file input
          **kwargs # Additional args for the outer div
          )->FT: # Div(Input(type='file'), Button(...))
    "A file upload component with default styling"
    input_kwargs = {'type': 'file', 'multiple': multiple}
    if accept: input_kwargs['accept'] = accept
    if id: input_kwargs['id'] = id
    if name: input_kwargs['name'] = name
    return Div(
        fh.Input(**input_kwargs),
        Button(*c, cls=button_cls, submit=False, tabindex="-1"),
        cls=('w-full js-upload', stringify(cls)),
        uk_form_custom=True)

def UploadZone(*c, # Contents of UploadZone tag (often text or other tags)
               cls=(), # Classes in addition to UploadZone styling
               multiple=False, # Whether to allow multiple file selection
               accept=None, # File types to accept (e.g. 'image/*')
               id=None, # ID for the file input
               name=None, # Name for the file input
               **kwargs # Additional args for the outer div
               )->FT:
    "A file drop zone component with default styling"
    input_kwargs = {'type': 'file', 'multiple': multiple}
    if accept: input_kwargs['accept'] = accept 
    if id: input_kwargs['id'] = id
    if name: input_kwargs['name'] = name
    return Div(
        Div(fh.Input(**input_kwargs),
            Span(*c),
            uk_form_custom=True, 
            cls='w-full'),
        cls=('js-upload uk-placeholder uk-text-center', stringify(cls)),
        **kwargs)

# %% ../nbs/02_franken.ipynb
def FormLabel(*c, # contents of FormLabel tag (often text)
               cls=(), # Classes in addition to FormLabel styling
               **kwargs # Additional args for FormLabel tag
               )->FT: # Label(..., cls='uk-form-label')
    "A Label with default styling"
    return fh.Label(*c, cls=('uk-form-label',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
class LabelT(VEnum):
    def _generate_next_value_(name, start, count, last_values): return str2ukcls('label', name)
    primary = auto()
    secondary = auto()
    destructive = auto()

# %% ../nbs/02_franken.ipynb
def Label(*c, # contents of Label tag (often text)
           cls=(), # Classes in addition to Label styling
           **kwargs # Additional args for Label tag
           )->FT: # Label(..., cls='uk-label')
    "FrankenUI labels, which look like pills"
    return fh.Label(*c, cls=('uk-label',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def UkFormSection(title, description, *c, button_txt='Update', outer_margin=6, inner_margin=6):
    "A form section with a title, description and optional button"
    return Div(cls=f'space-y-{inner_margin} py-{outer_margin}')(
        Div(H3(title), P(description, cls=TextPresets.muted_sm)),
        DividerSplit(), *c,
        Div(Button(button_txt, cls=ButtonT.primary)) if button_txt else None)

# %% ../nbs/02_franken.ipynb
def GenericLabelInput(
               label:str|FT, # FormLabel content (often text)
               lbl_cls='', # Additional classes for FormLabel
               input_cls='', # Additional classes for user input (Input, Select, etc)
               container=Div, # Container to wrap label and input in (default is Div)
               cls='', # Classes on container (default is '')
               id=None, # id for label and input (`id`, `name` and `for` attributes are set to this value).  If `label` is str, this defaults to `label.lower()`
               input_fn=noop, # User input FT component 
                **kwargs # Additional args for user input
                ): 
    "`Div(Label,Input)` component with Uk styling injected appropriately. Generally you should higher level API, such as `LabelInput` which is created for you in this library"
    if not id and isinstance(label, str): id = label.lower()
    if isinstance(label, str) or label.tag != 'label': 
        label = FormLabel(cls=stringify(lbl_cls), fr=id)(label)
    inp = input_fn(id=id, cls=stringify(input_cls), **kwargs)        
    if container: return container(label, inp, cls=stringify(cls))
    return label, inp

# %% ../nbs/02_franken.ipynb
def LabelInput(label:str|FT, # FormLabel content (often text)
               lbl_cls='', # Additional classes for `FormLabel`
               input_cls='', # Additional classes for `Input`
               cls='space-y-2', # Classes on container (default is `'space-y-2'` to prevent scrunched up form elements)
               id='', # id for `FormLabel` and `Input` (`id`, `name` and `for` attributes are set to this value)
                **kwargs # Additional args for `Input`
               )->FT:  # Div(cls='space-y-2')(`FormLabel`, `Input`)
    "A `FormLabel` and `Input` pair that provides default spacing and links/names them based on id"
    return GenericLabelInput(label=label, lbl_cls=lbl_cls, input_cls=input_cls,
                             container=Div, cls=cls, id=id, input_fn=Input, **kwargs)

# %% ../nbs/02_franken.ipynb
def LabelTextArea(label:str|FT, # FormLabel content (often text)
                  value='', # Value for the textarea
                  lbl_cls='', # Additional classes for `FormLabel`
                  input_cls='', # Additional classes for `TextArea`
                  cls='space-y-2', # Classes on container (default is `'space-y-2'` to prevent scrunched up form elements)
                  id='', # id for `FormLabel` and `TextArea` (`id`, `name` and `for` attributes are set to this value)
                  **kwargs # Additional args for `TextArea`
                  )->FT:  # Div(cls='space-y-2')(`FormLabel`, `TextArea`)
    def text_area_with_value(**kw): return TextArea(value, **kw)
    return GenericLabelInput(label=label, lbl_cls=lbl_cls, input_cls=input_cls,
                             container=Div, cls=cls, id=id, input_fn=text_area_with_value, **kwargs)

# %% ../nbs/02_franken.ipynb
@delegates(GenericLabelInput, but=['input_fn','cls'])
def LabelSwitch(label:str|FT, # FormLabel content (often text)
               lbl_cls='', # Additional classes for `FormLabel`
               input_cls='', # Additional classes for `Switch`
               cls='space-x-2', # Classes on container (default is `'space-x-2'` to prevent scrunched up form elements)
               id='', # id for `FormLabel` and `Switch` (`id`, `name` and `for` attributes are set to this value)
                **kwargs # Additional args for `Switch`
               )->FT:  # Div(cls='space-y-2')(`FormLabel`, `Switch`)
    return GenericLabelInput(label=label, lbl_cls=lbl_cls, input_cls=input_cls,
                             container=Div, cls=cls, id=id, input_fn=Switch, **kwargs)

# %% ../nbs/02_franken.ipynb
def LabelRadio(label:str|FT, # FormLabel content (often text)
               lbl_cls='', # Additional classes for `FormLabel`
               input_cls='', # Additional classes for `Radio`
               container=Div, # Container to wrap label and input in (default is Div)
               cls='flex items-center space-x-2', # Classes on container (default is 'flex items-center space-x-2')
               id='', # id for `FormLabel` and `Radio` (`id`, `name` and `for` attributes are set to this value)
                **kwargs # Additional args for `Radio`
               )->FT:  # Div(cls='flex items-center space-x-2')(`FormLabel`, `Radio`)
    "A FormLabel and Radio pair that provides default spacing and links/names them based on id"
    if isinstance(label, str) or label.tag != 'label': 
        label = FormLabel(cls=stringify(lbl_cls), fr=id)(label)
    inp = Radio(id=id, cls=stringify(input_cls), **kwargs)        
    if container: return container(inp, label, cls=stringify(cls))
    return inp, label

# %% ../nbs/02_franken.ipynb
def LabelCheckboxX(label:str|FT, # FormLabel content (often text)
               lbl_cls='', # Additional classes for `FormLabel`
               input_cls='', # Additional classes for `CheckboxX`
               container=Div, # Container to wrap label and input in (default is Div)
               cls='flex items-center space-x-2', # Classes on container (default is 'flex items-center space-x-2')
               id='', # id for `FormLabel` and `CheckboxX` (`id`, `name` and `for` attributes are set to this value)
                **kwargs # Additional args for `CheckboxX`
               )->FT:  # Div(cls='flex items-center space-x-2')(`FormLabel`, `CheckboxX`)
    "A FormLabel and CheckboxX pair that provides default spacing and links/names them based on id"
    if not id: id = fh.unqid()
    if isinstance(label, str) or label.tag != 'label': 
        label = FormLabel(cls=stringify(lbl_cls), fr=id)(label)
    inp = CheckboxX(id=id, cls=stringify(input_cls), **kwargs)        
    if container: return container(inp, label, cls=stringify(cls))
    return inp, label

# %% ../nbs/02_franken.ipynb
def Options(*c,                    # Content for an `Option`
            selected_idx:int=None, # Index location of selected `Option`
            disabled_idxs:set=None # Idex locations of disabled `Options`
           ):
    "Helper function to wrap things into `Option`s for use in `Select`"
    return [fh.Option(o,selected=i==selected_idx, disabled=disabled_idxs and i in disabled_idxs) for i,o in enumerate(c)]

# %% ../nbs/02_franken.ipynb
def Select(*option,            # Options for the select dropdown (can use `Options` helper function to create)
          inp_cls=(),         # Additional classes for the select input
          cls=('h-10',),      # Classes for the outer div (default h-10 for consistent height)
          cls_custom='button: uk-input-fake; dropdown: w-full', # Classes for the Uk_Select web component
          id="",              # ID for the select input
          name="",            # Name attribute for the select input
          placeholder="",     # Placeholder text for the select input
          searchable=False,   # Whether the select should be searchable
          insertable=False,   # Whether to allow user-defined options to be added
          select_kwargs=None, # Additional Arguments passed to Select
          
           **kwargs           # Additional arguments passed to Uk_select
          ):          
    "Creates a select dropdown with uk styling and option for adding a search box"
    inp_cls, cls, cls_custom= map(stringify, (inp_cls, cls, cls_custom))
    select_kwargs = ifnone(select_kwargs, {})

    if 'hx_trigger' not in kwargs: kwargs['hx_trigger']=''
    if 'change' in kwargs['hx_trigger']:
        if not id: id = fh.unqid()
        kwargs['hx_trigger'] = kwargs['hx_trigger'].replace('changed', f'uk-select:input from:#{id}')
        kwargs['hx_trigger'] = kwargs['hx_trigger'].replace('change', f'uk-select:input from:#{id}')
    
    if 'delay' not in kwargs['hx_trigger']:
        kwargs['hx_trigger'] +=  ' delay:100ms'
    
    if 'hx_include' not in kwargs: kwargs['hx_include']=''
    kwargs['hx_include'] += ' this'
    kwargs['hx_include'] = kwargs['hx_include'].strip()
        
    if id and not name: name = id

    uk_select = Uk_select(fh.Select(*option, hidden=True, 
                                    **select_kwargs, 
                                    
                                    ),
                         cls_custom=cls_custom,
                         searchable=searchable,
                         placeholder=placeholder,
                         insertable=insertable,
                         cls=inp_cls,
                          id=id,
                          name=name,
                          **kwargs
                         )
    
    return Div(cls=cls)(uk_select)

# %% ../nbs/02_franken.ipynb
def LabelSelect(*option,            # Options for the select dropdown (can use `Options` helper function to create) 
             label=(),           # String or FT component for the label
             lbl_cls=(),         # Additional classes for the label
             inp_cls=(),         # Additional classes for the select input
             cls=('space-y-2',), # Classes for the outer div
             id="",              # ID for the select input
             name="",            # Name attribute for the select input
             placeholder="",     # Placeholder text for the select input
             searchable=False,   # Whether the select should be searchable
             select_kwargs=None, # Additional Arguments passed to Select
             **kwargs):          # Additional arguments passed to Select
    "A FormLabel and Select pair that provides default spacing and links/names them based on id"
    lbl_cls, inp_cls, cls = map(stringify, (lbl_cls, inp_cls, cls))
    select_kwargs = ifnone(select_kwargs, {})
    if label:
        lbl = FormLabel(cls=f'{lbl_cls}', fr=id)(label)
    select = Select(*option, inp_cls=inp_cls, id=id, name=name if name else id, 
                   placeholder=placeholder, searchable=searchable, select_kwargs=select_kwargs, **kwargs)
    return Div(cls=cls)(lbl, select) if label else Div(cls=cls)(select)

# %% ../nbs/02_franken.ipynb
@delegates(GenericLabelInput, but=['input_fn','cls'])
def LabelRange(label:str|FT, # FormLabel content (often text)
               lbl_cls='', # Additional classes for `FormLabel`
               input_cls='', # Additional classes for `Range`
               cls='space-y-6', # Classes on container (default is `'space-y-2'` to prevent scrunched up form elements)
               id='', # id for `FormLabel` and `Range` (`id`, `name` and `for` attributes are set to this value)
               value='', # Value for the range input
               min=None, # Minimum value
               max=None, # Maximum value
               step=None, # Step size
               label_range=True, # Whether to show the range value label (label for the `Range` component)
               **kwargs # Additional args for `Range`
               )->FT:  # Div(cls='space-y-2')(`FormLabel`, `Range`)
    "A FormLabel and Range pair that provides default spacing and links/names them based on id"
    def range_with_value(**kw): 
        return Div(Range(value=value, min=min, max=max, step=step, label=label_range, **kw))
    return GenericLabelInput(label=label, lbl_cls=lbl_cls, input_cls=input_cls,
                           container=Div, cls=cls, id=id, input_fn=range_with_value, **kwargs)

# %% ../nbs/02_franken.ipynb
class AT(VEnum):
    'Link styles from https://franken-ui.dev/docs/link'
    def _generate_next_value_(name, start, count, last_values): return str2ukcls('link', name)
    muted = auto()
    text = auto()
    reset = auto()
    primary = 'uk-link text-primary hover:text-primary-focus underline'
    classic = 'text-blue-600 hover:text-blue-800 underline'

# %% ../nbs/02_franken.ipynb
class ListT(VEnum):
    'List styles using Tailwind CSS'
    disc = 'list-disc list-inside'
    circle = 'list-[circle] list-inside' 
    square = 'list-[square] list-inside'
    decimal = 'uk-list uk-list-decimal'
    hyphen = 'uk-list uk-list-hyphen'
    bullet = 'uk-list uk-list-bullet'
    divider = 'uk-list uk-list-divider'
    striped = 'uk-list uk-list-striped'

# %% ../nbs/02_franken.ipynb
def ModalContainer(*c, # Components to put in the modal (often `ModalDialog`)
                     cls=(), # Additional classes on the `ModalContainer`
                     **kwargs # Additional args for `Div` tag
                     )->FT: # Div(..., cls='uk-modal uk-modal-container')
    "Creates a modal container that components go in"
    return fh.Div(*c, cls=('uk-modal uk-modal-container',stringify(cls)), data_uk_modal=True, **kwargs)
def ModalDialog(*c, # Components to put in the `ModalDialog` (often `ModalBody`, `ModalHeader`, etc)
                  cls=(), # Additional classes on the `ModalDialog`
                  **kwargs # Additional args for `Div` tag
                  )->FT: # Div(..., cls='uk-modal-dialog')
    "Creates a modal dialog"
    return fh.Div(*c, cls=('uk-modal-dialog',   stringify(cls)),                **kwargs)
def ModalHeader(*c, # Components to put in the `ModalHeader`
                  cls=(), # Additional classes on the `ModalHeader`
                  **kwargs # Additional args for `Div` tag
                  )->FT: # Div(..., cls='uk-modal-header')
    "Creates a modal header"
    return fh.Div(*c, cls=('uk-modal-header',   stringify(cls)),                **kwargs)
def ModalBody(*c, # Components to put in the `ModalBody` (often forms, sign in buttons, images, etc.)
               cls=(), # Additional classes on the `ModalBody`
               **kwargs # Additional args for `Div` tag
               )->FT: # Div(..., cls='uk-modal-body')
    "Creates a modal body"
    return fh.Div(*c, cls=('uk-modal-body',     stringify(cls)),                **kwargs)
def ModalFooter(*c, # Components to put in the `ModalFooter` (often buttons)
                 cls=(), # Additional classes on the `ModalFooter`
                 **kwargs # Additional args for `Div` tag
                 )->FT: # Div(..., cls='uk-modal-footer')
    "Creates a modal footer"
    return fh.Div(*c, cls=('uk-modal-footer',   stringify(cls)),                **kwargs)
def ModalTitle(*c, # Components to put in the `ModalTitle` (often text)
                cls=(), # Additional classes on the `ModalTitle`
                **kwargs # Additional args for `H2` tag
                )->FT: # H2(..., cls='uk-modal-title')
    "Creates a modal title"
    return fh.H2(*c,  cls=('uk-modal-title',  stringify(cls)),  **kwargs)
def ModalCloseButton(*c, # Components to put in the button (often text and/or an icon)
                      cls="absolute top-3 right-3", # Additional classes on the button
                      **kwargs # Additional args for `Button` tag
                      )->FT: # Button(..., cls='uk-modal-close')
    "Creates a button that closes a modal with js"
    cls = (stringify(cls), 'uk-modal-close')
    kwargs['data-uk-close'] = True
    return Button(*c, cls=(stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Modal(*c,                 # Components to put in the `ModalBody` (often forms, sign in buttons, images, etc.)
        header=None,          # Components that go in the `ModalHeader` (often a `ModalTitle`)
        footer=None,          # Components that go in the `ModalFooter` (often a `ModalCloseButton`)
        cls=(),               # Additional classes on the outermost `ModalContainer` 
        dialog_cls=(),        # Additional classes on the `ModalDialog` 
        header_cls='p-6',     # Additional classes on the `ModalHeader`
        body_cls='space-y-6', # Additional classes on the `ModalBody`
        footer_cls=(),        # Additional classes on the `ModalFooter`
        id='',                # id for the outermost container
        hx_init=False,        # Initialize modal with UIKit on load (used for modals added to the DOM by HTMX)
        hx_open=False,        # Open modal on load (used for modals added to the DOM by HTMX)
        **kwargs              # Additional args for the outermost `Div` tag
        )->FT: # Fully styled modal FT Component
    "Creates a modal with the appropriate classes to put the boilerplate in the appropriate places for you"
    if not id: id = fh.unqid()
    if hx_open: kwargs["hx_on__load"] = f"UIkit.modal('#{id}').show()"
    if hx_init and not hx_open: kwargs["hx_on__load"] = f"UIkit.modal('#{id}')"
    if hx_open or hx_init: kwargs["hx-on:hidden"] = "this.remove()"
    cls, dialog_cls, header_cls, body_cls, footer_cls = map(stringify, (cls, dialog_cls, header_cls, body_cls, footer_cls))
    res = []
    if header: res.append(ModalHeader(cls=header_cls)(header))
    res.append(ModalBody(cls=body_cls)(*c))
    if footer: res.append(ModalFooter(cls=footer_cls)(footer))
    return ModalContainer(ModalDialog(*res, cls=dialog_cls), cls=cls, id=id, **kwargs)

# %% ../nbs/02_franken.ipynb
def Placeholder(*c, # Components to put in the placeholder
                  cls=(), # Additional classes on the placeholder
                  **kwargs # Additional args for `Div` tag
                  )->FT: # Div(..., cls='uk-placeholder')
    "Creates a placeholder"
    return fh.Div(*c, cls=('uk-placeholder',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Progress(*c, # Components to put in the progress bar (often nothing)
             cls=(), # Additional classes on the progress bar
             value="", # Value of the progress bar
             max="100", # Max value of the progress bar (defaults to 100 for percentage)
             **kwargs # Additional args for `Progress` tag
             )->FT: # Progress(..., cls='uk-progress')
    "Creates a progress bar"
    return fh.Progress(*c, value=value, max=max, cls=('uk-progress',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def UkIcon(icon:str, # Icon name from [lucide icons](https://lucide.dev/icons/)
           height:int=None, 
           width:int=None, 
           stroke_width:int=None, # Thickness of lines
           cls=(), # Additional classes on the `Uk_icon` tag
           **kwargs # Additional args for `Uk_icon` tag
           )->FT: # a lucide icon of the specified size 
    "Creates an icon using lucide icons"
    return Uk_icon(icon=icon, height=height, width=width, stroke_width=stroke_width, cls=cls, **kwargs)

# %% ../nbs/02_franken.ipynb
def UkIconLink(icon:str,  # Icon name from [lucide icons](https://lucide.dev/icons/)
           height:int=None, 
           width:int=None, 
           stroke_width:int=None, # Thickness of lines
           cls=(), # Additional classes on the icon
           button:bool=False, # Whether to use a button (defaults to a link)
           **kwargs # Additional args for `A` or `Button` tag
           )->FT: # a lucide icon  button or link of the specified size
    "Creates an icon link using lucide icons"
    fn = fh.Button if button else fh.A
    return fn(cls=(f"uk-icon-{'button' if button else 'link'}", stringify(cls)), **kwargs)(
        UkIcon(icon=icon, height=height, width=width, stroke_width=stroke_width))

# %% ../nbs/02_franken.ipynb
def DiceBearAvatar(seed_name:str, # Seed name (ie 'Isaac Flath')
                   h:int=20,         # Height 
                   w:int=20,          # Width
                  ):          # Span with Avatar
    "Creates an Avatar using https://dicebear.com/"
    url = 'https://api.dicebear.com/8.x/lorelei/svg?seed='
    return Span(cls=f"relative flex h-{h} w-{w} shrink-0 overflow-hidden rounded-full bg-secondary")(
            fh.Img(cls=f"aspect-square h-{h} w-{w}", alt="Avatar", loading="lazy", src=f"{url}{seed_name}"))

# %% ../nbs/02_franken.ipynb
def Center(*c, # Components to center
          vertical:bool=True, # Whether to center vertically
          horizontal:bool=True, # Whether to center horizontally 
          cls=(), # Additional classes
          **kwargs # Additional args for container div
          )->FT: # Div with centered contents
    "Centers contents both vertically and horizontally by default"
    classes = ['flex']
    if vertical: classes.append('items-center min-h-full') 
    if horizontal: classes.append('justify-center min-w-full')
    return fh_comp.Center(*c, cls=(stringify(classes), stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
class FlexT(VEnum):
    'Flexbox modifiers using Tailwind CSS'
    def _generate_next_value_(name, start, count, last_values): return name
    
    # Display
    block = 'flex'
    inline = 'inline-flex'
    
    # Horizontal Alignment
    left = 'justify-start' 
    center = 'justify-center'
    right = 'justify-end'
    between = 'justify-between'
    around = 'justify-around'
    
    # Vertical Alignment
    stretch = 'items-stretch'
    top = 'items-start'
    middle = 'items-center' 
    bottom = 'items-end'
    
    # Direction
    row = 'flex-row'
    row_reverse = 'flex-row-reverse'
    column = 'flex-col'
    column_reverse = 'flex-col-reverse'
    
    # Wrap
    nowrap = 'flex-nowrap'
    wrap = 'flex-wrap'
    wrap_reverse = 'flex-wrap-reverse'

# %% ../nbs/02_franken.ipynb
def Grid(*div, # `Div` components to put in the grid
         cols_min:int=1, # Minimum number of columns at any screen size
         cols_max:int=4, # Maximum number of columns allowed at any screen size
         cols_sm:int=None, # Number of columns on small screens
         cols_md:int=None, # Number of columns on medium screens
         cols_lg:int=None, # Number of columns on large screens
         cols_xl:int=None, # Number of columns on extra large screens
         cols:int=None, # Number of columns on all screens
         cls='gap-4', # Additional classes on the grid (tip: `gap` provides spacing for grids)
         **kwargs # Additional args for `Div` tag
         )->FT: # Responsive grid component
    "Creates a responsive grid layout with smart defaults based on content"
    if cols: cols_min = cols_sm = cols_md = cols_lg = cols_xl = cols
    else:
        n = len(div)
        cols_max = min(n, cols_max)
        cols_sm = cols_sm or min(n, cols_min, cols_max)
        cols_md = cols_md or min(n, cols_min+1, cols_max) 
        cols_lg = cols_lg or min(n, cols_min+2, cols_max) 
        cols_xl = cols_xl or cols_max
    return Div(cls=(f'grid grid-cols-{cols_min} sm:grid-cols-{cols_sm} md:grid-cols-{cols_md} lg:grid-cols-{cols_lg} xl:grid-cols-{cols_xl}', stringify(cls)), **kwargs)(*div)

# %% ../nbs/02_franken.ipynb
def DivFullySpaced(*c,                # Components
                   cls='w-full',# Classes for outer div (`w-full` makes it use all available width)
                   **kwargs           # Additional args for outer div
                  ):                  # Div with spaced components via flex classes
    "Creates a flex div with it's components having as much space between them as possible"
    cls = stringify(cls)
    return Div(cls=(FlexT.block,FlexT.between,FlexT.middle,cls), **kwargs)(*c)

# %% ../nbs/02_franken.ipynb
def DivCentered(*c,      # Components
                cls='space-y-4',  # Classes for outer div (`space-y-4` provides spacing between components)
                vstack=True, # Whether to stack the components vertically
                **kwargs # Additional args for outer div
               )->FT: # Div with components centered in it
    "Creates a flex div with it's components centered in it"
    cls=stringify(cls)
    return Div(cls=(FlexT.block,(FlexT.column if vstack else FlexT.row),FlexT.middle,FlexT.center,cls),**kwargs)(*c)

# %% ../nbs/02_franken.ipynb
def DivLAligned(*c, # Components
                cls='space-x-4',  # Classes for outer div
                **kwargs # Additional args for outer div
               )->FT: # Div with components aligned to the left
    "Creates a flex div with it's components aligned to the left"
    cls=stringify(cls)
    return Div(cls=(FlexT.block,FlexT.left,FlexT.middle,cls), **kwargs)(*c)

# %% ../nbs/02_franken.ipynb
def DivRAligned(*c, # Components
                cls='space-x-4',  # Classes for outer div
                **kwargs # Additional args for outer div
               )->FT: # Div with components aligned to the right
    "Creates a flex div with it's components aligned to the right"
    cls=stringify(cls)
    return Div(cls=(FlexT.block,FlexT.right,FlexT.middle,cls), **kwargs)(*c)

# %% ../nbs/02_franken.ipynb
def DivVStacked(*c, # Components
                cls='space-y-4', # Additional classes on the div  (tip: `space-y-4` provides spacing between components)
                **kwargs # Additional args for the div
               )->FT: # Div with components stacked vertically
    "Creates a flex div with it's components stacked vertically"
    cls=stringify(cls)
    return Div(cls=(FlexT.block,FlexT.column,FlexT.middle,cls), **kwargs)(*c)

# %% ../nbs/02_franken.ipynb
def DivHStacked(*c, # Components
                cls='space-x-4', # Additional classes on the div (`space-x-4` provides spacing between components)
                **kwargs # Additional args for the div
               )->FT: # Div with components stacked horizontally
    "Creates a flex div with it's components stacked horizontally"
    cls=stringify(cls)
    return Div(cls=(FlexT.block,FlexT.row,FlexT.middle,cls), **kwargs)(*c)

# %% ../nbs/02_franken.ipynb
class NavT(VEnum):
    def _generate_next_value_(name, start, count, last_values): return str2ukcls('nav', name)
    default = auto()
    primary = auto()
    secondary = auto()

# %% ../nbs/02_franken.ipynb
def NavContainer(*li, # List items are navigation elements (Special `Li` such as `NavParentLi`, `NavDividerLi`, `NavHeaderLi`, `NavSubtitle`, `NavCloseLi` can also be used)
                 cls=NavT.primary, # Additional classes on the nav
                 parent=True, # Whether this nav is a *parent* or *sub* nav
                 uk_nav=False, #True for default collapsible behavior, see [frankenui docs](https://franken-ui.dev/docs/nav#component-options) for more advanced options
                 uk_scrollspy_nav=False, # Activates scrollspy linking each item `A` tags `href` to content's `id` attribute
                 sticky=False, # Whether to stick to the top of the page while scrolling
                 **kwargs # Additional args
                 )->FT: # FT Component that is a list of `Li` styled for a sidebar navigation menu
    "Creates a navigation container (useful for creating a sidebar navigation).  A Nav is a list (NavBar is something different)"
    _uk_scrollspy_nav = False
    if uk_scrollspy_nav:
        if isinstance(uk_scrollspy_nav, bool):  _uk_scrollspy_nav = 'closest: li; scroll: true' if uk_scrollspy_nav else False
        else:  _uk_scrollspy_nav = uk_scrollspy_nav
    _sticky = 'float-left sticky top-4 hidden md:block' if sticky else ''
    return fh.Ul(*li, uk_nav=uk_nav, cls=(f"uk-nav{'' if parent else '-sub'}", stringify(cls), _sticky), uk_scrollspy_nav=_uk_scrollspy_nav, **kwargs)

# %% ../nbs/02_franken.ipynb
def NavParentLi(*nav_container, # `NavContainer` container for a nested nav with `parent=False`)
                cls=(), # Additional classes on the li
                **kwargs # Additional args for the li
               )->FT: # Navigation list item
    "Creates a navigation list item with a parent nav for nesting"
    return fh.Li(*nav_container,  cls=('uk-parent',  stringify(cls)),**kwargs)
def NavDividerLi(*c, # Components
                 cls=(), # Additional classes on the li
                 **kwargs # Additional args for the li
                )->FT: # Navigation list item with a divider
    "Creates a navigation list item with a divider"
    return fh.Li(*c, cls=('uk-nav-divider', stringify(cls)),**kwargs)
def NavHeaderLi(*c, # Components
                cls=(), # Additional classes on the li
                **kwargs # Additional args for the li
               )->FT: # Navigation list item with a header
    "Creates a navigation list item with a header"
    return fh.Li(*c, cls=('uk-nav-header', stringify(cls)),**kwargs)
def NavSubtitle(*c, # Components
                 cls=TextPresets.muted_sm, # Additional classes on the div
                 **kwargs # Additional args for the div
                )->FT: # Navigation subtitle
    "Creates a navigation subtitle"
    return fh.Div(*c, cls=('uk-nav-subtitle', stringify(cls)),**kwargs)
def NavCloseLi(*c, # Components
               cls=(), # Additional classes on the li
               **kwargs # Additional args for the li
              )->FT: # Navigation list item with a close button
    "Creates a navigation list item with a close button"
    return fh.Li(*c, cls=('uk-drop-close', stringify(cls)),**kwargs)

# %% ../nbs/02_franken.ipynb
class ScrollspyT(VEnum):
    underline = 'navbar-underline'
    bold = 'navbar-bold'

# %% ../nbs/02_franken.ipynb
def NavBar(*c, # Component for right side of navbar (Often A tag links)
           brand=H3("Title"), # Brand/logo component for left side
           right_cls='items-center space-x-4', # Spacing for desktop links
           mobile_cls='space-y-4', # Spacing for mobile links
           sticky:bool=False, # Whether to stick to the top of the page while scrolling
           uk_scrollspy_nav:bool|str=False, # Whether to use scrollspy for navigation
           cls='p-4', # Classes for navbar
           scrollspy_cls=ScrollspyT.underline, # Scrollspy class (usually ScrollspyT.*)
           menu_id=None, # ID for menu container (used for mobile toggle)
           )->FT: # Responsive NavBar
    "Creates a responsive navigation bar with mobile menu support"
    if menu_id is None: menu_id = fh.unqid()
    sticky_cls = 'sticky top-4 bg-base-100/80 backdrop-blur-sm z-50' if sticky else ''
    if uk_scrollspy_nav == True: uk_scrollspy_nav = 'closest: a; scroll: true'

    mobile_icon = A(UkIcon("menu", width=30, height=30), cls="md:hidden", data_uk_toggle=f"target: #{menu_id}; cls: hidden")
    return Div(
        Div(
            DivFullySpaced(
                brand, # Brand/logo component for left side
                mobile_icon, # Hamburger menu icon
                Div(*c,cls=(stringify(right_cls),'hidden md:flex'), uk_scrollspy_nav=uk_scrollspy_nav)),# Desktop Navbar (right side)
            cls=('monster-navbar', stringify(cls), stringify(scrollspy_cls))
            ),
        DivCentered(*c, 
                    cls=(stringify(mobile_cls),stringify(cls), stringify(scrollspy_cls),
                         'hidden md:hidden monster-navbar'), 
                    id=menu_id, uk_scrollspy_nav=uk_scrollspy_nav),
        cls=sticky_cls)

# %% ../nbs/02_franken.ipynb
def SliderContainer(
        *c, # Components
        cls='', # Additional classes on the container
        uk_slider=True, # See FrankenUI Slider docs for more options
        **kwargs # Additional args for the container
    ) -> FT: # Div(..., cls='relative', uk_slider=True, ...)
    "Creates a slider container"
    return Div(*c, cls=('relative', stringify(cls)), uk_slider=uk_slider, **kwargs)

# %% ../nbs/02_franken.ipynb
def SliderItems(
        *c, # Components
        cls='', # Additional classes for the items
        **kwargs # Additional args for the items
    ) -> FT: # Div(..., cls='uk-slider-items uk-grid', ...)
    "Creates a slider items container"
    return Div(*c, cls=('uk-slider-items uk-grid', stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def SliderNav(
        cls='uk-position-small uk-hidden-hover', # Additional classes for the navigation
        prev_cls='absolute left-0 top-1/2 -translate-y-1/2', # Additional classes for the previous navigation
        next_cls='absolute right-0 top-1/2 -translate-y-1/2', # Additional classes for the next navigation
        **kwargs # Additional args for the navigation
    ) -> FT: # Left and right navigation arrows for Slider component
    "Navigation arrows for Slider component"
    return (
        fh.A(cls=(prev_cls, stringify(cls)), href='',
             uk_slidenav_previous=True, uk_slider_item='previous', **kwargs),
        fh.A(cls=(next_cls, stringify(cls)), href='',
             uk_slidenav_next=True, uk_slider_item='next', **kwargs)
    )

# %% ../nbs/02_franken.ipynb
def Slider(*c, # Items to show in slider
           cls='', # Classes for slider container
           items_cls='gap-4', # Classes for items container
           nav=True, # Whether to show navigation arrows
           nav_cls='', # Classes for navigation arrows
           **kwargs # Additional args for slider container
    ) -> FT: # SliderContainer(SliderItems(..., cls='gap-4'), SliderNav?)
    "Creates a slider with optional navigation arrows"
    nav_comp = SliderNav(cls=nav_cls) if nav else ()
    return SliderContainer(
        SliderItems(*c, cls=items_cls),
        *nav_comp,
        cls=cls,
        **kwargs
    )

# %% ../nbs/02_franken.ipynb
def DropDownNavContainer(*li, # Components
                         cls=NavT.primary, # Additional classes on the nav
                         parent=True, # Whether to use a parent nav
                         uk_nav=False, #True for default collapsible behavior, see https://franken-ui.dev/docs/nav#component-options for more advanced options
                         uk_dropdown=True, # Whether to use a dropdown
                         **kwargs # Additional args for the nav
                        )->FT: # DropDown nav container
    "A Nav that is part of a DropDown"
    return Div(cls = 'uk-drop uk-dropdown',uk_dropdown=uk_dropdown)(NavContainer(*li, cls=('uk-dropdown-nav',stringify(cls)), uk_nav=uk_nav, parent=parent, **kwargs))

# %% ../nbs/02_franken.ipynb
def TabContainer(*li, # Components
                  cls='', # Additional classes on the `Ul`
                  alt=False, # Whether to use an alternative tab style
                  **kwargs # Additional args for the `Ul`
                 )->FT: # Tab container
    "A TabContainer where children will be different tabs"
    cls = stringify(cls)
    return Ul(cls=(f"uk-tab{'-alt' if alt else ''}",stringify(cls)),**kwargs)(*li)

# %% ../nbs/02_franken.ipynb
class CardT(VEnum):
    'Card styles from UIkit'
    def _generate_next_value_(name, start, count, last_values): return str2ukcls('card', name)
    default = auto()
    primary = auto()
    secondary = auto()
    destructive = auto()
    hover = 'uk-card hover:shadow-lg hover:-translate-y-1 transition-all duration-200'

# %% ../nbs/02_franken.ipynb
def CardTitle(*c, # Components (often a string)
              cls=(), # Additional classes on the div
              **kwargs # Additional args for the div
             ): 
    "Creates a card title"
    return fh.Div(*c, cls=('uk-card-title',stringify(cls)), **kwargs)

def CardHeader(*c, # Components that goes in the header (often a `ModalTitle` and description)
               cls=(), # Additional classes on the div
               **kwargs # Additional args for the div
              )->FT: # Container for the header of a card
    "Creates a card header"
    return fh.Div(*c, cls=('uk-card-header',stringify(cls)), **kwargs)

def CardBody(*c, # Components that go in the body (Main content of the card such as a form, and image, a signin form, etc.)
              cls=(), # Additional classes on the div
              **kwargs # Additional args for the div
             )->FT: # Container for the body of a card
    "Creates a card body"
    return fh.Div(*c, cls=('uk-card-body',stringify(cls)), **kwargs)

def CardFooter(*c, # Components that go in the footer (often a `ModalCloseButton`)
               cls=(), # Additional classes on the div
               **kwargs # Additional args for the div
              )->FT: # Container for the footer of a card
    "Creates a card footer"
    return fh.Div(*c, cls=('uk-card-footer',stringify(cls)), **kwargs)

def CardContainer(*c, # Components (typically `CardHeader`, `CardBody`, `CardFooter`)
                   cls=CardT.default, # Additional classes on the div
                   **kwargs # Additional args for the div
                  )->FT: # Container for a card
    "Creates a card container"
    return fh.Div(*c, cls=('uk-card',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def Card(*c, # Components that go in the body (Main content of the card such as a form, and image, a signin form, etc.)
        header:FT|Iterable[FT]=None, # Component(s) that goes in the header (often a `ModalTitle` and a subtitle)
        footer:FT|Iterable[FT]=None,  # Component(s) that goes in the footer (often a `ModalCloseButton`)
        body_cls='space-y-6', # classes for the body
        header_cls=(), # classes for the header
        footer_cls=(), # classes for the footer
        cls=(), #class for outermost component
        **kwargs # additional arguments for the `CardContainer`
        )->FT: # Card component
    "Creates a Card with a header, body, and footer"
    header_cls, footer_cls, body_cls, cls = map(stringify, (header_cls, footer_cls, body_cls, cls))
    res = []
    if header: res.append(CardHeader(cls=header_cls)(header))
    res.append(CardBody(cls=body_cls)(*c))
    if footer: res.append(CardFooter(cls=footer_cls)(footer))
    return CardContainer(cls=cls, **kwargs)(*res)

# %% ../nbs/02_franken.ipynb
class TableT(VEnum):
    def _generate_next_value_(name, start, count, last_values): return str2ukcls('table', name)
    divider = auto()
    striped = auto()
    hover = auto()
    sm = auto()
    lg = auto()
    justify = auto()
    middle = auto()
    responsive = auto()

# %% ../nbs/02_franken.ipynb
def Table(*c, # Components (typically `Thead`, `Tbody`, `Tfoot`)
          cls=(TableT.middle, TableT.divider, TableT.hover, TableT.sm), # Additional classes on the table
          **kwargs # Additional args for the table
         )->FT: # Table component
    "Creates a table"
    return fh.Table(cls=('uk-table', stringify(cls)), *c, **kwargs)

# %% ../nbs/02_franken.ipynb
def _TableCell(Component, 
               *c, # Components that go in the cell
               cls=(), # Additional classes on the cell container
               shrink=False, # Whether to shrink the cell
               expand=False, # Whether to expand the cell
               small=False, # Whether to use a small table
               **kwargs # Additional args for the cell
              )->FT: # Table cell
    "Creates a table cell"
    cls = stringify(cls)
    if shrink: cls += ' uk-table-shrink'
    if expand: cls += ' uk-table-expand'
    if small: cls += ' uk-table-small'
    return Component(*c,cls=cls, **kwargs)

@delegates(_TableCell, but=['Component'])
def Td(*c,**kwargs):  return _TableCell(fh.Td, *c, **kwargs)
@delegates(_TableCell, but=['Component'])
def Th(*c,**kwargs): return _TableCell(fh.Th, *c, **kwargs)

def Tbody(*rows, cls=(), sortable=False, **kwargs): return fh.Tbody(*rows, cls=stringify(cls), uk_sortable=sortable, **kwargs)

# %% ../nbs/02_franken.ipynb
def TableFromLists(header_data:Sequence, # List of header data
                   body_data:Sequence[Sequence], # List of lists of body data
                   footer_data=None, # List of footer data
                   header_cell_render=Th, # Function(content) -> FT that renders header cells
                   body_cell_render=Td, # Function(key, content) -> FT that renders body cells
                   footer_cell_render=Td, #  Function(key, content) -> FT that renders footer cells
                   cls=(TableT.middle, TableT.divider, TableT.hover, TableT.sm), # Additional classes on the table
                   sortable=False, # Whether to use sortable table
                   **kwargs # Additional args for the table
                  )->FT: # Table from lists
    "Creates a Table from a list of header data and a list of lists of body data"
    return Table(
                Thead(Tr(*map(header_cell_render, header_data))),
                Tbody(*[Tr(*map(body_cell_render, r)) for r in body_data], sortable=sortable),
                Tfoot(Tr(*map(footer_cell_render, footer_data))) if footer_data else '',
                cls=stringify(cls),    
                **kwargs)

# %% ../nbs/02_franken.ipynb
def TableFromDicts(header_data:Sequence, # List of header data
                   body_data:Sequence[dict], # List of dicts of body data
                   footer_data=None, # List of footer data
                   header_cell_render=Th, # Function(content) -> FT that renders header cells
                   body_cell_render=lambda k,v : Td(v), # Function(key, content) -> FT that renders body cells
                   footer_cell_render=lambda k,v : Td(v), #  Function(key, content) -> FT that renders footer cells
                   cls=(TableT.middle, TableT.divider, TableT.hover, TableT.sm), # Additional classes on the table
                   sortable=False, # Whether to use sortable table
                   **kwargs # Additional args for the table
                  )->FT: # Styled Table
    "Creates a Table from a list of header data and a list of dicts of body data"
    return Table(
        Thead(Tr(*[header_cell_render(h) for h in header_data])),
        Tbody(*[Tr(*[body_cell_render(k, r.get(k, '')) for k in header_data]) for r in body_data], sortable=sortable),
        Tfoot(Tr(*[footer_cell_render(k, footer_data.get(k.lower(), '')) for k in header_data])) if footer_data else '',
        cls=stringify(cls),    
        **kwargs
    )

# %% ../nbs/02_franken.ipynb
franken_class_map = {
    'h1': 'uk-h1 text-4xl font-bold mt-12 mb-6',
    'h2': 'uk-h2 text-3xl font-bold mt-10 mb-5', 
    'h3': 'uk-h3 text-2xl font-semibold mt-8 mb-4',
    'h4': 'uk-h4 text-xl font-semibold mt-6 mb-3',
    
    # Body text and links
    'p': 'text-lg leading-relaxed mb-6',
    'a': 'uk-link text-primary hover:text-primary-focus underline',
    
    # Lists with proper spacing
    'ul': 'uk-list uk-list-bullet space-y-2 mb-6 ml-6 text-lg',
    'ol': 'uk-list uk-list-decimal space-y-2 mb-6 ml-6 text-lg',
    'li': 'leading-relaxed',
    
    # Code and quotes
    'pre': 'bg-base-200 rounded-lg p-4 mb-6',
    'code': 'uk-codespan px-1',
    'pre code': 'uk-codespan px-1 block overflow-x-auto',
    'blockquote': 'uk-blockquote pl-4 border-l-4 border-primary italic mb-6',
    
    # Tables
    'table': 'uk-table uk-table-divider uk-table-hover uk-table-small w-full mb-6',
    'th': '!text-left p-2 font-semibold',
    'td': 'p-2',
    
    # Other elements
    'hr': 'uk-divider-icon my-8',
    'img': 'max-w-full h-auto rounded-lg mb-6'
}

# %% ../nbs/02_franken.ipynb
def apply_classes(html_str:str, # Html string
                  class_map=None, # Class map
                  class_map_mods=None # Class map that will modify the class map map (for small changes to base map)
                 )->str: # Html string with classes applied
    "Apply classes to html string"
    if not html_str: return html_str
    # Handles "Unicode strings with encoding declaration are not supported":
    if html_str[:100].lstrip().startswith('<?xml'): html_str = html_str.split('?>', 1)[1].strip()
    class_map = ifnone(class_map, franken_class_map)
    if class_map_mods: class_map = {**class_map, **class_map_mods}
    try:
        html_str = html.fragment_fromstring(html_str, create_parent=True)
        for selector, classes in class_map.items():
            # Handle descendant selectors (e.g., 'pre code')
            xpath = '//' + '/descendant::'.join(selector.split())
            for element in html_str.xpath(xpath):
                existing_class = element.get('class', '')
                new_class = f"{existing_class} {classes}".strip()
                element.set('class', new_class)
        return ''.join(etree.tostring(c, encoding='unicode', method='html') for c in html_str)
    except (etree.ParserError,ValueError): return html_str

# %% ../nbs/02_franken.ipynb
class FrankenRenderer(HTMLRenderer):
    "Custom renderer for Franken UI that handles image paths"
    def __init__(self, *args, img_dir=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.img_dir = img_dir

    def render_image(self, token):
        "Modify image paths if they're relative and self.img_dir is specified"
        template = '<img src="{}" alt="{}"{} class="max-w-full h-auto rounded-lg mb-6">'
        title = f' title="{token.title}"' if hasattr(token, 'title') else ''
        src = token.src
        if self.img_dir and not src.startswith(('http://', 'https://', '/', 'attachment:', 'blob:', 'data:')):
            src = f'{pathlib.Path(self.img_dir)}/{src}'
        return template.format(src, token.children[0].content if token.children else '', title)

# %% ../nbs/02_franken.ipynb
def render_md(md_content:str, # Markdown content
             class_map=None, # Class map
             class_map_mods=None, # Additional class map
             img_dir:str=None, # Directory containing images
             renderer=FrankenRenderer # custom renderer
             )->FT: # Rendered markdown
    "Renders markdown using mistletoe and lxml with custom image handling"
    if md_content=='': return md_content
    html_content = mistletoe.markdown(md_content, partial(renderer, img_dir=img_dir))
    return NotStr(apply_classes(html_content, class_map, class_map_mods))

# %% ../nbs/02_franken.ipynb
def ThemePicker(color=True, radii=True, shadows=True, font=True, mode=True, cls='p-4', custom_themes=[]):
    "Theme picker component with configurable sections"
    def _opt(val, txt, **kwargs): return Option(txt, value=val, **kwargs)
    def _optgrp(key, lbl, opts): return fh.Optgroup(data_key=key, label=lbl)(*opts)
    
    groups = []
    if color: groups.append(_optgrp('theme', 'Theme', [
        _opt('uk-theme-zinc', 'Zinc', data_hex='#52525b', selected=True),
        *[_opt(f'uk-theme-{c.lower()}', c, data_hex=h) for c,h in 
          [('Slate','#64748b'),('Stone','#78716c'),('Gray','#6b7280'),
           ('Neutral','#737373'),('Red','#dc2626'),('Rose','#e11d48'),
           ('Orange','#f97316'),('Green','#16a34a'),('Blue','#2563eb'),
           ('Yellow','#facc15'),('Violet','#7c3aed'),*custom_themes]]]))
    if radii: groups.append(_optgrp('radii', 'Radii', [
        _opt('uk-radii-none','None'), _opt('uk-radii-sm','Small'),
        _opt('uk-radii-md','Medium',selected=True), _opt('uk-radii-lg','Large')]))
    if shadows: groups.append(_optgrp('shadows', 'Shadows', [
        _opt('uk-shadows-none','None'), _opt('uk-shadows-sm','Small',selected=True),
        _opt('uk-shadows-md','Medium'), _opt('uk-shadows-lg','Large')]))
    if font: groups.append(_optgrp('font', 'Font', [
        _opt('uk-font-sm','Small',selected=True), _opt('uk-font-base','Default')]))
    if mode: groups.append(_optgrp('mode', 'Mode', [
        _opt('light','Light',data_icon='sun'), _opt('dark','Dark',data_icon='moon')]))
    from fasthtml.components import Uk_theme_switcher
    return Div(Uk_theme_switcher(fh.Select(*groups, hidden=True),  id="theme-switcher"), cls=stringify(cls))

# %% ../nbs/02_franken.ipynb
def LightboxContainer(*lightboxitem, # `LightBoxItem`s that will be inside lightbox
                      data_uk_lightbox='counter: true', # See https://franken-ui.dev/docs/2.0/lightbox for advanced options
                      **kwargs # Additional options for outer container
                     )->FT: # Lightbox
    "Lightbox container that will hold `LightboxItems`"
    return fh.Div(*lightboxitem, data_uk_lightbox=data_uk_lightbox, **kwargs)

# %% ../nbs/02_franken.ipynb
def LightboxItem(*c, # Component that when clicked will open the lightbox (often a button)
                 href, # Href to image, youtube video, vimeo, google maps, etc.
                 data_alt=None, # Alt text for the lightbox item/image
                 data_caption=None, # Caption for the item that shows below it
                 cls='', # Class for the A tag (often nothing or `uk-btn`)
                 **kwargs # Additional args for the `A` tag
                )->FT: # A(... href, data_alt, cls., ...)
    "Anchor tag with appropriate structure to go inside a `LightBoxContainer`"
    return fh.A(*c, href=href, data_alt=data_alt, cls=stringify(cls), **kwargs)

# %% ../nbs/02_franken.ipynb
def ApexChart(*, 
              opts:Dict, # ApexChart options used to render your chart (e.g. {"chart":{"type":"line"}, ...})
              cls: Enum | str | tuple = (), # Classes for the outer container
              **kws, # Additional args for the outer container
              )->FT:  # Div(Uk_chart(Script(...)))
    "Apex chart component"
    js=NotStr(f"<script type='application/json'>{json.dumps(opts)}</script>")
    return Div(Uk_chart(js), cls=stringify(cls), **kws)