trek 2.0.1 → 2.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.prettierignore +2 -0
  3. data/.rubocop.yml +11 -0
  4. data/AGENTS.md +5 -2
  5. data/CHANGELOG.md +47 -1
  6. data/Gemfile.lock +1 -1
  7. data/Makefile +13 -0
  8. data/app/assets/icons/trek/audit.svg +1 -0
  9. data/app/assets/icons/trek/print.svg +1 -0
  10. data/app/components/trek/dashboard/analytics_component/analytics_component.html.slim +2 -0
  11. data/app/components/trek/dashboard/analytics_component.rb +1 -0
  12. data/app/components/trek/dashboard/chart_component/chart_component.css +192 -0
  13. data/app/components/trek/dashboard/chart_component/chart_component.html.slim +6 -0
  14. data/app/components/trek/dashboard/chart_component/chart_component.js +1 -0
  15. data/app/components/trek/dashboard/chart_component.rb +27 -0
  16. data/app/components/trek/dashboard/tile_component/tile_component.css +40 -0
  17. data/app/components/trek/dashboard/tile_component/tile_component.html.slim +14 -4
  18. data/app/components/trek/dashboard/tile_component.rb +6 -2
  19. data/app/components/trek/entries/item_component/item_component.css +1 -0
  20. data/app/components/trek/form/actions_component/actions_component.css +6 -7
  21. data/app/components/trek/form/content_editor_component/content_editor_component.html.slim +10 -12
  22. data/app/components/trek/form/content_editor_component/content_editor_component.js +6 -0
  23. data/app/components/trek/form/content_editor_component.rb +4 -2
  24. data/app/components/trek/form/fieldset_component/fieldset_component.css +4 -3
  25. data/app/components/trek/form/group_component/group_component.css +5 -2
  26. data/app/components/trek/layout_component/layout_component.css +20 -0
  27. data/app/components/trek/layout_component/layout_component.html.slim +16 -0
  28. data/app/components/trek/layout_component/layout_component.js +35 -6
  29. data/app/components/trek/menu_component.rb +15 -2
  30. data/app/controllers/concerns/trek/model.rb +13 -6
  31. data/app/controllers/trek/resource_controller.rb +9 -1
  32. data/app/formatters/prosemirror_to_html/global_id_to_links_formatter.rb +3 -1
  33. data/app/helpers/trek/i18n_helper.rb +1 -1
  34. data/app/models/prosemirror.rb +5 -2
  35. data/app/views/trek/panels/images/_form.html.slim +1 -1
  36. data/app/views/trek/panels/images/create.turbo_stream.slim +3 -3
  37. data/app/views/trek/panels/images/new.html.slim +1 -1
  38. data/app/views/trek/panels/links/_form.html.slim +1 -1
  39. data/app/views/trek/panels/links/create.turbo_stream.slim +3 -3
  40. data/app/views/trek/panels/links/new.html.slim +1 -1
  41. data/app/views/trek/panels/prompts/_form.html.slim +1 -1
  42. data/app/views/trek/panels/prompts/create.turbo_stream.slim +3 -3
  43. data/app/views/trek/panels/prompts/new.html.slim +1 -1
  44. data/docs/.vitepress/config.mjs +2 -1
  45. data/docs/deploy.md +57 -0
  46. data/docs/getting-started.md +1 -0
  47. data/docs/reference/controllers.md +23 -0
  48. data/docs/reference/generators/install.md +2 -0
  49. data/docs/reference/generators/scaffold.md +12 -0
  50. data/lib/generators/trek/install/config_generator.rb +9 -1
  51. data/lib/generators/trek/install/postmark_generator.rb +2 -0
  52. data/lib/generators/trek/install/prosopite_generator.rb +56 -0
  53. data/lib/generators/trek/install/scalingo_generator.rb +19 -7
  54. data/lib/generators/trek/install/yarn_generator.rb +1 -1
  55. data/lib/generators/trek/install_generator.rb +1 -0
  56. data/lib/generators/trek/scaffold_generator.rb +37 -5
  57. data/lib/generators/trek/templates/controllers/admin/scaffold_controller.rb.tt +2 -2
  58. data/lib/generators/trek/templates/locales/scaffold/model.en.yml.tt +4 -3
  59. data/lib/generators/trek/templates/spec/requests/admin/n_plus_one_spec.rb +28 -0
  60. data/lib/generators/trek/templates/views/admin/scaffold/show.html.slim.tt +2 -2
  61. data/lib/generators/trek/upgrade/v2_generator.rb +31 -0
  62. data/lib/trek/version.rb +1 -1
  63. data/package.json +1 -1
  64. data/vendor/tom_select/css/tom-select.custom.css +0 -1
  65. metadata +11 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36520ed5aaf6b1c69a1b34ffc8d2bf89bf225c0548edbb62909edf18fa54c16a
4
- data.tar.gz: '05296e71c1df00a73fe624ac48e5c0631f048480c643fe01a194c077405bed16'
3
+ metadata.gz: 76621e65ede62feb9ed4fd8aa029352bfbdda31c814a7e9679330597a631b2f6
4
+ data.tar.gz: a28bce934474a0d57069466281121658c75115eb9b9b1a3056eab9c6ee03ca5f
5
5
  SHA512:
6
- metadata.gz: 796b4ecad9967d6d800ba0ffe37bd3b0e9eb51bbcbfe9d27c1c9e95a0ea6b7393561edf423dfdc2dcf16fda0402097bd83e9a30b2cacf3588133bdf66526502e
7
- data.tar.gz: 791afe1a759ffe45012cd98c6d49bf766d72233b65d8b51106eb77a9bd11a1fb45f3fe2a7a2d4247ebf5209807d46094494b3745d94d282a3f811553aac6a05a
6
+ metadata.gz: 5653df741bf84cedce799a58c4f9d8c13d8a13226793f05d1f158b4971b0b08b1806463c82fa818526c8a0454d5b489f65cae207373c581bae074e1e3ae037bd
7
+ data.tar.gz: aad5fb5a6e0e9010c5b611ef2ba67e7ddf112d2b983c85684ce698d32083d1da82ac24469404f3377a131d3ef1be56ec369baa81bd9cd7e71b4560a916800e62
data/.prettierignore ADDED
@@ -0,0 +1,2 @@
1
+ # esbuild output (gitignored), present locally after `yarn build`
2
+ app/assets/builds/
data/.rubocop.yml CHANGED
@@ -33,6 +33,17 @@ Style/StringLiteralsInInterpolation:
33
33
  Layout/LineLength:
34
34
  Max: 120
35
35
 
36
+ # rubocop-rails-omakase disables both so it can indent methods under
37
+ # `private`; we use the normal style (methods at the class-body level,
38
+ # `private` included) and want `rubocop --autocorrect` to fix indentation,
39
+ # generated files included.
40
+ Layout/IndentationWidth:
41
+ Enabled: true
42
+
43
+ Layout/IndentationConsistency:
44
+ Enabled: true
45
+ EnforcedStyle: normal
46
+
36
47
  Gemspec/RequiredRubyVersion:
37
48
  Enabled: false
38
49
 
data/AGENTS.md CHANGED
@@ -21,11 +21,13 @@ bin/setup # bundle install + yarn install (dev setup)
21
21
  bin/console # IRB with trek loaded
22
22
  rake # default: spec + rubocop
23
23
  rake spec # run tests (or: bundle exec rspec)
24
+ make lint # all linters below
25
+ make fix # rubocop/eslint/stylelint/prettier autocorrect
24
26
  make release # bump version (version.rb + package.json), stamp CHANGELOG, tag, push gem + npm
25
27
  yarn docs:dev # VitePress docs locally
26
28
  ```
27
29
 
28
- Linters (also run automatically via lefthook on pre-commit):
30
+ Linters (`make lint`; also run automatically via lefthook on pre-commit):
29
31
 
30
32
  ```bash
31
33
  bundle exec rubocop
@@ -51,5 +53,6 @@ Security: `bundle exec brakeman` · `bundle exec bundle-audit check --update`
51
53
  - **Slim** linted via slim_lint; **JS/CSS** via ESLint + Stylelint + Prettier 2.8.8
52
54
  - **i18n**: keep English + French locales in sync (`config/locales/trek.*.yml`); DeepL auto-translation via `DEEPL_AUTH_KEY`
53
55
  - **Versioning**: version lives in both `lib/trek/version.rb` and `package.json` — keep in sync via `make release`
54
- - **CHANGELOG.md**: Keep-a-Changelog format. **Every MR must add an `[Unreleased]` entry.** When the change requires action in the host app on upgrade, add an "Upgrade note" describing what to do. When the change lets host apps remove or simplify existing patches or custom implementations, mention that too (e.g. "you can now drop your custom …").
56
+ - **CHANGELOG.md**: Keep-a-Changelog format. **Every MR must add an `[Unreleased]` entry.** When the change requires action in the host app on upgrade, end the entry with a bold `**Upgrade note:**` label followed by what to do. Use the same label when the change lets host apps remove or simplify existing patches or custom implementations (e.g. "**Upgrade note:** you can now drop your custom …") — never as an unlabelled trailing sentence.
57
+ - **Public code**: Trek ships publicly (gem, npm, docs), so nothing committed may mention a client or project name, nor a client's domain models or business logic — not in code, comments, specs, templates, docs, CHANGELOG or commit messages. Use neutral examples (`Page`, `Shop::Product`, an engine's `Blog::Post`). Client context belongs in GitLab issues and MR descriptions only.
55
58
  - **CI**: GitLab CI (`.gitlab-ci.yml`), stages build → lint → security → test → deploy; docs deploy to Netlify; deps via Renovate
