senren-ui 0.1.0 → 0.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 789980b8355793bdfee36c7d930fe41a2c3ed379bb3a38b6960485c62acc9c69
4
- data.tar.gz: d54ebbc90d3ae06c0ea76a3cb5c6d331afb1598eec69c372358399eb6cd1b5f7
3
+ metadata.gz: 9b6cd2ac8863ce382b37a0f2e4d4efe3b109628d396be64186ecc3458b2afa22
4
+ data.tar.gz: a91a6fa5df442ae7c67c2a12dbe3b074f6ae7d2437edd9e3739eee1fd9d797ae
5
5
  SHA512:
6
- metadata.gz: f863e1bb2132548ee87c6eae7b113f3b0fad9ef2b6d388e4c9cf688852a14c43812c95dc75fdc08c2123dfb0de7541a5cf4c47b595e47c9eb23220a4f7cace6f
7
- data.tar.gz: '039c3f45102ebef05ef9dcd4cf2c8f64c7bdc8faa710baeffbe4f87ed061afc547f9390b2a2a8fea708e773d17bac07e47e06c05a2115c66217100a1f0632445'
6
+ metadata.gz: cf6dfdfda509f6bb568b50c0d5ea488d7c00c38537ee74621f2e05907dc2c5f78f7354ca21ccd04b055ba2f9df0b94d5da85606cdde91e271ea03526bb3018d9
7
+ data.tar.gz: a391a49b1f9152f7b2ccdab3c31276d34cc199929e5315bc10185c51ff6bf660101a42ea56a455df6def66fc215d66097b0610ea919d2531cda606a5076537a2
data/CHANGELOG.md CHANGED
@@ -9,6 +9,18 @@ bug fixes only.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.1.2] — 2026-05-02
13
+
14
+ ### Fixed
15
+
16
+ - Progress component no longer paints variant background on the full root
17
+ container. Variant color is now applied only to the indicator fill bar.
18
+ - Improved progress visuals by separating track/fill styling more clearly
19
+ (`h-2.5` track) and using smoother fill-width transition
20
+ (`transition-[width] duration-300 ease-out`).
21
+
22
+ ## [0.1.1] — 2026-05-02
23
+
12
24
  ### Added
13
25
 
14
26
  - Initial gem skeleton, engine, and version constant.
@@ -26,8 +38,32 @@ bug fixes only.
26
38
  - Centralized `.senren/skill.md` system with preserved user-region.
27
39
  - `public/llms.txt` and `public/llms-full.txt` generation.
28
40
  - `apps/todolist` Rails app dogfooding the gem via local path.
41
+ - Bun-based JS tooling for Stimulus templates:
42
+ - `bun run controllers:syntax`
43
+ - `bun run controllers:lint`
44
+ - `bun run controllers:lint:fix`
45
+ - `bun run controllers:check`
46
+ - Biome lint configuration (`biome.json`) scoped to
47
+ `templates/controllers/**/*.js`.
48
+
49
+ ### Changed
50
+
51
+ - `SidebarComponent` template + Stimulus controller now support robust
52
+ compact/expanded syncing:
53
+ - hides brand/footer in compact mode
54
+ - shows link initials in compact mode and full labels in expanded mode
55
+ - uses a hamburger icon toggle with `aria-expanded`
56
+ - applies smoother width/label transition behavior
57
+ - `TabsComponent` template + Stimulus controller now use
58
+ `data-state="active|inactive"` for tab and panel state, so header active
59
+ styling updates correctly after client-side tab switches.
60
+
61
+ ### Fixed
62
+
63
+ - Docs-site feedback issues now resolved at gem template level (not app-only):
64
+ sidebar compact truncation UX and tabs header active-state mismatch.
29
65
 
30
- ## [0.1.0] — TBD
66
+ ## [0.1.0] — 2026-04-27
31
67
 
32
68
  First tagged release once the Unreleased entries are validated end-to-end
33
69
  in `apps/todolist` per `plans/011_release_checklist.md`.
data/CONTRIBUTING.md CHANGED
@@ -19,7 +19,9 @@ this file before opening a PR.
19
19
  git clone <repo>
20
20
  cd senren-rails
21
21
  bundle install
22
+ bun install
22
23
  bundle exec rake test
24
+ bun run controllers:check
23
25
  ```
24
26
 
25
27
  To exercise the gem against a real Rails app, use the bundled workspace:
@@ -61,3 +63,5 @@ bin/rails server
61
63
  - One logical change per commit.
62
64
  - Mention the affected plan and history files in the commit body.
63
65
  - Run `bundle exec rake test` before pushing.
66
+ - Run `bun run controllers:check` before pushing if you touched
67
+ `templates/controllers/*.js`.
data/README.md CHANGED
@@ -118,7 +118,10 @@ See `registry/components.yml` for the canonical list. v0.1 ships:
118
118
 
119
119
  ```bash
120
120
  bundle install
121
+ bun install
121
122
  bundle exec rake test # gem tests
123
+ bun run controllers:check # lint + syntax check for templates/controllers/*.js
124
+ bun run controllers:lint:fix # auto-fix lint issues for controllers
122
125
  bundle exec rake test:system # Stimulus/system tests
123
126
  ```
124
127
 
@@ -61,12 +61,12 @@ module Senren
61
61
  @components.values
62
62
  end
63
63
 
64
- def each(&block)
65
- all.each(&block)
64
+ def each(&)
65
+ all.each(&)
66
66
  end
67
67
 
68
- def find_each(&block)
69
- each(&block)
68
+ def find_each(&)
69
+ each(&)
70
70
  end
71
71
 
72
72
  def names
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Senren
4
4
  module Rails
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.2'
6
6
  end
7
7
  end
@@ -5,7 +5,7 @@
5
5
  <span class="text-[hsl(var(--senren-muted-foreground))]"><%= percent %>%</span>
6
6
  </div>
7
7
  <% end %>
8
- <div role="progressbar" aria-valuemin="0" aria-valuemax="<%= max.to_i %>" aria-valuenow="<%= value.to_i %>" class="h-2 w-full overflow-hidden rounded-full bg-[hsl(var(--senren-muted))]">
9
- <div class="h-full rounded-full transition-all <%= self.class::VARIANTS[variant] %>" style="width: <%= percent %>%"></div>
8
+ <div role="progressbar" aria-valuemin="0" aria-valuemax="<%= max.to_i %>" aria-valuenow="<%= value.to_i %>" class="h-2.5 w-full overflow-hidden rounded-full bg-[hsl(var(--senren-muted))]">
9
+ <div class="h-full rounded-full transition-[width] duration-300 ease-out <%= indicator_class %>" style="width: <%= percent %>%"></div>
10
10
  </div>
11
11
  </div>
@@ -3,6 +3,12 @@
3
3
  module Senren
4
4
  class ProgressComponent < BaseComponent
5
5
  VARIANTS = {
6
+ default: '',
7
+ success: '',
8
+ warning: '',
9
+ destructive: ''
10
+ }.freeze
11
+ INDICATOR_VARIANTS = {
6
12
  default: 'bg-[hsl(var(--senren-primary))]',
7
13
  success: 'bg-[hsl(var(--senren-success))]',
8
14
  warning: 'bg-[hsl(var(--senren-warning))]',
@@ -22,5 +28,9 @@ module Senren
22
28
  def percent
23
29
  ((value / max) * 100).clamp(0, 100).round
24
30
  end
31
+
32
+ def indicator_class
33
+ INDICATOR_VARIANTS.fetch(variant)
34
+ end
25
35
  end
26
36
  end
@@ -1,14 +1,24 @@
1
- <aside <%= tag.attributes(**root_attrs("flex min-h-80 flex-col rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] p-3 text-[hsl(var(--senren-card-foreground))] #{self.class::VARIANTS[variant]}", data: { controller: "senren--sidebar" })) %>>
1
+ <aside <%= tag.attributes(**root_attrs("flex min-h-80 flex-col overflow-hidden rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] p-3 text-[hsl(var(--senren-card-foreground))] transition-[width] duration-300 ease-in-out", data: { controller: "senren--sidebar" })) %>>
2
2
  <div class="mb-4 flex items-center justify-between gap-2 px-2">
3
- <div class="truncate font-display text-sm font-semibold tracking-tight"><%= brand %></div>
4
- <button type="button" class="cursor-pointer rounded-md px-2 py-1 text-xs text-[hsl(var(--senren-muted-foreground))] hover:bg-[hsl(var(--senren-accent))]" data-action="click->senren--sidebar#toggle" aria-label="Toggle sidebar">Toggle</button>
3
+ <div data-senren--sidebar-target="brand" class="truncate font-display text-sm font-semibold tracking-tight"><%= brand %></div>
4
+ <button type="button" data-senren--sidebar-target="toggleButton" class="inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-[hsl(var(--senren-muted-foreground))] transition-colors hover:bg-[hsl(var(--senren-accent))] hover:text-[hsl(var(--senren-accent-foreground))]" data-action="click->senren--sidebar#toggle" aria-label="Toggle sidebar" aria-expanded="<%= variant != :compact %>">
5
+ <span class="sr-only">Toggle sidebar</span>
6
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
7
+ <path d="M3 6h18" />
8
+ <path d="M3 12h18" />
9
+ <path d="M3 18h18" />
10
+ </svg>
11
+ </button>
5
12
  </div>
6
13
  <nav aria-label="<%= label %>" class="space-y-1">
7
14
  <% items.each do |item| %>
8
- <%= link_to item[:label], item[:href], class: "block truncate rounded-md px-3 py-2 text-sm transition-colors #{item[:active] ? "bg-[hsl(var(--senren-primary))] text-[hsl(var(--senren-primary-foreground))]" : "text-[hsl(var(--senren-muted-foreground))] hover:bg-[hsl(var(--senren-accent))] hover:text-[hsl(var(--senren-accent-foreground))]"}" %>
15
+ <%= link_to item[:href], title: item[:label], data: { "senren--sidebar-target": "link" }, class: "flex items-center gap-2 rounded-md px-3 py-2 text-sm transition-all duration-200 #{item[:active] ? "bg-[hsl(var(--senren-primary))] text-[hsl(var(--senren-primary-foreground))]" : "text-[hsl(var(--senren-muted-foreground))] hover:bg-[hsl(var(--senren-accent))] hover:text-[hsl(var(--senren-accent-foreground))]"}" do %>
16
+ <span data-senren--sidebar-target="linkInitial" class="inline-flex h-4 w-0 items-center justify-center overflow-hidden text-xs font-semibold uppercase tracking-wide opacity-0 transition-all duration-200"><%= item[:label].to_s.first&.upcase || "•" %></span>
17
+ <span data-senren--sidebar-target="linkLabel" class="max-w-40 truncate opacity-100 transition-all duration-200"><%= item[:label] %></span>
18
+ <% end %>
9
19
  <% end %>
10
20
  </nav>
11
21
  <% if content? %>
12
- <div class="mt-auto pt-4 text-xs text-[hsl(var(--senren-muted-foreground))]"><%= content %></div>
22
+ <div data-senren--sidebar-target="footer" class="mt-auto pt-4 text-xs text-[hsl(var(--senren-muted-foreground))]"><%= content %></div>
13
23
  <% end %>
14
24
  </aside>
@@ -2,7 +2,7 @@
2
2
  <div role="tablist" aria-label="<%= label %>" class="<%= self.class::VARIANTS[variant] %> flex flex-wrap items-center gap-1">
3
3
  <% items.each do |item| %>
4
4
  <% selected = active_item?(item) %>
5
- <button type="button" role="tab" id="<%= item[:id] %>-tab" aria-selected="<%= selected %>" aria-controls="<%= item[:id] %>-panel" tabindex="<%= selected ? 0 : -1 %>" data-senren--tabs-target="tab" data-panel-id="<%= item[:id] %>" data-action="click->senren--tabs#select keydown->senren--tabs#onKey" class="cursor-pointer rounded-[calc(var(--senren-radius)-2px)] px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))] <%= selected ? "bg-[hsl(var(--senren-background))] text-[hsl(var(--senren-foreground))] shadow-sm" : "text-[hsl(var(--senren-muted-foreground))] hover:text-[hsl(var(--senren-foreground))]" %>">
5
+ <button type="button" role="tab" id="<%= item[:id] %>-tab" aria-selected="<%= selected %>" aria-controls="<%= item[:id] %>-panel" tabindex="<%= selected ? 0 : -1 %>" data-senren--tabs-target="tab" data-panel-id="<%= item[:id] %>" data-state="<%= selected ? "active" : "inactive" %>" data-action="click->senren--tabs#select keydown->senren--tabs#onKey" class="cursor-pointer rounded-[calc(var(--senren-radius)-2px)] px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))] text-[hsl(var(--senren-muted-foreground))] data-[state=active]:bg-[hsl(var(--senren-background))] data-[state=active]:text-[hsl(var(--senren-foreground))] data-[state=active]:shadow-sm data-[state=inactive]:hover:text-[hsl(var(--senren-foreground))]">
6
6
  <%= item[:label] %>
7
7
  </button>
8
8
  <% end %>
@@ -10,7 +10,7 @@
10
10
  <div class="mt-4">
11
11
  <% items.each do |item| %>
12
12
  <% selected = active_item?(item) %>
13
- <section id="<%= item[:id] %>-panel" role="tabpanel" aria-labelledby="<%= item[:id] %>-tab" data-senren--tabs-target="panel" data-panel-id="<%= item[:id] %>" <%= "hidden" unless selected %> class="rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] p-4 text-sm text-[hsl(var(--senren-card-foreground))]">
13
+ <section id="<%= item[:id] %>-panel" role="tabpanel" aria-labelledby="<%= item[:id] %>-tab" data-senren--tabs-target="panel" data-panel-id="<%= item[:id] %>" data-state="<%= selected ? "active" : "inactive" %>" <%= "hidden" unless selected %> class="rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] p-4 text-sm text-[hsl(var(--senren-card-foreground))]">
14
14
  <%= item[:content].presence || content %>
15
15
  </section>
16
16
  <% end %>
@@ -3,8 +3,52 @@ import { Controller } from "@hotwired/stimulus"
3
3
  // senren--sidebar
4
4
  // Local UI: compact/expanded visual state only.
5
5
  export default class extends Controller {
6
+ static targets = ["brand", "footer", "toggleButton", "link", "linkLabel", "linkInitial"]
7
+
8
+ connect() {
9
+ this.syncState()
10
+ }
11
+
6
12
  toggle() {
7
- this.element.classList.toggle("w-20")
8
- this.element.classList.toggle("w-64")
13
+ const isCompact = this.element.classList.contains("w-20")
14
+ this.element.classList.toggle("w-20", !isCompact)
15
+ this.element.classList.toggle("w-64", isCompact)
16
+ this.syncState()
17
+ }
18
+
19
+ syncState() {
20
+ const isCompact = this.element.classList.contains("w-20")
21
+
22
+ if (this.hasBrandTarget) {
23
+ this.brandTarget.classList.toggle("hidden", isCompact)
24
+ }
25
+
26
+ if (this.hasFooterTarget) {
27
+ this.footerTarget.classList.toggle("hidden", isCompact)
28
+ }
29
+
30
+ this.linkTargets.forEach((link) => {
31
+ link.classList.toggle("justify-center", isCompact)
32
+ link.classList.toggle("px-2", isCompact)
33
+ link.classList.toggle("px-3", !isCompact)
34
+ })
35
+
36
+ this.linkLabelTargets.forEach((label) => {
37
+ label.classList.toggle("max-w-0", isCompact)
38
+ label.classList.toggle("opacity-0", isCompact)
39
+ label.classList.toggle("max-w-40", !isCompact)
40
+ label.classList.toggle("opacity-100", !isCompact)
41
+ })
42
+
43
+ this.linkInitialTargets.forEach((initial) => {
44
+ initial.classList.toggle("w-4", isCompact)
45
+ initial.classList.toggle("opacity-100", isCompact)
46
+ initial.classList.toggle("w-0", !isCompact)
47
+ initial.classList.toggle("opacity-0", !isCompact)
48
+ })
49
+
50
+ if (this.hasToggleButtonTarget) {
51
+ this.toggleButtonTarget.setAttribute("aria-expanded", String(!isCompact))
52
+ }
9
53
  }
10
54
  }
@@ -26,9 +26,12 @@ export default class extends Controller {
26
26
  const selected = target === tab
27
27
  target.setAttribute("aria-selected", selected ? "true" : "false")
28
28
  target.tabIndex = selected ? 0 : -1
29
+ target.dataset.state = selected ? "active" : "inactive"
29
30
  })
30
31
  this.panelTargets.forEach((panel) => {
31
- panel.hidden = panel.dataset.panelId !== panelId
32
+ const selected = panel.dataset.panelId === panelId
33
+ panel.hidden = !selected
34
+ panel.dataset.state = selected ? "active" : "inactive"
32
35
  })
33
36
  }
34
37
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: senren-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - vutt