AI: address review on PR #475
このコミットが含まれているのは:
@@ -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 ()],
|
||||
}
|
||||
|
||||
@@ -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'])
|
||||
})
|
||||
})
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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%;
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする