feat: publish HoloLake model-native living system source

This commit is contained in:
冰朔 2026-08-03 10:04:41 +08:00
commit c395dd3a99
2467 changed files with 615073 additions and 0 deletions

View file

@ -0,0 +1,78 @@
import { render, screen } from '@testing-library/react'
import { useCallback, useLayoutEffect, useState, type ReactNode } from 'react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { isRecoveredActionTooltipError } from './actionTooltipRecovery'
afterEach(() => {
vi.doUnmock('./tooltip')
vi.resetModules()
vi.restoreAllMocks()
})
describe('ActionTooltip recovery', () => {
it('keeps the trigger mounted when tooltip content rendering fails', async () => {
const tooltipError = new Error('tooltip content render failed')
vi.doMock('./tooltip', () => {
return {
Tooltip: ({ children }: { children: ReactNode }) => <>{children}</>,
TooltipContent: () => {
throw tooltipError
},
TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children}</>,
}
})
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const { ActionTooltip } = await import('./action-tooltip')
render(
<ActionTooltip copy={{ label: 'Switch editor layout' }}>
<button type="button">Switch editor layout</button>
</ActionTooltip>,
)
expect(screen.getByRole('button', { name: 'Switch editor layout' })).toBeInTheDocument()
expect(isRecoveredActionTooltipError(tooltipError)).toBe(true)
expect(consoleError).toHaveBeenCalled()
})
it('keeps failed tooltip content disabled when fallback mounting updates parent state', async () => {
const tooltipError = new Error('tooltip content render failed')
vi.doMock('./tooltip', () => {
return {
Tooltip: ({ children }: { children: ReactNode }) => <>{children}</>,
TooltipContent: () => {
throw tooltipError
},
TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children}</>,
}
})
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const { ActionTooltip } = await import('./action-tooltip')
function FallbackButton({ onMount }: { onMount: () => void }) {
useLayoutEffect(() => {
onMount()
}, [onMount])
return <button type="button">Switch editor layout</button>
}
function ParentWithFallbackStateUpdate() {
const [renderCount, setRenderCount] = useState(0)
const bumpRenderCount = useCallback(() => {
setRenderCount((current) => current + 1)
}, [])
return (
<ActionTooltip copy={{ label: `Switch editor layout ${renderCount}` }}>
<FallbackButton onMount={bumpRenderCount} />
</ActionTooltip>
)
}
expect(() => render(<ParentWithFallbackStateUpdate />)).not.toThrow(/Maximum update depth/)
expect(screen.getByRole('button', { name: 'Switch editor layout' })).toBeInTheDocument()
expect(isRecoveredActionTooltipError(tooltipError)).toBe(true)
expect(consoleError).toHaveBeenCalled()
})
})

View file

@ -0,0 +1,83 @@
import { Component, type ComponentProps, type ReactNode } from 'react'
import { cn } from '@/lib/utils'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { markRecoveredActionTooltipError } from './actionTooltipRecovery'
export interface ActionTooltipCopy {
label: string
shortcut?: string
}
export interface ActionTooltipProps {
copy: ActionTooltipCopy
children: ReactNode
className?: string
contentTestId?: string
side?: ComponentProps<typeof TooltipContent>['side']
align?: ComponentProps<typeof TooltipContent>['align']
sideOffset?: number
open?: ComponentProps<typeof Tooltip>['open']
onOpenChange?: ComponentProps<typeof Tooltip>['onOpenChange']
}
interface ActionTooltipBoundaryProps {
children: ReactNode
fallback: ReactNode
}
interface ActionTooltipBoundaryState {
failed: boolean
}
class ActionTooltipBoundary extends Component<ActionTooltipBoundaryProps, ActionTooltipBoundaryState> {
state: ActionTooltipBoundaryState = { failed: false }
static getDerivedStateFromError(): ActionTooltipBoundaryState {
return { failed: true }
}
componentDidCatch(error: unknown) {
markRecoveredActionTooltipError(error)
}
render() {
return this.state.failed ? this.props.fallback : this.props.children
}
}
export function ActionTooltip({
copy,
children,
className,
contentTestId,
side = 'top',
align = 'center',
sideOffset = 6,
open,
onOpenChange,
}: ActionTooltipProps) {
return (
<ActionTooltipBoundary fallback={children}>
<Tooltip open={open} onOpenChange={onOpenChange}>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent
side={side}
align={align}
sideOffset={sideOffset}
data-align={align}
data-testid={contentTestId}
className={cn('px-2.5 py-2', className)}
>
<div className="flex min-w-0 items-center gap-3">
<span className="min-w-0 flex-1 text-[11px] font-medium leading-tight">{copy.label}</span>
{copy.shortcut && (
<span className="shrink-0 rounded border border-background/20 bg-background/10 px-1.5 py-0.5 font-mono text-[10px] leading-none text-background/80">
{copy.shortcut}
</span>
)}
</div>
</TooltipContent>
</Tooltip>
</ActionTooltipBoundary>
)
}

View file

@ -0,0 +1,21 @@
const ACTION_TOOLTIP_RECOVERY_BOUNDARY_NAME = 'ActionTooltipBoundary'
const RECOVERED_ACTION_TOOLTIP_ERROR_MARK = '__tolariaRecoveredActionTooltipError'
type MarkedRecoveredActionTooltipError = Error & {
[RECOVERED_ACTION_TOOLTIP_ERROR_MARK]?: true
}
function hasRecoveredActionTooltipMark(error: unknown): boolean {
if (!(error instanceof Error)) return false
return Reflect.get(error as MarkedRecoveredActionTooltipError, RECOVERED_ACTION_TOOLTIP_ERROR_MARK) === true
}
export function markRecoveredActionTooltipError(error: unknown): void {
if (!(error instanceof Error)) return
Reflect.set(error as MarkedRecoveredActionTooltipError, RECOVERED_ACTION_TOOLTIP_ERROR_MARK, true)
}
export function isRecoveredActionTooltipError(error: unknown, componentStack = ''): boolean {
return hasRecoveredActionTooltipMark(error)
|| componentStack.includes(ACTION_TOOLTIP_RECOVERY_BOUNDARY_NAME)
}

View file

@ -0,0 +1,49 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary:
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"bg-destructive text-destructive-foreground [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
link: "text-primary underline-offset-4 [a&]:hover:underline",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant = "default",
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span"
return (
<Comp
data-slot="badge"
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
// eslint-disable-next-line react-refresh/only-export-components -- shadcn/ui pattern
export { Badge, badgeVariants }

View file

@ -0,0 +1,65 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-4",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
// eslint-disable-next-line react-refresh/only-export-components -- shadcn/ui pattern
export { Button, buttonVariants }

View file

@ -0,0 +1,272 @@
"use client"
import * as React from "react"
import {
CaretDown as ChevronDownIcon,
CaretLeft as ChevronLeftIcon,
CaretRight as ChevronRightIcon,
} from '@phosphor-icons/react'
import {
DayPicker,
getDefaultClassNames,
type ChevronProps,
type DayButton,
type DropdownProps,
type RootProps,
type WeekNumberProps,
} from "react-day-picker"
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const CALENDAR_START_MONTH = new Date(1900, 0)
const CALENDAR_END_MONTH = new Date(2100, 11)
type CalendarProps = React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
}
function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = "dropdown",
navLayout,
buttonVariant = "ghost",
formatters,
components,
startMonth = CALENDAR_START_MONTH,
endMonth = CALENDAR_END_MONTH,
...props
}: CalendarProps) {
const defaultClassNames = getDefaultClassNames()
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className
)}
captionLayout={captionLayout}
navLayout={navLayout}
startMonth={startMonth}
endMonth={endMonth}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString("default", { month: "short" }),
...formatters,
}}
classNames={getCalendarClassNames({
defaultClassNames,
classNames,
captionLayout,
buttonVariant,
showWeekNumber: props.showWeekNumber,
})}
components={{
Root: CalendarRoot,
Chevron: CalendarChevron,
DayButton: CalendarDayButton,
WeekNumber: CalendarWeekNumber,
Dropdown: CalendarDropdown,
...components,
}}
{...props}
/>
)
}
function getCalendarClassNames({
defaultClassNames,
classNames,
captionLayout,
buttonVariant,
showWeekNumber,
}: {
defaultClassNames: ReturnType<typeof getDefaultClassNames>
classNames: CalendarProps["classNames"]
captionLayout: CalendarProps["captionLayout"]
buttonVariant: CalendarProps["buttonVariant"]
showWeekNumber: CalendarProps["showWeekNumber"]
}) {
return {
root: cn("w-fit", defaultClassNames.root),
months: cn("flex gap-4 flex-col md:flex-row relative", defaultClassNames.months),
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
nav: cn("pointer-events-none flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", defaultClassNames.nav),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"pointer-events-auto size-(--cell-size) aria-disabled:opacity-50 p-0 select-none hover:bg-transparent",
defaultClassNames.button_previous
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"pointer-events-auto size-(--cell-size) aria-disabled:opacity-50 p-0 select-none hover:bg-transparent",
defaultClassNames.button_next
),
month_caption: cn(
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
defaultClassNames.month_caption
),
dropdowns: cn(
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
defaultClassNames.dropdowns
),
dropdown_root: cn(
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
defaultClassNames.dropdown_root
),
dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown),
caption_label: cn(
"select-none font-medium",
captionLayout === "label"
? "text-sm"
: "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
defaultClassNames.caption_label
),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn("text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", defaultClassNames.weekday),
week: cn("flex w-full mt-2", defaultClassNames.week),
week_number_header: cn("select-none w-(--cell-size)", defaultClassNames.week_number_header),
week_number: cn("text-[0.8rem] select-none text-muted-foreground", defaultClassNames.week_number),
day: cn(
"relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
showWeekNumber
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
: "[&:first-child[data-selected=true]_button]:rounded-l-md",
defaultClassNames.day
),
range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
today: cn("bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", defaultClassNames.today),
outside: cn("text-muted-foreground aria-selected:text-muted-foreground", defaultClassNames.outside),
disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}
}
function CalendarRoot({ className, rootRef, ...props }: RootProps) {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
)
}
function CalendarChevron({ className, orientation, ...props }: ChevronProps) {
if (orientation === "left") {
return <ChevronLeftIcon className={cn("size-4", className)} {...props} />
}
if (orientation === "right") {
return <ChevronRightIcon className={cn("size-4", className)} {...props} />
}
return <ChevronDownIcon className={cn("size-4", className)} {...props} />
}
function CalendarWeekNumber({ children, ...props }: WeekNumberProps) {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
)
}
function CalendarDropdown({
options,
value,
onChange,
"aria-label": ariaLabel,
}: DropdownProps) {
const handleValueChange = (newValue: string) => {
const changeEvent = {
target: { value: newValue },
} as Parameters<NonNullable<DropdownProps['onChange']>>[0]
onChange?.(changeEvent)
}
return (
<Select value={value?.toString()} onValueChange={handleValueChange}>
<SelectTrigger
aria-label={ariaLabel}
size="sm"
className="h-8 min-w-[88px] justify-center gap-1 bg-background px-2 py-0 text-sm font-medium"
>
<SelectValue />
</SelectTrigger>
<SelectContent className="z-[13000] max-h-64">
<SelectGroup>
{options?.map((option) => (
<SelectItem
key={option.value}
value={option.value.toString()}
disabled={option.disabled}
>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
function CalendarDayButton({
className,
day,
modifiers,
...props
}: React.ComponentProps<typeof DayButton>) {
const defaultClassNames = getDefaultClassNames()
const ref = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus()
}, [modifiers.focused])
return (
<Button
ref={ref}
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString()}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day,
className
)}
{...props}
/>
)
}
export { Calendar, CalendarDayButton }

View file

@ -0,0 +1,92 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

View file

@ -0,0 +1,59 @@
import * as React from 'react'
import { Check } from '@phosphor-icons/react'
import { cn } from '@/lib/utils'
export type CheckedState = boolean | 'indeterminate'
interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'checked' | 'onChange' | 'type'> {
checked?: CheckedState
onCheckedChange?: (checked: CheckedState) => void
}
function Checkbox({
className,
checked = false,
disabled = false,
onCheckedChange,
...props
}: CheckboxProps) {
const isChecked = checked === true
const inputRef = React.useRef<HTMLInputElement>(null)
React.useEffect(() => {
if (inputRef.current) inputRef.current.indeterminate = checked === 'indeterminate'
}, [checked])
return (
<span className="relative inline-flex size-4 shrink-0 items-center justify-center">
<input
ref={inputRef}
type="checkbox"
checked={isChecked}
aria-checked={checked === 'indeterminate' ? 'mixed' : isChecked}
data-slot="checkbox"
data-state={isChecked ? 'checked' : 'unchecked'}
disabled={disabled}
className={cn(
'peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 appearance-none rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
onChange={() => {
if (disabled) return
onCheckedChange?.(!isChecked)
}}
{...props}
/>
{isChecked && (
<span
data-slot="checkbox-indicator"
className="pointer-events-none absolute inset-0 flex items-center justify-center text-primary-foreground transition-none"
>
<Check className="size-3.5" />
</span>
)}
</span>
)
}
export { Checkbox }

View file

@ -0,0 +1,171 @@
import * as React from "react"
import { X as XIcon } from '@phosphor-icons/react'
import { Dialog as DialogPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
function dialogSlotProps(slot: string, baseClassName: string, className?: string) {
return {
"data-slot": slot,
className: cn(baseClassName, className),
}
}
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
)
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean
}) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close asChild>
<Button variant="outline">Close</Button>
</DialogPrimitive.Close>
)}
</div>
)
}
type DialogTextPartProps =
| (React.ComponentProps<typeof DialogPrimitive.Title> & { textPart: "title" })
| (React.ComponentProps<typeof DialogPrimitive.Description> & { textPart: "description" })
function DialogTextPart(props: DialogTextPartProps) {
if (props.textPart === "title") {
const { textPart, className, ...titleProps } = props
void textPart
return (
<DialogPrimitive.Title
{...dialogSlotProps("dialog-title", "text-lg leading-none font-semibold", className)}
{...titleProps}
/>
)
}
const { textPart, className, ...descriptionProps } = props
void textPart
return (
<DialogPrimitive.Description
{...dialogSlotProps("dialog-description", "text-muted-foreground text-sm", className)}
{...descriptionProps}
/>
)
}
function DialogTitle(props: React.ComponentProps<typeof DialogPrimitive.Title>) {
return <DialogTextPart textPart="title" {...props} />
}
function DialogDescription(props: React.ComponentProps<typeof DialogPrimitive.Description>) {
return <DialogTextPart textPart="description" {...props} />
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}

View file

@ -0,0 +1,275 @@
"use client"
import * as React from "react"
import { CaretRight as ChevronRightIcon, Check as CheckIcon, Circle as CircleIcon } from '@phosphor-icons/react'
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
const dropdownMenuContentMotionClass =
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 origin-(--radix-dropdown-menu-content-transform-origin) rounded-md border p-1"
const dropdownMenuCheckedItemClass =
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
const dropdownMenuItemIndicatorClass =
"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"
function createDropdownMenuSlot<T extends React.ElementType>(
Component: T,
slot: string,
displayName: string
) {
function DropdownMenuSlot(props: React.ComponentProps<T>) {
return React.createElement(Component, { "data-slot": slot, ...props })
}
DropdownMenuSlot.displayName = displayName
return DropdownMenuSlot
}
function DropdownMenuItemIndicator({
children,
}: {
children: React.ReactNode
}) {
return (
<span className={dropdownMenuItemIndicatorClass}>
<DropdownMenuPrimitive.ItemIndicator>
{children}
</DropdownMenuPrimitive.ItemIndicator>
</span>
)
}
function createDropdownMenuCheckedItem<T extends React.ElementType>(
Component: T,
slot: string,
indicator: React.ReactNode,
displayName: string
) {
type Props = React.ComponentProps<T> & {
className?: string
children?: React.ReactNode
}
function CheckedItem({ className, children, ...props }: Props) {
return React.createElement(
Component,
{
"data-slot": slot,
...props,
className: cn(dropdownMenuCheckedItemClass, className),
},
<>
<DropdownMenuItemIndicator>{indicator}</DropdownMenuItemIndicator>
{children}
</>
)
}
CheckedItem.displayName = displayName
return CheckedItem
}
const DropdownMenu = createDropdownMenuSlot(
DropdownMenuPrimitive.Root,
"dropdown-menu",
"DropdownMenu"
)
const DropdownMenuPortal = createDropdownMenuSlot(
DropdownMenuPrimitive.Portal,
"dropdown-menu-portal",
"DropdownMenuPortal"
)
const DropdownMenuTrigger = createDropdownMenuSlot(
DropdownMenuPrimitive.Trigger,
"dropdown-menu-trigger",
"DropdownMenuTrigger"
)
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
dropdownMenuContentMotionClass,
"max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto shadow-md",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
)
}
const DropdownMenuGroup = createDropdownMenuSlot(
DropdownMenuPrimitive.Group,
"dropdown-menu-group",
"DropdownMenuGroup"
)
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
const DropdownMenuCheckboxItem = createDropdownMenuCheckedItem(
DropdownMenuPrimitive.CheckboxItem,
"dropdown-menu-checkbox-item",
<CheckIcon className="size-4" />,
"DropdownMenuCheckboxItem"
)
const DropdownMenuRadioGroup = createDropdownMenuSlot(
DropdownMenuPrimitive.RadioGroup,
"dropdown-menu-radio-group",
"DropdownMenuRadioGroup"
)
const DropdownMenuRadioItem = createDropdownMenuCheckedItem(
DropdownMenuPrimitive.RadioItem,
"dropdown-menu-radio-item",
<CircleIcon className="size-2 fill-current" />,
"DropdownMenuRadioItem"
)
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className
)}
{...props}
/>
)
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
}
const DropdownMenuSub = createDropdownMenuSlot(
DropdownMenuPrimitive.Sub,
"dropdown-menu-sub",
"DropdownMenuSub"
)
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
)
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
dropdownMenuContentMotionClass,
"min-w-[8rem] overflow-hidden shadow-lg",
className
)}
{...props}
/>
)
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
}

View file

@ -0,0 +1,23 @@
import * as React from "react"
import { nativeTextAssistanceDisabledProps } from "@/lib/nativeTextAssistance"
import { cn } from "@/lib/utils"
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
<input
type={type}
data-slot="input"
{...nativeTextAssistanceDisabledProps}
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
{...props}
/>
)
}
export { Input }

View file

@ -0,0 +1,107 @@
import { act, render, renderHook, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it } from 'vitest'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from './tooltip'
import { useZoom } from '@/hooks/useZoom'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from './popover'
const PRESENCE_ANIMATION_CLASS_PARTS = [
'animate-',
'fade-',
'zoom-in-',
'zoom-out-',
'slide-in-from',
]
function expectNoPresenceAnimationClasses(element: HTMLElement) {
const unstableClasses = element.className
.split(/\s+/)
.filter((className) =>
PRESENCE_ANIMATION_CLASS_PARTS.some((part) => className.includes(part)),
)
expect(unstableClasses).toEqual([])
}
describe('overlay presence stability', () => {
beforeEach(() => {
document.documentElement.style.removeProperty('--tolaria-overlay-zoom-factor')
document.documentElement.style.removeProperty('--tolaria-overlay-zoom-inverse')
})
it('keeps tooltip content free of Radix presence animation classes', () => {
render(
<TooltipProvider>
<Tooltip open>
<TooltipTrigger asChild>
<button type="button">Tooltip trigger</button>
</TooltipTrigger>
<TooltipContent data-testid="tooltip-content">Tooltip copy</TooltipContent>
</Tooltip>
</TooltipProvider>,
)
expectNoPresenceAnimationClasses(screen.getByTestId('tooltip-content'))
})
it('publishes zoom variables for overlay portal positioning and visual scale', () => {
const { result } = renderHook(() => useZoom())
act(() => {
result.current.zoomIn()
})
expect(document.documentElement.style.getPropertyValue('--tolaria-overlay-zoom-factor')).toBe(String(110 / 100))
expect(document.documentElement.style.getPropertyValue('--tolaria-overlay-zoom-inverse')).toBe(String(100 / 110))
})
it('keeps tooltip positioning and arrow geometry in the same Radix shell', async () => {
document.documentElement.style.setProperty('--tolaria-overlay-zoom-factor', '1.4')
document.documentElement.style.setProperty('--tolaria-overlay-zoom-inverse', String(1 / 1.4))
render(
<TooltipProvider>
<Tooltip open>
<TooltipTrigger asChild>
<button type="button">Tooltip trigger</button>
</TooltipTrigger>
<TooltipContent data-testid="tooltip-content">Tooltip copy</TooltipContent>
</Tooltip>
</TooltipProvider>,
)
const positionShell = document.querySelector('[data-slot="tooltip-content"]') as HTMLElement
const positionWrapper = positionShell.parentElement as HTMLElement
expect(positionWrapper).toHaveAttribute('data-radix-popper-content-wrapper')
await waitFor(() => {
expect(positionWrapper.style.transform).toContain('translate')
})
expect(positionWrapper).not.toHaveAttribute('data-tolaria-tooltip-position-zoom')
expect(positionWrapper.style.getPropertyValue('--tolaria-tooltip-wrapper-zoom')).toBe('')
expect(positionWrapper.style.getPropertyValue('zoom')).toBe('')
expect(positionShell.className).not.toContain('[zoom:var(--tolaria-overlay-zoom-inverse,1)]')
expect(positionShell.className).not.toContain('[zoom:var(--tolaria-overlay-zoom-factor,1)]')
expect(document.querySelector('[data-slot="tooltip-visual-scale"]')).not.toBeInTheDocument()
})
it('keeps popover content free of Radix presence animation classes', () => {
render(
<Popover open>
<PopoverTrigger asChild>
<button type="button">Popover trigger</button>
</PopoverTrigger>
<PopoverContent data-testid="popover-content">Popover copy</PopoverContent>
</Popover>,
)
expectNoPresenceAnimationClasses(screen.getByTestId('popover-content'))
})
})

View file

@ -0,0 +1,98 @@
import * as React from "react"
import { Popover as PopoverPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Popover({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />
}
function PopoverTrigger({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
}
function PopoverContent({
className,
align = "center",
sideOffset = 4,
collisionPadding = 8,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
collisionPadding={collisionPadding}
className={cn(
"bg-popover text-popover-foreground z-[12000] w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
)
}
function PopoverAnchor({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
}
interface PopoverTextSlotProps extends React.HTMLAttributes<HTMLElement> {
baseClassName: string
element: "div" | "p"
slot: string
}
function PopoverTextSlot({
baseClassName,
className,
element,
slot,
...props
}: PopoverTextSlotProps) {
return React.createElement(
element,
{
"data-slot": slot,
className: cn(baseClassName, className),
...props,
},
)
}
function PopoverHeader(props: React.ComponentProps<"div">) {
return (
<PopoverTextSlot
baseClassName="flex flex-col gap-1 text-sm"
element="div"
slot="popover-header"
{...props}
/>
)
}
function PopoverTitle(props: React.ComponentProps<"div">) {
return <PopoverTextSlot baseClassName="font-medium" element="div" slot="popover-title" {...props} />
}
function PopoverDescription(props: React.ComponentProps<"p">) {
return <PopoverTextSlot baseClassName="text-muted-foreground" element="p" slot="popover-description" {...props} />
}
export {
Popover,
PopoverTrigger,
PopoverContent,
PopoverAnchor,
PopoverHeader,
PopoverTitle,
PopoverDescription,
}

View file

@ -0,0 +1,56 @@
import * as React from "react"
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function ScrollArea({
className,
children,
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn("relative", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
}
function ScrollBar({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
)
}
export { ScrollArea, ScrollBar }

View file

@ -0,0 +1,202 @@
import * as React from "react"
import { CaretDown as ChevronDownIcon, CaretUp as ChevronUpIcon, Check as CheckIcon } from '@phosphor-icons/react'
import { Select as SelectPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function selectSlotProps(slot: string, baseClassName: string, className?: string) {
return {
"data-slot": slot,
className: cn(baseClassName, className),
}
}
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "item-aligned",
align = "center",
collisionPadding = 8,
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-[12000] max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-hidden rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
align={align}
collisionPadding={collisionPadding}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
type SelectStaticPartProps =
| (React.ComponentProps<typeof SelectPrimitive.Label> & { staticPart: "label" })
| (React.ComponentProps<typeof SelectPrimitive.Separator> & { staticPart: "separator" })
function SelectStaticPart(props: SelectStaticPartProps) {
if (props.staticPart === "label") {
const { staticPart, className, ...labelProps } = props
void staticPart
return (
<SelectPrimitive.Label
{...selectSlotProps("select-label", "text-muted-foreground px-2 py-1.5 text-xs", className)}
{...labelProps}
/>
)
}
const { staticPart, className, ...separatorProps } = props
void staticPart
return (
<SelectPrimitive.Separator
{...selectSlotProps("select-separator", "bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...separatorProps}
/>
)
}
function SelectLabel(props: React.ComponentProps<typeof SelectPrimitive.Label>) {
return <SelectStaticPart staticPart="label" {...props} />
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
>
<span
data-slot="select-item-indicator"
className="absolute right-2 flex size-3.5 items-center justify-center"
>
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator(props: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return <SelectStaticPart staticPart="separator" {...props} />
}
function SelectScrollButton({
className,
direction,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton> & {
direction: "down" | "up"
}) {
const Primitive = direction === "up"
? SelectPrimitive.ScrollUpButton
: SelectPrimitive.ScrollDownButton
const Icon = direction === "up" ? ChevronUpIcon : ChevronDownIcon
const slot = direction === "up" ? "select-scroll-up-button" : "select-scroll-down-button"
return (
<Primitive
{...selectSlotProps(slot, "flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<Icon className="size-4" />
</Primitive>
)
}
function SelectScrollUpButton(props: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return <SelectScrollButton direction="up" {...props} />
}
function SelectScrollDownButton({
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return <SelectScrollButton direction="down" {...props} />
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}

View file

@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import { Separator as SeparatorPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props}
/>
)
}
export { Separator }

View file

@ -0,0 +1,46 @@
import * as React from "react"
import { cn } from "@/lib/utils"
interface SwitchProps extends Omit<React.ComponentProps<"button">, "onChange"> {
checked?: boolean
onCheckedChange?: (checked: boolean) => void
}
function Switch({
checked = false,
className,
onCheckedChange,
onClick,
type = "button",
...props
}: SwitchProps) {
return (
<button
data-slot="switch"
role="switch"
aria-checked={checked}
type={type}
className={cn(
"peer inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-colors outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
checked ? "bg-primary" : "bg-input",
className,
)}
onClick={(event) => {
onClick?.(event)
if (!event.defaultPrevented) onCheckedChange?.(!checked)
}}
{...props}
>
<span
data-slot="switch-thumb"
className={cn(
"pointer-events-none block size-4 rounded-full bg-background transition-transform",
checked ? "translate-x-4" : "translate-x-0",
)}
/>
</button>
)
}
export { Switch }

View file

@ -0,0 +1,90 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Tabs as TabsPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Tabs({
className,
orientation = "horizontal",
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
data-orientation={orientation}
orientation={orientation}
className={cn(
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
className
)}
{...props}
/>
)
}
const tabsListVariants = cva(
"rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
{
variants: {
variant: {
default: "bg-muted",
line: "gap-1 bg-transparent",
},
},
defaultVariants: {
variant: "default",
},
}
)
function TabsList({
className,
variant = "default",
...props
}: React.ComponentProps<typeof TabsPrimitive.List> &
VariantProps<typeof tabsListVariants>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
data-variant={variant}
className={cn(tabsListVariants({ variant }), className)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
className
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
)
}
// eslint-disable-next-line react-refresh/only-export-components -- shadcn/ui pattern
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }

View file

@ -0,0 +1,26 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { Input } from './input'
import { Textarea } from './textarea'
describe('text controls', () => {
it('keeps inputs out of spellcheck without disabling IME autocorrection', () => {
render(<Input aria-label="Search" />)
const input = screen.getByLabelText('Search')
expect(input).toHaveAttribute('spellcheck', 'false')
expect(input).toHaveAttribute('autocomplete', 'off')
expect(input).not.toHaveAttribute('autocorrect')
expect(input).not.toHaveAttribute('autocapitalize')
})
it('keeps textareas out of spellcheck without disabling IME autocorrection', () => {
render(<Textarea aria-label="Message" />)
const textarea = screen.getByLabelText('Message')
expect(textarea).toHaveAttribute('spellcheck', 'false')
expect(textarea).toHaveAttribute('autocomplete', 'off')
expect(textarea).not.toHaveAttribute('autocorrect')
expect(textarea).not.toHaveAttribute('autocapitalize')
})
})

View file

@ -0,0 +1,24 @@
import * as React from "react"
import { nativeTextAssistanceDisabledProps } from "@/lib/nativeTextAssistance"
import { cn } from "@/lib/utils"
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
({ className, ...props }, ref) => (
<textarea
ref={ref}
data-slot="textarea"
{...nativeTextAssistanceDisabledProps}
className={cn(
"border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex min-h-20 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
{...props}
/>
),
)
Textarea.displayName = "Textarea"
export { Textarea }

View file

@ -0,0 +1,65 @@
"use client"
import * as React from "react"
import { Tooltip as TooltipPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function TooltipProvider({
delayDuration = 0,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delayDuration={delayDuration}
{...props}
/>
)
}
function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
}
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
}
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(function TooltipContent({
className,
sideOffset = 0,
collisionPadding = 8,
children,
style,
...props
}, forwardedRef) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={forwardedRef}
data-slot="tooltip-content"
sideOffset={sideOffset}
collisionPadding={collisionPadding}
className={cn(
"bg-foreground text-background z-50 w-fit max-w-[min(var(--radix-tooltip-content-available-width,22rem),22rem)] origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
className
)}
style={style}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
)
})
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }