gtm-ui-native 0.0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. cli/README.md +103 -0
  2. cli/__init__.py +0 -0
  3. cli/dev.py +330 -0
  4. cli/main.py +893 -0
  5. cli/preset_codec.py +136 -0
  6. cli/py.typed +0 -0
  7. cli/scrollbar_css.py +444 -0
  8. cli/shimmer_css.py +104 -0
  9. cli/tailwind_config.py +3 -0
  10. components/__init__.py +1 -0
  11. components/chart/__init__.py +1 -0
  12. components/chart/chart_tooltip.py +117 -0
  13. components/core/__init__.py +1 -0
  14. components/core/core.py +54 -0
  15. components/core/hugeicon.py +68 -0
  16. components/py.typed +0 -0
  17. components/ui/__init__.py +1 -0
  18. components/ui/accordion.py +115 -0
  19. components/ui/attachment.py +263 -0
  20. components/ui/avatar.py +172 -0
  21. components/ui/badge.py +79 -0
  22. components/ui/bubble.py +169 -0
  23. components/ui/button.py +148 -0
  24. components/ui/button_group.py +104 -0
  25. components/ui/card.py +140 -0
  26. components/ui/checkbox.py +107 -0
  27. components/ui/collapsible.py +71 -0
  28. components/ui/dialog.py +225 -0
  29. components/ui/field.py +257 -0
  30. components/ui/frame.py +175 -0
  31. components/ui/input.py +51 -0
  32. components/ui/input_group.py +246 -0
  33. components/ui/marker.py +97 -0
  34. components/ui/menu.py +234 -0
  35. components/ui/message.py +128 -0
  36. components/ui/radio_group.py +127 -0
  37. components/ui/select.py +115 -0
  38. components/ui/separator.py +45 -0
  39. components/ui/spinner.py +25 -0
  40. components/ui/switch.py +117 -0
  41. components/ui/table.py +551 -0
  42. components/ui/tabs.py +234 -0
  43. components/ui/textarea.py +46 -0
  44. gtm_ui_native-0.0.3.1.dist-info/METADATA +123 -0
  45. gtm_ui_native-0.0.3.1.dist-info/RECORD +348 -0
  46. gtm_ui_native-0.0.3.1.dist-info/WHEEL +4 -0
  47. gtm_ui_native-0.0.3.1.dist-info/entry_points.txt +3 -0
  48. gtm_ui_native-0.0.3.1.dist-info/licenses/LICENSE.md +21 -0
  49. native/__init__.py +0 -0
  50. native/config.py +68 -0
  51. native/core/__init__.py +17 -0
  52. native/core/constants.py +21 -0
  53. native/core/utils.py +39 -0
  54. native/engine/__init__.py +0 -0
  55. native/engine/constants.py +9 -0
  56. native/engine/generator.py +75 -0
  57. native/engine/parser.py +284 -0
  58. native/engine/utils.py +4 -0
  59. native/export.py +146 -0
  60. native/hooks/__init__.py +0 -0
  61. native/hooks/client_state.py +0 -0
  62. native/hooks/head_components.py +54 -0
  63. native/hooks/stylesheets.py +26 -0
  64. native/lib/blocks/__init__.py +0 -0
  65. native/lib/blocks/area_chart_01.py +134 -0
  66. native/lib/blocks/area_chart_02.py +132 -0
  67. native/lib/blocks/bar_chart_01.py +125 -0
  68. native/lib/blocks/bar_chart_02.py +111 -0
  69. native/lib/blocks/bar_chart_03.py +132 -0
  70. native/lib/blocks/bar_chart_04.py +97 -0
  71. native/lib/blocks/kpi_card_01.py +62 -0
  72. native/lib/blocks/kpi_card_02.py +131 -0
  73. native/lib/blocks/line_chart_01.py +151 -0
  74. native/lib/blocks/line_chart_02.py +170 -0
  75. native/lib/blocks/line_chart_03.py +196 -0
  76. native/lib/blocks/line_chart_04.py +105 -0
  77. native/lib/charts/area/__init__.py +0 -0
  78. native/lib/charts/area/v1.py +75 -0
  79. native/lib/charts/area/v2.py +76 -0
  80. native/lib/charts/area/v3.py +76 -0
  81. native/lib/charts/area/v4.py +84 -0
  82. native/lib/charts/area/v5.py +140 -0
  83. native/lib/charts/area/v6.py +85 -0
  84. native/lib/charts/area/v7.py +95 -0
  85. native/lib/charts/area/v8.py +131 -0
  86. native/lib/charts/bar/__init__.py +0 -0
  87. native/lib/charts/bar/v1.py +80 -0
  88. native/lib/charts/bar/v2.py +75 -0
  89. native/lib/charts/bar/v3.py +87 -0
  90. native/lib/charts/bar/v4.py +86 -0
  91. native/lib/charts/bar/v5.py +125 -0
  92. native/lib/charts/bar/v6.py +89 -0
  93. native/lib/charts/bar/v7.py +74 -0
  94. native/lib/charts/bar/v9.py +99 -0
  95. native/lib/charts/line/__init__.py +0 -0
  96. native/lib/charts/line/v1.py +70 -0
  97. native/lib/charts/line/v2.py +70 -0
  98. native/lib/charts/line/v3.py +77 -0
  99. native/lib/charts/line/v4.py +78 -0
  100. native/lib/charts/line/v5.py +70 -0
  101. native/lib/charts/line/v6.py +62 -0
  102. native/lib/charts/line/v7.py +85 -0
  103. native/lib/charts/line/v8.py +97 -0
  104. native/lib/components/__init__.py +0 -0
  105. native/lib/components/accordion_basic.py +45 -0
  106. native/lib/components/attachment_group.py +49 -0
  107. native/lib/components/attachment_with_image.py +61 -0
  108. native/lib/components/attachment_with_sizes.py +33 -0
  109. native/lib/components/attachment_with_states.py +82 -0
  110. native/lib/components/attachment_with_trigger.py +37 -0
  111. native/lib/components/avatar_badge_with_icon.py +23 -0
  112. native/lib/components/avatar_basic.py +19 -0
  113. native/lib/components/avatar_dropdown_menu.py +22 -0
  114. native/lib/components/avatar_group.py +31 -0
  115. native/lib/components/avatar_group_count_icon.py +34 -0
  116. native/lib/components/avatar_sizes.py +32 -0
  117. native/lib/components/avatar_with_badge.py +22 -0
  118. native/lib/components/avatar_with_group_count.py +31 -0
  119. native/lib/components/badge_as_link.py +15 -0
  120. native/lib/components/badge_custom_colors.py +29 -0
  121. native/lib/components/badge_variants.py +14 -0
  122. native/lib/components/badge_with_icon.py +20 -0
  123. native/lib/components/badge_wtih_spinner.py +20 -0
  124. native/lib/components/bubble_group.py +39 -0
  125. native/lib/components/bubble_link_button.py +50 -0
  126. native/lib/components/bubble_reactions.py +60 -0
  127. native/lib/components/bubble_with_align.py +22 -0
  128. native/lib/components/bubble_with_variants.py +65 -0
  129. native/lib/components/button_as_link.py +11 -0
  130. native/lib/components/button_default.py +7 -0
  131. native/lib/components/button_destructive.py +7 -0
  132. native/lib/components/button_ghost.py +7 -0
  133. native/lib/components/button_group_dropdown.py +33 -0
  134. native/lib/components/button_group_input.py +17 -0
  135. native/lib/components/button_group_orientation.py +23 -0
  136. native/lib/components/button_group_separator.py +12 -0
  137. native/lib/components/button_group_size.py +29 -0
  138. native/lib/components/button_group_split.py +17 -0
  139. native/lib/components/button_icon.py +12 -0
  140. native/lib/components/button_link.py +7 -0
  141. native/lib/components/button_loading.py +21 -0
  142. native/lib/components/button_outline.py +7 -0
  143. native/lib/components/button_rounded.py +20 -0
  144. native/lib/components/button_secondary.py +7 -0
  145. native/lib/components/button_sizes.py +50 -0
  146. native/lib/components/button_with_icon.py +20 -0
  147. native/lib/components/card_edge_to_edge.py +37 -0
  148. native/lib/components/card_image.py +31 -0
  149. native/lib/components/card_small.py +61 -0
  150. native/lib/components/card_spacing.py +115 -0
  151. native/lib/components/checkbox_basic.py +21 -0
  152. native/lib/components/checkbox_description.py +28 -0
  153. native/lib/components/checkbox_disabled.py +24 -0
  154. native/lib/components/checkbox_group.py +70 -0
  155. native/lib/components/collapsible_basic.py +35 -0
  156. native/lib/components/collapsible_nested.py +75 -0
  157. native/lib/components/dialog_close_button.py +45 -0
  158. native/lib/components/dialog_no_close_button.py +18 -0
  159. native/lib/components/dialog_sticky_footer.py +46 -0
  160. native/lib/components/field_general.py +122 -0
  161. native/lib/components/frame_basic.py +21 -0
  162. native/lib/components/frame_dense.py +23 -0
  163. native/lib/components/frame_no_border.py +24 -0
  164. native/lib/components/frame_stacked.py +22 -0
  165. native/lib/components/input_basic_demo.py +14 -0
  166. native/lib/components/input_custom_input.py +15 -0
  167. native/lib/components/input_disabled.py +15 -0
  168. native/lib/components/input_email.py +14 -0
  169. native/lib/components/input_file_input.py +13 -0
  170. native/lib/components/input_group_block_end.py +51 -0
  171. native/lib/components/input_group_block_start.py +54 -0
  172. native/lib/components/input_group_button.py +82 -0
  173. native/lib/components/input_group_dropdown.py +45 -0
  174. native/lib/components/input_group_icons.py +43 -0
  175. native/lib/components/input_group_inline_end.py +19 -0
  176. native/lib/components/input_group_inline_start.py +18 -0
  177. native/lib/components/input_group_spinner.py +46 -0
  178. native/lib/components/input_group_text.py +51 -0
  179. native/lib/components/input_password.py +14 -0
  180. native/lib/components/marker_border.py +25 -0
  181. native/lib/components/marker_links_and_buttons.py +35 -0
  182. native/lib/components/marker_separator.py +21 -0
  183. native/lib/components/marker_shimmer.py +18 -0
  184. native/lib/components/marker_status.py +21 -0
  185. native/lib/components/marker_variants.py +20 -0
  186. native/lib/components/marker_with_icon.py +24 -0
  187. native/lib/components/menu_basic.py +16 -0
  188. native/lib/components/menu_destructive.py +16 -0
  189. native/lib/components/menu_icons.py +16 -0
  190. native/lib/components/menu_shortcuts.py +19 -0
  191. native/lib/components/message_with_actions.py +67 -0
  192. native/lib/components/message_with_attachments.py +67 -0
  193. native/lib/components/message_with_avatar.py +62 -0
  194. native/lib/components/message_with_group.py +38 -0
  195. native/lib/components/message_with_header_footer.py +35 -0
  196. native/lib/components/radio_group_choice_card.py +35 -0
  197. native/lib/components/radio_group_description.py +32 -0
  198. native/lib/components/radio_group_disabled.py +31 -0
  199. native/lib/components/radio_group_fieldset.py +37 -0
  200. native/lib/components/radio_group_invalid.py +35 -0
  201. native/lib/components/select_basic.py +24 -0
  202. native/lib/components/select_disabled.py +23 -0
  203. native/lib/components/select_form.py +39 -0
  204. native/lib/components/select_optgroup.py +37 -0
  205. native/lib/components/select_sizes.py +22 -0
  206. native/lib/components/separator_horizontal.py +21 -0
  207. native/lib/components/separator_list.py +26 -0
  208. native/lib/components/separator_menu.py +32 -0
  209. native/lib/components/separator_vertical.py +14 -0
  210. native/lib/components/spinner_badge.py +13 -0
  211. native/lib/components/spinner_button.py +13 -0
  212. native/lib/components/spinner_marker.py +27 -0
  213. native/lib/components/spinner_size.py +13 -0
  214. native/lib/components/switch_basic.py +18 -0
  215. native/lib/components/switch_disabled.py +21 -0
  216. native/lib/components/switch_invalid.py +23 -0
  217. native/lib/components/switch_sizes.py +32 -0
  218. native/lib/components/switch_with_label.py +20 -0
  219. native/lib/components/table_demo.py +212 -0
  220. native/lib/components/table_demo_two.py +311 -0
  221. native/lib/components/tabs_basic.py +81 -0
  222. native/lib/components/tabs_disabled.py +17 -0
  223. native/lib/components/tabs_icons.py +22 -0
  224. native/lib/components/tabs_line_variant.py +15 -0
  225. native/lib/components/tabs_vertical.py +18 -0
  226. native/lib/components/textarea_basic_demo.py +7 -0
  227. native/lib/components/textarea_disabled.py +20 -0
  228. native/lib/components/textarea_field.py +21 -0
  229. native/lib/components/textarea_invalid.py +23 -0
  230. native/lib/components/typography_blockquote.py +10 -0
  231. native/lib/components/typography_h1.py +8 -0
  232. native/lib/components/typography_h2.py +8 -0
  233. native/lib/components/typography_h3.py +8 -0
  234. native/lib/components/typography_h4.py +8 -0
  235. native/lib/components/typography_inline_code.py +8 -0
  236. native/lib/components/typography_list.py +10 -0
  237. native/lib/components/typography_p.py +8 -0
  238. native/lib/components/typography_table.py +58 -0
  239. native/lib/dashboards/__init__.py +0 -0
  240. native/lib/dashboards/dashboard_01.py +358 -0
  241. native/lib/dashboards/dashboard_02.py +283 -0
  242. native/lib/dashboards/dashboard_03.py +444 -0
  243. native/lib/examples/__init__.py +0 -0
  244. native/lib/examples/card_01.py +80 -0
  245. native/lib/examples/card_02.py +59 -0
  246. native/lib/examples/card_03.py +81 -0
  247. native/lib/examples/card_04.py +92 -0
  248. native/lib/examples/card_05.py +64 -0
  249. native/lib/examples/card_06.py +81 -0
  250. native/lib/examples/card_07.py +48 -0
  251. native/lib/examples/card_08.py +37 -0
  252. native/lib/examples/card_09.py +28 -0
  253. native/lib/examples/card_10.py +76 -0
  254. native/lib/examples/card_11.py +128 -0
  255. native/lib/examples/card_12.py +39 -0
  256. native/lib/examples/card_13.py +33 -0
  257. native/lib/examples/card_14.py +35 -0
  258. native/lib/examples/card_15.py +65 -0
  259. native/lib/examples/decex_3.py +44 -0
  260. native/lib/examples/docex_1.py +31 -0
  261. native/lib/examples/docex_2.py +32 -0
  262. native/lib/examples/docex_4.py +48 -0
  263. native/lib/examples/docex_5.py +66 -0
  264. native/lib/typeset/set_four.md +28 -0
  265. native/lib/typeset/set_one.md +241 -0
  266. native/lib/typeset/set_three.md +35 -0
  267. native/lib/typeset/set_two.md +49 -0
  268. native/lib/utilities/__init__.py +0 -0
  269. native/lib/utilities/scroll_fade_demo.py +20 -0
  270. native/lib/utilities/scroll_fade_disabled.py +48 -0
  271. native/lib/utilities/scroll_fade_edges.py +109 -0
  272. native/lib/utilities/scroll_fade_horizontal.py +35 -0
  273. native/lib/utilities/scroll_fade_no_fade.py +20 -0
  274. native/lib/utilities/scroll_fade_size.py +48 -0
  275. native/lib/utilities/shimmer_angle.py +17 -0
  276. native/lib/utilities/shimmer_color.py +15 -0
  277. native/lib/utilities/shimmer_duration.py +20 -0
  278. native/lib/utilities/shimmer_none.py +9 -0
  279. native/lib/utilities/shimmer_play_once.py +28 -0
  280. native/lib/utilities/shimmer_reverse.py +12 -0
  281. native/lib/utilities/shimmer_spread.py +17 -0
  282. native/native.py +13 -0
  283. native/pages/__init__.py +0 -0
  284. native/pages/blocks.py +148 -0
  285. native/pages/components.py +295 -0
  286. native/pages/create.py +438 -0
  287. native/pages/dashboard.py +40 -0
  288. native/pages/docs.py +245 -0
  289. native/pages/landing.py +88 -0
  290. native/pages/typeset.py +274 -0
  291. native/py.typed +0 -0
  292. native/registry/__init__.py +0 -0
  293. native/registry/anatomy/accordion.py +12 -0
  294. native/registry/anatomy/attachment.py +10 -0
  295. native/registry/anatomy/avatar.py +6 -0
  296. native/registry/anatomy/badge.py +3 -0
  297. native/registry/anatomy/bubble.py +6 -0
  298. native/registry/anatomy/button.py +3 -0
  299. native/registry/anatomy/button_group.py +7 -0
  300. native/registry/anatomy/card.py +11 -0
  301. native/registry/anatomy/checkbox.py +5 -0
  302. native/registry/anatomy/collapsible.py +6 -0
  303. native/registry/anatomy/dialog.py +10 -0
  304. native/registry/anatomy/field.py +10 -0
  305. native/registry/anatomy/frame.py +11 -0
  306. native/registry/anatomy/input.py +3 -0
  307. native/registry/anatomy/input_group.py +6 -0
  308. native/registry/anatomy/marker.py +6 -0
  309. native/registry/anatomy/menu.py +12 -0
  310. native/registry/anatomy/message.py +11 -0
  311. native/registry/anatomy/radio_group.py +6 -0
  312. native/registry/anatomy/select.py +13 -0
  313. native/registry/anatomy/separator.py +3 -0
  314. native/registry/anatomy/spinner.py +3 -0
  315. native/registry/anatomy/switch.py +3 -0
  316. native/registry/anatomy/table.py +15 -0
  317. native/registry/anatomy/tabs.py +8 -0
  318. native/registry/anatomy/textarea.py +3 -0
  319. native/registry/colors.py +266 -0
  320. native/registry/components.py +176 -0
  321. native/registry/fonts.py +97 -0
  322. native/registry/radii.py +24 -0
  323. native/registry/styles.py +61 -0
  324. native/registry/themes.py +435 -0
  325. native/registry/typeset_options.py +77 -0
  326. native/templates/__init__.py +0 -0
  327. native/templates/_copy_btn.py +45 -0
  328. native/templates/_dashboard.py +84 -0
  329. native/templates/_dashboard_example.py +63 -0
  330. native/templates/_demo.py +81 -0
  331. native/templates/_get_code.py +89 -0
  332. native/templates/_get_typeset_code.py +115 -0
  333. native/templates/_intro.py +169 -0
  334. native/templates/_meta_tags.py +39 -0
  335. native/templates/_source.py +57 -0
  336. native/templates/_usage.py +112 -0
  337. native/templates/anatomy.py +147 -0
  338. native/templates/dashboard_introspect.py +65 -0
  339. native/templates/docpage.py +41 -0
  340. native/templates/footer.py +35 -0
  341. native/templates/github.py +25 -0
  342. native/templates/layout.py +57 -0
  343. native/templates/masonary.py +42 -0
  344. native/templates/navbar.py +116 -0
  345. native/templates/sidebar.py +145 -0
  346. native/templates/sublayout.py +63 -0
  347. native/templates/theme.py +47 -0
  348. native/templates/toc.py +225 -0
