sync: update from private repo (e62f5c7)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-11 01:52:48 +00:00
parent 000a2474aa
commit d061ad08d8
237 changed files with 8441 additions and 5549 deletions
+4 -2
View File
@@ -1,4 +1,5 @@
import { useEffect, useRef, type ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import type { PageId } from '../../lib/urlState';
import { useBackdropClose } from '../../lib/useBackdropClose';
@@ -96,6 +97,7 @@ export function NavDrawer({
logoUrl,
returnFocusRef,
}: NavDrawerProps) {
const { t } = useTranslation('layout');
const panelRef = useRef<HTMLDivElement>(null);
const firstItemRef = useRef<HTMLButtonElement>(null);
const backdrop = useBackdropClose(onClose);
@@ -163,7 +165,7 @@ export function NavDrawer({
id="nav-drawer"
role="dialog"
aria-modal="true"
aria-label="ナビゲーション"
aria-label={t('drawer.nav')}
aria-hidden={!open}
tabIndex={-1}
onKeyDown={onPanelKeyDown}
@@ -189,7 +191,7 @@ export function NavDrawer({
v{__APP_VERSION__}
</span>
</div>
<nav className="flex-1 overflow-y-auto py-2" aria-label="メインナビゲーション">
<nav className="flex-1 overflow-y-auto py-2" aria-label={t('nav.mainNav')}>
{visibleNav.map((item, idx) => {
const active = currentPage === item.id;
return (
+3 -1
View File
@@ -1,5 +1,6 @@
// ui/src/components/layout/ResizeHandle.tsx
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
interface ResizeHandleProps {
/** drag 中に呼ばれる。新しい chatPx を渡す。ref-based で React 再 render しない想定。 */
@@ -23,6 +24,7 @@ export function ResizeHandle({
minWorkspacePx,
handlePx,
}: ResizeHandleProps) {
const { t } = useTranslation('layout');
// latest-ref pattern: callback が render 毎に新しくなっても useEffect の
// listener を付け直さずに済む。これが無いと drag 中に listener が外れる。
const onResizeRef = useRef(onResize);
@@ -75,7 +77,7 @@ export function ResizeHandle({
<div
role="separator"
aria-orientation="vertical"
aria-label="Chat と Workspace の幅を調整"
aria-label={t('resize.chatWorkspace')}
onPointerDown={handlePointerDown}
onDoubleClick={onReset}
className="cursor-col-resize bg-transparent hover:bg-slate-300/60 transition-colors flex items-stretch group"
+9 -7
View File
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { type ThemePref, isThemePref, readStoredTheme, setThemePref, THEME_CHANGE_EVENT } from '../../lib/theme';
const ICON_PROPS = {
@@ -13,10 +14,10 @@ const ICON_PROPS = {
'aria-hidden': true,
};
const OPTIONS: Array<{ value: ThemePref; label: string; icon: JSX.Element }> = [
const OPTIONS: Array<{ value: ThemePref; labelKey: string; icon: JSX.Element }> = [
{
value: 'system',
label: 'システム設定に合わせる',
labelKey: 'theme.system',
icon: (
<svg {...ICON_PROPS}>
<rect x="2" y="4" width="20" height="13" rx="2" />
@@ -26,7 +27,7 @@ const OPTIONS: Array<{ value: ThemePref; label: string; icon: JSX.Element }> = [
},
{
value: 'light',
label: 'ライト',
labelKey: 'theme.light',
icon: (
<svg {...ICON_PROPS}>
<circle cx="12" cy="12" r="4" />
@@ -36,7 +37,7 @@ const OPTIONS: Array<{ value: ThemePref; label: string; icon: JSX.Element }> = [
},
{
value: 'dark',
label: 'ダーク',
labelKey: 'theme.dark',
icon: (
<svg {...ICON_PROPS}>
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
@@ -54,6 +55,7 @@ const OPTIONS: Array<{ value: ThemePref; label: string; icon: JSX.Element }> = [
* [data-theme] attribute correct on load and on OS changes.
*/
export function ThemeToggle() {
const { t } = useTranslation('layout');
const [pref, setPref] = useState<ThemePref>(() => readStoredTheme());
useEffect(() => {
@@ -75,7 +77,7 @@ export function ThemeToggle() {
return (
<div
role="group"
aria-label="テーマ"
aria-label={t('theme.label')}
className="inline-flex items-center gap-0.5 rounded-md border border-hairline bg-surface p-0.5"
>
{OPTIONS.map((opt) => {
@@ -85,9 +87,9 @@ export function ThemeToggle() {
key={opt.value}
type="button"
onClick={() => choose(opt.value)}
aria-label={opt.label}
aria-label={t(opt.labelKey)}
aria-pressed={active}
title={opt.label}
title={t(opt.labelKey)}
className={`flex items-center justify-center w-6 h-6 rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-ring ${
active
? 'bg-canvas text-slate-900 shadow-sm'
+20 -16
View File
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { PageId } from '../../lib/urlState';
import type { AuthUser } from '../../App';
import { ThemeToggle } from './ThemeToggle';
@@ -18,15 +19,17 @@ interface TopBarProps {
onOpenCommandK?: () => void;
}
export const NAV_ITEMS: Array<{ id: PageId; label: string; adminOnly: boolean; requiresAuth: boolean }> = [
{ id: 'tasks', label: 'タスク', adminOnly: false, requiresAuth: false },
{ id: 'schedules', label: 'スケジュール', adminOnly: false, requiresAuth: false },
{ id: 'pieces', label: 'Pieces', adminOnly: false, requiresAuth: false },
{ id: 'captcha', label: 'CAPTCHA', adminOnly: true, requiresAuth: false },
{ id: 'settings', label: '設定', adminOnly: false, requiresAuth: false },
{ id: 'users', label: 'ユーザー', adminOnly: true, requiresAuth: true },
{ id: 'help', label: 'ヘルプ', adminOnly: false, requiresAuth: false },
{ id: 'userfolder', label: 'ユーザーフォルダ', adminOnly: false, requiresAuth: false },
// labelKey resolves against the `layout` i18n namespace at render time (module
// scope can't call hooks). All consumers translate: TopBar, NavDrawer, App.tsx.
export const NAV_ITEMS: Array<{ id: PageId; labelKey: string; adminOnly: boolean; requiresAuth: boolean }> = [
{ id: 'tasks', labelKey: 'nav.tasks', adminOnly: false, requiresAuth: false },
{ id: 'schedules', labelKey: 'nav.schedules', adminOnly: false, requiresAuth: false },
{ id: 'pieces', labelKey: 'nav.pieces', adminOnly: false, requiresAuth: false },
{ id: 'captcha', labelKey: 'nav.captcha', adminOnly: true, requiresAuth: false },
{ id: 'settings', labelKey: 'nav.settings', adminOnly: false, requiresAuth: false },
{ id: 'users', labelKey: 'nav.users', adminOnly: true, requiresAuth: true },
{ id: 'help', labelKey: 'nav.help', adminOnly: false, requiresAuth: false },
{ id: 'userfolder', labelKey: 'nav.userfolder', adminOnly: false, requiresAuth: false },
];
export function estimateCollapseThreshold(navCount: number): number {
@@ -73,6 +76,7 @@ export function TopBar({
navDrawerOpen = false,
onOpenCommandK,
}: TopBarProps) {
const { t } = useTranslation('layout');
const visibleNav = visibleNavItemsFor(isAdmin, authEnabled);
const compactMode = useViewportNarrow(estimateCollapseThreshold(visibleNav.length));
const [showPwChange, setShowPwChange] = useState(false);
@@ -92,7 +96,7 @@ export function TopBar({
ref={hamburgerButtonRef}
type="button"
onClick={onOpenDrawer}
aria-label="メニューを開く"
aria-label={t('nav.openMenu')}
aria-expanded={navDrawerOpen}
aria-haspopup="dialog"
aria-controls="nav-drawer"
@@ -118,7 +122,7 @@ export function TopBar({
</span>
{!compactMode && (
<nav className="flex gap-5 items-stretch -mb-[13px] ml-2" aria-label="メインナビゲーション">
<nav className="flex gap-5 items-stretch -mb-[13px] ml-2" aria-label={t('nav.mainNav')}>
{visibleNav.map(item => {
const active = currentPage === item.id;
return (
@@ -133,7 +137,7 @@ export function TopBar({
: 'font-medium text-slate-500 border-transparent hover:text-slate-800'
}`}
>
{item.label}
{t(item.labelKey)}
</button>
);
})}
@@ -146,8 +150,8 @@ export function TopBar({
<button
type="button"
onClick={onOpenCommandK}
aria-label="コマンドパレットを開く"
title="コマンドパレット (⌘K)"
aria-label={t('commandPalette.open')}
title={t('commandPalette.title')}
className="hidden sm:inline-flex items-center gap-1 px-2 h-7 rounded-md border border-hairline text-2xs text-slate-500 hover:text-slate-800 hover:bg-surface transition-colors"
>
<span aria-hidden>K</span>
@@ -177,13 +181,13 @@ export function TopBar({
onClick={() => setShowPwChange(true)}
className="px-2 py-1 rounded-md text-2xs text-slate-500 hover:text-slate-800 hover:bg-surface transition-colors"
>
{t('user.changePassword')}
</button>
<a
href="/auth/logout"
className="px-2 py-1 rounded-md text-2xs text-slate-500 hover:text-slate-800 hover:bg-surface transition-colors"
>
{t('user.logout')}
</a>
</div>
)}
@@ -1,4 +1,5 @@
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
interface Props {
/** drag 中に呼ばれる。upperPct (0..100) を渡す。 */
@@ -17,6 +18,7 @@ export function VerticalResizeHandle({
onResize, onResizeEnd, onReset, parentSelector,
minUpperPct = 20, minLowerPct = 15,
}: Props) {
const { t } = useTranslation('layout');
const onResizeRef = useRef(onResize);
const onResizeEndRef = useRef(onResizeEnd);
onResizeRef.current = onResize;
@@ -57,7 +59,7 @@ export function VerticalResizeHandle({
<div
role="separator"
aria-orientation="horizontal"
aria-label="タスクリストと情報パネルの高さを調整"
aria-label={t('resize.listPanel')}
onPointerDown={(e) => {
e.preventDefault();
draggingRef.current = true;