Skip to content

✨ feat(input): 再利用 hooks の named export と triggerProps による ARIA フォワード (v1.0.3) - #272

Merged
touyou merged 5 commits into
mainfrom
feature/263-268-input-reusable
May 19, 2026
Merged

✨ feat(input): 再利用 hooks の named export と triggerProps による ARIA フォワード (v1.0.3)#272
touyou merged 5 commits into
mainfrom
feature/263-268-input-reusable

Conversation

@touyou

@touyou touyou commented May 19, 2026

Copy link
Copy Markdown
Member

Summary

変更点

新しい public hooks (sparkle-design/hooks から import)

import { useMergeRefs, useInputContainerFocus } from "sparkle-design/hooks";
  • useMergeRefs(...refs) — 複数の ref を 1 つの ref callback にまとめる
  • useInputContainerFocus({ targetRef, isDisabled?, excludeRefs? }) — コンテナクリック時に内側の input にフォーカスを移すクリックハンドラを返す。excludeRefs で除外要素を指定可能

Input.triggerProps

<Input
  isTrigger
  triggerIcon="calendar_today"
  triggerAriaLabel="日付を選択"
  triggerProps={{
    "aria-haspopup": "dialog",
    "aria-expanded": open,
    "aria-controls": "date-picker-popover",
  }}
/>

優先順位は JSDoc に明記:

  • triggerAriaLabel > triggerProps["aria-label"]
  • onIconButtonClick > triggerProps.onClick
  • 以下は Input 側で固定され triggerProps で渡しても無視される: ref / onFocus / onBlur / type / disabled / icon / theme / variant / size(pin テスト済み)

package.json

  • version: 1.0.2 → 1.0.3
  • exports."./hooks" を追加

Test plan

  • pnpm test — 全 607 unit test pass(hooks 6 件 + Input triggerProps forwarding 6 件を新規追加)
  • tsc --noEmit — 型エラーなし
  • next lint — エラーなし(ローカル worktree でのプラグイン衝突警告は環境要因)
  • prettier --check — clean
  • 後方互換: 既存 Input テスト全件継続 pass
  • Storybook での目視確認(リリース前推奨)

リリース手順

マージ後:

  1. main から gh workflow run "Publish to GitHub Packages" を実行
  2. v1.0.3 タグの作成(ワークフローが行う/手動)

🤖 Generated with Claude Code

touyou and others added 3 commits May 19, 2026 17:00
closes #263
closes #268

