mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-14 00:01:03 +00:00
6835fd7bf3
Page transitions: on the phone, pages move like a native navigation stack, built on the View Transitions API (mobile/routeTransition.ts). A page opened from another slides in from the trailing edge, back slides it away, and switching between the dock's sections cross-fades while the dock stands still. Lazy routes do not flash their skeleton: the swap waits, behind the frozen snapshot, until the new page rendered. The edge-swipe back gesture already moves the page and skips the transition. RTL mirrors the motion, reduced motion and desktop get none. Pull-to-refresh on the mobile dashboard: the page follows the finger with resistance, ticks once past the threshold and re-reads trips and notifications quietly, without a loading state. Share: trip share and invite links and the journey link get a share button next to copy, opening the system share sheet (Capacitor Share in the apps, Web Share in browsers that have it). Haptics: a selection click for switches and segmented controls, a tick when a sheet is flung closed, and a firmer bump for the chat's long press. Theme lint: this branch's new screens use the type and z-index tokens, the Vacay company tint is a token, and the baseline is lowered to the new counts.
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
import { ReactNode, useRef } from 'react'
|
|
import { useActivePill } from './useActivePill'
|
|
import { selectionHaptic } from '../../native/haptics'
|
|
|
|
export interface MSegmentedOption<T extends string = string> {
|
|
value: T
|
|
label: ReactNode
|
|
}
|
|
|
|
interface MSegmentedProps<T extends string = string> {
|
|
options: MSegmentedOption<T>[]
|
|
value: T
|
|
onChange: (value: T) => void
|
|
/** stretch = equal-width segments (costs tabs), intrinsic = pill hugs its label (dashboard filter) */
|
|
variant?: 'stretch' | 'intrinsic'
|
|
className?: string
|
|
}
|
|
|
|
/**
|
|
* Segment pill with a sliding active indicator. Active segment reads in
|
|
* --m-actfg on the --m-act pill, idle segments in --m-ink (Spec 07 §6).
|
|
*/
|
|
export default function MSegmented<T extends string = string>({
|
|
options,
|
|
value,
|
|
onChange,
|
|
variant = 'stretch',
|
|
className = '',
|
|
}: MSegmentedProps<T>) {
|
|
const trackRef = useRef<HTMLDivElement>(null)
|
|
const pill = useActivePill(trackRef, '[data-active="true"]', [value, options.length])
|
|
|
|
return (
|
|
<div
|
|
ref={trackRef}
|
|
role="tablist"
|
|
className={`relative flex rounded-full bg-[color:var(--m-ic)] p-[3px] ${className}`}
|
|
>
|
|
{pill && <div aria-hidden="true" className="absolute bottom-[3px] left-0 top-[3px] rounded-full bg-m-act" style={pill} />}
|
|
{options.map((opt) => {
|
|
const active = opt.value === value
|
|
return (
|
|
<button
|
|
key={opt.value}
|
|
type="button"
|
|
role="tab"
|
|
aria-selected={active}
|
|
data-active={active}
|
|
onClick={() => {
|
|
if (!active) selectionHaptic()
|
|
onChange(opt.value)
|
|
}}
|
|
className={`relative z-[1] rounded-full py-[7px] text-center ${
|
|
variant === 'intrinsic'
|
|
? 'whitespace-nowrap px-[11px] text-[0.75rem]'
|
|
: 'flex-1 text-[0.71875rem]'
|
|
} ${active ? 'font-semibold text-m-actfg' : 'font-medium text-m-ink'}`}
|
|
>
|
|
{opt.label}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|