seams 0.1.0 → 0.2.0

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 (222) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +212 -12
  3. data/README.md +235 -82
  4. data/lib/generators/seams/accounts/accounts_generator.rb +31 -3
  5. data/lib/generators/seams/accounts/templates/app/controllers/memberships_controller.rb.tt +100 -0
  6. data/lib/generators/seams/accounts/templates/app/views/memberships/index.html.erb.tt +52 -0
  7. data/lib/generators/seams/accounts/templates/config/routes.rb.tt +10 -4
  8. data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +35 -2
  9. data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +13 -0
  10. data/lib/generators/seams/accounts/templates/spec/runtime/authorization_spec.rb.tt +102 -0
  11. data/lib/generators/seams/accounts/templates/spec/runtime/memberships_flow_spec.rb.tt +145 -0
  12. data/lib/generators/seams/admin/admin_generator.rb +51 -4
  13. data/lib/generators/seams/admin/templates/README.md.tt +33 -3
  14. data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +156 -8
  15. data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +9 -1
  16. data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +9 -1
  17. data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +6 -0
  18. data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +7 -1
  19. data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +6 -0
  20. data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +6 -0
  21. data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +6 -0
  22. data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +6 -0
  23. data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +6 -0
  24. data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +6 -0
  25. data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +12 -2
  26. data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +7 -1
  27. data/lib/generators/seams/admin/templates/app/fields/admin/fields/belongs_to.rb.tt +12 -0
  28. data/lib/generators/seams/admin/templates/app/fields/admin/fields/dashboard_option.rb.tt +22 -0
  29. data/lib/generators/seams/admin/templates/app/fields/admin/fields/has_many.rb.tt +12 -0
  30. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +4 -0
  31. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +6 -0
  32. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +51 -10
  33. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +4 -0
  34. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +6 -0
  35. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +6 -0
  36. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +6 -0
  37. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +6 -0
  38. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +6 -0
  39. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +7 -0
  40. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +6 -0
  41. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +6 -0
  42. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +6 -0
  43. data/lib/generators/seams/admin/templates/app/views/seams/admin/application/_index_header.html.erb.tt +57 -0
  44. data/lib/generators/seams/admin/templates/app/views/seams/admin/application/_navigation.html.erb.tt +14 -0
  45. data/lib/generators/seams/admin/templates/config/routes.rb.tt +24 -14
  46. data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +3 -0
  47. data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +3 -1
  48. data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +4 -2
  49. data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +1 -2
  50. data/lib/generators/seams/auth/auth_generator.rb +1 -2
  51. data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +3 -0
  52. data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +4 -0
  53. data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +5 -0
  54. data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +5 -0
  55. data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +5 -1
  56. data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +12 -1
  57. data/lib/generators/seams/auth/templates/lib/engine.rb.tt +27 -0
  58. data/lib/generators/seams/billing/billing_generator.rb +4 -3
  59. data/lib/generators/seams/billing/templates/README.md.tt +26 -0
  60. data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +2 -0
  61. data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +2 -0
  62. data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +8 -7
  63. data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +9 -7
  64. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +18 -3
  65. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +7 -21
  66. data/lib/generators/seams/billing/templates/lib/engine.rb.tt +15 -0
  67. data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +10 -10
  68. data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +40 -14
  69. data/lib/generators/seams/billing/templates/lib/stripe/payload.rb.tt +90 -0
  70. data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +11 -1
  71. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +1 -0
  72. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +1 -0
  73. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +15 -4
  74. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +7 -1
  75. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +18 -1
  76. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +26 -5
  77. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +16 -2
  78. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +16 -2
  79. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +17 -2
  80. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +16 -2
  81. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +16 -2
  82. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +1 -0
  83. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +1 -0
  84. data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +47 -1
  85. data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +39 -1
  86. data/lib/generators/seams/design/design_generator.rb +647 -0
  87. data/lib/generators/seams/design/templates/README.md.tt +129 -0
  88. data/lib/generators/seams/design/templates/app/assets/tailwind/_tokens.css +322 -0
  89. data/lib/generators/seams/design/templates/app/assets/tailwind/themes/_quire.css +42 -0
  90. data/lib/generators/seams/design/templates/app/controllers/design/dashboard_controller.rb.tt +30 -0
  91. data/lib/generators/seams/design/templates/app/controllers/design/guide_controller.rb.tt +28 -0
  92. data/lib/generators/seams/design/templates/app/form_builders/design/form_builder.rb.tt +90 -0
  93. data/lib/generators/seams/design/templates/app/helpers/design/ui_helper.rb.tt +37 -0
  94. data/lib/generators/seams/design/templates/app/views/design/dashboard/index.html.erb.tt +38 -0
  95. data/lib/generators/seams/design/templates/app/views/design/guide/index.html.erb.tt +19 -0
  96. data/lib/generators/seams/design/templates/app/views/layouts/application.html.erb.tt +61 -0
  97. data/lib/generators/seams/design/templates/app/views/layouts/design/guide.html.erb.tt +21 -0
  98. data/lib/generators/seams/design/templates/app/views/ui/_banner.html.erb.tt +10 -0
  99. data/lib/generators/seams/design/templates/app/views/ui/_breadcrumb.html.erb.tt +8 -0
  100. data/lib/generators/seams/design/templates/app/views/ui/_build_row.html.erb.tt +14 -0
  101. data/lib/generators/seams/design/templates/app/views/ui/_button.html.erb.tt +4 -0
  102. data/lib/generators/seams/design/templates/app/views/ui/_card.html.erb.tt +2 -0
  103. data/lib/generators/seams/design/templates/app/views/ui/_chapter_row.html.erb.tt +11 -0
  104. data/lib/generators/seams/design/templates/app/views/ui/_checkbox.html.erb.tt +12 -0
  105. data/lib/generators/seams/design/templates/app/views/ui/_counter.html.erb.tt +2 -0
  106. data/lib/generators/seams/design/templates/app/views/ui/_data_table.html.erb.tt +25 -0
  107. data/lib/generators/seams/design/templates/app/views/ui/_dialog.html.erb.tt +14 -0
  108. data/lib/generators/seams/design/templates/app/views/ui/_diff.html.erb.tt +12 -0
  109. data/lib/generators/seams/design/templates/app/views/ui/_drawer.html.erb.tt +7 -0
  110. data/lib/generators/seams/design/templates/app/views/ui/_empty.html.erb.tt +5 -0
  111. data/lib/generators/seams/design/templates/app/views/ui/_field.html.erb.tt +14 -0
  112. data/lib/generators/seams/design/templates/app/views/ui/_icon.html.erb.tt +2 -0
  113. data/lib/generators/seams/design/templates/app/views/ui/_icon_sprite.html.erb.tt +22 -0
  114. data/lib/generators/seams/design/templates/app/views/ui/_input_group.html.erb.tt +11 -0
  115. data/lib/generators/seams/design/templates/app/views/ui/_kbd.html.erb.tt +4 -0
  116. data/lib/generators/seams/design/templates/app/views/ui/_menu.html.erb.tt +18 -0
  117. data/lib/generators/seams/design/templates/app/views/ui/_meter.html.erb.tt +15 -0
  118. data/lib/generators/seams/design/templates/app/views/ui/_note.html.erb.tt +2 -0
  119. data/lib/generators/seams/design/templates/app/views/ui/_outline.html.erb.tt +9 -0
  120. data/lib/generators/seams/design/templates/app/views/ui/_pagination.html.erb.tt +12 -0
  121. data/lib/generators/seams/design/templates/app/views/ui/_panel.html.erb.tt +2 -0
  122. data/lib/generators/seams/design/templates/app/views/ui/_popover.html.erb.tt +2 -0
  123. data/lib/generators/seams/design/templates/app/views/ui/_radio.html.erb.tt +5 -0
  124. data/lib/generators/seams/design/templates/app/views/ui/_savestate.html.erb.tt +5 -0
  125. data/lib/generators/seams/design/templates/app/views/ui/_segmented.html.erb.tt +7 -0
  126. data/lib/generators/seams/design/templates/app/views/ui/_stepper.html.erb.tt +12 -0
  127. data/lib/generators/seams/design/templates/app/views/ui/_switch.html.erb.tt +6 -0
  128. data/lib/generators/seams/design/templates/app/views/ui/_tag.html.erb.tt +2 -0
  129. data/lib/generators/seams/design/templates/app/views/ui/_toast.html.erb.tt +12 -0
  130. data/lib/generators/seams/design/templates/app/views/ui/_toolbar.html.erb.tt +15 -0
  131. data/lib/generators/seams/design/templates/app/views/ui/previews/_banner.html.erb.tt +8 -0
  132. data/lib/generators/seams/design/templates/app/views/ui/previews/_breadcrumb.html.erb.tt +5 -0
  133. data/lib/generators/seams/design/templates/app/views/ui/previews/_build_row.html.erb.tt +6 -0
  134. data/lib/generators/seams/design/templates/app/views/ui/previews/_button.html.erb.tt +9 -0
  135. data/lib/generators/seams/design/templates/app/views/ui/previews/_card.html.erb.tt +12 -0
  136. data/lib/generators/seams/design/templates/app/views/ui/previews/_chapter_row.html.erb.tt +6 -0
  137. data/lib/generators/seams/design/templates/app/views/ui/previews/_checkbox.html.erb.tt +6 -0
  138. data/lib/generators/seams/design/templates/app/views/ui/previews/_counter.html.erb.tt +6 -0
  139. data/lib/generators/seams/design/templates/app/views/ui/previews/_data_table.html.erb.tt +14 -0
  140. data/lib/generators/seams/design/templates/app/views/ui/previews/_dialog.html.erb.tt +8 -0
  141. data/lib/generators/seams/design/templates/app/views/ui/previews/_diff.html.erb.tt +10 -0
  142. data/lib/generators/seams/design/templates/app/views/ui/previews/_drawer.html.erb.tt +5 -0
  143. data/lib/generators/seams/design/templates/app/views/ui/previews/_empty.html.erb.tt +5 -0
  144. data/lib/generators/seams/design/templates/app/views/ui/previews/_field.html.erb.tt +5 -0
  145. data/lib/generators/seams/design/templates/app/views/ui/previews/_input_group.html.erb.tt +5 -0
  146. data/lib/generators/seams/design/templates/app/views/ui/previews/_kbd.html.erb.tt +5 -0
  147. data/lib/generators/seams/design/templates/app/views/ui/previews/_menu.html.erb.tt +9 -0
  148. data/lib/generators/seams/design/templates/app/views/ui/previews/_meter.html.erb.tt +6 -0
  149. data/lib/generators/seams/design/templates/app/views/ui/previews/_note.html.erb.tt +2 -0
  150. data/lib/generators/seams/design/templates/app/views/ui/previews/_outline.html.erb.tt +8 -0
  151. data/lib/generators/seams/design/templates/app/views/ui/previews/_pagination.html.erb.tt +5 -0
  152. data/lib/generators/seams/design/templates/app/views/ui/previews/_panel.html.erb.tt +5 -0
  153. data/lib/generators/seams/design/templates/app/views/ui/previews/_popover.html.erb.tt +2 -0
  154. data/lib/generators/seams/design/templates/app/views/ui/previews/_radio.html.erb.tt +6 -0
  155. data/lib/generators/seams/design/templates/app/views/ui/previews/_savestate.html.erb.tt +4 -0
  156. data/lib/generators/seams/design/templates/app/views/ui/previews/_segmented.html.erb.tt +15 -0
  157. data/lib/generators/seams/design/templates/app/views/ui/previews/_stepper.html.erb.tt +6 -0
  158. data/lib/generators/seams/design/templates/app/views/ui/previews/_switch.html.erb.tt +5 -0
  159. data/lib/generators/seams/design/templates/app/views/ui/previews/_tag.html.erb.tt +8 -0
  160. data/lib/generators/seams/design/templates/app/views/ui/previews/_toast.html.erb.tt +6 -0
  161. data/lib/generators/seams/design/templates/app/views/ui/previews/_toolbar.html.erb.tt +11 -0
  162. data/lib/generators/seams/design/templates/lib/design/components.rb.tt +22 -0
  163. data/lib/generators/seams/design/templates/lib/design.rb.tt +18 -0
  164. data/lib/generators/seams/design/templates/lib/engine.rb.tt +38 -0
  165. data/lib/generators/seams/design/templates/lib/generators/design/component/component_generator.rb.tt +52 -0
  166. data/lib/generators/seams/design/templates/lib/generators/design/component/templates/component.html.erb.tt +5 -0
  167. data/lib/generators/seams/design/templates/lib/generators/design/component/templates/preview.html.erb.tt +3 -0
  168. data/lib/generators/seams/design/templates/spec/runtime/design_boot_spec.rb.tt +79 -0
  169. data/lib/generators/seams/design/templates/spec/runtime/form_builder_spec.rb.tt +97 -0
  170. data/lib/generators/seams/design/templates/spec/runtime/guide_spec.rb.tt +40 -0
  171. data/lib/generators/seams/design/templates/spec/runtime/ui_components_spec.rb.tt +70 -0
  172. data/lib/generators/seams/engine/engine_generator.rb +6 -0
  173. data/lib/generators/seams/engine/templates/Gemfile.tt +1 -1
  174. data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
  175. data/lib/generators/seams/engine/templates/rubocop.yml.tt +9 -0
  176. data/lib/generators/seams/install/install_generator.rb +72 -6
  177. data/lib/generators/seams/install/templates/Dockerfile.tt +2 -2
  178. data/lib/generators/seams/install/templates/bin_seams.tt +7 -4
  179. data/lib/generators/seams/install/templates/ci.yml.tt +18 -8
  180. data/lib/generators/seams/install/templates/deploy.yml.tt +4 -2
  181. data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +1 -1
  182. data/lib/generators/seams/install/templates/docker-entrypoint.tt +4 -2
  183. data/lib/generators/seams/install/templates/herb.yml.tt +10 -0
  184. data/lib/generators/seams/install/templates/lefthook.yml.tt +25 -0
  185. data/lib/generators/seams/install/templates/ruby-version.tt +1 -1
  186. data/lib/generators/seams/install/templates/seams.rake.tt +13 -1
  187. data/lib/generators/seams/install/templates/strong_migrations.rb.tt +26 -0
  188. data/lib/generators/seams/notifications/notifications_generator.rb +3 -2
  189. data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +5 -13
  190. data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +12 -0
  191. data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +1 -0
  192. data/lib/generators/seams/notifications/templates/lib/preferences.rb.tt +36 -0
  193. data/lib/generators/seams/permissions/permissions_generator.rb +97 -0
  194. data/lib/generators/seams/permissions/templates/config/initializers/seams_permissions.rb.tt +35 -0
  195. data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +27 -11
  196. data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +5 -13
  197. data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +2 -4
  198. data/lib/generators/seams/teams/templates/lib/engine.rb.tt +13 -0
  199. data/lib/seams/cli/list.rb +15 -0
  200. data/lib/seams/cli/quality.rb +46 -5
  201. data/lib/seams/cli/resolve.rb +9 -5
  202. data/lib/seams/cli/test_changed.rb +5 -1
  203. data/lib/seams/cli.rb +20 -0
  204. data/lib/seams/configuration.rb +24 -1
  205. data/lib/seams/cops/no_cross_engine_dependency.rb +39 -7
  206. data/lib/seams/cops/no_cross_engine_model_access.rb +79 -1
  207. data/lib/seams/event_registry.rb +19 -0
  208. data/lib/seams/events/adapter.rb +9 -0
  209. data/lib/seams/events/adapters/active_support.rb +4 -0
  210. data/lib/seams/events/publisher.rb +29 -0
  211. data/lib/seams/events.rb +1 -0
  212. data/lib/seams/generators/dummy_app_writer.rb +21 -0
  213. data/lib/seams/generators/follow_up_generator.rb +1 -2
  214. data/lib/seams/generators/host_injector.rb +9 -2
  215. data/lib/seams/generators/splicer.rb +2 -2
  216. data/lib/seams/observability/adapter.rb +22 -0
  217. data/lib/seams/observability.rb +6 -0
  218. data/lib/seams/permission_registry.rb +66 -0
  219. data/lib/seams/permissions.rb +120 -0
  220. data/lib/seams/version.rb +1 -1
  221. data/lib/seams.rb +7 -0
  222. metadata +107 -3
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Design
4
+ # The host-wide design-system helper. The only hand-written helper is the one
5
+ # that isn't a plain "render a component" — currently just ui_icon. Every
6
+ # public component (one with a preview partial) gets a `ui_<name>` helper
7
+ # defined automatically (see define_component_helpers!), so adding a component
8
+ # never means adding a helper.
9
+ #
10
+ # Mixed into ActionController::Base by the engine's to_prepare hook, so the
11
+ # helpers resolve in the host's views AND every other engine's views.
12
+ module UiHelper
13
+ # <%%= ui_icon(:close) %> — references the inline sprite by fragment.
14
+ def ui_icon(name, size: nil)
15
+ return if name.nil?
16
+
17
+ render "ui/icon", name: name, size: size
18
+ end
19
+
20
+ # Define ui_<name>(**locals, &block) for every public component. The block,
21
+ # if given, is captured and passed as the `content` local — so a component
22
+ # that takes content declares `content: nil` in its strict locals, and every
23
+ # other keyword flows straight through (extras land in the partial's trailing
24
+ # **attrs and are splatted onto its root element).
25
+ def self.define_component_helpers!
26
+ Design.component_names.each do |name|
27
+ method_name = "ui_#{name}"
28
+ next if instance_methods(false).include?(method_name.to_sym)
29
+
30
+ define_method(method_name) do |**locals, &block|
31
+ locals = locals.merge(content: capture(&block)) if block
32
+ render "ui/#{name}", **locals
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ <%% content_for :title, "Dashboard — <%= app_name %>" %>
2
+
3
+ <div class="stack-lg">
4
+ <header class="stack">
5
+ <p class="eyebrow">Dashboard</p>
6
+ <h1 class="display">Welcome to <%= app_name %></h1>
7
+ <p class="lede">
8
+ This starter dashboard ships with <code>bin/seams design --shell</code>. It is
9
+ built entirely from <code>ui_*</code> components on the seams design system —
10
+ replace it with your real home page when you have data to show.
11
+ </p>
12
+ <div class="row-wrap">
13
+ <%%= ui_button(variant: :accent) { "Primary action" } %>
14
+ <%%= link_to "Component gallery", "/design/guide", class: "btn btn-ghost" if Rails.env.local? %>
15
+ </div>
16
+ </header>
17
+
18
+ <section class="stack">
19
+ <h2 class="h2">Add an engine</h2>
20
+ <p class="small muted">
21
+ A fresh host has no feature engines yet. Generate one with <code>bin/seams &lt;name&gt;</code>
22
+ and its styled views render on top of this design system.
23
+ </p>
24
+
25
+ <div class="grid grid-auto">
26
+ <%% @suggested_engines.each do |name, summary| %>
27
+ <%%= ui_card do %>
28
+ <div class="row">
29
+ <span class="mono small muted">bin/seams <%%= name %></span>
30
+ <%%= ui_tag(label: "available", status: :neutral) %>
31
+ </div>
32
+ <h3 class="h3" style="margin-top:var(--s-2)"><%%= name.capitalize %></h3>
33
+ <p class="small muted" style="margin-top:var(--s-1)"><%%= summary %></p>
34
+ <%% end %>
35
+ <%% end %>
36
+ </div>
37
+ </section>
38
+ </div>
@@ -0,0 +1,19 @@
1
+ <%% content_for :title, "Design — component guide" %>
2
+ <main id="main" style="max-width:var(--shell-max);margin:0 auto;padding:var(--s-7) var(--s-5) var(--s-9);font-family:var(--sans);color:var(--ink)">
3
+ <p style="font-size:var(--t-cap);letter-spacing:.08em;text-transform:uppercase;color:var(--muted)">Design · living gallery</p>
4
+ <h1 style="font-family:var(--display);font-size:var(--t-display);margin:var(--s-3) 0 0">Components, rendered from the real partials</h1>
5
+ <p style="font-size:var(--t-body);color:var(--ink-2);max-width:var(--measure);margin:var(--s-4) 0 0">Every example below is the real <code><%%= "ui_*" %></code> helper rendering its
6
+ strict-locals partial. This page is built from the components themselves — add one (a partial + a preview under
7
+ <code>app/views/ui/</code>) and it appears here, with its helper, automatically.</p>
8
+ <p style="font-size:var(--t-sm);color:var(--muted);margin:var(--s-4) 0 0"><%%= @components.size %> components</p>
9
+
10
+ <div style="display:flex;flex-direction:column;gap:var(--s-7);margin-top:var(--s-7)">
11
+ <%% @components.each do |name| %>
12
+ <section>
13
+ <p style="font-size:var(--t-cap);letter-spacing:.04em;color:var(--muted);font-family:var(--mono)">ui_<%%= name %></p>
14
+ <h2 style="font-size:var(--t-h2);margin:var(--s-2) 0 var(--s-4)"><%%= name.humanize %></h2>
15
+ <div style="padding:var(--s-5);border:1px solid var(--line);border-radius:var(--r-lg);background:var(--card)"><%%= render "ui/previews/#{name}" %></div>
16
+ </section>
17
+ <%% end %>
18
+ </div>
19
+ </main>
@@ -0,0 +1,61 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="h-full">
3
+ <head>
4
+ <title><%%= content_for(:title) || "<%= app_name %>" %></title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%%= csrf_meta_tags %>
7
+ <%%= csp_meta_tag %>
8
+ <%%= yield :head %>
9
+ <%%# The "tailwind" build is the compiled entrypoint that carries the
10
+ seams:design @theme tokens + the ui_* component layer. It must be
11
+ linked, or the host boots unstyled. The "application" manifest is
12
+ kept so hosts can add their own non-Tailwind styles. %>
13
+ <%%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
14
+ <%%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
15
+ <%%= javascript_importmap_tags if respond_to?(:javascript_importmap_tags) %>
16
+ </head>
17
+
18
+ <body style="font-family:var(--sans);color:var(--ink);background:var(--paper);min-height:100vh">
19
+ <a href="#main" class="skip sr-only">Skip to content</a>
20
+
21
+ <%%# seams design — icon sprite for ui_* components %>
22
+ <%%= render "ui/icon_sprite" %>
23
+
24
+ <header class="app-header" role="banner">
25
+ <nav class="app-nav" aria-label="Primary">
26
+ <a href="/" class="app-brand">
27
+ <span class="mark" aria-hidden="true"></span>
28
+ <%= app_name %>
29
+ </a>
30
+ <span class="spacer"></span>
31
+ <%%# A starter account menu, rendered from the ui_menu component. A host
32
+ with auth swaps this for its real sign-in / account links. %>
33
+ <%%= ui_button(variant: :ghost, size: :sm) { "Account" } %>
34
+ </nav>
35
+ </header>
36
+
37
+ <%%# Flash messages, rendered through the banner component's tone variants. %>
38
+ <%% if flash[:notice].present? %>
39
+ <div class="shell" style="margin-top:var(--s-4)">
40
+ <%%= ui_banner(message: flash[:notice], kind: :info, role: "status") %>
41
+ </div>
42
+ <%% end %>
43
+ <%% if flash[:alert].present? %>
44
+ <div class="shell" style="margin-top:var(--s-4)">
45
+ <%%= ui_banner(message: flash[:alert], kind: :alert, role: "alert") %>
46
+ </div>
47
+ <%% end %>
48
+
49
+ <main id="main" class="shell" style="padding-top:var(--s-7);padding-bottom:var(--s-7)">
50
+ <%%= yield %>
51
+ </main>
52
+
53
+ <footer class="app-foot" role="contentinfo">
54
+ <div class="shell row-wrap">
55
+ <span><%= app_name %></span>
56
+ <span class="spacer"></span>
57
+ <span class="small muted">Built with the seams design system</span>
58
+ </div>
59
+ </footer>
60
+ </body>
61
+ </html>
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title><%%= content_for(:title) || "Design — component guide" %></title>
7
+ <%%= csrf_meta_tags %>
8
+ <%%= csp_meta_tag %>
9
+ <%%# The "tailwind" build is the compiled entrypoint carrying the @theme
10
+ tokens + ui_* component layer; link it or the guide renders unstyled.
11
+ The "application" manifest is kept for the host's own styles. %>
12
+ <%%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" if defined?(stylesheet_link_tag) %>
13
+ <%%= stylesheet_link_tag "application", "data-turbo-track": "reload" if defined?(stylesheet_link_tag) %>
14
+ </head>
15
+ <body>
16
+ <%%# The design-system icon sprite, so ui_icon and any sprite-referencing
17
+ component resolves on the guide page without the host layout. %>
18
+ <%%= render "ui/icon_sprite" %>
19
+ <%%= yield %>
20
+ </body>
21
+ </html>
@@ -0,0 +1,10 @@
1
+ <%%# locals: (message:, kind: :info, content: nil, **attrs) -%>
2
+ <%% icon = kind.to_sym == :alert ? :warning : :info -%>
3
+ <%%= tag.div class: class_names("banner", "banner-#{kind}"), role: "status", **attrs do %>
4
+ <%%= ui_icon(icon) %>
5
+ <%%= tag.span message %>
6
+ <%% if content -%>
7
+ <%%= tag.span class: "spacer" %>
8
+ <%%= content %>
9
+ <%% end -%>
10
+ <%% end %>
@@ -0,0 +1,8 @@
1
+ <%%# locals: (trail:, current:, **attrs) -%>
2
+ <%%= tag.nav class: "crumbs", "aria-label": "Breadcrumb", **attrs do %>
3
+ <%% trail.each do |label, href| -%>
4
+ <%%= tag.a label, href: href %>
5
+ <%%= tag.span "/", class: "sep", "aria-hidden": "true" %>
6
+ <%% end -%>
7
+ <%%= tag.b current, "aria-current": "page" %>
8
+ <%% end %>
@@ -0,0 +1,14 @@
1
+ <%%# locals: (format:, state: :queued, percent: 0, **attrs) -%>
2
+ <%% width = [[percent.to_i, 0].max, 100].min -%>
3
+ <%% status, label = { ready: [:ready, "Ready"], running: [:progress, "Building…"], queued: [:neutral, "Queued"] }.fetch(state, [:neutral, "Queued"]) -%>
4
+ <%%= tag.div class: "buildrow", **attrs do %>
5
+ <%%= tag.span format, class: "fmt" %>
6
+ <%%= tag.span class: "meter" do %>
7
+ <%%= tag.div class: "meter-track", role: "progressbar",
8
+ "aria-valuenow": width, "aria-valuemin": 0, "aria-valuemax": 100,
9
+ "aria-label": "#{format} build progress" do %>
10
+ <%%= tag.div class: class_names("meter-fill", state == :ready ? "at" : nil), style: "width:#{width}%" %>
11
+ <%% end %>
12
+ <%% end %>
13
+ <%%= ui_tag(label: label, status: status) %>
14
+ <%% end %>
@@ -0,0 +1,4 @@
1
+ <%%# locals: (variant: :default, size: nil, type: "button", content: nil, **attrs) -%>
2
+ <%%= tag.button type: type,
3
+ class: class_names("btn", "btn-#{variant}" => variant.to_s != "default", "btn-#{size}" => size.present?),
4
+ **attrs do %><%%= content %><%% end %>
@@ -0,0 +1,2 @@
1
+ <%%# locals: (content: nil, **attrs) -%>
2
+ <%%= tag.div class: "card", **attrs do %><%%= content %><%% end %>
@@ -0,0 +1,11 @@
1
+ <%%# locals: (index:, title:, status: :neutral, status_label: "Draft", **attrs) -%>
2
+ <%%= tag.li class: "chapter-row", **attrs do %>
3
+ <%%= tag.span "⠿", class: "drag", "aria-hidden": "true" %>
4
+ <%%= tag.span format("%02d", index), class: "idx" %>
5
+ <%%= tag.span title, class: "title" %>
6
+ <%%= ui_tag(label: status_label, status: status) %>
7
+ <%%= tag.span class: "moves" do %>
8
+ <%%= tag.button "▲", type: "button", "aria-label": "Move up" %>
9
+ <%%= tag.button "▼", type: "button", "aria-label": "Move down" %>
10
+ <%% end %>
11
+ <%% end %>
@@ -0,0 +1,12 @@
1
+ <%%# locals: (label:, name:, value: "1", checked: false, hint: nil, **attrs) -%>
2
+ <%% box_id = "c-#{name.to_s.parameterize}-#{value.to_s.parameterize}" -%>
3
+ <%%= tag.label class: "choice", for: box_id, **attrs do %>
4
+ <%%= tag.input type: "checkbox", name: name, id: box_id, value: value, checked: checked,
5
+ "aria-describedby": (hint ? "#{box_id}-hint" : nil) %>
6
+ <span>
7
+ <%%= tag.span label, class: "ct" %>
8
+ <%% if hint -%>
9
+ <%%= tag.span hint, class: "cd", id: "#{box_id}-hint" %>
10
+ <%% end -%>
11
+ </span>
12
+ <%% end %>
@@ -0,0 +1,2 @@
1
+ <%%# locals: (current:, max:, **attrs) -%>
2
+ <%%= tag.span class: class_names("counter", "warn" => current >= max * 0.9 && current < max, "over" => current > max), **attrs do %><%%= current %> / <%%= max %><%% end %>
@@ -0,0 +1,25 @@
1
+ <%%# locals: (columns:, rows:, **attrs) -%>
2
+ <%%= tag.div class: "table-wrap", **attrs do %>
3
+ <%%= tag.table class: "table" do %>
4
+ <thead>
5
+ <tr>
6
+ <%% columns.each do |col| -%>
7
+ <%%= tag.th scope: "col",
8
+ class: class_names("sortable" => col[:sortable], "num" => col[:num]),
9
+ "aria-sort": col[:sort] do %>
10
+ <%%= col[:label] %><%% if col[:sortable] %><%%= tag.span "↕", class: "ar", "aria-hidden": "true" %><%% end %>
11
+ <%% end %>
12
+ <%% end -%>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <%% rows.each do |row| -%>
17
+ <tr>
18
+ <%% row.each_with_index do |cell, i| -%>
19
+ <%%= tag.td cell, class: class_names("num" => columns[i] && columns[i][:num]) %>
20
+ <%% end -%>
21
+ </tr>
22
+ <%% end -%>
23
+ </tbody>
24
+ <%% end %>
25
+ <%% end %>
@@ -0,0 +1,14 @@
1
+ <%%# locals: (id:, title:, trigger:, destructive: false, content: nil, **attrs) -%>
2
+ <%%= tag.div data: { controller: "ui-dialog" }, **attrs do %>
3
+ <%%= ui_button(variant: (destructive ? :danger : :default), data: { action: "ui-dialog#open" }) { trigger } %>
4
+ <%%= tag.dialog id: id, class: "cdialog", "aria-labelledby": "#{id}-title",
5
+ data: { "ui-dialog-target": "dialog" } do %>
6
+ <div class="dialog">
7
+ <div class="dialog-head">
8
+ <%%= tag.h2 title, id: "#{id}-title" %>
9
+ <button type="button" class="x" aria-label="Close" data-action="ui-dialog#close"><%%= ui_icon(:close) %></button>
10
+ </div>
11
+ <div class="dialog-body"><%%= content %></div>
12
+ </div>
13
+ <%% end %>
14
+ <%% end %>
@@ -0,0 +1,12 @@
1
+ <%%# locals: (file:, lines:, **attrs) -%>
2
+ <%%= tag.div class: "diff", **attrs do %>
3
+ <%%= tag.p file, class: "diff-file" %>
4
+ <%% lines.each do |kind, text| -%>
5
+ <%% sign = kind == :add ? "+" : kind == :del ? "−" : " " -%>
6
+ <%% sign_label = kind == :add ? "added" : kind == :del ? "removed" : nil -%>
7
+ <%%= tag.div class: class_names("diff-row", "diff-#{kind}") do %>
8
+ <%%= tag.span sign, class: "diff-sign", "aria-label": sign_label %>
9
+ <%%= tag.span text, class: "diff-text" %>
10
+ <%% end %>
11
+ <%% end -%>
12
+ <%% end %>
@@ -0,0 +1,7 @@
1
+ <%%# locals: (title:, content: nil, **attrs) -%>
2
+ <%%= tag.aside class: "drawer", "aria-label": title, **attrs do %>
3
+ <div class="drawer-head">
4
+ <%%= tag.h2 title %>
5
+ </div>
6
+ <div class="drawer-body"><%%= content %></div>
7
+ <%% end %>
@@ -0,0 +1,5 @@
1
+ <%%# locals: (title:, content: nil, **attrs) -%>
2
+ <%%= tag.div class: "empty", **attrs do %>
3
+ <%%= tag.h3 title, class: "h3" %>
4
+ <%%= content %>
5
+ <%% end %>
@@ -0,0 +1,14 @@
1
+ <%%# locals: (label:, name:, type: "text", hint: nil, error: nil, value: nil, id: nil, **attrs) -%>
2
+ <%% field_id = id || "f-#{name.to_s.parameterize}" -%>
3
+ <%% described_by = error ? "#{field_id}-error" : (hint ? "#{field_id}-hint" : nil) -%>
4
+ <div class="field<%%= " field-error" if error %>">
5
+ <%%= tag.label label, class: "label", for: field_id %>
6
+ <%%= tag.input class: "input", type: type, name: name, id: field_id, value: value,
7
+ "aria-invalid": (error ? "true" : nil), "aria-describedby": described_by, **attrs %>
8
+ <%% if hint && !error -%>
9
+ <%%= tag.p hint, class: "hint", id: "#{field_id}-hint" %>
10
+ <%% end -%>
11
+ <%% if error -%>
12
+ <%%= tag.p error, class: "hint", id: "#{field_id}-error" %>
13
+ <%% end -%>
14
+ </div>
@@ -0,0 +1,2 @@
1
+ <%%# locals: (name:, size: nil) -%>
2
+ <%%= tag.svg class: class_names("ic", "ic-#{size}" => size.present?), "aria-hidden": "true" do %><use href="#ui-icon-<%%= name %>"></use><%% end %>
@@ -0,0 +1,22 @@
1
+ <%%# locals: () -%>
2
+ <%%# The design-system icon sprite, injected once per page. Components reference
3
+ symbols by fragment (<use href="#ui-icon-NAME">) so they work without an
4
+ external request. Render this once near the top of <body>. -%>
5
+ <svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
6
+ <symbol id="ui-icon-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="6"/><line x1="20" y1="20" x2="15.5" y2="15.5"/></symbol>
7
+ <symbol id="ui-icon-settings" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3.2"/><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1"/></symbol>
8
+ <symbol id="ui-icon-bell" viewBox="0 0 24 24"><path d="M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6z"/><path d="M10 20a2 2 0 0 0 4 0"/></symbol>
9
+ <symbol id="ui-icon-plus" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></symbol>
10
+ <symbol id="ui-icon-trash" viewBox="0 0 24 24"><polyline points="4 7 20 7"/><path d="M6 7l1 13h10l1-13"/><path d="M9 7V4h6v3"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></symbol>
11
+ <symbol id="ui-icon-chevron-up" viewBox="0 0 24 24"><polyline points="6 15 12 9 18 15"/></symbol>
12
+ <symbol id="ui-icon-chevron-down" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></symbol>
13
+ <symbol id="ui-icon-chevron-right" viewBox="0 0 24 24"><polyline points="9 6 15 12 9 18"/></symbol>
14
+ <symbol id="ui-icon-check" viewBox="0 0 24 24"><polyline points="5 12 10 17 19 7"/></symbol>
15
+ <symbol id="ui-icon-close" viewBox="0 0 24 24"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></symbol>
16
+ <symbol id="ui-icon-warning" viewBox="0 0 24 24"><path d="M12 3l9 16H3z"/><line x1="12" y1="9" x2="12" y2="14"/><circle cx="12" cy="16.8" r="0.6" fill="currentColor" stroke="none"/></symbol>
17
+ <symbol id="ui-icon-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><line x1="12" y1="11" x2="12" y2="16"/><circle cx="12" cy="8" r="0.7" fill="currentColor" stroke="none"/></symbol>
18
+ <symbol id="ui-icon-upload" viewBox="0 0 24 24"><path d="M7 18a4 4 0 0 1-.8-7.9A5 5 0 0 1 16 9a3.5 3.5 0 0 1 1 6.9"/><polyline points="9 13.5 12 10.5 15 13.5"/><line x1="12" y1="10.5" x2="12" y2="19"/></symbol>
19
+ <symbol id="ui-icon-download" viewBox="0 0 24 24"><path d="M4 16v3a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-3"/><polyline points="8 11 12 15 16 11"/><line x1="12" y1="4" x2="12" y2="15"/></symbol>
20
+ <symbol id="ui-icon-edit" viewBox="0 0 24 24"><path d="M4 20l4-1L19 8l-3-3L5 16z"/><line x1="14.5" y1="6.5" x2="17.5" y2="9.5"/></symbol>
21
+ <symbol id="ui-icon-eye" viewBox="0 0 24 24"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></symbol>
22
+ </svg>
@@ -0,0 +1,11 @@
1
+ <%%# locals: (name:, value: nil, prefix: nil, suffix: nil, placeholder: nil, **attrs) -%>
2
+ <%% input_id = "ig-#{name.to_s.parameterize}" -%>
3
+ <div class="input-group">
4
+ <%% if prefix -%>
5
+ <%%= tag.span prefix, class: "affix" %>
6
+ <%% end -%>
7
+ <%%= tag.input class: "input", type: "text", name: name, id: input_id, value: value, placeholder: placeholder, **attrs %>
8
+ <%% if suffix -%>
9
+ <%%= tag.span suffix, class: "affix suffix" %>
10
+ <%% end -%>
11
+ </div>
@@ -0,0 +1,4 @@
1
+ <%%# locals: (keys:, **attrs) -%>
2
+ <%%= tag.span **attrs do -%>
3
+ <%%= safe_join(keys.map { |key| tag.span(key, class: "kbd") }, " ") %>
4
+ <%% end -%>
@@ -0,0 +1,18 @@
1
+ <%%# locals: (items:, label: nil, **attrs) -%>
2
+ <%%= tag.div class: "menu", role: "menu", **attrs do %>
3
+ <%% if label.present? -%>
4
+ <%%= tag.p label, class: "menu-label" %>
5
+ <%% end -%>
6
+ <%% items.each do |item| -%>
7
+ <%% if item[:separator_before] -%>
8
+ <%%= tag.div "", class: "menu-sep" %>
9
+ <%% end -%>
10
+ <%% if item[:href].present? -%>
11
+ <%%= tag.a item[:label], href: item[:href], role: "menuitem",
12
+ class: class_names("danger" => item[:danger]) %>
13
+ <%% else -%>
14
+ <%%= tag.button item[:label], type: "button", role: "menuitem",
15
+ class: class_names("danger" => item[:danger]) %>
16
+ <%% end -%>
17
+ <%% end -%>
18
+ <%% end %>
@@ -0,0 +1,15 @@
1
+ <%%# locals: (value:, max:, state: nil, label: nil, **attrs) -%>
2
+ <%% pct = [(value.to_f / max * 100).round, 100].min -%>
3
+ <%%= tag.div class: "meter", **attrs do %>
4
+ <%% if label -%>
5
+ <div class="meter-label">
6
+ <%%= tag.span label %>
7
+ <%%= tag.span "#{value} / #{max}", class: "tnum" %>
8
+ </div>
9
+ <%% end -%>
10
+ <%%= tag.div class: "meter-track", role: "progressbar",
11
+ "aria-valuenow": value, "aria-valuemin": 0, "aria-valuemax": max,
12
+ "aria-label": label do %>
13
+ <%%= tag.div class: class_names("meter-fill", state), style: "width: #{pct}%" %>
14
+ <%% end %>
15
+ <%% end %>
@@ -0,0 +1,2 @@
1
+ <%%# locals: (content: nil, **attrs) -%>
2
+ <%%= tag.div class: "note", **attrs do %><%%= content %><%% end %>
@@ -0,0 +1,9 @@
1
+ <%%# locals: (items:, **attrs) -%>
2
+ <%%= tag.nav class: "outline", "aria-label": "Outline", **attrs do %>
3
+ <%% items.each do |item| -%>
4
+ <%% level = item.fetch(:level, 1) -%>
5
+ <%%= tag.a item[:label], href: item.fetch(:href, "#"),
6
+ class: class_names("l#{level}" => level > 1),
7
+ "aria-current": (item[:current] ? "true" : nil) %>
8
+ <%% end -%>
9
+ <%% end %>
@@ -0,0 +1,12 @@
1
+ <%%# locals: (current:, total:, **attrs) -%>
2
+ <%%= tag.nav class: "pagination", "aria-label": "Pagination", **attrs do %>
3
+ <%%= tag.button "‹", type: "button", "aria-label": "Previous page",
4
+ disabled: (current <= 1) %>
5
+ <%% (1..total).each do |page| -%>
6
+ <%%= tag.button page, type: "button",
7
+ "aria-current": (page == current ? "page" : nil),
8
+ "aria-label": "Page #{page}" %>
9
+ <%% end -%>
10
+ <%%= tag.button "›", type: "button", "aria-label": "Next page",
11
+ disabled: (current >= total) %>
12
+ <%% end %>
@@ -0,0 +1,2 @@
1
+ <%%# locals: (content: nil, **attrs) -%>
2
+ <%%= tag.div class: "panel", **attrs do %><%%= content %><%% end %>
@@ -0,0 +1,2 @@
1
+ <%%# locals: (content: nil, **attrs) -%>
2
+ <%%= tag.div content, class: "popover", role: "note", **attrs %>
@@ -0,0 +1,5 @@
1
+ <%%# locals: (label:, name:, value:, checked: false, **attrs) -%>
2
+ <%%= tag.label class: "choice", **attrs do %>
3
+ <%%= tag.input type: "radio", name: name, value: value, checked: checked %>
4
+ <%%= tag.span label, class: "ct" %>
5
+ <%% end %>
@@ -0,0 +1,5 @@
1
+ <%%# locals: (state: :saved, text: nil, **attrs) -%>
2
+ <%% default = { "saved" => "All changes saved", "saving" => "Saving…", "unsaved" => "Unsaved" }[state.to_s] -%>
3
+ <%%= tag.span (text || default),
4
+ class: class_names("savestate", "saving" => state.to_s == "saving", "unsaved" => state.to_s == "unsaved"),
5
+ role: "status", **attrs %>
@@ -0,0 +1,7 @@
1
+ <%%# locals: (name:, options:, selected: nil, label: nil, **attrs) -%>
2
+ <%%= tag.div class: "seg", role: "group", "aria-label": label, **attrs do %>
3
+ <%% options.each do |value, option_label| -%>
4
+ <%%= tag.button option_label, type: "button", name: name, value: value,
5
+ "aria-pressed": (value.to_s == selected.to_s).to_s %>
6
+ <%% end -%>
7
+ <%% end %>
@@ -0,0 +1,12 @@
1
+ <%%# locals: (steps:, current: 1, **attrs) -%>
2
+ <%%= tag.ol class: "stepper", **attrs do %>
3
+ <%% steps.each_with_index do |label, idx| -%>
4
+ <%% i = idx + 1 -%>
5
+ <%% done = i < current -%>
6
+ <%%= tag.li class: class_names("done" => done, "current" => i == current),
7
+ "aria-current": (i == current ? "step" : nil) do %>
8
+ <%%= tag.span class: "dot" do %><%% if done %><%%= ui_icon(:check, size: :sm) %><span class="sr-only">Completed</span><%% else %><%%= i %><%% end %><%% end %>
9
+ <%%= label %>
10
+ <%% end %>
11
+ <%% end -%>
12
+ <%% end %>
@@ -0,0 +1,6 @@
1
+ <%%# locals: (label:, name:, checked: false, **attrs) -%>
2
+ <%%= tag.label class: "switch", **attrs do %>
3
+ <%%= tag.input type: "checkbox", name: name, checked: checked, role: "switch" %>
4
+ <%%= tag.span class: "track" %>
5
+ <%%= tag.span label %>
6
+ <%% end %>
@@ -0,0 +1,2 @@
1
+ <%%# locals: (label:, status: :neutral, dot: true, **attrs) -%>
2
+ <%%= tag.span label, class: class_names("tag", "tag-#{status}", "tag-plain" => !dot), **attrs %>
@@ -0,0 +1,12 @@
1
+ <%%# locals: (message:, kind: :info, title: nil, **attrs) -%>
2
+ <%% icon = { ok: :check, alert: :warning, info: :info }.fetch(kind, :info) -%>
3
+ <%%= tag.div class: class_names("toast", "toast-#{kind}"), role: (kind == :alert ? "alert" : "status"), **attrs do %>
4
+ <%%= ui_icon(icon) %>
5
+ <div class="body">
6
+ <%% if title.present? -%>
7
+ <%%= tag.div title, class: "t" %>
8
+ <%% end -%>
9
+ <%%= tag.div message %>
10
+ </div>
11
+ <%%= tag.button class: "x", type: "button", "aria-label": "Dismiss" do %><%%= ui_icon(:close) %><%% end %>
12
+ <%% end %>
@@ -0,0 +1,15 @@
1
+ <%%# locals: (items:, label: "Formatting", **attrs) -%>
2
+ <%%= tag.div class: "toolbar", role: "toolbar", "aria-label": label, **attrs do %>
3
+ <%% items.each do |item| -%>
4
+ <%% if item[:sep_before] -%>
5
+ <%%= tag.span class: "tool-sep", "aria-hidden": "true" %>
6
+ <%% end -%>
7
+ <%%= tag.button type: "button", class: "tool", "aria-label": item[:label] do %>
8
+ <%% if item[:icon] -%>
9
+ <%%= ui_icon(item[:icon]) %>
10
+ <%% else -%>
11
+ <%%= item[:label] %>
12
+ <%% end -%>
13
+ <%% end %>
14
+ <%% end -%>
15
+ <%% end %>
@@ -0,0 +1,8 @@
1
+ <%%# locals: () -%>
2
+ <div class="stack">
3
+ <%%= ui_banner(message: "A new version is available", kind: :info) %>
4
+ <%%= ui_banner(message: "Something went wrong — please try again", kind: :alert) %>
5
+ <%%= ui_banner(message: "Scheduled maintenance Sunday 02:00 UTC", kind: :info) do %>
6
+ <%%= ui_button(variant: :primary, size: :sm) { "Learn more" } %>
7
+ <%% end %>
8
+ </div>
@@ -0,0 +1,5 @@
1
+ <%%# locals: () -%>
2
+ <%%= ui_breadcrumb(
3
+ trail: [["Home", "/"], ["Settings", "/settings"]],
4
+ current: "Profile"
5
+ ) %>
@@ -0,0 +1,6 @@
1
+ <%%# locals: () -%>
2
+ <div class="buildlist">
3
+ <%%= ui_build_row(format: "HTML", state: :ready, percent: 100) %>
4
+ <%%= ui_build_row(format: "PDF", state: :running, percent: 72) %>
5
+ <%%= ui_build_row(format: "ZIP", state: :queued, percent: 0) %>
6
+ </div>
@@ -0,0 +1,9 @@
1
+ <%%# locals: () -%>
2
+ <div class="row-wrap">
3
+ <%%= ui_button(variant: :primary) { "Primary" } %>
4
+ <%%= ui_button { "Default" } %>
5
+ <%%= ui_button(variant: :ghost) { "Ghost" } %>
6
+ <%%= ui_button(variant: :danger) { "Danger" } %>
7
+ <%%= ui_button(variant: :primary, size: :sm) { "Small" } %>
8
+ <%%= ui_button(disabled: true) { "Disabled" } %>
9
+ </div>
@@ -0,0 +1,12 @@
1
+ <%%# locals: () -%>
2
+ <div class="stack">
3
+ <%%= ui_card do %>
4
+ <h3 class="h3">Project settings</h3>
5
+ <p class="small muted">Control how this project is configured.</p>
6
+ <%% end %>
7
+
8
+ <%%= ui_card(id: "card-export", data: { panel: "export" }) do %>
9
+ <h3 class="h3">Output formats</h3>
10
+ <p class="small muted">Files are generated on every change.</p>
11
+ <%% end %>
12
+ </div>
@@ -0,0 +1,6 @@
1
+ <%%# locals: () -%>
2
+ <ul class="chapter-list">
3
+ <%%= ui_chapter_row(index: 1, title: "Getting started", status: :ready, status_label: "Saved") %>
4
+ <%%= ui_chapter_row(index: 2, title: "Core concepts", status: :progress, status_label: "Editing") %>
5
+ <%%= ui_chapter_row(index: 3, title: "Advanced usage", status: :neutral, status_label: "Draft") %>
6
+ </ul>
@@ -0,0 +1,6 @@
1
+ <%%# locals: () -%>
2
+ <div class="stack">
3
+ <%%= ui_checkbox(label: "Email", name: "channels[]", value: "email", checked: true) %>
4
+ <%%= ui_checkbox(label: "SMS", name: "channels[]", value: "sms", checked: true) %>
5
+ <%%= ui_checkbox(label: "Webhook", name: "channels[]", value: "webhook", hint: "POSTs a JSON payload") %>
6
+ </div>