Migrate UI to COSS components + COSS neutral theme; add i18next

Components:
- Replace components/ui primitives with COSS equivalents from the @coss/* shadcn
  registry. Add menu/preview-card/group; remove the superseded dropdown-menu,
  hover-card, button-group; keep carousel (no COSS equivalent).
- Migrate live call sites to canonical COSS APIs preserving layout/behavior:
  sidebar menus (nav-user, team-switcher, nav-notifications), chat-input radio
  menu, patient dialogs/cards, auth forms (FieldGroup -> flex column), settings.
- ai-elements: migrate the live conversation/message with behavior parity
  (Tooltip via render, Group/GroupText); repoint dormant files (hover-card ->
  preview-card, dropdown-menu -> menu, button-group -> group, InputGroupButton
  -> Button). Residual pre-existing Base UI type drift stays behind
  ignoreBuildErrors.

Theme:
- Adopt COSS default neutral tokens in globals.css with light + dark palettes.
- Add next-themes (defaultTheme=dark, enableSystem) and drop the forced dark
  class. Align font variables to the COSS contract (--font-sans/-heading/-mono).
  Make scrollbars theme-aware.

i18n:
- Add i18next + react-i18next (lib/i18n/config.ts, locales/en/translation.json,
  components/i18n-provider.tsx mounted in layout). Convert auth forms, sidebar
  nav, and settings tabs to useTranslation() as the reference pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-06-03 18:39:20 +03:00
parent 26e933fb6c
commit 4d6a5dc008
63 changed files with 3729 additions and 2545 deletions
+181 -128
View File
@@ -1,204 +1,257 @@
"use client"
"use client";
import * as React from "react"
import { Select as SelectPrimitive } from "@base-ui/react/select"
import { mergeProps } from "@base-ui/react/merge-props";
import { Select as SelectPrimitive } from "@base-ui/react/select";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import {
ChevronDownIcon,
ChevronsUpDownIcon,
ChevronUpIcon,
} from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils";
import { cn } from "@/lib/utils"
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
export const Select: typeof SelectPrimitive.Root = SelectPrimitive.Root;
const Select = SelectPrimitive.Root
export const selectTriggerVariants = cva(
"relative inline-flex min-h-9 w-full min-w-36 select-none items-center justify-between gap-2 rounded-lg border border-input bg-background not-dark:bg-clip-padding px-[calc(--spacing(3)-1px)] text-left text-base text-foreground shadow-xs/5 outline-none ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 focus-visible:border-ring focus-visible:ring-[3px] aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/16 data-disabled:pointer-events-none data-disabled:opacity-64 sm:min-h-8 sm:text-sm dark:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 [[data-disabled],:focus-visible,[aria-invalid],[data-pressed]]:shadow-none",
{
defaultVariants: {
size: "default",
},
variants: {
size: {
default: "",
lg: "min-h-10 sm:min-h-9",
sm: "min-h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:min-h-7",
},
},
},
);
function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
return (
<SelectPrimitive.Group
data-slot="select-group"
className={cn("scroll-my-1.5 p-1.5", className)}
{...props}
/>
)
export const selectTriggerIconClassName = "-me-1 size-4.5 opacity-80 sm:size-4";
export interface SelectButtonProps extends useRender.ComponentProps<"button"> {
size?: VariantProps<typeof selectTriggerVariants>["size"];
}
function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
return (
<SelectPrimitive.Value
data-slot="select-value"
className={cn("flex flex-1 text-left", className)}
{...props}
/>
)
export function SelectButton({
className,
size,
render,
children,
...props
}: SelectButtonProps): React.ReactElement {
const typeValue: React.ButtonHTMLAttributes<HTMLButtonElement>["type"] =
render ? undefined : "button";
const defaultProps = {
children: (
<>
<span className="flex-1 truncate in-data-placeholder:text-muted-foreground/72">
{children}
</span>
<ChevronsUpDownIcon className={selectTriggerIconClassName} />
</>
),
className: cn(selectTriggerVariants({ size }), "min-w-0", className),
"data-slot": "select-button",
type: typeValue,
};
return useRender({
defaultTagName: "button",
props: mergeProps<"button">(defaultProps, props),
render,
});
}
function SelectTrigger({
export function SelectTrigger({
className,
size = "default",
children,
...props
}: SelectPrimitive.Trigger.Props & {
size?: "sm" | "default"
}) {
}: SelectPrimitive.Trigger.Props &
VariantProps<typeof selectTriggerVariants>): React.ReactElement {
return (
<SelectPrimitive.Trigger
className={cn(selectTriggerVariants({ size }), className)}
data-slot="select-trigger"
data-size={size}
className={cn(
"flex w-fit items-center justify-between gap-1.5 rounded-3xl border border-transparent bg-input/50 px-3 py-2 text-sm whitespace-nowrap transition-[color,box-shadow,background-color] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground 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-1.5 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon
render={
<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
}
/>
<SelectPrimitive.Icon data-slot="select-icon">
<ChevronsUpDownIcon className={selectTriggerIconClassName} />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
);
}
function SelectContent({
export function SelectValue({
className,
...props
}: SelectPrimitive.Value.Props): React.ReactElement {
return (
<SelectPrimitive.Value
className={cn(
"flex-1 truncate data-placeholder:text-muted-foreground",
className,
)}
data-slot="select-value"
{...props}
/>
);
}
export function SelectPopup({
className,
children,
side = "bottom",
sideOffset = 4,
align = "center",
align = "start",
alignOffset = 0,
alignItemWithTrigger = true,
anchor,
portalProps,
...props
}: SelectPrimitive.Popup.Props &
Pick<
SelectPrimitive.Positioner.Props,
"align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
>) {
}: SelectPrimitive.Popup.Props & {
portalProps?: SelectPrimitive.Portal.Props;
side?: SelectPrimitive.Positioner.Props["side"];
sideOffset?: SelectPrimitive.Positioner.Props["sideOffset"];
align?: SelectPrimitive.Positioner.Props["align"];
alignOffset?: SelectPrimitive.Positioner.Props["alignOffset"];
alignItemWithTrigger?: SelectPrimitive.Positioner.Props["alignItemWithTrigger"];
anchor?: SelectPrimitive.Positioner.Props["anchor"];
}): React.ReactElement {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Portal {...portalProps}>
<SelectPrimitive.Positioner
align={align}
alignItemWithTrigger={alignItemWithTrigger}
alignOffset={alignOffset}
anchor={anchor}
className="z-50 select-none"
data-slot="select-positioner"
side={side}
sideOffset={sideOffset}
align={align}
alignOffset={alignOffset}
alignItemWithTrigger={alignItemWithTrigger}
className="isolate z-50"
>
<SelectPrimitive.Popup
data-slot="select-content"
data-align-trigger={alignItemWithTrigger}
className={cn("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-3xl bg-popover text-popover-foreground shadow-lg ring-1 ring-foreground/5 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 dark:ring-foreground/10 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
className="origin-(--transform-origin) text-foreground outline-none"
data-slot="select-popup"
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.List>{children}</SelectPrimitive.List>
<SelectScrollDownButton />
<SelectPrimitive.ScrollUpArrow
className="top-0 z-50 flex h-6 w-full cursor-default items-center justify-center before:pointer-events-none before:absolute before:inset-x-px before:top-px before:h-[200%] before:rounded-t-[calc(var(--radius-lg)-1px)] before:bg-linear-to-b before:from-50% before:from-popover"
data-slot="select-scroll-up-arrow"
>
<ChevronUpIcon className="relative size-4.5 sm:size-4" />
</SelectPrimitive.ScrollUpArrow>
<div className="relative h-full min-w-(--anchor-width) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]">
<SelectPrimitive.List
className={cn(
"max-h-(--available-height) overflow-y-auto p-1",
className,
)}
data-slot="select-list"
>
{children}
</SelectPrimitive.List>
</div>
<SelectPrimitive.ScrollDownArrow
className="bottom-0 z-50 flex h-6 w-full cursor-default items-center justify-center before:pointer-events-none before:absolute before:inset-x-px before:bottom-px before:h-[200%] before:rounded-b-[calc(var(--radius-lg)-1px)] before:bg-linear-to-t before:from-50% before:from-popover"
data-slot="select-scroll-down-arrow"
>
<ChevronDownIcon className="relative size-4.5 sm:size-4" />
</SelectPrimitive.ScrollDownArrow>
</SelectPrimitive.Popup>
</SelectPrimitive.Positioner>
</SelectPrimitive.Portal>
)
);
}
function SelectLabel({
className,
...props
}: SelectPrimitive.GroupLabel.Props) {
return (
<SelectPrimitive.GroupLabel
data-slot="select-label"
className={cn("px-3 py-2.5 text-xs text-muted-foreground", className)}
{...props}
/>
)
}
function SelectItem({
export function SelectItem({
className,
children,
...props
}: SelectPrimitive.Item.Props) {
}: SelectPrimitive.Item.Props): React.ReactElement {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"relative flex w-full cursor-default items-center gap-2.5 rounded-2xl py-2 pr-8 pl-3 text-sm font-medium outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground 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
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
data-slot="select-item"
{...props}
>
<SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
<SelectPrimitive.ItemIndicator className="col-start-1">
<svg
aria-hidden="true"
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemText className="col-start-2 min-w-0">
{children}
</SelectPrimitive.ItemText>
<SelectPrimitive.ItemIndicator
render={
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
}
>
<CheckIcon className="pointer-events-none" />
</SelectPrimitive.ItemIndicator>
</SelectPrimitive.Item>
)
);
}
function SelectSeparator({
export function SelectSeparator({
className,
...props
}: SelectPrimitive.Separator.Props) {
}: SelectPrimitive.Separator.Props): React.ReactElement {
return (
<SelectPrimitive.Separator
className={cn("mx-2 my-1 h-px bg-border", className)}
data-slot="select-separator"
className={cn(
"pointer-events-none -mx-1.5 my-1.5 h-px bg-border",
className
)}
{...props}
/>
)
);
}
function SelectScrollUpButton({
export function SelectGroup(
props: SelectPrimitive.Group.Props,
): React.ReactElement {
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}
export function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
}: SelectPrimitive.Label.Props): React.ReactElement {
return (
<SelectPrimitive.ScrollUpArrow
data-slot="select-scroll-up-button"
<SelectPrimitive.Label
className={cn(
"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
"not-in-data-[slot=field]:mb-2 inline-flex cursor-default items-center gap-2 font-medium text-base/4.5 text-foreground sm:text-sm/4",
className,
)}
data-slot="select-label"
{...props}
>
<ChevronUpIcon
/>
</SelectPrimitive.ScrollUpArrow>
)
/>
);
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
export function SelectGroupLabel(
props: SelectPrimitive.GroupLabel.Props,
): React.ReactElement {
return (
<SelectPrimitive.ScrollDownArrow
data-slot="select-scroll-down-button"
className={cn(
"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
<SelectPrimitive.GroupLabel
className="px-2 py-1.5 font-medium text-muted-foreground text-xs"
data-slot="select-group-label"
{...props}
>
<ChevronDownIcon
/>
</SelectPrimitive.ScrollDownArrow>
)
/>
);
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}
export { SelectPrimitive, SelectPopup as SelectContent };