AI: address review on PR #475

このコミットが含まれているのは:
Codex Night Worker
2026-11-15 13:39:52 +09:00
コミット 37b1c63543
5個のファイルの変更、117行の追加、26行の削除
+27 -4
ファイルの表示
@@ -1,6 +1,29 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
import autoprefixer from 'autoprefixer'
import tailwindcss from 'tailwindcss'
// Apply the same breakpoints to handwritten CSS and Tailwind utilities.
export const inlineBreakpoints = {
postcssPlugin: 'inline-breakpoints',
OnceExit (root) {
const queries = []
root.walkAtRules ('media', query => {
if (/\b(?:min|max)-width\s*:/.test (query.params))
queries.push (query)
})
for (const query of queries) {
const vertical = query.cloneAfter ({
params: query.params.replace (/\b(min|max)-width(?=\s*:)/g, '$1-height') })
for (const [branch, scope] of [
[query, ':where(html:not(:lang(mn-Mong)))'],
[vertical, ':where(html:lang(mn-Mong))']]) {
branch.walkRules (rule => {
rule.selectors = rule.selectors.map (selector => `${scope} ${selector}`)
})
}
}
},
}
export default {
plugins: [tailwindcss (), inlineBreakpoints, autoprefixer ()],
}
+52
ファイルの表示
@@ -0,0 +1,52 @@
import postcss from 'postcss'
import tailwindcss from 'tailwindcss'
import { describe, expect, it } from 'vitest'
import { inlineBreakpoints } from './postcss.config.js'
describe ('inline breakpoints', () => {
it ('uses height for vertical menus and width for horizontal menus', async () => {
const source = `
@media (min-width: 768px) { .menu, .account { display: flex } }
@media (min-width: 1024px) { .menu { font-size: 1rem } }
@media (max-width: 767px) { .mobile { display: block } }
@media (prefers-reduced-motion: reduce) { .menu { transition: none } }
`
const { css } = await postcss ([inlineBreakpoints]).process (
source, { from: 'menus.css' })
const rules = []
postcss.parse (css).walkRules (rule => {
rules.push ([rule.parent.params, rule.selector])
})
expect (rules).toEqual ([
['(min-width: 768px)',
':where(html:not(:lang(mn-Mong))) .menu, '
+ ':where(html:not(:lang(mn-Mong))) .account'],
['(min-height: 768px)',
':where(html:lang(mn-Mong)) .menu, :where(html:lang(mn-Mong)) .account'],
['(min-width: 1024px)', ':where(html:not(:lang(mn-Mong))) .menu'],
['(min-height: 1024px)', ':where(html:lang(mn-Mong)) .menu'],
['(max-width: 767px)', ':where(html:not(:lang(mn-Mong))) .mobile'],
['(max-height: 767px)', ':where(html:lang(mn-Mong)) .mobile'],
['(prefers-reduced-motion: reduce)', '.menu']])
})
it ('includes responsive utilities generated by Tailwind', async () => {
const { css } = await postcss ([tailwindcss ({
content: [{ raw: '<div class="md:hidden lg:text-base"></div>' }],
corePlugins: { preflight: false } }), inlineBreakpoints]).process (
'@tailwind utilities;', { from: 'utilities.css' })
const rules = []
postcss.parse (css).walkRules (rule => {
rules.push ([rule.parent.params, rule.selector])
})
expect (rules).toContainEqual ([
'(min-width: 768px)', ':where(html:not(:lang(mn-Mong))) .md\\:hidden'])
expect (rules).toContainEqual ([
'(min-height: 768px)', ':where(html:lang(mn-Mong)) .md\\:hidden'])
expect (rules).toContainEqual ([
'(min-height: 1024px)', ':where(html:lang(mn-Mong)) .lg\\:text-base'])
})
})
+9 -5
ファイルの表示
@@ -1,7 +1,9 @@
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { cn } from '@/lib/utils'
import { useEffect, useRef, useState } from 'react'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { useVerticalPreview } from '@/lib/useLocale'
import { cn } from '@/lib/utils'
import type { FC } from 'react'
type Props = {
@@ -11,7 +13,7 @@ type Props = {
title?: string }
const DESKTOP_MARQUEE_MEDIA =
'(min-width: 768px) and (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)'
'(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)'
const MARQUEE_START_DELAY_MS = 1600
const MARQUEE_END_HOLD_MS = 2400
const MARQUEE_SCROLL_PX_PER_SECOND = 43
@@ -21,6 +23,7 @@ const MIN_MARQUEE_OVERFLOW_PX = 1
const ResponsiveMarqueeText: FC<Props> = (
{ text, className, truncateOnMobile = false, title },
) => {
const verticalPreview = useVerticalPreview ()
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const outerRef = useRef<HTMLSpanElement | null> (null)
@@ -69,7 +72,8 @@ const ResponsiveMarqueeText: FC<Props> = (
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function')
return
const media = window.matchMedia (DESKTOP_MARQUEE_MEDIA)
const sizeMedia = verticalPreview ? '(min-height: 768px)' : '(min-width: 768px)'
const media = window.matchMedia (`${ sizeMedia } and ${ DESKTOP_MARQUEE_MEDIA }`)
const update = () => {
setDesktopMarqueeEnabled (media.matches)
}
@@ -80,7 +84,7 @@ const ResponsiveMarqueeText: FC<Props> = (
return () => {
media.removeEventListener ('change', update)
}
}, [])
}, [verticalPreview])
useEffect (() => {
const clearScheduled = () => {
+19 -17
ファイルの表示
@@ -299,21 +299,14 @@ const TopNav: FC<Props> = ({ user, locales, resolveLocaleSwitch }) => {
let active = true
const update = () => {
if (!(window.matchMedia ('(min-width: 768px)').matches))
const desktopMedia = verticalPreview ? '(min-height: 768px)' : '(min-width: 768px)'
if (!(window.matchMedia (desktopMedia).matches))
{
setDesktop (false)
setDensity ('auto')
return
}
if (verticalPreview)
{
setDesktop (true)
setDensity ('auto')
measure (moreVsbl ? -1 : activeIdx)
return
}
// Measure the same desktop elements while the mobile layout is active.
layout.setAttribute ('data-measuring', '')
const previousDensity = layout.getAttribute ('data-density') ?? 'auto'
@@ -324,16 +317,25 @@ const TopNav: FC<Props> = ({ user, locales, resolveLocaleSwitch }) => {
{
layout.setAttribute ('data-density', candidate)
const style = getComputedStyle (bar)
const primaryWidth = Math.max (
primary.getBoundingClientRect ().width, primary.scrollWidth)
const accountWidth =
const primarySize =
verticalPreview
? Math.max (primary.getBoundingClientRect ().height, primary.scrollHeight)
: Math.max (primary.getBoundingClientRect ().width, primary.scrollWidth)
const accountSize =
account == null
? 0
: Math.max (account.getBoundingClientRect ().width, account.scrollWidth)
const required = primaryWidth + accountWidth
+ parseFloat (style.paddingLeft) + parseFloat (style.paddingRight)
+ (account == null ? 0 : parseFloat (style.columnGap))
if (Math.ceil (required) <= bar.clientWidth)
: (verticalPreview
? Math.max (account.getBoundingClientRect ().height, account.scrollHeight)
: Math.max (account.getBoundingClientRect ().width, account.scrollWidth))
const padding =
verticalPreview
? parseFloat (style.paddingTop) + parseFloat (style.paddingBottom)
: parseFloat (style.paddingLeft) + parseFloat (style.paddingRight)
const gap = verticalPreview ? style.rowGap : style.columnGap
const required = primarySize + accountSize + padding
+ (account == null ? 0 : parseFloat (gap))
const available = verticalPreview ? bar.clientHeight : bar.clientWidth
if (Math.ceil (required) <= available)
return candidate
}
return null
+10
ファイルの表示
@@ -1355,6 +1355,16 @@ html:lang(mn-Mong) .inheritance-dialogue-fields > button
height: auto;
}
[data-vertical-preview] .top-nav-layout[data-measuring] .top-nav-primary,
[data-vertical-preview] .top-nav-layout[data-measuring] .top-nav-desktop-menu,
[data-vertical-preview] .top-nav-layout[data-measuring] .top-nav-account
{
flex: 0 0 auto;
width: 100%;
height: max-content;
max-height: none;
}
[data-vertical-preview] .top-nav-desktop-menu > div:not(.top-nav-highlight)
{
width: 100%;