senren-ui 0.1.0 → 0.1.1

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: 8bcfbb253e663c59e6628841c25bf5584fb665385243e791d318bb65d8500dfb
4
+ data.tar.gz: f091c8049f283fca041ca8c1c716feda255008365239d9728f1b6ef2c38c378a
5
5
  SHA512:
6
- metadata.gz: f863e1bb2132548ee87c6eae7b113f3b0fad9ef2b6d388e4c9cf688852a14c43812c95dc75fdc08c2123dfb0de7541a5cf4c47b595e47c9eb23220a4f7cace6f
7
- data.tar.gz: '039c3f45102ebef05ef9dcd4cf2c8f64c7bdc8faa710baeffbe4f87ed061afc547f9390b2a2a8fea708e773d17bac07e47e06c05a2115c66217100a1f0632445'
6
+ metadata.gz: c42f55ffa59c1d82416c98f336b3927ecb72255bc32d530c2fa8f55ce60fa3cd2d6331159a30efabb78ac60b838853bdcfe17368248873d6a39a48d819be1118
7
+ data.tar.gz: abb2a2e92a1138842d5a06820de2f39006b8519d3ad010d2300154c5b6ac9986eb23e80e077e23e1ef386530944b1956ee1c15e1d774aa26828b5d256cfa75f3
data/CHANGELOG.md CHANGED
@@ -9,6 +9,8 @@ bug fixes only.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.1.1] — 2026-05-02
13
+
12
14
  ### Added
13
15
 
14
16
  - Initial gem skeleton, engine, and version constant.
@@ -26,8 +28,32 @@ bug fixes only.
26
28
  - Centralized `.senren/skill.md` system with preserved user-region.
27
29
  - `public/llms.txt` and `public/llms-full.txt` generation.
28
30
  - `apps/todolist` Rails app dogfooding the gem via local path.
31
+ - Bun-based JS tooling for Stimulus templates:
32
+ - `bun run controllers:syntax`
33
+ - `bun run controllers:lint`
34
+ - `bun run controllers:lint:fix`
35
+ - `bun run controllers:check`
36
+ - Biome lint configuration (`biome.json`) scoped to
37
+ `templates/controllers/**/*.js`.
38
+
39
+ ### Changed
40
+
41
+ - `SidebarComponent` template + Stimulus controller now support robust
42
+ compact/expanded syncing:
43
+ - hides brand/footer in compact mode
44
+ - shows link initials in compact mode and full labels in expanded mode
45
+ - uses a hamburger icon toggle with `aria-expanded`
46
+ - applies smoother width/label transition behavior
47
+ - `TabsComponent` template + Stimulus controller now use
48
+ `data-state="active|inactive"` for tab and panel state, so header active
49
+ styling updates correctly after client-side tab switches.
50
+
51
+ ### Fixed
52
+
53
+ - Docs-site feedback issues now resolved at gem template level (not app-only):
54
+ sidebar compact truncation UX and tabs header active-state mismatch.
29
55
 
30
- ## [0.1.0] — TBD
56
+ ## [0.1.0] — 2026-04-27
31
57
 
32
58
  First tagged release once the Unreleased entries are validated end-to-end
33
59
  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.1'
6
6
  end
7
7
  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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - vutt