data/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.2.0] - 2026-08-28
4
+
5
+ ### Added
6
+
7
+ - `Trek::Dashboard::ChartComponent`, a dashboard card wrapping [Chartkick](https://chartkick.com) — pass `title`, `data`, a `type` (`:line_chart`, `:bar_chart`, …), optional `info`/`options`, and a `color` (default `:slate`); it renders `public_send(type, data, **options)` inside a coloured card matching the tiles. Chartkick is an **optional** dependency: the component only includes `Chartkick::Helper` when it is loaded, so Trek keeps working without it. `Trek::Dashboard::AnalyticsComponent` gained a `renders_many :charts` slot (alongside the existing `section`) so charts can be dropped straight into the dashboard with `a.with_chart(...)`. **Upgrade note:** to use `ChartComponent`, add `gem "chartkick"` to your app and `import "chartkick/chart.js"` in your admin JS bundle (Trek does not pull Chartkick in for you).
8
+ - `Trek::Dashboard::TileComponent` gained a `title` option (a large heading above the text, coloured per the tile's `color`) and a `wide` option (default `false`) that spans two tile columns. Its `href` is now optional: without one the tile renders as a plain `div` instead of a link, for read-only stat tiles.
9
+ - The layout gains a **stacked panel**: a second panel frame (`trek--layout-stacked-panel`) that opens on top of the primary one. The content editor's link/image/prompt inserters now open there, so an editor hosted _inside_ a panel (e.g. a Sectionable payload edited in a side panel) no longer loses its unsaved content when inserting a link — the primary panel stays put underneath and the inserted element lands back in the live editor. Closing the primary panel also closes the stacked one; _Annuler_ in the stacked panel closes only that one. The stacked panel is 1rem narrower than the primary panel (only while it stacks on an open one). **Upgrade note:** host apps that override the panel views (`app/views/<engine>/panels/{links,images,prompts}/*`) must retarget them to the `trek--layout-stacked-panel` frame and the `stackedPanelCloser` target, as Trek's own views now do.
10
+
11
+ ### Fixed
12
+
13
+ - Internal links inserted through the content editor (stored as `gid://` URLs) rendered as literal `gid://…` hrefs — or were dropped by the sanitizer — in formatted content: `Prosemirror#format_content` now resolves GlobalIDs to page paths before rendering, and the editor's Link extension accepts the `gid` protocol so reopening such a link shows the linked page instead of an empty field.
14
+ - `Trek::Form::ActionsComponent`: the sticky footer styling (sticky positioning, `--accent-1` background, `4rem` vertical padding) now only applies to the `default` aspect. It was set on the root rule, so the `seamless` aspect — used by `Trek::DialogComponent` — was also sticky and padded inside dialogs.
15
+
16
+ ## [2.1.0] - 2026-08-25
17
+
18
+ ### Added
19
+
20
+ - Docs: a [Deploying to Scalingo](https://trek.etamin.studio/deploy) guide covering the trek-buildpack setup, the environment to configure, and how to migrate an app from the stock buildpacks.
21
+ - `trek:install:prosopite` generator (wired into `trek:install`) sets up [Prosopite](https://github.com/charkost/prosopite) N+1 query detection (#211): adds `prosopite` and `pg_query` to the `development`/`test` Gemfile group and a `config/initializers/prosopite.rb` that mounts the Rack middleware — so every request is scanned, Trek's engine controllers included — raising `Prosopite::NPlusOneQueriesError` in test and logging in development. It also generates `spec/requests/admin/n_plus_one_spec.rb`, a smoke request spec rendering the dashboard, pages index and fragments index as an admin (extend it with the app's other admin routes). Documented under [Controllers › N+1 detection](https://trek.etamin.studio/reference/controllers#n-1-detection) with the `Prosopite.pause` escape hatch. **Upgrade note:** existing apps can run `rails g trek:install:prosopite`; apps that already wired Prosopite by hand can keep their setup (the generator skips the gems when `prosopite` is already in the Gemfile and won't overwrite an existing initializer).
22
+ - `make lint` and `make fix` targets in Trek's own Makefile, mirroring the ones generated for host apps (RuboCop, slim-lint, ESLint, Stylelint, Prettier — the `fix` variant autocorrects all but slim-lint). A `.prettierignore` excludes the gitignored esbuild output in `app/assets/builds/`, which made the Prettier check fail on a local checkout that had run `yarn build`.
23
+ - `Trek::MenuComponent` items accept an `active:` option for menu entries whose pages live under other controllers: a `String` is matched as a `controller_path` prefix, a `Regexp` against the whole path, and a boolean forces (or clears) the state — e.g. `s.with_item(text: …, href: [:admin, Shop], active: "admin/shop")` keeps _Shop_ highlighted while editing its products. Without it, the item stays active when its href matches the current controller, as before.
24
+ - `content_editor` accepts an `autofocus:` option (default `false`): when true, the editor grabs focus on mount with the cursor at the end of the content — useful when the editor is the primary field of a side panel.
25
+
26
+ ### Changed
27
+
28
+ - The shared `.rubocop.yml` (Trek's own, and the one `trek:install:rubocop` copies into host apps) re-enables `Layout/IndentationWidth` and `Layout/IndentationConsistency` with `EnforcedStyle: normal`. rubocop-rails-omakase disables both to allow its house style of indenting methods under `private`; Trek's style keeps methods at the class-body level, and a plain `rubocop --autocorrect` now fixes indentation — in generated files too. **Upgrade note:** copy the two cop blocks from the template (`rails g trek:install:rubocop` will prompt to overwrite) into your app's `.rubocop.yml`, then run `bundle exec rubocop --autocorrect` once; methods indented under `private` and mis-indented generated files get fixed.
29
+ - `trek:install:scalingo` now targets the dedicated [trek-buildpack](https://github.com/etaminstudio/trek-buildpack) (#193) instead of the stock apt + ruby buildpack pair: it no longer generates `.buildpacks` (and removes an existing one) and prints the `env-set` commands to run (`BUILDPACK_URL` pinned to the buildpack's release tag, and the `DATABASE_URL=$SCALINGO_POSTGRESQL_URL` mapping). The buildpack pins Ruby/Node/Yarn from `.ruby-version`, the `.node-version` that `rails new` already generates, and `package.json`'s `packageManager`, runs `yarn install` once, and ships a slimmed image (~40% smaller, measured deploys down from ~3min to ~1min35) — see the buildpack README for details. **Upgrade note:** on each Scalingo app, set `BUILDPACK_URL=https://github.com/etaminstudio/trek-buildpack#v0.1.0`, delete `.buildpacks`, make sure `.node-version` exists (apps generated with `rails new -j esbuild` have it), and check that `config/database.yml` reads `DATABASE_URL` in production (mapped with `scalingo env-set 'DATABASE_URL=$SCALINGO_POSTGRESQL_URL'`).
30
+ - `Trek::Form::FieldsetComponent`: the fieldset title (`.c-trek--form--fieldset-title`) now shares the form group label style (`--slate-12`, semibold, `1.2rem` bottom margin) so a titled fieldset reads like a labelled group; the `section` modifier only adds its larger font size on top. Hosts that restyled the plain fieldset title themselves can drop that override.
31
+ - `trek:install:yarn` now installs **Yarn 4.18.0** (was 4.17.0). **Upgrade note:** existing apps can upgrade with `yarn set version 4.18.0` followed by `yarn install`.
32
+
33
+ ### Fixed
34
+
35
+ - `trek:scaffold` now references the model top-level qualified (`::Shop::Product`) in the generated controller's `model` and in the `Admin::Dashboard::MODELS_IN_MENU` entry: once the scaffold creates the `Admin::Shop` namespace, a bare `Shop` under `module Admin` resolves to that module instead of the model — the scaffolded controller raised `NameError`, and previously generated code referencing a `Shop` model silently got the module. The generator also warns when a namespace segment matches an existing model file that `rails g model` would replace with a namespace module. (#201) **Upgrade note:** if a scaffold introduced a namespace named like an existing model, qualify that model as `::Shop` in the existing `Admin::…Controller#model` and `MODELS_IN_MENU` entries.
36
+ - `Trek::Entries::ItemComponent`: the details list (`.c-trek--entries--item-details`) now wraps (`flex-wrap: wrap`) instead of overflowing or squeezing its items when an entry has many details or a narrow container.
37
+ - `trek:install:postmark` is idempotent again: re-running it on an app where it had already run appended a second `config.action_mailer.delivery_method = :postmark` / `postmark_settings` block to `config/environments/production.rb` (the gem, credentials and mailer steps were already guarded). The config injection is now skipped when the file already sets `delivery_method = :postmark`. (#210)
38
+ - Namespaced models (e.g. `Shop::Product`, routed under `namespace :shop`) now work out of the box with `Trek::ResourceController` (#212): `object_params` requires `model_name.param_key` (`shop_product`) instead of the demodulized element, so create/update no longer 400; `model_element_route` no longer prefixes the namespace when the route key already carries it (`shop_product`), so the delete button and list item links build `admin_shop_product_path` instead of the double-namespaced `admin_shop_shop_product_path` — models from isolated engines with relative naming (e.g. an engine's `Blog::Post`, route key `post`) keep the `[:blog, :post]` form; the controller defines `implicit_authorization_target` (`@object || model`) so `authorized(params)` and `Trek::Reorderable#reorder`'s bare `authorize!` find the policy; and `i18n_model_gender` reads `activerecord.models.<i18n_key>.gender` (`shop/product`), so `i18n_resource_label` picks the declared gender instead of the neutral fallback. **Upgrade note:** hosts that worked around this with a controller concern overriding `implicit_authorization_target`/`object_params`/`model_element_route`, or a prepend on `Trek::I18nHelper#i18n_model_gender`, can drop them.
39
+ - `Trek::Form::GroupComponent` now styles `input[type="time"]` like `input[type="date"]` (field frame, 16rem max width, lighter picker colour); `time_field`s used to render as bare browser inputs.
40
+ - Removed the dangling `sourceMappingURL` comment from the vendored `tom_select/css/tom-select.custom.css`: the referenced `.map` file is not shipped, so every host-app esbuild build (including `assets:precompile` on deploy) logged a `missing-source-map` debug warning. (#209)
41
+ - `trek:scaffold` now re-indents the generated controller and policy: for a namespaced model the nested `module` wrappers were emitted without indenting the class body, tripping the host app's linters. The generator runs a targeted `rubocop --autocorrect --only Layout/IndentationWidth,Layout/IndentationConsistency` on both files (forced with `--only` since rubocop-rails-omakase leaves those cops disabled). (#199)
42
+ - The `config/initializers/host.rb` generated by `trek:install:config` no longer references `ActionMailer::Base` during the initializer phase: doing so fires Action Mailer's load hook before the app's autoloader can resolve app classes, so an app configuring a custom `config.action_mailer.delivery_job` (e.g. `"MailDeliveryJob"`) crashed with a `NameError` on every production boot — breaking `assets:precompile` on deploy. The assignment is now deferred with `ActiveSupport.on_load(:action_mailer)`. (#208) **Upgrade note:** apply the same change to your app's `config/initializers/host.rb` — replace the `ActionMailer::Base.default_url_options = … if Rails.env.production?` line with the deferred block (see the generator template).
43
+ - `trek:scaffold` now generates the right controller class for a namespaced model (e.g. `rails g trek:scaffold Shop::Product`): the template used the still-namespaced `controller_class_name` inside the per-namespace `module` wrappers, defining `Admin::Shop::Shop::ProductsController` while the generated route expected `Admin::Shop::ProductsController` — every request 404ed with an uninitialized constant. The class name is now demodulized, like the policy template already did. (#200)
44
+ - The `show.html.slim` generated by `trek:scaffold` no longer ends with a trailing blank line: the attribute loop emitted its separator after every item instead of between items, so slim-lint failed in host apps with `TrailingBlankLines: Multiple empty lines in the end of file`. (#205)
45
+ - `trek:upgrade:v2` now upgrades Yarn to 4.18.0 first (applying the 0.1.26 upgrade note: `yarn set version` + dropping the `interactive-tools` plugin) when the app still runs Yarn 3. Previously the generator wrote `npmPreapprovedPackages` — a Yarn 4-only setting — unconditionally, which under Yarn 3 is a hard error that breaks every yarn command, including the generator's own `yarn add` step.
46
+ - Clarified the 0.1.26 changelog upgrade note about the Yarn minimal-age-gate exemption: it only applied to the pre-2.0.0 private GitLab distribution — since 2.0.0 the (Yarn 4-only) `npmPreapprovedPackages` entry, set up automatically by `trek:install` and `trek:upgrade:v2`, replaces it.
47
+ - Fixed whitespace in `model.en.yml.tt` which caused prettier to fail.
48
+
3
49
  ## [2.0.1] - 2026-08-18
4
50
 
5
51
  ### Fixed
@@ -41,7 +87,7 @@
41
87
  ### Fixed
42
88
 
43
89
  - Added the missing French translations that fell back to English: `admin.dialog_component.{title,text}` (the confirmation dialog shown on every destroy action) and `admin.{pages,fragments}.{show.intro,reorder.success,search.alert}`. Also added the English `admin.section`/`admin.sections` flash messages that previously existed only in French. (#194)
44
- - `trek:install:yarn` now exempts the `@etaminstudio` scope from Yarn's minimal-age gate (`npmMinimalAgeGate: 0` under `npmScopes.etaminstudio` in the generated `.yarnrc.yml`), so a freshly published `@etaminstudio/trek` release installs without being quarantined; the gate still applies to public packages. **Upgrade note:** existing apps can add `npmMinimalAgeGate: 0` under `npmScopes.etaminstudio` in their `.yarnrc.yml`.
90
+ - `trek:install:yarn` now exempts the `@etaminstudio` scope from Yarn's minimal-age gate (`npmMinimalAgeGate: 0` under `npmScopes.etaminstudio` in the generated `.yarnrc.yml`), so a freshly published `@etaminstudio/trek` release installs without being quarantined; the gate still applies to public packages. **Upgrade note:** existing apps can add `npmMinimalAgeGate: 0` under `npmScopes.etaminstudio` in their `.yarnrc.yml`. _(Only relevant to the private GitLab distribution used before 2.0.0: the `@etaminstudio` scope is retired since then, so this scope exemption does nothing for the public unscoped `trek` package. From 2.0.0 the equivalent is `trek` listed under `npmPreapprovedPackages` — a Yarn 4-only setting, hence the Yarn upgrade above — which `trek:install` generates and `trek:upgrade:v2` adds during migration, so no manual step is needed.)_
45
91
  - `trek:install:locales` no longer generates an invalid `config/initializers/locales.rb` (`I18n.available_locales = [:]` / `I18n.default_locale = :`) when passed no or blank locales. A blank option/env now falls back to the default (`en, fr`), blank entries are dropped, and the list can never end up empty. (#169)
46
92
 
47
93
  ## [0.1.25] - 2026-07-01
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trek (2.0.1)
4
+ trek (2.2.0)
5
5
  action_policy (~> 0.6)
6
6
  actioncable
7
7
  acts_as_list (~> 1.1)
data/Makefile CHANGED
@@ -1,5 +1,18 @@
1
1
  .PHONY: *
2
2
 
3
+ lint:
4
+ bundle exec rubocop
5
+ bundle exec slim-lint "app/**/*.slim"
6
+ yarn eslint "app/**/*.js"
7
+ yarn stylelint "app/**/*.css"
8
+ yarn prettier --check "{app,config,lib,spec}/**/*.{css,js,json,md,yml}"
9
+
10
+ fix:
11
+ bundle exec rubocop --autocorrect
12
+ yarn eslint "app/**/*.js" --fix
13
+ yarn stylelint "app/**/*.css" --fix
14
+ yarn prettier --write "{app,config,lib,spec}/**/*.{css,js,json,md,yml}"
15
+
3
16
  release:
4
17
  @read -p "New version (current: $$(sed -n 's/.*VERSION = "\(.*\)"/\1/p' lib/trek/version.rb)): " version; \
5
18
  if [ -z "$$version" ]; then echo "Version cannot be empty"; exit 1; fi; \
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 18a2 2 0 0 0-4 0"/><path d="m19 11-2.11-6.657a2 2 0 0 0-2.752-1.148l-1.276.61A2 2 0 0 1 12 4H8.5a2 2 0 0 0-1.925 1.456L5 11"/><path d="M2 11h20"/><circle cx="17" cy="18" r="3"/><circle cx="7" cy="18" r="3"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><path d="M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6"/><rect x="6" y="14" width="12" height="8" rx="1"/></svg>
@@ -1,2 +1,4 @@
1
1
  div(class=root_class)
2
2
  div(class=class_for("info")) = section
3
+ - charts.each do |chart|
4
+ = chart
@@ -3,5 +3,6 @@
3
3
  module Trek
4
4
  class Dashboard::AnalyticsComponent < Trek::Component
5
5
  renders_one :section, Trek::Dashboard::SectionComponent
6
+ renders_many :charts, Trek::Dashboard::ChartComponent
6
7
  end
7
8
  end
@@ -0,0 +1,192 @@
1
+ .root {
2
+ border: 0.1rem solid;
3
+ border-radius: 0.8rem;
4
+ font-weight: var(--semibold);
5
+ margin-bottom: 2rem;
6
+ max-width: 83.7rem;
7
+ padding: 2rem;
8
+ transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out,
9
+ color 0.2s ease-in-out;
10
+ }
11
+
12
+ .root:last-child {
13
+ margin-bottom: 0;
14
+ }
15
+
16
+ .title {
17
+ color: var(--mint-12);
18
+ font-size: 1.6rem;
19
+ font-weight: var(--semibold);
20
+ margin: 0 0 0.5rem;
21
+ }
22
+
23
+ .data {
24
+ margin-bottom: 2rem;
25
+ }
26
+
27
+ .info {
28
+ color: var(--slate-11);
29
+ font-size: 1.4rem;
30
+ font-weight: var(--medium);
31
+ padding-bottom: 1.6rem;
32
+ }
33
+
34
+ .root.color-amber {
35
+ --icon-color: var(--amber-11);
36
+
37
+ background-color: var(--amber-2);
38
+ border-color: var(--amber-5);
39
+ color: var(--amber-12);
40
+ }
41
+
42
+ .root.color-amber:hover {
43
+ --icon-color: var(--amber-12);
44
+
45
+ background-color: var(--amber-3);
46
+ border-color: var(--amber-6);
47
+ }
48
+
49
+ .root.color-amber:active {
50
+ background-color: var(--amber-5);
51
+ border-color: var(--amber-7);
52
+ }
53
+
54
+ .root.color-lime {
55
+ --icon-color: var(--lime-11);
56
+
57
+ background-color: var(--lime-2);
58
+ border-color: var(--lime-5);
59
+ color: var(--lime-12);
60
+ }
61
+
62
+ .root.color-lime:hover {
63
+ --icon-color: var(--lime-12);
64
+
65
+ background-color: var(--lime-3);
66
+ border-color: var(--lime-6);
67
+ }
68
+
69
+ .root.color-lime:active {
70
+ background-color: var(--lime-5);
71
+ border-color: var(--lime-7);
72
+ }
73
+
74
+ .root.color-mint {
75
+ --icon-color: var(--mint-11);
76
+
77
+ background-color: var(--mint-2);
78
+ border-color: var(--mint-5);
79
+ color: var(--mint-12);
80
+ }
81
+
82
+ .root.color-mint:hover {
83
+ --icon-color: var(--mint-12);
84
+
85
+ background-color: var(--mint-3);
86
+ border-color: var(--mint-6);
87
+ }
88
+
89
+ .root.color-mint:active {
90
+ background-color: var(--mint-5);
91
+ border-color: var(--mint-7);
92
+ }
93
+
94
+ .root.color-purple {
95
+ --icon-color: var(--purple-11);
96
+
97
+ background-color: var(--purple-2);
98
+ border-color: var(--purple-5);
99
+ color: var(--purple-12);
100
+ }
101
+
102
+ .root.color-purple:hover {
103
+ --icon-color: var(--purple-12);
104
+
105
+ background-color: var(--purple-3);
106
+ border-color: var(--purple-6);
107
+ }
108
+
109
+ .root.color-purple:active {
110
+ background-color: var(--purple-5);
111
+ border-color: var(--purple-7);
112
+ }
113
+
114
+ .root.color-sky {
115
+ --icon-color: var(--sky-11);
116
+
117
+ background-color: var(--sky-2);
118
+ border-color: var(--sky-5);
119
+ color: var(--sky-12);
120
+ }
121
+
122
+ .root.color-sky:hover {
123
+ --icon-color: var(--sky-12);
124
+
125
+ background-color: var(--sky-3);
126
+ border-color: var(--sky-6);
127
+ }
128
+
129
+ .root.color-sky:active {
130
+ background-color: var(--sky-5);
131
+ border-color: var(--sky-7);
132
+ }
133
+
134
+ .root.color-slate {
135
+ --icon-color: var(--slate-11);
136
+
137
+ background-color: var(--slate-2);
138
+ border-color: var(--slate-5);
139
+ color: var(--slate-12);
140
+ }
141
+
142
+ .root.color-slate:hover {
143
+ --icon-color: var(--slate-12);
144
+
145
+ background-color: var(--slate-3);
146
+ border-color: var(--slate-6);
147
+ }
148
+
149
+ .root.color-slate:active {
150
+ background-color: var(--slate-5);
151
+ border-color: var(--slate-7);
152
+ }
153
+
154
+ .root.color-teal {
155
+ --icon-color: var(--teal-11);
156
+
157
+ background-color: var(--teal-2);
158
+ border-color: var(--teal-5);
159
+ color: var(--teal-12);
160
+ }
161
+
162
+ .root.color-teal:hover {
163
+ --icon-color: var(--teal-12);
164
+
165
+ background-color: var(--teal-3);
166
+ border-color: var(--teal-6);
167
+ }
168
+
169
+ .root.color-teal:active {
170
+ background-color: var(--teal-5);
171
+ border-color: var(--teal-7);
172
+ }
173
+
174
+ .root.color-violet {
175
+ --icon-color: var(--violet-11);
176
+
177
+ background-color: var(--violet-2);
178
+ border-color: var(--violet-5);
179
+ color: var(--violet-12);
180
+ }
181
+
182
+ .root.color-violet:hover {
183
+ --icon-color: var(--violet-12);
184
+
185
+ background-color: var(--violet-3);
186
+ border-color: var(--violet-6);
187
+ }
188
+
189
+ .root.color-violet:active {
190
+ background-color: var(--violet-5);
191
+ border-color: var(--violet-7);
192
+ }
@@ -0,0 +1,6 @@
1
+ div(class=classes)
2
+ div(class=class_for("title")) = title
3
+ - if info.present?
4
+ span(class=class_for("info")) = info
5
+ div(class=class_for("data"))
6
+ = chart
@@ -0,0 +1 @@
1
+ import "./chart_component.css";
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trek
4
+ class Dashboard::ChartComponent < Trek::Component
5
+ # Chartkick is an optional dependency: host apps that use this component must
6
+ # add `gem "chartkick"` and import "chartkick/chart.js" in their JS bundle.
7
+ include Chartkick::Helper if defined?(Chartkick)
8
+
9
+ option :title
10
+ option :info, optional: true
11
+ option :data
12
+ option :type
13
+ option :options, optional: true, default: proc { {} }
14
+ option :color, default: proc { :slate }
15
+
16
+ def chart
17
+ public_send(type, data, **options)
18
+ end
19
+
20
+ def classes
21
+ class_names(
22
+ root_class,
23
+ class_for("color-#{color}")
24
+ )
25
+ end
26
+ end
27
+ end
@@ -20,6 +20,14 @@
20
20
  width: 20rem;
21
21
  }
22
22
 
23
+ .root.wide {
24
+ width: calc(40rem + 1.2rem);
25
+ }
26
+
27
+ .title {
28
+ font-size: 3.6rem;
29
+ }
30
+
23
31
  .root:hover .icon {
24
32
  transform: scale(1.125);
25
33
  }
@@ -58,6 +66,10 @@
58
66
  border-color: var(--amber-7);
59
67
  }
60
68
 
69
+ .root.color-amber .title {
70
+ color: var(--amber-11);
71
+ }
72
+
61
73
  .root.color-lime {
62
74
  --icon-color: var(--lime-11);
63
75
 
@@ -78,6 +90,10 @@
78
90
  border-color: var(--lime-7);
79
91
  }
80
92
 
93
+ .root.color-lime .title {
94
+ color: var(--lime-11);
95
+ }
96
+
81
97
  .root.color-mint {
82
98
  --icon-color: var(--mint-11);
83
99
 
@@ -98,6 +114,10 @@
98
114
  border-color: var(--mint-7);
99
115
  }
100
116
 
117
+ .root.color-mint .title {
118
+ color: var(--mint-11);
119
+ }
120
+
101
121
  .root.color-purple {
102
122
  --icon-color: var(--purple-11);
103
123
 
@@ -118,6 +138,10 @@
118
138
  border-color: var(--purple-7);
119
139
  }
120
140
 
141
+ .root.color-purple .title {
142
+ color: var(--purple-11);
143
+ }
144
+
121
145
  .root.color-sky {
122
146
  --icon-color: var(--sky-11);
123
147
 
@@ -138,6 +162,10 @@
138
162
  border-color: var(--sky-7);
139
163
  }
140
164
 
165
+ .root.color-sky .title {
166
+ color: var(--sky-11);
167
+ }
168
+
141
169
  .root.color-slate {
142
170
  --icon-color: var(--slate-11);
143
171
 
@@ -158,6 +186,10 @@
158
186
  border-color: var(--slate-7);
159
187
  }
160
188
 
189
+ .root.color-slate .title {
190
+ color: var(--slate-11);
191
+ }
192
+
161
193
  .root.color-teal {
162
194
  --icon-color: var(--teal-11);
163
195
 
@@ -178,6 +210,10 @@
178
210
  border-color: var(--teal-7);
179
211
  }
180
212
 
213
+ .root.color-teal .title {
214
+ color: var(--teal-11);
215
+ }
216
+
181
217
  .root.color-violet {
182
218
  --icon-color: var(--violet-11);
183
219
 
@@ -197,3 +233,7 @@
197
233
  background-color: var(--violet-5);
198
234
  border-color: var(--violet-7);
199
235
  }
236
+
237
+ .root.color-violet .title {
238
+ color: var(--violet-11);
239
+ }
@@ -1,4 +1,14 @@
1
- = link_to href, class: classes, rel: rel, target: target
2
- - if icon
3
- = render Trek::IconComponent.new(icon, classnames: class_for("icon"))
4
- span(class=class_for("text")) = formatted_text
1
+ - if href.present?
2
+ = link_to href, class: classes, rel: rel, target: target
3
+ - if title
4
+ span(class=class_for("title")) = title
5
+ - if icon
6
+ = render Trek::IconComponent.new(icon, classnames: class_for("icon"))
7
+ span(class=class_for("text")) = formatted_text
8
+ - else
9
+ div(class=classes)
10
+ - if title
11
+ span(class=class_for("title")) = title
12
+ - if icon
13
+ = render Trek::IconComponent.new(icon, classnames: class_for("icon"))
14
+ span(class=class_for("text")) = formatted_text
@@ -3,12 +3,15 @@
3
3
  module Trek
4
4
  class Dashboard::TileComponent < Trek::Component
5
5
  option :color, default: proc { :slate }
6
- option :href
6
+ option :href, optional: true
7
7
  option :icon, optional: true
8
8
  option :rel, optional: true
9
9
  option :target, optional: true
10
10
  option :text
11
+ option :title, optional: true
11
12
  option :allowed_to, default: -> { true }
13
+ # Spans two tile columns (including the gap between them).
14
+ option :wide, default: proc { false }
12
15
 
13
16
  def render?
14
17
  allowed_to
@@ -17,7 +20,8 @@ module Trek
17
20
  def classes
18
21
  class_names(
19
22
  root_class,
20
- class_for("color-#{color}")
23
+ class_for("color-#{color}"),
24
+ class_for("wide") => wide
21
25
  )
22
26
  end
23
27
 
@@ -92,6 +92,7 @@ table .root {
92
92
  .details {
93
93
  color: var(--slate-9);
94
94
  display: flex;
95
+ flex-wrap: wrap;
95
96
  font-size: 1.3rem;
96
97
  line-height: 1.2;
97
98
  list-style: none;
@@ -2,14 +2,8 @@
2
2
 
3
3
  .root {
4
4
  align-items: center;
5
- background-color: var(--accent-1);
6
- bottom: 0;
7
5
  display: flex;
8
6
  max-width: 120rem;
9
- padding: 4rem 0;
10
- position: sticky;
11
- width: 100%;
12
- z-index: 2;
13
7
  }
14
8
 
15
9
  .buttons {
@@ -36,9 +30,14 @@
36
30
 
37
31
  /* aspect */
38
32
  .root.aspect-default {
33
+ background-color: var(--accent-1);
39
34
  border-top: 1px solid var(--slate-6);
35
+ bottom: 0;
40
36
  margin-top: 3.2rem;
41
- padding-top: 3.2rem;
37
+ padding: 3.2rem 0 4rem;
38
+ position: sticky;
39
+ width: 100%;
40
+ z-index: 2;
42
41
  }
43
42
 
44
43
  /* .root.aspect-seamless {
@@ -29,16 +29,14 @@ div(
29
29
  / data-action=stimulus_action("toggleCode")
30
30
  / ) code
31
31
 
32
- / slim-lint:disable ControlStatementSpacing
32
+ / The panel opening is handled by the hidden button's
33
+ openStackedPanelWhenLoaded action — a direct openPanel here would
34
+ pop the (possibly empty) primary panel alongside the stacked one.
33
35
  button.link(
34
36
  class=class_for("button")
35
37
  type="button"
36
- data-action=[ \
37
- stimulus_action("toggleLink"),
38
- "trek--layout#openPanel",
39
- ].join(" ")
38
+ data-action=stimulus_action("toggleLink")
40
39
  ) = render Trek::IconComponent.new("trek/link", title: t(".link"))
41
- / slim-lint:enable ControlStatementSpacing
42
40
 
43
41
  = link_to "",
44
42
  new_link_path,
@@ -46,8 +44,8 @@ div(
46
44
  hidden: true,
47
45
  data: stimulus_target_hash("hiddenLinkButton").merge( \
48
46
  'base-path': new_link_path,
49
- action: "trek--layout#openPanelWhenLoaded",
50
- 'turbo-frame': "trek--layout-panel",
47
+ action: "trek--layout#openStackedPanelWhenLoaded",
48
+ 'turbo-frame': "trek--layout-stacked-panel",
51
49
  )
52
50
 
53
51
  button(
@@ -239,8 +237,8 @@ div(
239
237
  hidden: true,
240
238
  data: stimulus_target_hash("hiddenPromptsButton").merge( \
241
239
  'base-path': new_prompt_path,
242
- action: "trek--layout#openPanelWhenLoaded",
243
- 'turbo-frame': "trek--layout-panel",
240
+ action: "trek--layout#openStackedPanelWhenLoaded",
241
+ 'turbo-frame': "trek--layout-stacked-panel",
244
242
  )
245
243
  = link_to "",
246
244
  new_image_path,
@@ -248,6 +246,6 @@ div(
248
246
  hidden: true,
249
247
  data: stimulus_target_hash("hiddenImageButton").merge( \
250
248
  'base-path': new_image_path,
251
- action: "trek--layout#openPanelWhenLoaded",
252
- 'turbo-frame': "trek--layout-panel",
249
+ action: "trek--layout#openStackedPanelWhenLoaded",
250
+ 'turbo-frame': "trek--layout-stacked-panel",
253
251
  )