Fix store item variant defaults and listing sorting
Auto-select first option in variation modals and sort store items by availability, then update time. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b861ab3dc1
commit
5f4b0b4f92
@@ -6,7 +6,6 @@
|
||||
.tip {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
transform: translate(-50%, calc(-100% + 4px));
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
@@ -16,7 +15,7 @@
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
|
||||
transition: opacity 0.15s ease, visibility 0.15s;
|
||||
background: var(--elevated-surface);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
@@ -28,15 +27,22 @@
|
||||
.tipVisible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translate(-50%, -100%);
|
||||
}
|
||||
|
||||
.tip::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
left: var(--tooltip-arrow-left, 50%);
|
||||
transform: translateX(-50%);
|
||||
border: 5px solid transparent;
|
||||
}
|
||||
|
||||
.tipTop::after {
|
||||
top: 100%;
|
||||
border-top-color: var(--elevated-surface);
|
||||
}
|
||||
|
||||
.tipBottom::after {
|
||||
bottom: 100%;
|
||||
border-bottom-color: var(--elevated-surface);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,87 @@
|
||||
import { useEffect, useRef, useState, type ReactElement } from 'react'
|
||||
import {
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type CSSProperties,
|
||||
type ReactElement,
|
||||
} from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import styles from './Tooltip.module.css'
|
||||
|
||||
const GAP = 8
|
||||
const PAD = 8
|
||||
const ARROW = 5
|
||||
|
||||
interface TooltipProps {
|
||||
label: string
|
||||
children: ReactElement
|
||||
}
|
||||
|
||||
type TipPlacement = 'top' | 'bottom'
|
||||
|
||||
export function Tooltip({ label, children }: TooltipProps) {
|
||||
const wrapRef = useRef<HTMLSpanElement>(null)
|
||||
const tipRef = useRef<HTMLSpanElement>(null)
|
||||
const [visible, setVisible] = useState(false)
|
||||
const [coords, setCoords] = useState({ top: 0, left: 0 })
|
||||
const [ready, setReady] = useState(false)
|
||||
const [coords, setCoords] = useState<{
|
||||
top: number
|
||||
left: number
|
||||
placement: TipPlacement
|
||||
arrowLeft: number
|
||||
}>({ top: 0, left: 0, placement: 'top', arrowLeft: 50 })
|
||||
|
||||
function updatePosition() {
|
||||
const el = wrapRef.current
|
||||
if (!el) return
|
||||
const rect = el.getBoundingClientRect()
|
||||
setCoords({
|
||||
top: rect.top - 8,
|
||||
left: rect.left + rect.width / 2,
|
||||
})
|
||||
const trigger = wrapRef.current
|
||||
const tip = tipRef.current
|
||||
if (!trigger || !tip) return
|
||||
|
||||
const rect = trigger.getBoundingClientRect()
|
||||
const vw = window.innerWidth
|
||||
const vh = window.innerHeight
|
||||
const tipW = tip.offsetWidth
|
||||
const tipH = tip.offsetHeight
|
||||
|
||||
const spaceAbove = rect.top - PAD
|
||||
const spaceBelow = vh - rect.bottom - PAD
|
||||
const needH = tipH + GAP
|
||||
const fitsAbove = spaceAbove >= needH
|
||||
const fitsBelow = spaceBelow >= needH
|
||||
const placement: TipPlacement =
|
||||
fitsAbove || (!fitsBelow && spaceAbove >= spaceBelow) ? 'top' : 'bottom'
|
||||
|
||||
const top = placement === 'top' ? rect.top - GAP - tipH : rect.bottom + GAP
|
||||
|
||||
let left = rect.left + rect.width / 2 - tipW / 2
|
||||
left = Math.min(Math.max(left, PAD), Math.max(PAD, vw - PAD - tipW))
|
||||
|
||||
const triggerCenter = rect.left + rect.width / 2
|
||||
const arrowLeft = Math.min(Math.max(triggerCenter - left, ARROW + 4), Math.max(ARROW + 4, tipW - ARROW - 4))
|
||||
|
||||
setCoords({ top, left, placement, arrowLeft })
|
||||
setReady(true)
|
||||
}
|
||||
|
||||
function show() {
|
||||
updatePosition()
|
||||
setVisible(true)
|
||||
}
|
||||
|
||||
function hide() {
|
||||
setVisible(false)
|
||||
setReady(false)
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!visible) return
|
||||
updatePosition()
|
||||
}, [visible, label])
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible || ready) return
|
||||
updatePosition()
|
||||
}, [visible, ready, label])
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return
|
||||
|
||||
@@ -46,6 +97,12 @@ export function Tooltip({ label, children }: TooltipProps) {
|
||||
}
|
||||
}, [visible])
|
||||
|
||||
const style = {
|
||||
top: coords.top,
|
||||
left: coords.left,
|
||||
['--tooltip-arrow-left' as string]: `${coords.arrowLeft}px`,
|
||||
} as CSSProperties
|
||||
|
||||
return (
|
||||
<span
|
||||
ref={wrapRef}
|
||||
@@ -63,9 +120,12 @@ export function Tooltip({ label, children }: TooltipProps) {
|
||||
{visible
|
||||
? createPortal(
|
||||
<span
|
||||
className={`${styles.tip} ${styles.tipVisible}`}
|
||||
ref={tipRef}
|
||||
className={`${styles.tip} ${ready ? styles.tipVisible : ''} ${
|
||||
coords.placement === 'bottom' ? styles.tipBottom : styles.tipTop
|
||||
}`}
|
||||
role="tooltip"
|
||||
style={{ top: coords.top, left: coords.left }}
|
||||
style={style}
|
||||
>
|
||||
{label}
|
||||
</span>,
|
||||
|
||||
Reference in New Issue
Block a user