- ♻️ `useMergeRefs` と `useInputContainerFocus` を `src/hooks/` 配下に切り出し、
  `sparkle-design/hooks` から named export
  - InputSearch などの派生コンポーネントから ref マージ / コンテナクリック→入力
    フォーカスのロジックを直接利用できるようにする (#263)
  - 既存 Input の挙動は変えず、内部実装をこれらの hook に置き換えた(後方互換維持)
- ✨ Input に `triggerProps?: ButtonHTMLAttributes<HTMLButtonElement>` を追加
  - `isTrigger` 時にトリガー `<button>` へ `aria-haspopup` / `aria-expanded` /
    `aria-controls` などの HTML / ARIA 属性を宣言的にフォワード可能に (#268)
  - 既存の `triggerAriaLabel` / `onIconButtonClick` が優先される実装で後方互換を維持
- ✅ hooks の単体テストと Input の triggerProps フォワードテストを追加(全 606 unit
  test 通過)
- 📦 `package.json` の `exports` に `./hooks` サブパスを追加

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Input まわりの再利用 hooks の named export (#263) と
  Input.triggerProps による ARIA / HTML 属性フォワード (#268) を含むパッチリリース
- 後方互換は維持

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
レビュー指摘 (#268) への対応:

- `triggerProps` で渡しても無視される Input 内部制御属性(`ref` / `onFocus` /
  `onBlur` / `type` / `disabled` / `aria-label`、および見た目 props
  `icon` / `theme` / `variant` / `size`)を JSDoc に明記
- `onClick` は `onIconButtonClick` が未指定なら `triggerProps.onClick` に
  フォールバックする挙動も併記
- 現挙動を pin するテストを追加(`triggerProps.onFocus` / `onBlur` / `type` /
  `disabled` が Input 内部値で上書きされることを検証)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 19, 2026 08:08
@vercel

vercel Bot commented May 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sparkle-design Ready Ready Preview, Comment May 19, 2026 8:21am

Request Review

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6765647e-1a4a-469f-a6d2-a91ea5d5a070

📥 Commits

Reviewing files that changed from the base of the PR and between 10d4cb3 and 70d128f.

📒 Files selected for processing (2)
  • src/components/ui/input/index.test.tsx
  • src/hooks/useMergeRefs.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks/useMergeRefs.ts
  • src/components/ui/input/index.test.tsx

Walkthrough

Input の内部 ref マージとコンテナクリック→フォーカス処理を外部 hook 化し、トリガーボタンへ任意の HTML/ARIA 属性を渡す triggerProps を追加。両フックと hook バレルは公開エクスポートされ、関連テストが追加されています。

Changes

Input Hook Extraction & triggerProps API

Layer / File(s) Summary
useMergeRefs hook extraction
src/hooks/useMergeRefs.ts, src/hooks/useMergeRefs.test.tsx
複数の ref(callback ref と ref object)をマージして単一 ref コールバックを返す useMergeRefs を実装。各 ref への値反映とコールバック ref フォワードの動作をテストで検証。
useInputContainerFocus hook extraction
src/hooks/useInputContainerFocus.ts, src/hooks/useInputContainerFocus.test.tsx
コンテナクリック時にターゲット要素へフォーカスを移すロジックを hook 化。isDisabled 判定、excludeRefs による除外判定、フォーカス実行の 3 つのテストケースで動作を検証。
Input component refactoring
src/components/ui/input/index.tsx
Input が新規フック(useMergeRefsuseInputContainerFocus)を import してローカル useMergeRefs 実装を削除、コンテナクリック時のフォーカス処理を useInputContainerFocus に置き換え。
Input triggerProps API
src/components/ui/input/index.tsx
InputPropstriggerProps?: React.ButtonHTMLAttributes<HTMLButtonElement> を追加。isTrigger モード時にトリガーボタンへ ARIA/HTML 属性を宣言的にフォワード、内部制御属性(aria-labelonClickdisabledtype、focus/blur 等)は優先順位付きで上書きする実装とドキュメント例を追加。
Input triggerProps forwarding tests
src/components/ui/input/index.test.tsx
triggerProps が正しくフォワード・上書きされる 6 つのテストケースを追加:aria-* 転送、triggerAriaLabel 優先、onIconButtonClick 優先、triggerProps.onClick フォールバック、id/data-* 転送、内部制御属性(focus/blur/type/disabled)の上書きとハンドラ非呼び出し。
useInputContainerFocus tests and harness
src/hooks/useInputContainerFocus.test.tsx
テストハーネスおよび beforeEach/afterEach を含むセットアップの下で、コンテナクリック挙動(通常、excludeRefs、isDisabled)を検証。
Public API exports
src/hooks/index.ts, src/index.ts
src/hooks/index.ts を追加して useMergeRefsuseInputContainerFocusUseInputContainerFocusOptions を再エクスポート。src/index.ts./hooks を公開エクスポートに含める変更。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

ぼくウサギ、ぴょんと来たよ 🐇
ref を結び、焦点を導き
トリガーに aria をそっと渡す
小さな hook が道を作るよ
さあ、コードが踊り出す ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed タイトルは PR の主な変更内容(再利用可能な hooks の named export と triggerProps による ARIA フォワード)を適切に反映しており、新バージョン(v1.0.3)も記載されています。
Description check ✅ Passed PR の説明は詳細で、リンク Issue の close (#263, #268)、変更内容の詳細、テスト計画、リリース手順が記載されていますが、公式テンプレートで指定される動作確認チェックリストが完全には埋められていません。
Linked Issues check ✅ Passed PR は #263(useMergeRefs と useInputContainerFocus の named export)と #268(triggerProps による ARIA フォワード)の両要件を満たしています。hooks の exported と Input の triggerProps 実装が確認でき、テストも追加されています。
Out of Scope Changes check ✅ Passed すべての変更が #263#268 の要件に関連しており、out-of-scope な変更は見当たりません。package.json の version 更新と export 追加も適切です。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/263-268-input-reusable

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Input 内部の useMergeRefs とコンテナフォーカスロジックを src/hooks/ に切り出し sparkle-design/hooks から named export し(#263)、InputtriggerProps を追加してトリガーボタンへ ARIA / HTML 属性を宣言的にフォワードできるようにする(#268)。v1.0.3 リリース。

Changes:

  • src/hooks/ 配下に useMergeRefs / useInputContainerFocus を切り出し、sparkle-design/hooks サブパスから export(テスト付き)
  • InputtriggerProps?: ButtonHTMLAttributes<HTMLButtonElement> を追加。triggerAriaLabel / onIconButtonClick を優先、ref / onFocus / onBlur / type / disabled 等は内部制御を維持
  • package.json を 1.0.3 に bump し ./hooks exports を追加

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/hooks/useMergeRefs.ts Input から抽出した汎用 ref マージ hook
src/hooks/useMergeRefs.test.tsx RefObject / callback ref / undefined ケースのテスト
src/hooks/useInputContainerFocus.ts コンテナクリック→ターゲット要素フォーカスを返す hook(excludeRefs 対応)
src/hooks/useInputContainerFocus.test.tsx フォーカス遷移・除外・disabled の振る舞いをテスト
src/hooks/index.ts hooks barrel export
src/index.ts ルートからの ./hooks 再 export
src/components/ui/input/index.tsx 内製ロジックを新 hooks に置換し、triggerProps を実装。precedence は JSDoc 明記
src/components/ui/input/index.test.tsx triggerProps フォワード / precedence / pin テスト 6 件追加
package.json version 1.0.3、exports."./hooks" 追加

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/ui/input/index.test.tsx`:
- Around line 548-549: Tests call button.focus() and button.blur() directly
which bypasses the shared event utilities; replace direct DOM calls with the
EventHelpers wrapper by invoking EventHelpers.focus(button) and
EventHelpers.blur(button) in the test where button.focus()/button.blur() appear
so focus/blur flows go through the common EventHelpers path used for other
interactions.

In `@src/hooks/useInputContainerFocus.test.tsx`:
- Around line 28-33: The test markup uses a clickable div with
onClick={handleContainerClick} but lacks keyboard accessibility; update the
container element used in the test to include role="button", tabIndex={0}, and
an onKeyDown handler that calls the same handler (handleContainerClick) for
Enter and Space keys so keyboard users trigger the same behavior; ensure you
reference the same refs (inputRef, buttonRef) and keep the existing
onClick={handleContainerClick} intact so both mouse and keyboard interactions
are covered.

In `@src/hooks/useMergeRefs.ts`:
- Around line 26-32: Update the ref callback in useMergeRefs so its parameter
accepts null (change the arrow function argument from value: T to value: T |
null) and adjust the cast for object refs to React.MutableRefObject<T | null>;
ensure the callback signature matches React.RefCallback<T> and the line that
assigns to (ref as React.MutableRefObject<...>).current uses the T | null
variant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c3cc3e95-6d39-4cde-a07a-60b934c68500

📥 Commits

Reviewing files that changed from the base of the PR and between f646918 and d669e2d.

⛔ Files ignored due to path filters (1)
  • package.json is excluded by !**/*.json
📒 Files selected for processing (8)
  • src/components/ui/input/index.test.tsx
  • src/components/ui/input/index.tsx
  • src/hooks/index.ts
  • src/hooks/useInputContainerFocus.test.tsx
  • src/hooks/useInputContainerFocus.ts
  • src/hooks/useMergeRefs.test.tsx
  • src/hooks/useMergeRefs.ts
  • src/index.ts

Comment thread src/components/ui/input/index.test.tsx Outdated
Comment on lines +28 to +33
<div data-testid="container" onClick={handleContainerClick}>
<input ref={inputRef} data-testid="input" />
<button ref={buttonRef} data-testid="button" type="button">
btn
</button>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

CI 失敗中: クリック可能な div にキーボード操作対応が不足しています。

Line 28 の divonClick を持つのにキーボードハンドラがなく、提示されている lint エラーの原因になっています。role/tabIndex/onKeyDown を追加して解消してください。

💡 修正案
+  const handleContainerKeyDown = React.useCallback(
+    (e: React.KeyboardEvent<HTMLDivElement>) => {
+      if (e.key === "Enter" || e.key === " ") {
+        e.preventDefault();
+        inputRef.current?.focus();
+      }
+    },
+    []
+  );
+
   return (
-    <div data-testid="container" onClick={handleContainerClick}>
+    <div
+      data-testid="container"
+      onClick={handleContainerClick}
+      role="button"
+      tabIndex={0}
+      onKeyDown={handleContainerKeyDown}
+    >
       <input ref={inputRef} data-testid="input" />
       <button ref={buttonRef} data-testid="button" type="button">
         btn
       </button>
     </div>
   );

As per coding guidelines **/*.{ts,tsx}: Use semantic HTML and ARIA labels for accessibility in React components.

🧰 Tools
🪛 GitHub Actions: CI / 0_lint-and-test.txt

[error] 28-28: ESLint (jsx-a11y): Visible, non-interactive elements with click handlers must have at least one keyboard listener. (jsx-a11y/click-events-have-key-events)


[error] 28-28: ESLint (jsx-a11y): Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element. (jsx-a11y/no-static-element-interactions)

🪛 GitHub Actions: CI / lint-and-test

[error] 28-28: next lint (ESLint) error: Visible, non-interactive elements with click handlers must have at least one keyboard listener. (jsx-a11y/click-events-have-key-events)


[error] 28-28: next lint (ESLint) error: Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs. (jsx-a11y/no-static-element-interactions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useInputContainerFocus.test.tsx` around lines 28 - 33, The test
markup uses a clickable div with onClick={handleContainerClick} but lacks
keyboard accessibility; update the container element used in the test to include
role="button", tabIndex={0}, and an onKeyDown handler that calls the same
handler (handleContainerClick) for Enter and Space keys so keyboard users
trigger the same behavior; ensure you reference the same refs (inputRef,
buttonRef) and keep the existing onClick={handleContainerClick} intact so both
mouse and keyboard interactions are covered.

Comment thread src/hooks/useMergeRefs.ts Outdated
- テストハーネスの div に Input 本体と同じ role="presentation" + tabIndex={-1}
  を付与し、jsx-a11y/click-events-have-key-events と
  jsx-a11y/no-static-element-interactions の lint エラーを解消
- 挙動・テスト結果は変わらず(3 件 pass)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- useMergeRefs: 引数を React.RefCallback<T> シグネチャに正確に合わせて
  `T | null` を受け取れるよう修正。MutableRefObject 型も `T | null` に。
- Input テスト: `button.focus()` / `button.blur()` を直接呼んでいたものを
  プロジェクト共通の `EventHelpers.focus` / `EventHelpers.blur` 経由に統一。
- useInputContainerFocus テストハーネスは Input 本体と同じ
  `role="presentation"` + `tabIndex={-1}` パターンを維持(実利用と整合)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 19, 2026 08:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

@touyou
touyou merged commit 157e2b6 into main May 19, 2026
8 checks passed
@touyou
touyou deleted the feature/263-268-input-reusable branch May 19, 2026 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants