@gradeui/ui 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,7 +43,7 @@ toolbar buttons render when \`toolbar\` is on.
43
43
 
44
44
  Shares the lib/demo step vocabulary with <Code> so scripted
45
45
  typing/format/mention demos animate in the same rhythm as your
46
- terminal demos.`,import:"@gradeui/ui",composesWith:["AIChatComposer (preset wrapping this with paperclip + send + attachments)","ComposerReply (preset for comment threads)","AIChat (uses AIChatComposer internally)","Card (host above for reply boxes)",'Avatar (in leftActions slot for "your" avatar next to the input)'],styleDefaults:{ComposerToolbar:"flex flex-wrap items-center gap-0.5",AttachmentChips:"absolute -top-1.5 -right-1.5 h-5 w-5 rounded-full",ComposerInner:"w-full"},props:{placeholder:{schema:z.string().optional(),design:"content"},initialText:{schema:z.string().optional(),design:"content",description:"plain text content to seed on mount"},initialJson:{schema:z.string().optional(),design:"content",description:"Lexical state JSON (from a previous onSubmit round-trip)"},formats:{schema:z.unknown().optional(),design:"plumbing",description:"available formats (defaults to bold/italic/underline/strikethrough/code/h1/h2/blockquote/ul/ol); pass false for plain text only"},toolbar:{schema:z.boolean().optional(),design:"knob",description:"show the formatting toolbar above the editor; default false"},triggers:{schema:z.unknown().optional(),design:"plumbing",description:'mention/slash configs, eg. `[{ char: "@", items: people }, { char: "/", items: commands }]`'},attachments:{schema:z.boolean().optional(),design:"knob",description:"enable image paste + paperclip when true/object; default off"},onSubmit:{schema:z.unknown().optional(),design:"event"},isLoading:{schema:z.boolean().optional(),design:"knob",description:"disables editor, swaps default Send for Stop"},onStop:{schema:z.unknown().optional(),design:"event"},maxLength:{schema:z.number().optional(),design:"knob"},autoFocus:{schema:z.boolean().optional(),design:"plumbing"},submitOnEnter:{schema:z.boolean().optional(),design:"knob",description:"default true (Shift-Enter still inserts newline)"},leftActions:{schema:z.unknown().optional(),design:"plumbing",description:"override the default paperclip"},rightActions:{schema:z.unknown().optional(),design:"plumbing",description:"override the default Send/Stop"},hideSend:{schema:z.boolean().optional(),design:"knob",description:"hide the default Send without replacing it"},steps:{schema:z.unknown().optional(),design:"plumbing",description:"scripted demo sequence"},trigger:{schema:z.unknown().optional(),design:"plumbing",description:'"mount" | "inView" | "manual"; default "mount"'},play:{schema:z.boolean().optional(),design:"knob",description:'for trigger="manual"'},speed:{schema:z.unknown().optional(),design:"plumbing",description:'"slow" | "normal" | "fast"; default "normal"'},loop:{schema:z.boolean().optional(),design:"knob"},loopDelay:{schema:z.number().optional(),design:"knob",description:"ms between loop iterations, default 2000"},readOnly:{schema:z.boolean().optional(),design:"knob",description:"disables editing AND focusability; programmatic playback still works; use for marketing demos so the script doesn't steal focus"},bare:{schema:z.boolean().optional(),design:"knob",description:"strip the card chrome"},className:{schema:z.string().optional(),design:"plumbing"}}};var Ge={name:"DataView",description:'One dataset, drawn as a table, a list of cards, or a grid \u2014 without re-typing the TanStack boilerplate (sortable headers, flexRender, selection, view switch) on every page. Hand it data + a columns schema; columns declare a `type` (badge/tags/number/currency/percent/date/boolean/url/text) that DataView renders, with a `cell` override for bespoke cells (avatars, relations). The view toggle can live anywhere \u2014 `useDataView()` holds the state so a `<DataViewToggle>` or `<DataViewColumns>` in a page header drives a `<DataView>` lower down. Mark a column `pinned="left"` (with a `width`) for a fixed column and `stickyHeader` to freeze the header. For a single record\'s fields use PropertyList; for the raw table primitive use Table.',import:"@gradeui/ui",aliases:["data view","data table","datatable","data grid","dataview","table view","card view","grid view","list view","gallery","records list","master list","tanstack table","sortable table","column visibility","pinned column","frozen column","sticky header","view switcher"],composesWith:["Table","Card","Badge","Avatar","ToggleGroup","DropdownMenu","PropertyList","Combobox"],props:{data:{schema:z.unknown(),design:"plumbing",description:"the rows"},columns:{schema:z.unknown(),design:"plumbing",description:"the schema; one list drives table, cards, and grid"},getRowId:{schema:z.unknown().optional(),design:"plumbing",description:"defaults to row.id"},view:{schema:z.unknown().optional(),design:"plumbing",description:"controlled or uncontrolled view"},views:{schema:z.unknown().optional(),design:"plumbing",description:"allowed views; one entry = single view, no toggle"},activeId:{schema:z.unknown().optional(),design:"plumbing",description:"the selected row; click emits it"},sorting:{schema:z.unknown().optional(),design:"plumbing",description:"TanStack SortingState"},columnVisibility:{schema:z.unknown().optional(),design:"plumbing",description:"which fields show"},stickyHeader:{schema:z.boolean().optional(),design:"knob",description:"freeze the header row on scroll"},toolbar:{schema:z.boolean().optional(),design:"knob",description:"render the built-in columns menu + view toggle above the view"},renderCard:{schema:z.unknown().optional(),design:"plumbing",description:"override card / grid tiles"},emptyMessage:{schema:z.unknown().optional(),design:"plumbing"}}};var Oe={name:"DatePicker",description:'Any date or date-range entry. Use DatePicker for a single date (DOB, due date, booking). Use DateRangePicker for a span (report period, stay dates, filter window). Prefer these over <Input type="date"> \u2014 consistent theming, keyboard nav, a11y, and no browser-native UI drift.',import:"@gradeui/ui",aliases:["datepicker","calendar input","date field","date range","datepickerios","react native date picker","calendar input field","date field control"],subcomponents:["DateRangePicker"],composesWith:["Label","Form","Card (in CardContent)","Button (form submit)"],styleDefaults:{DatePicker:"w-[280px] justify-start text-left font-normal data-[empty=true]:text-muted-foreground",DateRangePicker:"w-[300px] justify-start text-left font-normal data-[empty=true]:text-muted-foreground"},props:{value:{schema:z.unknown().optional(),design:"plumbing"},onChange:{schema:z.unknown().optional(),design:"event",description:"called on select or clear"},placeholder:{schema:z.string().optional(),design:"content",description:'trigger label when empty (default "Pick a date" / "Pick a date range")'},disabled:{schema:z.boolean().optional(),design:"knob"},format:{schema:z.string().optional(),design:"content",description:'date-fns format token for the trigger label (default "PPP" single, "LLL dd, y" range)'},align:{schema:z.enum(["start","center","end"]).optional(),design:"knob",description:'popover align (default "start")'},side:{schema:z.enum(["top","right","bottom","left"]).optional(),design:"knob",description:"popover side"},captionLayout:{schema:z.enum(["label","dropdown","dropdown-months","dropdown-years"]).optional(),design:"knob"},className:{schema:z.string().optional(),design:"plumbing",description:"on the trigger button"},contentClassName:{schema:z.string().optional(),design:"content",description:"on the PopoverContent"},icon:{schema:z.unknown().optional(),design:"plumbing",description:"replaces the default CalendarIcon"},numberOfMonths:{schema:z.number().optional(),design:"knob",description:"DateRangePicker only, default 2"}}};var Ee={name:"Dialog",description:'Modal interruptions \u2014 confirmations, focused forms, detail views, AI suggestion sheets. Dialog is the right primitive for Apple HIG / React Native "Alert" (modal) semantics. For non-blocking inline messaging use Callout; for transient notifications use Toaster (Sonner). Always include DialogTitle (a11y requirement).',import:"@gradeui/ui",aliases:["modal","popup","overlay","alert","system alert","alert dialog","modal dialog","confirm dialog","react native modal","rn alert","glass modal","frosted modal","ai suggestion modal"],subcomponents:["DialogTrigger","DialogContent","DialogHeader","DialogTitle","DialogDescription","DialogFooter","DialogClose"],composesWith:["Button (as DialogTrigger asChild","and inside DialogFooter)","Input/Textarea/Select inside DialogContent","Code (for changelog / diff modals)","MediaSurface (for image / preview modals)"],styleDefaults:{DialogOverlay:"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",DialogContent:"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",DialogHeader:"flex flex-col space-y-1.5 text-center sm:text-left",DialogFooter:"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",DialogTitle:"text-lg font-semibold leading-none tracking-tight",DialogDescription:"text-sm text-muted-foreground"},props:{open:{schema:z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:z.unknown().optional(),design:"event",description:"Radix controlled/uncontrolled pattern"},asChild:{schema:z.enum(["wrap a Button"]).optional(),design:"plumbing"},surface:{schema:z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the modal panel is *made of*. Defaults to `solid` (opaque `bg-background`). `glass` lets the page show through softly \u2014 pairs with rich backdrops or AI-suggestion modals."},accepts:{schema:z.unknown().optional(),design:"plumbing"},used:{schema:z.unknown().optional(),design:"plumbing"}}};var He={name:"DropdownMenu",description:'A small action menu attached to a trigger \u2014 overflow "\u2026" buttons on cards, user-avatar menus in headers, "Insert" menus in editors. For a full searchable list, use Command. For ONE primary action plus a secondary, use a Button next to a smaller ghost Button instead of a dropdown.',import:"@gradeui/ui",aliases:["dropdown","dropdown menu","overflow menu","kebab menu","more menu","action menu","context-style menu","menu","pull-down menu","pulldown menu","context menu","popup menu","actions menu","glass menu","frosted menu","ios menu","hig menu"],subcomponents:["DropdownMenuTrigger","DropdownMenuContent","DropdownMenuItem","DropdownMenuCheckboxItem","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuLabel","DropdownMenuSeparator","DropdownMenuShortcut","DropdownMenuGroup","DropdownMenuSub","DropdownMenuSubTrigger","DropdownMenuSubContent"],composesWith:["Button (as trigger asChild)","Avatar (user menu)","Card (overflow on a tile)","Tooltip (on the trigger)"],styleDefaults:{DropdownMenuSubContent:"z-50 min-w-[8rem] overflow-hidden rounded-md border text-popover-foreground shadow-lg 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 origin-[--radix-dropdown-menu-content-transform-origin]",DropdownMenuContent:"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border text-popover-foreground shadow-md",DropdownMenuItem:"relative flex cursor-default select-none items-center gap-2 rounded-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:shrink-0",DropdownMenuCheckboxItem:"relative flex cursor-default select-none items-center rounded-sm pr-2 outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",DropdownMenuRadioItem:"relative flex cursor-default select-none items-center rounded-sm pr-2 outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",DropdownMenuLabel:"font-semibold",DropdownMenuSeparator:"-mx-1 my-1 h-px bg-muted",DropdownMenuShortcut:"ml-auto text-xs tracking-widest opacity-60"},props:{open:{schema:z.unknown().optional(),design:"plumbing"},defaultOpen:{schema:z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:z.unknown().optional(),design:"event"},modal:{schema:z.unknown().optional(),design:"plumbing"},asChild:{schema:z.boolean().optional(),design:"plumbing",description:"usually wraps a Button"},align:{schema:z.enum(["start","center","end"]).optional(),design:"knob"},side:{schema:z.enum(["top","right","bottom","left"]).optional(),design:"knob"},sideOffset:{schema:z.number().optional(),design:"knob"},surface:{schema:z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the menu surface is *made of*. `solid` (default) is `bg-popover`. `translucent` matches Apple HIG / iOS menu sheets. `glass` for menus floating over rich canvases."},size:{schema:z.enum(["default","sm","xs"]).optional(),design:"knob",description:'menu density; cascades to every item (Item, Checkbox, Radio, SubTrigger, Label) via context so a compact trigger gets a compact menu. Use "xs" in dense tool panels.'},onSelect:{schema:z.unknown().optional(),design:"event"},disabled:{schema:z.unknown().optional(),design:"plumbing"},inset:{schema:z.unknown().optional(),design:"plumbing"},DropdownMenuCheckboxItem:{schema:z.unknown(),design:"plumbing"},DropdownMenuSub:{schema:z.unknown(),design:"plumbing",description:"sub-trigger shows children, sub-content holds the deeper items"},children:{schema:z.unknown().optional(),design:"plumbing",description:"right-aligned kbd hint"}}};var Ve={name:"Field",description:'Pair a bare control with a label and optional description in a row, with id + aria-describedby wired automatically. Use layout="setting" for the classic settings row (label on the left, Switch on the right). For a selectable CARD where the whole surface is the control, use RadioCard / CheckboxCard / SwitchCard instead.',import:"@gradeui/ui",aliases:["field","form field","control row","label and description","two line checkbox","option row","setting row","toggle row"],composesWith:["Checkbox","RadioGroup","RadioGroupItem","Switch","Badge (inside Field.Trailing)"],styleDefaults:{FieldLabel:"text-sm font-medium leading-none text-foreground cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70",FieldDescription:"text-sm text-muted-foreground",FieldTrailing:"flex shrink-0 items-center gap-2",FieldRoot:"flex gap-3"},props:{layout:{schema:z.enum(["option","setting"]).optional(),design:"knob",description:"option (default): control leads, text beside it; setting: text leads, control pinned trailing"},children:{schema:z.unknown(),design:"plumbing",description:"order does not matter"}}};var qe={name:"FillPicker",description:`Grade's paint picker \u2014 the control for choosing a frame's background fill, modelled on Figma's fill popover. A fill-type icon row (solid \xB7 gradient \xB7 image \xB7 pattern \xB7 video \xB7 shader) switches the panel below; a global opacity sits at the foot. Emits a FillValue that maps 1:1 onto BackgroundFill props. This is a Studio/inspector chrome control \u2014 pair it with BackgroundFill, which renders the chosen paint. Not for app content. Use the FillSection subcomponent to edit a LIST of fills (the Figma "Fill" inspector section): each row is a Solid/Gradient/Image toggle, the matching value control (ColorPicker / GradientEditor popover / image URL), an opacity %, a visibility eye, and a remove button, with an add button in the header.`,import:"@gradeui/ui",aliases:["fill picker","paint picker","background picker","fill chooser","fill popover","fill section","fill list","fills inspector","paint section"],subcomponents:["FillSection"],composesWith:["BackgroundFill (renders the FillValue)","Popover (host it in a popover)","ColorPicker (the solid value)","GradientEditor (the gradient value)","ShaderPresetPicker (the shader tab)","the inspector Fill section"],styleDefaults:{Swatch:"h-7 w-7 rounded-md border border-border/60 transition-shadow",FillPicker:"flex flex-col gap-3",FillSection:"flex flex-col gap-2"},props:{value:{schema:z.unknown(),design:"plumbing",description:"current paint ({ type, color?, gradient?, src?, fit?, repeat?, tileSize?, preset?, palette?, postPreset?, opacity? }) (required)"},onChange:{schema:z.unknown(),design:"event",description:"called on any change (required)"},title:{schema:z.string().optional(),design:"plumbing",description:'section heading (default "Fills")'}}};var Ue={name:"Flex",description:"The unopinionated flexbox primitive \u2014 reach for Flex when Stack, Row, or Grid don't quite fit. Specifically when you need reverse direction (`row-reverse` / `col-reverse`), CSS defaults instead of Row's baked-in `items-center gap-md`, or baseline alignment. Otherwise prefer Stack / Row / Grid \u2014 they're easier to read and tuned for the 95% case. Flex is the escape hatch, not the default.",import:"@gradeui/ui",aliases:["flex","flexbox","flex container","hstack","vstack","horizontal","vertical","generic container","layout view"],composesWith:["any content component"],props:{direction:{schema:z.enum(["row","col","row-reverse","col-reverse"]).optional(),design:"knob",description:"main-axis direction",default:"row"},gap:{schema:z.enum(["none","xs","sm","md","lg","xl","2xl"]).optional(),design:"knob",description:"gap between children",default:"none"},align:{schema:z.enum(["start","center","end","stretch","baseline"]).optional(),design:"knob",description:"cross-axis alignment",default:"stretch"},justify:{schema:z.enum(["start","center","end","between","around","evenly"]).optional(),design:"knob",description:"main-axis distribution",default:"start"},wrap:{schema:z.enum(["nowrap","wrap","wrap-reverse"]).optional(),design:"knob",description:"wrap behaviour when children overflow",default:"nowrap"},asChild:{schema:z.boolean().optional(),design:"plumbing",description:"render as the child element via Slot",default:false},className:{schema:z.string().optional(),design:"plumbing"},children:{schema:z.unknown(),design:"plumbing"}}};var Ke={name:"GradientEditor",description:'Edit a multi-stop CSS gradient with token-led stops. A type Select (Linear / Radial / Angular) with reverse + rotate actions, a live full-width preview bar (a Swatch type="gradient"), then a Stops list where each stop is a position %, a colour (ColorPicker token or raw), an opacity %, and a remove button; an add button appends a stop. Token stops resolve to oklch(var(--<token>)) so the preview re-voices with the theme. Emits the structured GradientValue (kept editable + serialisable); the caller turns it into CSS with the exported gradientToCss(value). Use inside a Popover from a FillSection gradient row, or standalone in a theme builder. For a single solid colour use ColorPicker; for a full paint (solid / gradient / image / shader) use FillPicker.',import:"@gradeui/ui",aliases:["gradient editor","gradient picker","gradient builder","css gradient editor","stop editor","gradient stops","linear gradient editor","conic gradient editor"],composesWith:["Select","Button","Input","ColorPicker","Swatch","Popover","FillSection"],styleDefaults:{GradientEditor:"flex flex-col gap-3"},props:{value:{schema:z.unknown(),design:"plumbing",description:"the structured gradient (type linear/radial/angular, optional angle in deg, ordered stops). NOT a CSS string \u2014 render the string via gradientToCss(value)."},onChange:{schema:z.unknown(),design:"event",description:"fired with the next structured gradient on any edit"}}};var Ye={name:"Grid",description:"2D layouts where Stack (vertical) and Row (horizontal) don't fit \u2014 stat-card grids, feature tiles, pricing columns, photo grids. Reach for Grid over hand-rolled `grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4` so the column count is a prop the settings panel can mutate and the responsive ladder stays consistent across designs.",import:"@gradeui/ui",aliases:["grid","tiles","cards grid","stat grid","columns","feature grid","grid view","lazy v grid","lazyvgrid","lazy h grid","lazyhgrid","tile grid","masonry"],composesWith:["Card","Stack (inside each cell)","Row","Button","any content component"],styleDefaults:{Grid:"gds-grid grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 items-stretch"},variantDefaults:{cols:"3",gap:"md",align:"stretch"},props:{cols:{schema:z.enum(["1","2","3","4","5","6","12"]).optional(),design:"knob",description:'desktop column count; each value has a baked-in responsive ladder (e.g. "4" \u2192 1 col mobile, 2 tablet, 4 desktop)',default:"3"},gap:{schema:z.enum(["none","xs","sm","md","lg","xl","2xl"]).optional(),design:"knob",description:"gap between grid cells (same scale as Stack/Row)",default:"md"},align:{schema:z.enum(["start","center","end","stretch"]).optional(),design:"knob",description:"cross-axis alignment of cells",default:"stretch"},asChild:{schema:z.boolean().optional(),design:"plumbing",description:"render as the child element via Slot",default:false},className:{schema:z.string().optional(),design:"plumbing"},children:{schema:z.unknown(),design:"plumbing"}}};var Je={name:"HoverCard",description:"Rich preview content surfaced on hover \u2014 user profile mini-cards on @-mentions, link previews, definition popups, layer-thumbnail peeks. Pointer-only by design (no touch-friendly trigger); pair with a click target for touch devices, or fall back to Popover. NEVER use HoverCard for critical info \u2014 if the user can't reach it via keyboard or touch, it might as well not exist for accessibility.",import:"@gradeui/ui",aliases:["hover card","hover preview","mention preview","profile peek","link preview","rich tooltip","link preview card","profile hover","peek card","glass preview","frosted preview"],subcomponents:["HoverCardTrigger","HoverCardContent"],composesWith:["Avatar (user preview)","Card (richer content)","Link (the trigger)","MediaSurface (link/layer previews)","Code (snippet previews)"],styleDefaults:{HoverCardContent:"z-50 w-64 rounded-md border p-4 text-popover-foreground shadow-md outline-none 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 origin-[--radix-hover-card-content-transform-origin]"},props:{open:{schema:z.unknown().optional(),design:"plumbing"},defaultOpen:{schema:z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:z.unknown().optional(),design:"event"},openDelay:{schema:z.unknown().optional(),design:"plumbing"},closeDelay:{schema:z.unknown().optional(),design:"plumbing"},asChild:{schema:z.boolean().optional(),design:"plumbing",description:"usually a Link or Button"},side:{schema:z.unknown().optional(),design:"plumbing"},align:{schema:z.unknown().optional(),design:"plumbing"},sideOffset:{schema:z.unknown().optional(),design:"plumbing"},alignOffset:{schema:z.unknown().optional(),design:"plumbing"},className:{schema:z.unknown().optional(),design:"plumbing"},surface:{schema:z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the preview surface is *made of*. `solid` (default) is `bg-popover`. `glass` for hover previews over rich content (a media feed, a layout canvas)."}}};var Xe={name:"Input",description:'Any single-line text entry. Always pair with a Label for accessibility. Use startSlot/endSlot for icons, prefixes and units instead of hand-positioning absolute children; use size="sm"/"xs" in dense tool panels.',import:"@gradeui/ui",aliases:["text field","textbox","textfield","form field","text input","secure field","search field","url field","number field","textinput","text input field","react native textinput","unit input","input with icon"],composesWith:["Label","Form","Card (in CardContent)","Button (form submit)"],styleDefaults:{Input:"pointer-events-none absolute inset-y-0 left-0 flex items-center text-muted-foreground [&_svg]:size-3.5"},props:{type:{schema:z.string().optional(),design:"content"},placeholder:{schema:z.string().optional(),design:"content",description:"hint text shown while the input is empty. Model it explicitly (not just a native passthrough) so generated screens carry placeholders and the validator accepts them."},size:{schema:z.enum(["default","sm","xs"]).optional(),design:"knob",description:"control density. `default` (h-9) for forms; `sm` (h-8) and `xs` (h-7) for dense tool panels like the inspector. NOTE: pre-unification scale \u2014 see Figma parity audit; due to migrate to the t-shirt scale (xs 24 | sm 28 | md 32 | lg 40, default\u2192md)."},startSlot:{schema:z.unknown().optional(),design:"plumbing",description:"adornment rendered inside the leading edge (icon, prefix, currency symbol). Non-interactive by default so clicks focus the input."},endSlot:{schema:z.unknown().optional(),design:"plumbing",description:'adornment rendered inside the trailing edge (unit like "px", a clear button, a stepper). Same pointer rules as startSlot.'}}};var Ze={name:"Label",description:'Every Input / Textarea / Checkbox / Switch / RadioGroup. Always use htmlFor so clicking the label focuses the control. Match `size` to the field it labels (size="xs" label over a size="xs" input).',import:"@gradeui/ui",aliases:["label","form label","field label","caption"],composesWith:["Input","Textarea","Checkbox","Switch","RadioGroup","Select"],styleDefaults:{Label:"font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-sm"},variantDefaults:{size:"default"},props:{htmlFor:{schema:z.string().optional(),design:"content",description:"binds to the input's id"},size:{schema:z.enum(["default","sm","xs"]).optional(),design:"knob",description:"text size, mirrors Input/Select/Textarea so a field and its label scale together. default = text-sm; xs = 11px for dense tool panels."}}};var $e={name:"Logo",description:"ALWAYS use <Logo> wherever a screen carries a brand mark \u2014",import:"@gradeui/ui",aliases:["logo","brand","brandmark","wordmark","lockup","brand logo","app logo","logotype","grade mark","g arrow"],composesWith:["AppShell","AppShellHeader","Sidebar","SidebarHeader","Toolbar","MotionOverlay","Row","Stack"],styleDefaults:{Logo:"gds-logo inline-flex shrink-0 select-none items-center"},props:{sources:{schema:z.unknown().optional(),design:"plumbing",description:"artwork keyed by lockup then appearance: { square?: { light?, dark?, mono? }, horizontal?: {...}, icon?: {...} }. Each slot is any node (inline <svg>, <img>, component). OMIT ENTIRELY and the GRADE MARK renders (the square G-arrow, painted with currentColor so it sits correctly on any surface). A bare <Logo /> is always correct branding."},size:{schema:z.enum(["sm","md","lg","xl"]).optional(),design:"knob",description:'height of the mark: 20 / 28 / 40 / 56px (a raw pixel number also works). Width is intrinsic (square/icon 1:1, horizontal keeps its ratio). Default "md"; use "sm" in dense rails and toolbars.'},lockup:{schema:z.enum(["square","horizontal","icon"]).optional(),design:"knob",description:'which shape to show; falls back to the next-best available artwork. Default "horizontal".'},mode:{schema:z.enum(["light","dark"]).optional(),design:"knob",description:'the background the logo SITS ON, selecting light/dark artwork. Explicit, not theme-coupled. Default "light".'},mono:{schema:z.boolean().optional(),design:"knob",description:"render the single-colour treatment; inherits currentColor from the parent. Default false."},label:{schema:z.string().optional(),design:"content",description:'accessible name (aria-label + role="img"); pair with decorative when the brand name is already beside it.'},decorative:{schema:z.boolean().optional(),design:"knob",description:"aria-hidden, no role; use when text nearby names the brand."},href:{schema:z.string().optional(),design:"content",description:"renders the logo as a link (logo-links-home)."},className:{schema:z.string().optional(),design:"plumbing"}}};var et={name:"Map",description:"Any layout that needs a real map \u2014 listings (real estate, Airbnb-style), fleet/logistics dashboards, store locators, anywhere a user picks a location from a viewport. Reach for the controlled `hoveredId` prop when a sibling list and the map need to highlight each other.",import:"@gradeui/ui",aliases:["map","maps","mapbox","maplibre","google maps","geo","location","latlng","coordinates","marker","pin","airbnb","listings","fleet","real estate","logistics","map view","mapkit","mapview","react native maps","rn maps"],subcomponents:["MapMarker"],composesWith:["Card (as marker content)","Badge","Avatar","Button","Row","Stack","Skeleton"],props:{provider:{schema:z.unknown(),design:"plumbing",description:'"maplibre" (default, free, no key) | "mapbox" (needs accessToken) | "google" (needs apiKey). Switching is one prop change.'},center:{schema:z.unknown(),design:"plumbing",description:"`[lng, lat]` tuple. ALWAYS lng first. Required."},zoom:{schema:z.unknown(),design:"plumbing",description:"number, 0\u201322. Required."},bounds:{schema:z.unknown(),design:"plumbing",description:"`[[swLng, swLat], [neLng, neLat]]`. When set, takes precedence over center+zoom."},appearance:{schema:z.unknown(),design:"plumbing",description:'"light" | "dark" | "satellite" | "auto" (default "auto", follows GradeThemeProvider mode).'},hoveredId:{schema:z.unknown(),design:"plumbing",description:'controlled string id, pairs with onHoveredIdChange. The matching MapMarker gets `data-gds-state="hovered"` automatically. This is how you build list \u2194 map two-way sync.'},onHoveredIdChange:{schema:z.unknown(),design:"event",description:"`(id: string | null) => void`. The other half of the controlled hover pair: fires when the pointer enters/leaves a marker so the sibling list can highlight in step. Without this entry in the contract, screens using the documented two-way sync fail save validation."},interactive:{schema:z.unknown(),design:"plumbing",description:"false freezes pan/zoom, useful for static cards."},tools:{schema:z.unknown(),design:"plumbing",description:'"auto" (default, zoom buttons follow `interactive`) | "zoom" (force zoom buttons) | "none" (chrome-free map; attribution always stays \u2014 license). One vocabulary across all providers.'},toolsPosition:{schema:z.unknown(),design:"plumbing",description:'"top-left" (default) | "top-right" | "bottom-left" | "bottom-right". Corner the tools dock to; use when a search bar or legend sits over the default corner.'},onLoad:{schema:z.unknown(),design:"event",description:"handle exposes flyTo, panTo, fitBounds, getCenter, getZoom, getBounds, instance."},tilerKey:{schema:z.unknown().optional(),design:"plumbing",description:'MapLibre only (provider="maplibre"). Optional everywhere: omit on `gradeui.com`/`localhost` and the referrer-locked demo key is used; set it only when embedding off-domain. The contract never requires it.'},accessToken:{schema:z.unknown().optional(),design:"plumbing",description:'Mapbox only. Pass it whenever provider="mapbox" \u2014 the component itself enforces this at runtime (throws a clear `provider="mapbox" requires an accessToken prop` error via onError if missing). It is OPTIONAL in the contract on purpose, so the validator never demands it from maplibre/google maps.'},apiKey:{schema:z.unknown().optional(),design:"plumbing",description:'Google only. Pass it whenever provider="google" \u2014 the component enforces it at runtime (throws `provider="google" requires an apiKey prop` via onError if missing). OPTIONAL in the contract on purpose, so it\'s never demanded from maplibre/mapbox.'},id:{schema:z.unknown().optional(),design:"plumbing",description:"string. Required. Stable marker id; pair with Map's `hoveredId` for list\u2194map hover sync."},at:{schema:z.unknown().optional(),design:"plumbing",description:"`[lng, lat]` tuple. Required. THE coordinate prop. ALWAYS lng first. The prop is literally named `at` \u2014 it is NOT `lngLat`, `coordinates`, `position`, `latLng`, `center`, or separate `lng`/`lat` props. Passing any other name leaves the marker coord `undefined`, and MapLibre throws on mount, crashing the WHOLE screen in every renderer. When in doubt, copy the `airbnb-listings` scaffold: `<MapMarker id={l.id} at={l.coords}>`."},anchor:{schema:z.unknown().optional(),design:"plumbing",description:'"center" | "bottom" (default "bottom", pin tip sits on the coord). Only these two values.'},onClick:{schema:z.unknown().optional(),design:"event",description:"handler called with `({ id, coords, native })` on marker click."},children:{schema:z.unknown().optional(),design:"plumbing",description:"DOM rendered as the marker (Badge, Card, Avatar, or any element). Inherits `--gds-*` tokens."}}};var _t=z.object({kind:z.literal("album"),artist:z.string(),title:z.string(),year:z.number().optional(),description:z.string().optional()}),Kt=z.object({kind:z.literal("tv-show"),title:z.string(),year:z.number().optional(),description:z.string().optional()}),Yt=z.object({kind:z.literal("movie"),title:z.string(),year:z.number().optional(),description:z.string().optional()}),Jt=z.object({kind:z.literal("game"),title:z.string(),description:z.string().optional()}),Xt=z.object({kind:z.literal("book"),title:z.string().optional(),author:z.string().optional(),isbn:z.string().optional(),description:z.string().optional()}),Qt=z.object({kind:z.literal("poster"),title:z.string(),year:z.number().optional(),description:z.string().optional()}),Zt=z.object({kind:z.literal("portrait"),name:z.string().optional(),role:z.string().optional()}),$t=z.object({kind:z.literal("landscape"),location:z.string().optional(),mood:z.string().optional()}),eo=z.object({kind:z.literal("product"),name:z.string().optional(),brand:z.string().optional()}),to=z.object({kind:z.literal("food"),dish:z.string().optional(),cuisine:z.string().optional()}),oo=z.object({kind:z.literal("generic"),prompt:z.string()}),no=z.union([z.object({kind:z.literal("video")}),z.object({kind:z.literal("audio")}),z.object({kind:z.literal("embed")}),z.object({kind:z.literal("3d")})]),io=z.union([_t,Kt,Yt,Jt,Xt,Qt,Zt,$t,eo,to,oo,no]),ao=z.enum(["video","square","portrait","wide","auto"]),ro=z.enum(["none","sm","md","lg","xl"]),so=z.enum(["album","tv-show","movie","game","book","portrait","landscape","poster","product","food","video","audio","embed","3d","generic"]),lo=z.union([z.literal("auto"),z.literal("icon"),z.literal("none")]),tt={name:"MediaSurface",description:"The canonical media slot for ALL non-person imagery \u2014 album art, posters, hero images, landscape photos, video and 3D containers.",when:"Pass `hint` + `alt` + (optionally) `source` so the empty-state placeholder is meaningful and the generation pipeline can later fill the slot with a real image. Use directly for declarative slots; the higher-level VideoPlayer / RivePlayer / ThreeScene wrap this for runtime-heavy media.",antipatterns:["Don't wrap <Avatar> inside <MediaSurface> to get an initials fallback. Set `alt` + `hint` on MediaSurface directly \u2014 the placeholder renders initials at small sizes derived from `alt`.","Don't use <Avatar> for album art, posters, products, food, landscapes, etc. Avatar is for PEOPLE only.","Don't inline manual gradient backgrounds (`bg-gradient-to-br \u2026`) on MediaSurface as a 'placeholder vibe' \u2014 the empty-state is already styled via `--gds-media-placeholder-bg/-fg` tokens."],composesWith:["Card","CardBlock","MediaBlock","VideoPlayer","RivePlayer","ThreeScene"],aliases:["media","image slot","media slot","image placeholder","cover","thumbnail","poster slot"],import:"@gradeui/ui",props:{hint:{schema:so.optional(),design:"knob",group:"image",control:"glyph-picker",label:"Slot kind",description:"Picks the placeholder glyph + the default aspect + the future generation provider. Defaults to 'generic'.",default:"generic",examples:["album","portrait","landscape","poster"]},aspect:{schema:ao.optional(),design:"knob",control:"toggle-group",label:"Aspect ratio",description:"Override the slot's natural framing. When omitted, derived from `hint`: album/product/food \u2192 square, portrait/poster \u2192 portrait, landscape \u2192 wide, video/audio/embed/generic \u2192 video."},radius:{schema:ro.optional(),design:"knob",control:"toggle-group",label:"Corner radius",default:"lg",description:"Driven by the `--gds-media-radius` CSS variable."},border:{schema:z.boolean().optional(),design:"knob",label:"Show border",default:false},loading:{schema:z.boolean().optional(),design:"knob",label:"Loading state",default:false,description:"Overlays the muted-pulse skeleton on top of the slot."},emptyState:{schema:lo.optional(),design:"knob",control:"select",label:"Empty state",default:"auto",description:"'auto' renders the size-tiered placeholder (initials \u2192 glyph \u2192 glyph + caption). 'icon' is a legacy alias. 'none' renders a truly empty surface."},alt:{schema:z.string().optional(),design:"content",group:"image",control:"text",label:"Alt text",description:"Becomes the eventual `<img alt>`. Also drives the placeholder caption (>160px slots) and the 2-letter initials fallback (<64px slots).",examples:["Travelling Without Moving \u2014 Jamiroquai","Sunset over Mount Fuji"]},src:{schema:z.string().url().optional(),design:"content",group:"image",control:"url",label:"Image URL",description:"When set, renders an `<img>` filling the slot via object-cover. The wrapper keeps its aspect/radius/border. Generators patch this prop; manual values always win."},instanceId:{schema:z.string().optional(),design:"content",group:"image",control:"text",label:"Instance id",description:"Stable per-instance id stamped as `data-gds-instance-id`. Use when rendering MediaSurfaces from a data array (`.map(item => <MediaSurface instanceId={item.id} \u2026/>)`) \u2014 it's how Studio's selection + Fill flows tell one card apart from its siblings and patch only that entry. Was missing from this hand-authored contract while the component documented it, which made save validation reject the documented pattern (June 2026)."},source:{schema:io.optional(),design:"structured",label:"Source descriptor",description:"Structured metadata for the generation pipeline. Opaque to MediaSurface itself; read by the resolver to look up real imagery from the right provider (MusicBrainz / Pollinations / etc.).",perKindFields:{album:{artist:"string",title:"string",year:"number?"},poster:{title:"string",year:"number?"},portrait:{name:"string?",role:"string?"},landscape:{location:"string?",mood:"string?"},product:{name:"string?",brand:"string?"},food:{dish:"string?",cuisine:"string?"},generic:{prompt:"string"},video:{},audio:{},embed:{},"3d":{}}},className:{schema:z.string().optional(),design:"plumbing"},style:{schema:z.record(z.string(),z.unknown()).optional(),design:"plumbing"},children:{schema:z.unknown().optional(),design:"plumbing",description:"Escape hatch for putting a custom <video>, <canvas>, Rive runtime, etc. inside. When supplied, the placeholder is suppressed."},overlay:{schema:z.unknown().optional(),design:"plumbing",description:"Decorative layer rendered ABOVE the media/placeholder (play buttons, hover gradients, corner badges). Does NOT suppress the placeholder."},glyph:{schema:z.unknown().optional(),design:"plumbing",description:"Per-instance override of the hint-derived placeholder glyph. Most consumers should pick a `hint` and let the map decide."},fallback:{schema:z.unknown().optional(),design:"plumbing",description:"Custom node shown while `loading` is true."},onVisibilityChange:{schema:z.function().optional(),design:"event",description:"Fires when the surface enters / leaves the viewport (IntersectionObserver)."}},actions:{fill:{label:"Fill image",icon:"Sparkles",description:"Resolve this slot's source via the free providers (MusicBrainz \u2192 Pollinations \u2192 Picsum) and patch the result into the runtime URL map.",kind:"resolve-media-source",enabledWhen:{propPresent:"source"}}}};var ot={name:"Message",description:`The canonical "avatar + author + timestamp + body" row. THE PRIMITIVE
46
+ terminal demos.`,import:"@gradeui/ui",composesWith:["AIChatComposer (preset wrapping this with paperclip + send + attachments)","ComposerReply (preset for comment threads)","AIChat (uses AIChatComposer internally)","Card (host above for reply boxes)",'Avatar (in leftActions slot for "your" avatar next to the input)'],styleDefaults:{ComposerToolbar:"flex flex-wrap items-center gap-0.5",AttachmentChips:"absolute -top-1.5 -right-1.5 h-5 w-5 rounded-full",ComposerInner:"w-full"},props:{placeholder:{schema:z.string().optional(),design:"content"},initialText:{schema:z.string().optional(),design:"content",description:"plain text content to seed on mount"},initialJson:{schema:z.string().optional(),design:"content",description:"Lexical state JSON (from a previous onSubmit round-trip)"},formats:{schema:z.unknown().optional(),design:"plumbing",description:"available formats (defaults to bold/italic/underline/strikethrough/code/h1/h2/blockquote/ul/ol); pass false for plain text only"},toolbar:{schema:z.boolean().optional(),design:"knob",description:"show the formatting toolbar above the editor; default false"},triggers:{schema:z.unknown().optional(),design:"plumbing",description:'mention/slash configs, eg. `[{ char: "@", items: people }, { char: "/", items: commands }]`'},attachments:{schema:z.boolean().optional(),design:"knob",description:"enable image paste + paperclip when true/object; default off"},onSubmit:{schema:z.unknown().optional(),design:"event"},isLoading:{schema:z.boolean().optional(),design:"knob",description:"disables editor, swaps default Send for Stop"},onStop:{schema:z.unknown().optional(),design:"event"},maxLength:{schema:z.number().optional(),design:"knob"},autoFocus:{schema:z.boolean().optional(),design:"plumbing"},submitOnEnter:{schema:z.boolean().optional(),design:"knob",description:"default true (Shift-Enter still inserts newline)"},leftActions:{schema:z.unknown().optional(),design:"plumbing",description:"override the default paperclip"},rightActions:{schema:z.unknown().optional(),design:"plumbing",description:"override the default Send/Stop"},hideSend:{schema:z.boolean().optional(),design:"knob",description:"hide the default Send without replacing it"},steps:{schema:z.unknown().optional(),design:"plumbing",description:"scripted demo sequence"},trigger:{schema:z.unknown().optional(),design:"plumbing",description:'"mount" | "inView" | "manual"; default "mount"'},play:{schema:z.boolean().optional(),design:"knob",description:'for trigger="manual"'},speed:{schema:z.unknown().optional(),design:"plumbing",description:'"slow" | "normal" | "fast"; default "normal"'},loop:{schema:z.boolean().optional(),design:"knob"},loopDelay:{schema:z.number().optional(),design:"knob",description:"ms between loop iterations, default 2000"},readOnly:{schema:z.boolean().optional(),design:"knob",description:"disables editing AND focusability; programmatic playback still works; use for marketing demos so the script doesn't steal focus"},bare:{schema:z.boolean().optional(),design:"knob",description:"strip the card chrome"},className:{schema:z.string().optional(),design:"plumbing"}}};var Ge={name:"DataView",description:'One dataset, drawn as a table, a list of cards, or a grid \u2014 without re-typing the TanStack boilerplate (sortable headers, flexRender, selection, view switch) on every page. Hand it data + a columns schema; columns declare a `type` (badge/tags/number/currency/percent/date/boolean/url/text) that DataView renders, with a `cell` override for bespoke cells (avatars, relations). The view toggle can live anywhere \u2014 `useDataView()` holds the state so a `<DataViewToggle>` or `<DataViewColumns>` in a page header drives a `<DataView>` lower down. Mark a column `pinned="left"` (with a `width`) for a fixed column and `stickyHeader` to freeze the header. For a single record\'s fields use PropertyList; for the raw table primitive use Table.',import:"@gradeui/ui",aliases:["data view","data table","datatable","data grid","dataview","table view","card view","grid view","list view","gallery","records list","master list","tanstack table","sortable table","column visibility","pinned column","frozen column","sticky header","view switcher"],composesWith:["Table","Card","Badge","Avatar","ToggleGroup","DropdownMenu","PropertyList","Combobox"],props:{data:{schema:z.unknown(),design:"plumbing",description:"the rows"},columns:{schema:z.unknown(),design:"plumbing",description:"the schema; one list drives table, cards, and grid"},getRowId:{schema:z.unknown().optional(),design:"plumbing",description:"defaults to row.id"},view:{schema:z.unknown().optional(),design:"plumbing",description:"controlled or uncontrolled view"},views:{schema:z.unknown().optional(),design:"plumbing",description:"allowed views; one entry = single view, no toggle"},activeId:{schema:z.unknown().optional(),design:"plumbing",description:"the selected row; click emits it"},sorting:{schema:z.unknown().optional(),design:"plumbing",description:"TanStack SortingState"},columnVisibility:{schema:z.unknown().optional(),design:"plumbing",description:"which fields show"},stickyHeader:{schema:z.boolean().optional(),design:"knob",description:"freeze the header row on scroll"},toolbar:{schema:z.boolean().optional(),design:"knob",description:"render the built-in columns menu + view toggle above the view"},renderCard:{schema:z.unknown().optional(),design:"plumbing",description:"override card / grid tiles"},emptyMessage:{schema:z.unknown().optional(),design:"plumbing"}}};var Oe={name:"DatePicker",description:'Any date or date-range entry. Use DatePicker for a single date (DOB, due date, booking). Use DateRangePicker for a span (report period, stay dates, filter window). Prefer these over <Input type="date"> \u2014 consistent theming, keyboard nav, a11y, and no browser-native UI drift.',import:"@gradeui/ui",aliases:["datepicker","calendar input","date field","date range","datepickerios","react native date picker","calendar input field","date field control"],subcomponents:["DateRangePicker"],composesWith:["Label","Form","Card (in CardContent)","Button (form submit)"],styleDefaults:{DatePicker:"w-[280px] justify-start text-left font-normal data-[empty=true]:text-muted-foreground",DateRangePicker:"w-[300px] justify-start text-left font-normal data-[empty=true]:text-muted-foreground"},props:{value:{schema:z.unknown().optional(),design:"plumbing"},onChange:{schema:z.unknown().optional(),design:"event",description:"called on select or clear"},placeholder:{schema:z.string().optional(),design:"content",description:'trigger label when empty (default "Pick a date" / "Pick a date range")'},disabled:{schema:z.boolean().optional(),design:"knob"},format:{schema:z.string().optional(),design:"content",description:'date-fns format token for the trigger label (default "PPP" single, "LLL dd, y" range)'},align:{schema:z.enum(["start","center","end"]).optional(),design:"knob",description:'popover align (default "start")'},side:{schema:z.enum(["top","right","bottom","left"]).optional(),design:"knob",description:"popover side"},captionLayout:{schema:z.enum(["label","dropdown","dropdown-months","dropdown-years"]).optional(),design:"knob"},className:{schema:z.string().optional(),design:"plumbing",description:"on the trigger button"},contentClassName:{schema:z.string().optional(),design:"content",description:"on the PopoverContent"},icon:{schema:z.unknown().optional(),design:"plumbing",description:"replaces the default CalendarIcon"},numberOfMonths:{schema:z.number().optional(),design:"knob",description:"DateRangePicker only, default 2"}}};var Ee={name:"Dialog",description:'Modal interruptions \u2014 confirmations, focused forms, detail views, AI suggestion sheets. Dialog is the right primitive for Apple HIG / React Native "Alert" (modal) semantics. For non-blocking inline messaging use Callout; for transient notifications use Toaster (Sonner). Always include DialogTitle (a11y requirement).',import:"@gradeui/ui",aliases:["modal","popup","overlay","alert","system alert","alert dialog","modal dialog","confirm dialog","react native modal","rn alert","glass modal","frosted modal","ai suggestion modal"],subcomponents:["DialogTrigger","DialogContent","DialogHeader","DialogTitle","DialogDescription","DialogFooter","DialogClose"],composesWith:["Button (as DialogTrigger asChild","and inside DialogFooter)","Input/Textarea/Select inside DialogContent","Code (for changelog / diff modals)","MediaSurface (for image / preview modals)"],styleDefaults:{DialogOverlay:"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",DialogContent:"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",DialogHeader:"flex flex-col space-y-1.5 text-center sm:text-left",DialogFooter:"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",DialogTitle:"text-lg font-semibold leading-none tracking-tight",DialogDescription:"text-sm text-muted-foreground"},props:{open:{schema:z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:z.unknown().optional(),design:"event",description:"Radix controlled/uncontrolled pattern"},asChild:{schema:z.enum(["wrap a Button"]).optional(),design:"plumbing"},surface:{schema:z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the modal panel is *made of*. Defaults to `solid` (opaque `bg-background`). `glass` lets the page show through softly \u2014 pairs with rich backdrops or AI-suggestion modals."},accepts:{schema:z.unknown().optional(),design:"plumbing"},used:{schema:z.unknown().optional(),design:"plumbing"}}};var He={name:"DropdownMenu",description:'A small action menu attached to a trigger \u2014 overflow "\u2026" buttons on cards, user-avatar menus in headers, "Insert" menus in editors. For a full searchable list, use Command. For ONE primary action plus a secondary, use a Button next to a smaller ghost Button instead of a dropdown.',import:"@gradeui/ui",aliases:["dropdown","dropdown menu","overflow menu","kebab menu","more menu","action menu","context-style menu","menu","pull-down menu","pulldown menu","context menu","popup menu","actions menu","glass menu","frosted menu","ios menu","hig menu"],subcomponents:["DropdownMenuTrigger","DropdownMenuContent","DropdownMenuItem","DropdownMenuCheckboxItem","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuLabel","DropdownMenuSeparator","DropdownMenuShortcut","DropdownMenuGroup","DropdownMenuSub","DropdownMenuSubTrigger","DropdownMenuSubContent"],composesWith:["Button (as trigger asChild)","Avatar (user menu)","Card (overflow on a tile)","Tooltip (on the trigger)"],styleDefaults:{DropdownMenuSubContent:"z-50 min-w-[8rem] overflow-hidden rounded-md border text-popover-foreground shadow-lg 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 origin-[--radix-dropdown-menu-content-transform-origin]",DropdownMenuContent:"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border text-popover-foreground shadow-md",DropdownMenuItem:"relative flex cursor-default select-none items-center gap-2 rounded-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:shrink-0",DropdownMenuCheckboxItem:"relative flex cursor-default select-none items-center rounded-sm pr-2 outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",DropdownMenuRadioItem:"relative flex cursor-default select-none items-center rounded-sm pr-2 outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",DropdownMenuLabel:"font-semibold",DropdownMenuSeparator:"-mx-1 my-1 h-px bg-muted",DropdownMenuShortcut:"ml-auto text-xs tracking-widest opacity-60"},props:{open:{schema:z.unknown().optional(),design:"plumbing"},defaultOpen:{schema:z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:z.unknown().optional(),design:"event"},modal:{schema:z.unknown().optional(),design:"plumbing"},asChild:{schema:z.boolean().optional(),design:"plumbing",description:"usually wraps a Button"},align:{schema:z.enum(["start","center","end"]).optional(),design:"knob"},side:{schema:z.enum(["top","right","bottom","left"]).optional(),design:"knob"},sideOffset:{schema:z.number().optional(),design:"knob"},surface:{schema:z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the menu surface is *made of*. `solid` (default) is `bg-popover`. `translucent` matches Apple HIG / iOS menu sheets. `glass` for menus floating over rich canvases."},size:{schema:z.enum(["default","sm","xs"]).optional(),design:"knob",description:'menu density; cascades to every item (Item, Checkbox, Radio, SubTrigger, Label) via context so a compact trigger gets a compact menu. Use "xs" in dense tool panels.'},onSelect:{schema:z.unknown().optional(),design:"event"},disabled:{schema:z.unknown().optional(),design:"plumbing"},inset:{schema:z.unknown().optional(),design:"plumbing"},DropdownMenuCheckboxItem:{schema:z.unknown(),design:"plumbing"},DropdownMenuSub:{schema:z.unknown(),design:"plumbing",description:"sub-trigger shows children, sub-content holds the deeper items"},children:{schema:z.unknown().optional(),design:"plumbing",description:"right-aligned kbd hint"}}};var Ve={name:"Field",description:'Pair a bare control with a label and optional description in a row, with id + aria-describedby wired automatically. Use layout="setting" for the classic settings row (label on the left, Switch on the right). For a selectable CARD where the whole surface is the control, use RadioCard / CheckboxCard / SwitchCard instead.',import:"@gradeui/ui",aliases:["field","form field","control row","label and description","two line checkbox","option row","setting row","toggle row"],composesWith:["Checkbox","RadioGroup","RadioGroupItem","Switch","Badge (inside Field.Trailing)"],styleDefaults:{FieldLabel:"text-sm font-medium leading-none text-foreground cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70",FieldDescription:"text-sm text-muted-foreground",FieldTrailing:"flex shrink-0 items-center gap-2",FieldRoot:"flex gap-3"},props:{layout:{schema:z.enum(["option","setting"]).optional(),design:"knob",description:"option (default): control leads, text beside it; setting: text leads, control pinned trailing"},children:{schema:z.unknown(),design:"plumbing",description:"order does not matter"}}};var qe={name:"FillPicker",description:`Grade's paint picker \u2014 the control for choosing a frame's background fill, modelled on Figma's fill popover. A fill-type icon row (solid \xB7 gradient \xB7 image \xB7 pattern \xB7 video \xB7 shader) switches the panel below; a global opacity sits at the foot. Emits a FillValue that maps 1:1 onto BackgroundFill props. This is a Studio/inspector chrome control \u2014 pair it with BackgroundFill, which renders the chosen paint. Not for app content. Use the FillSection subcomponent to edit a LIST of fills (the Figma "Fill" inspector section): each row is a Solid/Gradient/Image toggle, the matching value control (ColorPicker / GradientEditor popover / image URL), an opacity %, a visibility eye, and a remove button, with an add button in the header.`,import:"@gradeui/ui",aliases:["fill picker","paint picker","background picker","fill chooser","fill popover","fill section","fill list","fills inspector","paint section"],subcomponents:["FillSection"],composesWith:["BackgroundFill (renders the FillValue)","Popover (host it in a popover)","ColorPicker (the solid value)","GradientEditor (the gradient value)","ShaderPresetPicker (the shader tab)","the inspector Fill section"],styleDefaults:{Swatch:"h-7 w-7 rounded-md border border-border/60 transition-shadow",FillPicker:"flex flex-col gap-3",FillSection:"flex flex-col gap-2"},props:{value:{schema:z.unknown(),design:"plumbing",description:"current paint ({ type, color?, gradient?, src?, fit?, repeat?, tileSize?, preset?, palette?, postPreset?, opacity? }) (required)"},onChange:{schema:z.unknown(),design:"event",description:"called on any change (required)"},title:{schema:z.string().optional(),design:"plumbing",description:'section heading (default "Fills")'}}};var Ue={name:"Flex",description:"The unopinionated flexbox primitive \u2014 reach for Flex when Stack, Row, or Grid don't quite fit. Specifically when you need reverse direction (`row-reverse` / `col-reverse`), CSS defaults instead of Row's baked-in `items-center gap-md`, or baseline alignment. Otherwise prefer Stack / Row / Grid \u2014 they're easier to read and tuned for the 95% case. Flex is the escape hatch, not the default.",import:"@gradeui/ui",aliases:["flex","flexbox","flex container","hstack","vstack","horizontal","vertical","generic container","layout view"],composesWith:["any content component"],props:{direction:{schema:z.enum(["row","col","row-reverse","col-reverse"]).optional(),design:"knob",description:"main-axis direction",default:"row"},gap:{schema:z.enum(["none","xs","sm","md","lg","xl","2xl"]).optional(),design:"knob",description:"gap between children",default:"none"},align:{schema:z.enum(["start","center","end","stretch","baseline"]).optional(),design:"knob",description:"cross-axis alignment",default:"stretch"},justify:{schema:z.enum(["start","center","end","between","around","evenly"]).optional(),design:"knob",description:"main-axis distribution",default:"start"},wrap:{schema:z.enum(["nowrap","wrap","wrap-reverse"]).optional(),design:"knob",description:"wrap behaviour when children overflow",default:"nowrap"},asChild:{schema:z.boolean().optional(),design:"plumbing",description:"render as the child element via Slot",default:false},className:{schema:z.string().optional(),design:"plumbing"},children:{schema:z.unknown(),design:"plumbing"}}};var Ke={name:"GradientEditor",description:'Edit a multi-stop CSS gradient with token-led stops. A type Select (Linear / Radial / Angular) with reverse + rotate actions, a live full-width preview bar (a Swatch type="gradient"), then a Stops list where each stop is a position %, a colour (ColorPicker token or raw), an opacity %, and a remove button; an add button appends a stop. Token stops resolve to oklch(var(--<token>)) so the preview re-voices with the theme. Emits the structured GradientValue (kept editable + serialisable); the caller turns it into CSS with the exported gradientToCss(value). Use inside a Popover from a FillSection gradient row, or standalone in a theme builder. For a single solid colour use ColorPicker; for a full paint (solid / gradient / image / shader) use FillPicker.',import:"@gradeui/ui",aliases:["gradient editor","gradient picker","gradient builder","css gradient editor","stop editor","gradient stops","linear gradient editor","conic gradient editor"],composesWith:["Select","Button","Input","ColorPicker","Swatch","Popover","FillSection"],styleDefaults:{GradientEditor:"flex flex-col gap-3"},props:{value:{schema:z.unknown(),design:"plumbing",description:"the structured gradient (type linear/radial/angular, optional angle in deg, ordered stops). NOT a CSS string \u2014 render the string via gradientToCss(value)."},onChange:{schema:z.unknown(),design:"event",description:"fired with the next structured gradient on any edit"}}};var Ye={name:"Grid",description:"2D layouts where Stack (vertical) and Row (horizontal) don't fit \u2014 stat-card grids, feature tiles, pricing columns, photo grids. Reach for Grid over hand-rolled `grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4` so the column count is a prop the settings panel can mutate and the responsive ladder stays consistent across designs.",import:"@gradeui/ui",aliases:["grid","tiles","cards grid","stat grid","columns","feature grid","grid view","lazy v grid","lazyvgrid","lazy h grid","lazyhgrid","tile grid","masonry"],composesWith:["Card","Stack (inside each cell)","Row","Button","any content component"],styleDefaults:{Grid:"gds-grid grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 items-stretch"},variantDefaults:{cols:"3",gap:"md",align:"stretch"},props:{cols:{schema:z.enum(["1","2","3","4","5","6","12"]).optional(),design:"knob",description:'desktop column count; each value has a baked-in responsive ladder (e.g. "4" \u2192 1 col mobile, 2 tablet, 4 desktop)',default:"3"},gap:{schema:z.enum(["none","xs","sm","md","lg","xl","2xl"]).optional(),design:"knob",description:"gap between grid cells (same scale as Stack/Row)",default:"md"},align:{schema:z.enum(["start","center","end","stretch"]).optional(),design:"knob",description:"cross-axis alignment of cells",default:"stretch"},asChild:{schema:z.boolean().optional(),design:"plumbing",description:"render as the child element via Slot",default:false},className:{schema:z.string().optional(),design:"plumbing"},children:{schema:z.unknown(),design:"plumbing"}}};var Je={name:"HoverCard",description:"Rich preview content surfaced on hover \u2014 user profile mini-cards on @-mentions, link previews, definition popups, layer-thumbnail peeks. Pointer-only by design (no touch-friendly trigger); pair with a click target for touch devices, or fall back to Popover. NEVER use HoverCard for critical info \u2014 if the user can't reach it via keyboard or touch, it might as well not exist for accessibility.",import:"@gradeui/ui",aliases:["hover card","hover preview","mention preview","profile peek","link preview","rich tooltip","link preview card","profile hover","peek card","glass preview","frosted preview"],subcomponents:["HoverCardTrigger","HoverCardContent"],composesWith:["Avatar (user preview)","Card (richer content)","Link (the trigger)","MediaSurface (link/layer previews)","Code (snippet previews)"],styleDefaults:{HoverCardContent:"z-50 w-64 rounded-md border p-4 text-popover-foreground shadow-md outline-none 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 origin-[--radix-hover-card-content-transform-origin]"},props:{open:{schema:z.unknown().optional(),design:"plumbing"},defaultOpen:{schema:z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:z.unknown().optional(),design:"event"},openDelay:{schema:z.unknown().optional(),design:"plumbing"},closeDelay:{schema:z.unknown().optional(),design:"plumbing"},asChild:{schema:z.boolean().optional(),design:"plumbing",description:"usually a Link or Button"},side:{schema:z.unknown().optional(),design:"plumbing"},align:{schema:z.unknown().optional(),design:"plumbing"},sideOffset:{schema:z.unknown().optional(),design:"plumbing"},alignOffset:{schema:z.unknown().optional(),design:"plumbing"},className:{schema:z.unknown().optional(),design:"plumbing"},surface:{schema:z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the preview surface is *made of*. `solid` (default) is `bg-popover`. `glass` for hover previews over rich content (a media feed, a layout canvas)."}}};var Xe={name:"Input",description:'Any single-line text entry. Always pair with a Label for accessibility. Use startSlot/endSlot for icons, prefixes and units instead of hand-positioning absolute children; use size="sm"/"xs" in dense tool panels.',import:"@gradeui/ui",aliases:["text field","textbox","textfield","form field","text input","secure field","search field","url field","number field","textinput","text input field","react native textinput","unit input","input with icon"],composesWith:["Label","Form","Card (in CardContent)","Button (form submit)"],styleDefaults:{Input:"pointer-events-none absolute inset-y-0 left-0 flex items-center text-muted-foreground [&_svg]:size-3.5"},props:{type:{schema:z.string().optional(),design:"content"},placeholder:{schema:z.string().optional(),design:"content",description:"hint text shown while the input is empty. Model it explicitly (not just a native passthrough) so generated screens carry placeholders and the validator accepts them."},size:{schema:z.enum(["default","sm","xs"]).optional(),design:"knob",description:"control density. `default` (h-9) for forms; `sm` (h-8) and `xs` (h-7) for dense tool panels like the inspector. NOTE: pre-unification scale \u2014 see Figma parity audit; due to migrate to the t-shirt scale (xs 24 | sm 28 | md 32 | lg 40, default\u2192md)."},startSlot:{schema:z.unknown().optional(),design:"plumbing",description:"adornment rendered inside the leading edge (icon, prefix, currency symbol). Non-interactive by default so clicks focus the input."},endSlot:{schema:z.unknown().optional(),design:"plumbing",description:'adornment rendered inside the trailing edge (unit like "px", a clear button, a stepper). Same pointer rules as startSlot.'}}};var Ze={name:"Label",description:'Every Input / Textarea / Checkbox / Switch / RadioGroup. Always use htmlFor so clicking the label focuses the control. Match `size` to the field it labels (size="xs" label over a size="xs" input).',import:"@gradeui/ui",aliases:["label","form label","field label","caption"],composesWith:["Input","Textarea","Checkbox","Switch","RadioGroup","Select"],styleDefaults:{Label:"font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-sm"},variantDefaults:{size:"default"},props:{htmlFor:{schema:z.string().optional(),design:"content",description:"binds to the input's id"},size:{schema:z.enum(["default","sm","xs"]).optional(),design:"knob",description:"text size, mirrors Input/Select/Textarea so a field and its label scale together. default = text-sm; xs = 11px for dense tool panels."}}};var $e={name:"Logo",description:"ALWAYS use <Logo> wherever a screen carries a brand mark \u2014",import:"@gradeui/ui",aliases:["logo","brand","brandmark","wordmark","lockup","brand logo","app logo","logotype","grade mark","g arrow"],composesWith:["AppShell","AppShellHeader","Sidebar","SidebarHeader","Toolbar","MotionOverlay","Row","Stack"],styleDefaults:{Logo:"gds-logo inline-flex shrink-0 select-none items-center"},props:{sources:{schema:z.unknown().optional(),design:"plumbing",description:"artwork keyed by lockup then appearance: { square?: { light?, dark?, mono? }, horizontal?: {...}, icon?: {...} }. Each slot is any node (inline <svg>, <img>, component). OMIT ENTIRELY and the GRADE MARK renders (the square G-arrow, painted with currentColor so it sits correctly on any surface). A bare <Logo /> is always correct branding."},size:{schema:z.enum(["sm","md","lg","xl"]).optional(),design:"knob",description:'height of the mark: 20 / 28 / 40 / 56px (a raw pixel number also works). Width is intrinsic (square/icon 1:1, horizontal keeps its ratio). Default "md"; use "sm" in dense rails and toolbars.'},lockup:{schema:z.enum(["square","horizontal","icon"]).optional(),design:"knob",description:'which shape to show; falls back to the next-best available artwork. Default "horizontal".'},mode:{schema:z.enum(["light","dark"]).optional(),design:"knob",description:'the background the logo SITS ON, selecting light/dark artwork. Explicit, not theme-coupled. Default "light".'},mono:{schema:z.boolean().optional(),design:"knob",description:"render the single-colour treatment; inherits currentColor from the parent. Default false."},label:{schema:z.string().optional(),design:"content",description:'accessible name (aria-label + role="img"); pair with decorative when the brand name is already beside it.'},decorative:{schema:z.boolean().optional(),design:"knob",description:"aria-hidden, no role; use when text nearby names the brand."},href:{schema:z.string().optional(),design:"content",description:"renders the logo as a link (logo-links-home)."},className:{schema:z.string().optional(),design:"plumbing"}}};var et={name:"Map",description:"Any layout that needs a real map \u2014 listings (real estate, Airbnb-style), fleet/logistics dashboards, store locators, anywhere a user picks a location from a viewport. Reach for the controlled `hoveredId` prop when a sibling list and the map need to highlight each other.",import:"@gradeui/ui",aliases:["map","maps","mapbox","maplibre","google maps","geo","location","latlng","coordinates","marker","pin","airbnb","listings","fleet","real estate","logistics","map view","mapkit","mapview","react native maps","rn maps"],subcomponents:["MapMarker"],composesWith:["Card (as marker content)","Badge","Avatar","Button","Row","Stack","Skeleton"],props:{provider:{schema:z.unknown(),design:"plumbing",description:'"maplibre" (default, free, no key) | "mapbox" (needs accessToken) | "google" (needs apiKey). Switching is one prop change.'},center:{schema:z.unknown(),design:"plumbing",description:"`[lng, lat]` tuple. ALWAYS lng first. Required."},zoom:{schema:z.unknown(),design:"plumbing",description:"number, 0\u201322. Required."},bounds:{schema:z.unknown(),design:"plumbing",description:"`[[swLng, swLat], [neLng, neLat]]`. When set, takes precedence over center+zoom."},appearance:{schema:z.unknown(),design:"plumbing",description:'"light" | "dark" | "satellite" | "auto" (default "auto", follows GradeThemeProvider mode).'},hoveredId:{schema:z.unknown(),design:"plumbing",description:'controlled string id, pairs with onHoveredIdChange. The matching MapMarker gets `data-gds-state="hovered"` automatically. This is how you build list \u2194 map two-way sync.'},onHoveredIdChange:{schema:z.unknown(),design:"event",description:"`(id: string | null) => void`. The other half of the controlled hover pair: fires when the pointer enters/leaves a marker so the sibling list can highlight in step. Without this entry in the contract, screens using the documented two-way sync fail save validation."},interactive:{schema:z.unknown(),design:"plumbing",description:"false freezes pan/zoom, useful for static cards."},tools:{schema:z.unknown(),design:"plumbing",description:'"auto" (default, zoom buttons follow `interactive`) | "zoom" (force zoom buttons) | "none" (chrome-free map; attribution always stays \u2014 license). One vocabulary across all providers.'},toolsPosition:{schema:z.unknown(),design:"plumbing",description:'"top-left" (default) | "top-right" | "bottom-left" | "bottom-right". Corner the tools dock to; use when a search bar or legend sits over the default corner.'},onLoad:{schema:z.unknown(),design:"event",description:"handle exposes flyTo, panTo, fitBounds, getCenter, getZoom, getBounds, instance."},tilerKey:{schema:z.unknown().optional(),design:"plumbing",description:'MapLibre only (provider="maplibre"). Optional everywhere: omit on `gradeui.com`/`localhost` and the referrer-locked demo key is used; set it only when embedding off-domain. The contract never requires it.'},accessToken:{schema:z.unknown().optional(),design:"plumbing",description:'Mapbox only. Pass it whenever provider="mapbox" \u2014 the component itself enforces this at runtime (throws a clear `provider="mapbox" requires an accessToken prop` error via onError if missing). It is OPTIONAL in the contract on purpose, so the validator never demands it from maplibre/google maps.'},apiKey:{schema:z.unknown().optional(),design:"plumbing",description:'Google only. Pass it whenever provider="google" \u2014 the component enforces it at runtime (throws `provider="google" requires an apiKey prop` via onError if missing). OPTIONAL in the contract on purpose, so it\'s never demanded from maplibre/mapbox.'},id:{schema:z.unknown().optional(),design:"plumbing",description:"string. Required. Stable marker id; pair with Map's `hoveredId` for list\u2194map hover sync."},at:{schema:z.unknown().optional(),design:"plumbing",description:"`[lng, lat]` tuple. Required. THE coordinate prop. ALWAYS lng first. The prop is literally named `at` \u2014 it is NOT `lngLat`, `coordinates`, `position`, `latLng`, `center`, or separate `lng`/`lat` props. Passing any other name leaves the marker coord `undefined`, and MapLibre throws on mount, crashing the WHOLE screen in every renderer. When in doubt, copy the `airbnb-listings` scaffold: `<MapMarker id={l.id} at={l.coords}>`."},anchor:{schema:z.unknown().optional(),design:"plumbing",description:'"center" | "bottom" (default "bottom", pin tip sits on the coord). Only these two values.'},onClick:{schema:z.unknown().optional(),design:"event",description:"handler called with `({ id, coords, native })` on marker click."},children:{schema:z.unknown().optional(),design:"plumbing",description:"DOM rendered as the marker (Badge, Card, Avatar, or any element). Inherits `--gds-*` tokens."}}};var _t=z.object({kind:z.literal("album"),artist:z.string(),title:z.string(),year:z.number().optional(),description:z.string().optional()}),Kt=z.object({kind:z.literal("tv-show"),title:z.string(),year:z.number().optional(),description:z.string().optional()}),Yt=z.object({kind:z.literal("movie"),title:z.string(),year:z.number().optional(),description:z.string().optional()}),Jt=z.object({kind:z.literal("game"),title:z.string(),description:z.string().optional()}),Xt=z.object({kind:z.literal("book"),title:z.string().optional(),author:z.string().optional(),isbn:z.string().optional(),description:z.string().optional()}),Qt=z.object({kind:z.literal("poster"),title:z.string(),year:z.number().optional(),description:z.string().optional()}),Zt=z.object({kind:z.literal("portrait"),name:z.string().optional(),role:z.string().optional()}),$t=z.object({kind:z.literal("landscape"),location:z.string().optional(),mood:z.string().optional()}),eo=z.object({kind:z.literal("product"),name:z.string().optional(),brand:z.string().optional()}),to=z.object({kind:z.literal("food"),dish:z.string().optional(),cuisine:z.string().optional()}),oo=z.object({kind:z.literal("generic"),prompt:z.string()}),no=z.union([z.object({kind:z.literal("video")}),z.object({kind:z.literal("audio")}),z.object({kind:z.literal("embed")}),z.object({kind:z.literal("3d")})]),io=z.union([_t,Kt,Yt,Jt,Xt,Qt,Zt,$t,eo,to,oo,no]),ao=z.enum(["video","square","portrait","wide","auto"]),ro=z.enum(["none","sm","md","lg","xl"]),so=z.enum(["album","tv-show","movie","game","book","portrait","landscape","poster","product","food","video","audio","embed","3d","generic"]),lo=z.union([z.literal("auto"),z.literal("icon"),z.literal("none")]),tt={name:"MediaSurface",description:"The canonical media slot for ALL non-person imagery \u2014 album art, posters, hero images, landscape photos, video and 3D containers.",when:"Pass `hint` + `alt` + (optionally) `source` so the empty-state placeholder is meaningful and the generation pipeline can later fill the slot with a real image. Use directly for declarative slots; the higher-level VideoPlayer / RivePlayer / ThreeScene wrap this for runtime-heavy media.",antipatterns:["Don't wrap <Avatar> inside <MediaSurface> to get an initials fallback. Set `alt` + `hint` on MediaSurface directly \u2014 the placeholder renders initials at small sizes derived from `alt`.","Don't use <Avatar> for album art, posters, products, food, landscapes, etc. Avatar is for PEOPLE only.","Don't inline manual gradient backgrounds (`bg-gradient-to-br \u2026`) on MediaSurface as a 'placeholder vibe' \u2014 the empty-state is already styled via `--gds-media-placeholder-bg/-fg` tokens."],composesWith:["Card","CardBlock","MediaBlock","VideoPlayer","RivePlayer","ThreeScene"],aliases:["media","image slot","media slot","image placeholder","cover","thumbnail","poster slot"],import:"@gradeui/ui",props:{hint:{schema:so.optional(),design:"knob",group:"image",control:"glyph-picker",label:"Slot kind",description:"Picks the placeholder glyph + the default aspect + the future generation provider. Defaults to 'generic'.",default:"generic",examples:["album","portrait","landscape","poster"]},aspect:{schema:ao.optional(),design:"knob",control:"toggle-group",label:"Aspect ratio",description:"Override the slot's natural framing. When omitted, derived from `hint`: album/product/food \u2192 square, portrait/poster \u2192 portrait, landscape \u2192 wide, video/audio/embed/generic \u2192 video."},radius:{schema:ro.optional(),design:"knob",control:"toggle-group",label:"Corner radius",default:"none",description:"Driven by the `--gds-media-radius` CSS variable. Defaults to `none` so the slot sits flush \u2014 set `lg`/`xl` for a standalone rounded image. (A media slot mounted flush at the top of a Card should stay square and let the Card clip it.)"},border:{schema:z.boolean().optional(),design:"knob",label:"Show border",default:false},loading:{schema:z.boolean().optional(),design:"knob",label:"Loading state",default:false,description:"Overlays the muted-pulse skeleton on top of the slot."},emptyState:{schema:lo.optional(),design:"knob",control:"select",label:"Empty state",default:"auto",description:"'auto' renders the size-tiered placeholder (initials \u2192 glyph \u2192 glyph + caption). 'icon' is a legacy alias. 'none' renders a truly empty surface."},alt:{schema:z.string().optional(),design:"content",group:"image",control:"text",label:"Alt text",description:"Becomes the eventual `<img alt>`. Also drives the placeholder caption (>160px slots) and the 2-letter initials fallback (<64px slots).",examples:["Travelling Without Moving \u2014 Jamiroquai","Sunset over Mount Fuji"]},src:{schema:z.string().url().optional(),design:"content",group:"image",control:"url",label:"Image URL",description:"When set, renders an `<img>` filling the slot via object-cover. The wrapper keeps its aspect/radius/border. Generators patch this prop; manual values always win."},instanceId:{schema:z.string().optional(),design:"content",group:"image",control:"text",label:"Instance id",description:"Stable per-instance id stamped as `data-gds-instance-id`. Use when rendering MediaSurfaces from a data array (`.map(item => <MediaSurface instanceId={item.id} \u2026/>)`) \u2014 it's how Studio's selection + Fill flows tell one card apart from its siblings and patch only that entry. Was missing from this hand-authored contract while the component documented it, which made save validation reject the documented pattern (June 2026)."},source:{schema:io.optional(),design:"structured",label:"Source descriptor",description:"Structured metadata for the generation pipeline. Opaque to MediaSurface itself; read by the resolver to look up real imagery from the right provider (MusicBrainz / Pollinations / etc.).",perKindFields:{album:{artist:"string",title:"string",year:"number?"},poster:{title:"string",year:"number?"},portrait:{name:"string?",role:"string?"},landscape:{location:"string?",mood:"string?"},product:{name:"string?",brand:"string?"},food:{dish:"string?",cuisine:"string?"},generic:{prompt:"string"},video:{},audio:{},embed:{},"3d":{}}},className:{schema:z.string().optional(),design:"plumbing"},style:{schema:z.record(z.string(),z.unknown()).optional(),design:"plumbing"},children:{schema:z.unknown().optional(),design:"plumbing",description:"Escape hatch for putting a custom <video>, <canvas>, Rive runtime, etc. inside. When supplied, the placeholder is suppressed."},overlay:{schema:z.unknown().optional(),design:"plumbing",description:"Decorative layer rendered ABOVE the media/placeholder (play buttons, hover gradients, corner badges). Does NOT suppress the placeholder."},glyph:{schema:z.unknown().optional(),design:"plumbing",description:"Per-instance override of the hint-derived placeholder glyph. Most consumers should pick a `hint` and let the map decide."},fallback:{schema:z.unknown().optional(),design:"plumbing",description:"Custom node shown while `loading` is true."},onVisibilityChange:{schema:z.function().optional(),design:"event",description:"Fires when the surface enters / leaves the viewport (IntersectionObserver)."}},actions:{fill:{label:"Fill image",icon:"Sparkles",description:"Resolve this slot's source via the free providers (MusicBrainz \u2192 Pollinations \u2192 Picsum) and patch the result into the runtime URL map.",kind:"resolve-media-source",enabledWhen:{propPresent:"source"}}}};var ot={name:"Message",description:`The canonical "avatar + author + timestamp + body" row. THE PRIMITIVE
47
47
  for any chat surface, comment thread, post-reply, activity log, or
48
48
  notification feed that follows the people-and-text shape.
49
49