cli/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # buridan
2
+
3
+ A command line interface (CLI) for adding components and themes to your project.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ uv add gtm-ui-native
9
+ # or
10
+ pip install gtm-ui-native
11
+ ```
12
+
13
+ The package on PyPI is `gtm-ui-native`; the command it installs is `buridan`.
14
+
15
+ ## Package naming
16
+
17
+ This fork publishes `gtm-ui-native` from [elviskahoro/native](https://github.com/elviskahoro/native). Upstream `LineIndent/native` publishes `buridan`, and `LineIndent/ui` publishes `buridan-create`. All three install a `buridan` command, so install only one per environment.
18
+
19
+ ## create
20
+
21
+ Opens the Buridan Native theme builder in your browser. Use it to customize your design system and generate a unique preset ID.
22
+
23
+ ```bash
24
+ buridan create
25
+ ```
26
+
27
+ ## init
28
+
29
+ Initialize Buridan Native in your project. This command sets up CSS utilities (shimmer, scroll fade) in `assets/globals.css` and updates `rxconfig.py` with the required Tailwind configuration.
30
+
31
+ ```bash
32
+ buridan init
33
+ ```
34
+
35
+ ## apply
36
+
37
+ Apply a theme preset to your project. Generates `:root` and `.dark` CSS variable blocks in `assets/globals.css` based on the preset ID from the theme builder.
38
+
39
+ ```bash
40
+ buridan apply --preset <ID>
41
+ ```
42
+
43
+ Arguments:
44
+
45
+ - `--preset`: The theme preset ID from the Buridan Native theme builder. Use `b0` for the default theme.
46
+
47
+ Example:
48
+
49
+ ```bash
50
+ buridan apply --preset b0
51
+ buridan apply --preset b1000
52
+ ```
53
+
54
+ ## add
55
+
56
+ Add components and their dependencies to your project.
57
+
58
+ ```bash
59
+ buridan add <name>
60
+ ```
61
+
62
+ You can add multiple components at once:
63
+
64
+ ```bash
65
+ buridan add button input select
66
+ ```
67
+
68
+ Blocks (charts, dashboards, etc.) can be added the same way:
69
+
70
+ ```bash
71
+ buridan add line_chart_01
72
+ ```
73
+
74
+ Components are placed in `components/`, blocks in `blocks/`. Dependencies are resolved and added automatically.
75
+
76
+ > **Note:** Components require a theme to render correctly. Run `buridan apply` before using components.
77
+
78
+ ## list
79
+
80
+ Display all available components and blocks.
81
+
82
+ ```bash
83
+ buridan list
84
+ ```
85
+
86
+ ## Recommended workflow
87
+
88
+ ```bash
89
+ buridan create # build your theme, copy the preset ID
90
+ buridan init # set up utilities and Tailwind config
91
+ buridan apply --preset <ID> # apply your theme
92
+ buridan add button input select # add the components you need
93
+ ```
94
+
95
+ # Documentation
96
+
97
+ Visit [https://native.buridan.dev/components](https://ui.buridan.dev/components) to see all the components.
98
+
99
+ Visit [https://native.buridan.dev/docs/getting-started/cli](https://ui.buridan.dev/docs/getting-started/cli) section to get started.
100
+
101
+ ## License
102
+
103
+ Licensed under the [MIT license](https://github.com/elviskahoro/native/blob/main/LICENSE.md)
cli/__init__.py ADDED
File without changes
cli/dev.py ADDED
@@ -0,0 +1,330 @@
1
+ import argparse
2
+ import os
3
+ import subprocess # nosec B404 - fixed-argv dev runner
4
+ from pathlib import Path
5
+
6
+ import questionary
7
+ from InquirerPy.base.control import Choice
8
+ from InquirerPy.prompts.fuzzy import FuzzyPrompt
9
+
10
+ DOCS_DIR = Path("docs")
11
+ MIN_PATH_PARTS = 2
12
+
13
+
14
+ def build_index() -> dict[str, list[str]]:
15
+ """Builds:
16
+ {
17
+ "getting-started": ["introduction", "installation"],
18
+ "components": ["button", "input"]
19
+ }.
20
+ """
21
+ sections: dict[str, list[str]] = {}
22
+
23
+ for file in DOCS_DIR.glob("**/*.md"):
24
+ rel = file.relative_to(DOCS_DIR)
25
+ parts = rel.with_suffix("").parts
26
+
27
+ if len(parts) < MIN_PATH_PARTS:
28
+ continue
29
+
30
+ section = parts[0].replace("_", "-")
31
+ page = parts[1].replace("_", "-")
32
+
33
+ sections.setdefault(section, []).append(page)
34
+
35
+ for pages in sections.values():
36
+ pages.sort()
37
+
38
+ return sections
39
+
40
+
41
+ def build_arg_parser() -> argparse.ArgumentParser:
42
+ parser = argparse.ArgumentParser(
43
+ prog="dev",
44
+ description="Run the docs site with a specific set of pages loaded.",
45
+ epilog=(
46
+ "Examples:\n"
47
+ " uv run dev interactive picker\n"
48
+ " uv run dev button input load pages by name\n"
49
+ " uv run dev components/button load by full path\n"
50
+ " uv run dev --section components load a whole section\n"
51
+ " uv run dev --section components auth load multiple sections\n"
52
+ " uv run dev prod run full site, prod mode\n"
53
+ ),
54
+ formatter_class=argparse.RawDescriptionHelpFormatter,
55
+ )
56
+
57
+ parser.add_argument(
58
+ "pages",
59
+ nargs="*",
60
+ help="page names (e.g. 'button') or full paths (e.g. 'components/button')",
61
+ )
62
+ parser.add_argument(
63
+ "--section",
64
+ "-s",
65
+ nargs="+",
66
+ default=[],
67
+ metavar="SECTION",
68
+ help="load every page in the given section(s)",
69
+ )
70
+ parser.add_argument(
71
+ "--prod",
72
+ action="store_true",
73
+ help="run in prod mode (loads the full site)",
74
+ )
75
+
76
+ return parser
77
+
78
+
79
+ def _resolve_single_name(
80
+ name: str,
81
+ name_to_paths: dict[str, list[str]],
82
+ sections: dict[str, list[str]],
83
+ ) -> tuple[str | None, str | None]:
84
+ """Resolves a single page name (bare or full path).
85
+
86
+ Returns: (resolved_path, error_message) tuple. error_message is None if successful.
87
+ """
88
+ if "/" in name:
89
+ section, _, page = name.partition("/")
90
+ if page in sections.get(section, []):
91
+ return name, None
92
+ return None, f"page not found: '{name}'"
93
+
94
+ matches = name_to_paths.get(name)
95
+ if not matches:
96
+ return None, f"page not found: '{name}'"
97
+ if len(matches) > 1:
98
+ error = (
99
+ f"'{name}' is ambiguous (matches: {', '.join(matches)}) "
100
+ f"— use the full path, e.g. '{matches[0]}'"
101
+ )
102
+ return None, error
103
+ return matches[0], None
104
+
105
+
106
+ def _add_path_if_new(path: str, seen: set[str], resolved: list[str]) -> None:
107
+ """Add path to resolved list if not already seen (deduplication)."""
108
+ if path not in seen:
109
+ seen.add(path)
110
+ resolved.append(path)
111
+
112
+
113
+ def _process_section_names(
114
+ section_names: list[str],
115
+ sections: dict[str, list[str]],
116
+ ) -> tuple[list[str], list[str]]:
117
+ """Expands section names into full page paths.
118
+
119
+ Returns: (paths, errors) tuple with all expanded paths and error messages.
120
+ """
121
+ paths = []
122
+ errors = []
123
+ for section in section_names:
124
+ if section not in sections:
125
+ errors.append(f"section not found: '{section}'")
126
+ continue
127
+ paths.extend(f"{section}/{page}" for page in sections[section])
128
+ return paths, errors
129
+
130
+
131
+ def resolve_pages(
132
+ names: list[str],
133
+ section_names: list[str],
134
+ sections: dict[str, list[str]],
135
+ ) -> list[str] | None:
136
+ """Resolves bare page names ('button'), full paths ('components/button'),
137
+ and --section names into a deduped list of full 'section/page' paths.
138
+
139
+ Returns None (and prints errors) if anything couldn't be resolved.
140
+ """
141
+ name_to_paths: dict[str, list[str]] = {}
142
+ for section, pages in sections.items():
143
+ for page in pages:
144
+ name_to_paths.setdefault(page, []).append(f"{section}/{page}")
145
+
146
+ resolved = []
147
+ seen: set[str] = set()
148
+ errors = []
149
+
150
+ for name in names:
151
+ path, error = _resolve_single_name(name, name_to_paths, sections)
152
+ if error:
153
+ errors.append(error)
154
+ elif path:
155
+ _add_path_if_new(path, seen, resolved)
156
+
157
+ section_paths, section_errors = _process_section_names(section_names, sections)
158
+ errors.extend(section_errors)
159
+ for path in section_paths:
160
+ _add_path_if_new(path, seen, resolved)
161
+
162
+ if errors:
163
+ for e in errors:
164
+ print(f"Error: {e}")
165
+ return None
166
+
167
+ if not resolved:
168
+ return None
169
+
170
+ return resolved
171
+
172
+
173
+ def select_env() -> str | None:
174
+ env = questionary.select(
175
+ "Run mode:",
176
+ choices=["dev", "prod"],
177
+ ).ask()
178
+
179
+ if env is None:
180
+ return None
181
+
182
+ return env
183
+
184
+
185
+ def select_pages_fuzzy(sections: dict[str, list[str]]) -> list[str] | None:
186
+ """Fuzzy search + multi-select over all pages."""
187
+ choices = []
188
+
189
+ for section, pages in sections.items():
190
+ for page in pages:
191
+ path = f"{section}/{page}"
192
+ choices.append(Choice(value=path, name=f" {path}"))
193
+
194
+ result = FuzzyPrompt(
195
+ message=(
196
+ "Search pages (type to filter, tab to select multiple, enter to confirm):"
197
+ ),
198
+ choices=choices,
199
+ multiselect=True,
200
+ max_height="70%",
201
+ instruction="(type a page or section/page path)",
202
+ ).execute()
203
+
204
+ if not result:
205
+ return None
206
+
207
+ pages = []
208
+ seen = set()
209
+
210
+ for item in result:
211
+ if item not in seen:
212
+ seen.add(item)
213
+ pages.append(item)
214
+
215
+ return pages
216
+
217
+
218
+ def run_reflex(env: str, pages: list[str] | None) -> str | None:
219
+ """Runs Reflex and prints correct URLs ONLY after the actual port is known."""
220
+ if env == "dev":
221
+ os.environ["BURIDAN_DEV_MODE"] = "true"
222
+
223
+ if pages:
224
+ os.environ["BURIDAN_DEV_PAGES"] = ",".join(pages)
225
+ else:
226
+ os.environ.pop("BURIDAN_DEV_PAGES", None)
227
+ else:
228
+ os.environ.pop("BURIDAN_DEV_MODE", None)
229
+ os.environ.pop("BURIDAN_DEV_PAGES", None)
230
+
231
+ cmd = ["uv", "run", "reflex", "run", "--env", env]
232
+
233
+ process = subprocess.Popen( # noqa: S603 # nosec B603 - fixed argv, env validated upstream
234
+ cmd,
235
+ stdout=subprocess.PIPE,
236
+ stderr=subprocess.STDOUT,
237
+ text=True,
238
+ )
239
+
240
+ port = None
241
+
242
+ if process.stdout is None:
243
+ msg = "reflex subprocess has no stdout pipe"
244
+ raise RuntimeError(msg)
245
+
246
+ for line in process.stdout:
247
+ print(line, end="")
248
+
249
+ # Detect actual runtime port from Reflex output (only once)
250
+ if port is None and "App running at:" in line:
251
+ try:
252
+ port = line.split("http://localhost:")[1].split("/")[0]
253
+
254
+ # Print correct clickable URLs ONLY when server is ready
255
+ if pages:
256
+ base_url = f"http://localhost:{port}/docs"
257
+
258
+ print("\nLoading specified URLs:")
259
+ for page in pages:
260
+ print(f"{base_url}/{page}")
261
+
262
+ except IndexError:
263
+ pass
264
+
265
+ return port
266
+
267
+
268
+ def _run_interactive_mode(sections: dict[str, list[str]]) -> None:
269
+ """Interactive mode: prompts for environment and pages, then runs reflex."""
270
+ env = select_env()
271
+ if env is None:
272
+ print("Exiting")
273
+ return
274
+
275
+ if env == "prod":
276
+ print("Launching full site (prod mode)...")
277
+ run_reflex(env, pages=None)
278
+ return
279
+
280
+ selected_pages = select_pages_fuzzy(sections)
281
+ if selected_pages is None:
282
+ print("Exiting")
283
+ return
284
+
285
+ confirm = questionary.confirm(
286
+ f"Run reflex in {env} with {len(selected_pages)} pages?",
287
+ ).ask()
288
+
289
+ if not confirm:
290
+ print("Cancelled")
291
+ return
292
+
293
+ run_reflex(env, selected_pages)
294
+
295
+
296
+ def main() -> None:
297
+ sections = build_index()
298
+
299
+ parser = build_arg_parser()
300
+ args = parser.parse_args()
301
+
302
+ if args.prod or (args.pages and args.pages[0] == "prod"):
303
+ print("Launching full site (prod mode)...")
304
+ run_reflex("prod", pages=None)
305
+ return
306
+
307
+ requested_pages = list(args.pages)
308
+ if requested_pages and requested_pages[0] == "dev":
309
+ requested_pages = requested_pages[1:]
310
+
311
+ if requested_pages or args.section:
312
+ resolved_pages = resolve_pages(requested_pages, args.section, sections)
313
+ if resolved_pages is None:
314
+ return
315
+
316
+ print(f"Running reflex in dev with {len(resolved_pages)} page(s):")
317
+ for page in resolved_pages:
318
+ print(f" {page}")
319
+
320
+ run_reflex("dev", resolved_pages)
321
+ return
322
+
323
+ _run_interactive_mode(sections)
324
+
325
+
326
+ if __name__ == "__main__":
327
+ try:
328
+ main()
329
+ except KeyboardInterrupt:
330
+ print("\nExiting")