lucent-ui 0.35.0 → 0.37.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.
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),I=require("./LucentProvider-we0nRXn-.cjs"),c=require("react"),J=require("react-dom"),wt={id:"button",name:"Button",tier:"atom",domain:"neutral",specVersion:"1.0",description:"A clickable control that triggers an action. The primary interactive primitive in Lucent UI.",designIntent:'Buttons communicate available actions. Variant conveys hierarchy: use "primary" for the single most important action in a view, "secondary" for supporting actions, "ghost" for low-emphasis actions in dense UIs, "outline" for bordered buttons with transparent background, and "danger" exclusively for destructive or irreversible operations. Use "danger-ghost" for low-emphasis destructive actions (red text, no fill) and "danger-outline" for bordered destructive buttons (also transparent background). Size should match surrounding content density — prefer "md" as the default, "sm" for toolbars or tables, "xs" for compact UIs like customizer panels, and "2xs" for ultra-dense inline controls (~22px height) such as table-inline actions or toolbar icon triggers. Icon-only buttons (leftIcon/rightIcon without children) automatically render as square with aspect-ratio: 1.',props:[{name:"variant",type:"enum",required:!1,default:"primary",description:'Visual style conveying action hierarchy. "primary" — filled accent for the single most important action. "secondary" — subtle accent-tinted fill for supporting actions. "outline" — bordered with no fill, for neutral secondary actions. "ghost" — transparent with no border, for low-emphasis or inline actions. "danger" — filled red for irreversible destructive actions (e.g. "Delete account"). "danger-outline" — red border + red text for destructive actions that need visual weight without a filled background. "danger-ghost" — red text only, for low-emphasis destructive actions (e.g. "Remove" in a list row).',enumValues:["primary","secondary","outline","ghost","danger","danger-outline","danger-ghost"]},{name:"size",type:"enum",required:!1,default:"md",description:'Controls height and padding. "lg" (48px) — hero sections, onboarding flows. "md" (42px) — default for most forms and dialogs. "sm" (34px) — toolbars, table headers, card actions. "xs" (26px) — compact UIs like customizer panels, inline controls. "2xs" (22px) — ultra-dense inline icon triggers, table-row actions, dashboard toolbar buttons.',enumValues:["2xs","xs","sm","md","lg"]},{name:"children",type:"ReactNode",required:!1,description:"Button label or content. Omit for icon-only buttons (provide leftIcon or rightIcon instead)."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Prevents interaction and applies disabled styling."},{name:"loading",type:"boolean",required:!1,default:"false",description:"Shows a spinner and prevents interaction while an async action is in progress."},{name:"fullWidth",type:"boolean",required:!1,default:"false",description:"Stretches the button to fill its container width."},{name:"bordered",type:"boolean",required:!1,default:"true",description:"When false removes the button border entirely, producing a flat look."},{name:"leftIcon",type:"ReactNode",required:!1,description:"Icon element rendered before the label."},{name:"rightIcon",type:"ReactNode",required:!1,description:"Icon element rendered after the label."},{name:"chevron",type:"boolean",required:!1,default:"false",description:"Appends a chevron-down icon after the label. Useful for dropdown triggers."},{name:"spread",type:"boolean",required:!1,default:"false",description:"Spaces content to the edges (justify-content: space-between). Useful with fullWidth + rightIcon/chevron."},{name:"onClick",type:"function",required:!1,description:"Called when the button is clicked and not disabled or loading."},{name:"type",type:"enum",required:!1,default:"button",description:"Native button type attribute.",enumValues:["button","submit","reset"]}],usageExamples:[{title:"Primary action",code:'<Button variant="primary" onClick={handleSave}>Save changes</Button>'},{title:"Destructive action",code:'<Button variant="danger" onClick={handleDelete}>Delete account</Button>'},{title:"Loading state",code:'<Button variant="primary" loading={isSaving}>Save changes</Button>'},{title:"With icon",code:'<Button variant="secondary" leftIcon={<PlusIcon />}>Add member</Button>'},{title:"Ghost in toolbar",code:'<Button variant="ghost" size="sm">Edit</Button>'},{title:"Full-width submit",code:'<Button variant="primary" type="submit" fullWidth>Sign in</Button>'},{title:"Outline with swatch",code:`<Button size="xs" variant="outline" leftIcon={<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#6366f1' }} />}>Indigo</Button>`},{title:"Dropdown trigger",code:'<Button variant="outline" chevron>Options</Button>'},{title:"Bordered destructive action",code:'<Button variant="danger-outline" onClick={handleRevoke}>Revoke access</Button>'},{title:"Low-emphasis destructive action",code:'<Button variant="danger-ghost" onClick={handleRemove}>Remove</Button>'},{title:"Dense inline action",code:'<Button variant="ghost" size="2xs" leftIcon={<RefreshIcon />}>Retry</Button>'},{title:"Icon-only (square)",code:'<Button variant="outline" size="2xs" leftIcon={<CloseIcon />} aria-label="Close" />',description:"Omitting children auto-sizes the button as a square via aspect-ratio: 1."}],compositionGraph:[],accessibility:{role:"button",ariaAttributes:["aria-disabled","aria-busy"],keyboardInteractions:["Enter — activates the button","Space — activates the button"]}},kt={id:"input",name:"Input",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A single-line text field with optional label, helper text, and error state.",designIntent:"Always pair with a visible label — never rely on placeholder text alone as it disappears on input and is inaccessible. Use errorText (not helperText) to surface validation failures; the component applies danger styling automatically. leftElement and rightElement accept icons or small controls (e.g. currency symbol, clear button).",props:[{name:"size",type:"enum",required:!1,default:"md",description:"Controls height, font size, and padding. Label and helper text scale accordingly.",enumValues:["sm","md","lg"]},{name:"type",type:"enum",required:!1,default:"text",description:"HTML input type.",enumValues:["text","number","password","email","tel","url","search","color"]},{name:"label",type:"string",required:!1,description:"Visible label rendered above the input."},{name:"helperText",type:"string",required:!1,description:"Supplementary hint shown below the input."},{name:"errorText",type:"string",required:!1,description:"Validation error message. When set, input renders in error state."},{name:"leftElement",type:"ReactNode",required:!1,description:"Icon or adornment rendered inside the left edge."},{name:"rightElement",type:"ReactNode",required:!1,description:"Icon or adornment rendered inside the right edge."},{name:"placeholder",type:"string",required:!1,description:"Placeholder text. Use as a hint, not a label."},{name:"prefix",type:"ReactNode",required:!1,description:'Inset label attached to the left of the field (e.g. "$", "https://").'},{name:"suffix",type:"ReactNode",required:!1,description:'Inset label attached to the right of the field (e.g. "kg", ".com").'},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables the input."},{name:"value",type:"string",required:!1,description:"Controlled value."},{name:"onChange",type:"function",required:!1,description:"Change handler."}],usageExamples:[{title:"Basic",code:'<Input label="Email" type="email" placeholder="you@example.com" />'},{title:"With helper text",code:'<Input label="Username" helperText="3–20 characters, letters and numbers only" />'},{title:"Error state",code:'<Input label="Password" type="password" value={value} errorText="Must be at least 8 characters" />'},{title:"With icon",code:'<Input label="Search" leftElement={<SearchIcon />} placeholder="Search…" />'}],compositionGraph:[],accessibility:{role:"textbox",ariaAttributes:["aria-invalid","aria-describedby","aria-label"],keyboardInteractions:["Tab — focuses the input"]}},St={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-md)",lg:"var(--lucent-font-size-md)"},Tt={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-md)"},Ct={sm:"var(--lucent-space-3)",md:"var(--lucent-space-4)",lg:"var(--lucent-space-4)"},nt=c.forwardRef(({label:t,helperText:r,errorText:n,autoResize:o=!1,maxLength:a,showCount:i=!1,size:s="md",id:l,value:d,onChange:p,disabled:m,style:f,...w},y)=>{const g=c.useRef(null),h=y??g,u=l??`lucent-textarea-${Math.random().toString(36).slice(2,7)}`,b=!!n,x=!!m,k=typeof d=="string"?d.length:0;c.useEffect(()=>{if(!o)return;const C=h.current;C&&(C.style.height="auto",C.style.height=`${C.scrollHeight}px`)},[d,o,h]);const S=x?"transparent":b?"var(--lucent-danger-default)":"var(--lucent-border-default)";return e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-1)",width:"100%"},children:[t&&e.jsx("label",{htmlFor:u,style:{fontSize:Tt[s],fontWeight:"var(--lucent-font-weight-medium)",color:x?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)"},children:t}),e.jsx("textarea",{ref:h,id:u,maxLength:a,value:d,onChange:p,disabled:m,"aria-invalid":b,"aria-describedby":b?`${u}-error`:r?`${u}-helper`:void 0,style:{width:"100%",minHeight:"100px",padding:Ct[s],fontSize:St[s],fontFamily:"var(--lucent-font-family-base)",color:x?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",background:x?"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)":"var(--lucent-surface)",border:`1px solid ${S}`,borderRadius:"var(--lucent-radius-lg)",outline:"none",resize:o?"none":"vertical",boxSizing:"border-box",lineHeight:"var(--lucent-line-height-base)",cursor:x?"not-allowed":void 0,transition:["border-color var(--lucent-duration-fast) var(--lucent-easing-default)","box-shadow var(--lucent-duration-fast) var(--lucent-easing-default)"].join(", "),...f},onMouseEnter:C=>{var v;!x&&C.currentTarget!==document.activeElement&&(C.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-border-strong)"),(v=w.onMouseEnter)==null||v.call(w,C)},onMouseLeave:C=>{var v;!x&&C.currentTarget!==document.activeElement&&(C.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-border-default)"),(v=w.onMouseLeave)==null||v.call(w,C)},onFocus:C=>{var v;x||(C.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-accent-border)",C.currentTarget.style.boxShadow=`0 0 0 3px ${b?"var(--lucent-danger-subtle)":"var(--lucent-accent-subtle)"}`,(v=w.onFocus)==null||v.call(w,C))},onBlur:C=>{var v;x||(C.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-border-default)",C.currentTarget.style.boxShadow="none",(v=w.onBlur)==null||v.call(w,C))},...w}),e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"flex-start"},children:[e.jsxs("div",{children:[b&&e.jsx("span",{id:`${u}-error`,role:"alert",style:{fontSize:"var(--lucent-font-size-sm)",color:"var(--lucent-danger-text)",fontFamily:"var(--lucent-font-family-base)"},children:n}),!b&&r&&e.jsx("span",{id:`${u}-helper`,style:{fontSize:"var(--lucent-font-size-sm)",color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)"},children:r})]}),(i||a)&&e.jsxs("span",{style:{fontSize:"var(--lucent-font-size-xs)",color:a&&k>=a?"var(--lucent-danger-text)":"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-mono)",flexShrink:0,marginLeft:"var(--lucent-space-2)"},children:[k,a?`/${a}`:""]})]})]})});nt.displayName="Textarea";const It={id:"textarea",name:"Textarea",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A multi-line text input with optional auto-resize and character count.",designIntent:"Use autoResize for open-ended fields (bio, description) where content length is unpredictable. Use maxLength + showCount for fields with hard limits (tweet-style). Behaves identically to Input for label/helper/error patterns.",props:[{name:"label",type:"string",required:!1,description:"Visible label above the textarea."},{name:"helperText",type:"string",required:!1,description:"Hint text shown below."},{name:"errorText",type:"string",required:!1,description:"Validation error. Triggers error styling."},{name:"autoResize",type:"boolean",required:!1,default:"false",description:"Grows with content, disables manual resize handle."},{name:"maxLength",type:"number",required:!1,description:"Character limit. Displays counter when set."},{name:"showCount",type:"boolean",required:!1,default:"false",description:"Always show character counter even without maxLength."},{name:"value",type:"string",required:!1,description:"Controlled value."},{name:"onChange",type:"function",required:!1,description:"Change handler."},{name:"placeholder",type:"string",required:!1,description:"Placeholder text."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls font size and padding.",enumValues:["sm","md","lg"]},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables the textarea."}],usageExamples:[{title:"Basic",code:'<Textarea label="Bio" placeholder="Tell us about yourself…" />'},{title:"Auto-resize",code:'<Textarea label="Description" autoResize value={value} onChange={e => setValue(e.target.value)} />'},{title:"With character count",code:'<Textarea label="Tweet" maxLength={280} showCount value={value} onChange={e => setValue(e.target.value)} />'},{title:"Error state",code:'<Textarea label="Notes" errorText="Required" value="" />'}],compositionGraph:[],accessibility:{role:"textbox",ariaAttributes:["aria-multiline","aria-invalid","aria-describedby"],keyboardInteractions:["Tab — focuses the textarea"]}},jt={id:"badge",name:"Badge",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A small inline label for status, count, or category.",designIntent:'Badges communicate status or category at a glance. Match variant to semantic meaning — never use "danger" for non-critical states or "success" for neutral counts. Use dot=true when a single colour indicator is enough context (e.g. online status). Keep badge text short: 1–3 words maximum.',props:[{name:"variant",type:"enum",required:!1,default:"neutral",description:"Colour scheme conveying semantic meaning.",enumValues:["neutral","success","warning","danger","info","accent"]},{name:"size",type:"enum",required:!1,default:"md",description:"Controls height and font size.",enumValues:["sm","md"]},{name:"dot",type:"boolean",required:!1,default:"false",description:"Prepends a coloured dot indicator."},{name:"children",type:"ReactNode",required:!0,description:"Badge label."}],usageExamples:[{title:"Status",code:'<Badge variant="success" dot>Active</Badge>'},{title:"Count",code:'<Badge variant="danger">12</Badge>'},{title:"Category",code:'<Badge variant="info">Beta</Badge>'},{title:"Neutral tag",code:"<Badge>Draft</Badge>"}],compositionGraph:[],accessibility:{role:"status",notes:"Use aria-label on the parent element when badge meaning depends on context."}},Mt={neutral:{bg:"var(--lucent-surface-secondary)",color:"var(--lucent-text-secondary)",border:"var(--lucent-border-default)",hoverBg:"var(--lucent-surface-hover, #e5e7eb)",hoverBorder:"var(--lucent-border-strong)"},accent:{bg:"var(--lucent-accent-default)",color:"var(--lucent-accent-fg)",border:"var(--lucent-accent-default)",hoverBg:"var(--lucent-accent-hover)",hoverBorder:"var(--lucent-accent-hover)"},success:{bg:"var(--lucent-success-subtle)",color:"var(--lucent-success-text)",border:"var(--lucent-success-subtle)",hoverBg:"color-mix(in srgb, var(--lucent-success-default) 15%, var(--lucent-success-subtle))",hoverBorder:"var(--lucent-success-default)"},warning:{bg:"var(--lucent-warning-subtle)",color:"var(--lucent-warning-text)",border:"var(--lucent-warning-subtle)",hoverBg:"color-mix(in srgb, var(--lucent-warning-default) 15%, var(--lucent-warning-subtle))",hoverBorder:"var(--lucent-warning-default)"},danger:{bg:"var(--lucent-danger-subtle)",color:"var(--lucent-danger-text)",border:"var(--lucent-danger-subtle)",hoverBg:"color-mix(in srgb, var(--lucent-danger-default) 15%, var(--lucent-danger-subtle))",hoverBorder:"var(--lucent-danger-default)"},info:{bg:"var(--lucent-info-subtle)",color:"var(--lucent-info-text)",border:"var(--lucent-info-subtle)",hoverBg:"color-mix(in srgb, var(--lucent-info-default) 15%, var(--lucent-info-subtle))",hoverBorder:"var(--lucent-info-default)"}},zt={sm:{fontSize:"var(--lucent-font-size-xs)",height:"calc(var(--lucent-space-5) * 0.5 + 10px)",padding:"var(--lucent-space-1) var(--lucent-space-2)",paddingDismiss:"var(--lucent-space-1) var(--lucent-space-1) var(--lucent-space-1) var(--lucent-space-2)",iconSize:12,dotSize:6,gap:"var(--lucent-space-1)"},md:{fontSize:"var(--lucent-font-size-sm)",height:"calc(var(--lucent-space-6) * 0.5 + 12px)",padding:"var(--lucent-space-1) var(--lucent-space-2)",paddingDismiss:"var(--lucent-space-1) var(--lucent-space-1) var(--lucent-space-1) var(--lucent-space-2)",iconSize:14,dotSize:7,gap:"var(--lucent-space-2)"},lg:{fontSize:"var(--lucent-font-size-md)",height:"calc(var(--lucent-space-8) * 0.5 + 14px)",padding:"var(--lucent-space-1) var(--lucent-space-3)",paddingDismiss:"var(--lucent-space-1) var(--lucent-space-2) var(--lucent-space-1) var(--lucent-space-3)",iconSize:16,dotSize:8,gap:"var(--lucent-space-2)"}},Et=`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),I=require("./LucentProvider-LqNc0AxD.cjs"),c=require("react"),J=require("react-dom"),wt={id:"button",name:"Button",tier:"atom",domain:"neutral",specVersion:"1.0",description:"A clickable control that triggers an action. The primary interactive primitive in Lucent UI.",designIntent:'Buttons communicate available actions. Variant conveys hierarchy: use "primary" for the single most important action in a view, "secondary" for supporting actions, "ghost" for low-emphasis actions in dense UIs, "outline" for bordered buttons with transparent background, and "danger" exclusively for destructive or irreversible operations. Use "danger-ghost" for low-emphasis destructive actions (red text, no fill) and "danger-outline" for bordered destructive buttons (also transparent background). Size should match surrounding content density — prefer "md" as the default, "sm" for toolbars or tables, "xs" for compact UIs like customizer panels, and "2xs" for ultra-dense inline controls (~22px height) such as table-inline actions or toolbar icon triggers. Icon-only buttons (leftIcon/rightIcon without children) automatically render as square with aspect-ratio: 1.',props:[{name:"variant",type:"enum",required:!1,default:"primary",description:'Visual style conveying action hierarchy. "primary" — filled accent for the single most important action. "secondary" — subtle accent-tinted fill for supporting actions. "outline" — bordered with no fill, for neutral secondary actions. "ghost" — transparent with no border, for low-emphasis or inline actions. "danger" — filled red for irreversible destructive actions (e.g. "Delete account"). "danger-outline" — red border + red text for destructive actions that need visual weight without a filled background. "danger-ghost" — red text only, for low-emphasis destructive actions (e.g. "Remove" in a list row).',enumValues:["primary","secondary","outline","ghost","danger","danger-outline","danger-ghost"]},{name:"size",type:"enum",required:!1,default:"md",description:'Controls height and padding. "lg" (48px) — hero sections, onboarding flows. "md" (42px) — default for most forms and dialogs. "sm" (34px) — toolbars, table headers, card actions. "xs" (26px) — compact UIs like customizer panels, inline controls. "2xs" (22px) — ultra-dense inline icon triggers, table-row actions, dashboard toolbar buttons.',enumValues:["2xs","xs","sm","md","lg"]},{name:"children",type:"ReactNode",required:!1,description:"Button label or content. Omit for icon-only buttons (provide leftIcon or rightIcon instead)."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Prevents interaction and applies disabled styling."},{name:"loading",type:"boolean",required:!1,default:"false",description:"Shows a spinner and prevents interaction while an async action is in progress."},{name:"fullWidth",type:"boolean",required:!1,default:"false",description:"Stretches the button to fill its container width."},{name:"bordered",type:"boolean",required:!1,default:"true",description:"When false removes the button border entirely, producing a flat look."},{name:"leftIcon",type:"ReactNode",required:!1,description:"Icon element rendered before the label."},{name:"rightIcon",type:"ReactNode",required:!1,description:"Icon element rendered after the label."},{name:"chevron",type:"boolean",required:!1,default:"false",description:"Appends a chevron-down icon after the label. Useful for dropdown triggers."},{name:"spread",type:"boolean",required:!1,default:"false",description:"Spaces content to the edges (justify-content: space-between). Useful with fullWidth + rightIcon/chevron."},{name:"onClick",type:"function",required:!1,description:"Called when the button is clicked and not disabled or loading."},{name:"type",type:"enum",required:!1,default:"button",description:"Native button type attribute.",enumValues:["button","submit","reset"]}],usageExamples:[{title:"Primary action",code:'<Button variant="primary" onClick={handleSave}>Save changes</Button>'},{title:"Destructive action",code:'<Button variant="danger" onClick={handleDelete}>Delete account</Button>'},{title:"Loading state",code:'<Button variant="primary" loading={isSaving}>Save changes</Button>'},{title:"With icon",code:'<Button variant="secondary" leftIcon={<PlusIcon />}>Add member</Button>'},{title:"Ghost in toolbar",code:'<Button variant="ghost" size="sm">Edit</Button>'},{title:"Full-width submit",code:'<Button variant="primary" type="submit" fullWidth>Sign in</Button>'},{title:"Outline with swatch",code:`<Button size="xs" variant="outline" leftIcon={<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#6366f1' }} />}>Indigo</Button>`},{title:"Dropdown trigger",code:'<Button variant="outline" chevron>Options</Button>'},{title:"Bordered destructive action",code:'<Button variant="danger-outline" onClick={handleRevoke}>Revoke access</Button>'},{title:"Low-emphasis destructive action",code:'<Button variant="danger-ghost" onClick={handleRemove}>Remove</Button>'},{title:"Dense inline action",code:'<Button variant="ghost" size="2xs" leftIcon={<RefreshIcon />}>Retry</Button>'},{title:"Icon-only (square)",code:'<Button variant="outline" size="2xs" leftIcon={<CloseIcon />} aria-label="Close" />',description:"Omitting children auto-sizes the button as a square via aspect-ratio: 1."}],compositionGraph:[],accessibility:{role:"button",ariaAttributes:["aria-disabled","aria-busy"],keyboardInteractions:["Enter — activates the button","Space — activates the button"]}},kt={id:"input",name:"Input",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A single-line text field with optional label, helper text, and error state.",designIntent:"Always pair with a visible label — never rely on placeholder text alone as it disappears on input and is inaccessible. Use errorText (not helperText) to surface validation failures; the component applies danger styling automatically. leftElement and rightElement accept icons or small controls (e.g. currency symbol, clear button).",props:[{name:"size",type:"enum",required:!1,default:"md",description:"Controls height, font size, and padding. Label and helper text scale accordingly.",enumValues:["sm","md","lg"]},{name:"type",type:"enum",required:!1,default:"text",description:"HTML input type.",enumValues:["text","number","password","email","tel","url","search","color"]},{name:"label",type:"string",required:!1,description:"Visible label rendered above the input."},{name:"helperText",type:"string",required:!1,description:"Supplementary hint shown below the input."},{name:"errorText",type:"string",required:!1,description:"Validation error message. When set, input renders in error state."},{name:"leftElement",type:"ReactNode",required:!1,description:"Icon or adornment rendered inside the left edge."},{name:"rightElement",type:"ReactNode",required:!1,description:"Icon or adornment rendered inside the right edge."},{name:"placeholder",type:"string",required:!1,description:"Placeholder text. Use as a hint, not a label."},{name:"prefix",type:"ReactNode",required:!1,description:'Inset label attached to the left of the field (e.g. "$", "https://").'},{name:"suffix",type:"ReactNode",required:!1,description:'Inset label attached to the right of the field (e.g. "kg", ".com").'},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables the input."},{name:"value",type:"string",required:!1,description:"Controlled value."},{name:"onChange",type:"function",required:!1,description:"Change handler."}],usageExamples:[{title:"Basic",code:'<Input label="Email" type="email" placeholder="you@example.com" />'},{title:"With helper text",code:'<Input label="Username" helperText="3–20 characters, letters and numbers only" />'},{title:"Error state",code:'<Input label="Password" type="password" value={value} errorText="Must be at least 8 characters" />'},{title:"With icon",code:'<Input label="Search" leftElement={<SearchIcon />} placeholder="Search…" />'}],compositionGraph:[],accessibility:{role:"textbox",ariaAttributes:["aria-invalid","aria-describedby","aria-label"],keyboardInteractions:["Tab — focuses the input"]}},St={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-md)",lg:"var(--lucent-font-size-md)"},Tt={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-md)"},Ct={sm:"var(--lucent-space-3)",md:"var(--lucent-space-4)",lg:"var(--lucent-space-4)"},nt=c.forwardRef(({label:t,helperText:r,errorText:n,autoResize:o=!1,maxLength:a,showCount:i=!1,size:s="md",id:l,value:d,onChange:p,disabled:m,style:f,...w},y)=>{const g=c.useRef(null),h=y??g,u=l??`lucent-textarea-${Math.random().toString(36).slice(2,7)}`,b=!!n,x=!!m,k=typeof d=="string"?d.length:0;c.useEffect(()=>{if(!o)return;const C=h.current;C&&(C.style.height="auto",C.style.height=`${C.scrollHeight}px`)},[d,o,h]);const S=x?"transparent":b?"var(--lucent-danger-default)":"var(--lucent-border-default)";return e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-1)",width:"100%"},children:[t&&e.jsx("label",{htmlFor:u,style:{fontSize:Tt[s],fontWeight:"var(--lucent-font-weight-medium)",color:x?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)"},children:t}),e.jsx("textarea",{ref:h,id:u,maxLength:a,value:d,onChange:p,disabled:m,"aria-invalid":b,"aria-describedby":b?`${u}-error`:r?`${u}-helper`:void 0,style:{width:"100%",minHeight:"100px",padding:Ct[s],fontSize:St[s],fontFamily:"var(--lucent-font-family-base)",color:x?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",background:x?"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)":"var(--lucent-surface)",border:`1px solid ${S}`,borderRadius:"var(--lucent-radius-lg)",outline:"none",resize:o?"none":"vertical",boxSizing:"border-box",lineHeight:"var(--lucent-line-height-base)",cursor:x?"not-allowed":void 0,transition:["border-color var(--lucent-duration-fast) var(--lucent-easing-default)","box-shadow var(--lucent-duration-fast) var(--lucent-easing-default)"].join(", "),...f},onMouseEnter:C=>{var v;!x&&C.currentTarget!==document.activeElement&&(C.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-border-strong)"),(v=w.onMouseEnter)==null||v.call(w,C)},onMouseLeave:C=>{var v;!x&&C.currentTarget!==document.activeElement&&(C.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-border-default)"),(v=w.onMouseLeave)==null||v.call(w,C)},onFocus:C=>{var v;x||(C.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-accent-border)",C.currentTarget.style.boxShadow=`0 0 0 3px ${b?"var(--lucent-danger-subtle)":"var(--lucent-accent-subtle)"}`,(v=w.onFocus)==null||v.call(w,C))},onBlur:C=>{var v;x||(C.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-border-default)",C.currentTarget.style.boxShadow="none",(v=w.onBlur)==null||v.call(w,C))},...w}),e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"flex-start"},children:[e.jsxs("div",{children:[b&&e.jsx("span",{id:`${u}-error`,role:"alert",style:{fontSize:"var(--lucent-font-size-sm)",color:"var(--lucent-danger-text)",fontFamily:"var(--lucent-font-family-base)"},children:n}),!b&&r&&e.jsx("span",{id:`${u}-helper`,style:{fontSize:"var(--lucent-font-size-sm)",color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)"},children:r})]}),(i||a)&&e.jsxs("span",{style:{fontSize:"var(--lucent-font-size-xs)",color:a&&k>=a?"var(--lucent-danger-text)":"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-mono)",flexShrink:0,marginLeft:"var(--lucent-space-2)"},children:[k,a?`/${a}`:""]})]})]})});nt.displayName="Textarea";const It={id:"textarea",name:"Textarea",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A multi-line text input with optional auto-resize and character count.",designIntent:"Use autoResize for open-ended fields (bio, description) where content length is unpredictable. Use maxLength + showCount for fields with hard limits (tweet-style). Behaves identically to Input for label/helper/error patterns.",props:[{name:"label",type:"string",required:!1,description:"Visible label above the textarea."},{name:"helperText",type:"string",required:!1,description:"Hint text shown below."},{name:"errorText",type:"string",required:!1,description:"Validation error. Triggers error styling."},{name:"autoResize",type:"boolean",required:!1,default:"false",description:"Grows with content, disables manual resize handle."},{name:"maxLength",type:"number",required:!1,description:"Character limit. Displays counter when set."},{name:"showCount",type:"boolean",required:!1,default:"false",description:"Always show character counter even without maxLength."},{name:"value",type:"string",required:!1,description:"Controlled value."},{name:"onChange",type:"function",required:!1,description:"Change handler."},{name:"placeholder",type:"string",required:!1,description:"Placeholder text."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls font size and padding.",enumValues:["sm","md","lg"]},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables the textarea."}],usageExamples:[{title:"Basic",code:'<Textarea label="Bio" placeholder="Tell us about yourself…" />'},{title:"Auto-resize",code:'<Textarea label="Description" autoResize value={value} onChange={e => setValue(e.target.value)} />'},{title:"With character count",code:'<Textarea label="Tweet" maxLength={280} showCount value={value} onChange={e => setValue(e.target.value)} />'},{title:"Error state",code:'<Textarea label="Notes" errorText="Required" value="" />'}],compositionGraph:[],accessibility:{role:"textbox",ariaAttributes:["aria-multiline","aria-invalid","aria-describedby"],keyboardInteractions:["Tab — focuses the textarea"]}},jt={id:"badge",name:"Badge",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A small inline label for status, count, or category.",designIntent:'Badges communicate status or category at a glance. Match variant to semantic meaning — never use "danger" for non-critical states or "success" for neutral counts. Use dot=true when a single colour indicator is enough context (e.g. online status). Keep badge text short: 1–3 words maximum.',props:[{name:"variant",type:"enum",required:!1,default:"neutral",description:"Colour scheme conveying semantic meaning.",enumValues:["neutral","success","warning","danger","info","accent"]},{name:"size",type:"enum",required:!1,default:"md",description:"Controls height and font size.",enumValues:["sm","md"]},{name:"dot",type:"boolean",required:!1,default:"false",description:"Prepends a coloured dot indicator."},{name:"children",type:"ReactNode",required:!0,description:"Badge label."}],usageExamples:[{title:"Status",code:'<Badge variant="success" dot>Active</Badge>'},{title:"Count",code:'<Badge variant="danger">12</Badge>'},{title:"Category",code:'<Badge variant="info">Beta</Badge>'},{title:"Neutral tag",code:"<Badge>Draft</Badge>"}],compositionGraph:[],accessibility:{role:"status",notes:"Use aria-label on the parent element when badge meaning depends on context."}},Mt={neutral:{bg:"var(--lucent-surface-secondary)",color:"var(--lucent-text-secondary)",border:"var(--lucent-border-default)",hoverBg:"var(--lucent-surface-hover, #e5e7eb)",hoverBorder:"var(--lucent-border-strong)"},accent:{bg:"var(--lucent-accent-default)",color:"var(--lucent-accent-fg)",border:"var(--lucent-accent-default)",hoverBg:"var(--lucent-accent-hover)",hoverBorder:"var(--lucent-accent-hover)"},success:{bg:"var(--lucent-success-subtle)",color:"var(--lucent-success-text)",border:"var(--lucent-success-subtle)",hoverBg:"color-mix(in srgb, var(--lucent-success-default) 15%, var(--lucent-success-subtle))",hoverBorder:"var(--lucent-success-default)"},warning:{bg:"var(--lucent-warning-subtle)",color:"var(--lucent-warning-text)",border:"var(--lucent-warning-subtle)",hoverBg:"color-mix(in srgb, var(--lucent-warning-default) 15%, var(--lucent-warning-subtle))",hoverBorder:"var(--lucent-warning-default)"},danger:{bg:"var(--lucent-danger-subtle)",color:"var(--lucent-danger-text)",border:"var(--lucent-danger-subtle)",hoverBg:"color-mix(in srgb, var(--lucent-danger-default) 15%, var(--lucent-danger-subtle))",hoverBorder:"var(--lucent-danger-default)"},info:{bg:"var(--lucent-info-subtle)",color:"var(--lucent-info-text)",border:"var(--lucent-info-subtle)",hoverBg:"color-mix(in srgb, var(--lucent-info-default) 15%, var(--lucent-info-subtle))",hoverBorder:"var(--lucent-info-default)"}},zt={sm:{fontSize:"var(--lucent-font-size-xs)",height:"calc(var(--lucent-space-5) * 0.5 + 10px)",padding:"var(--lucent-space-1) var(--lucent-space-2)",paddingDismiss:"var(--lucent-space-1) var(--lucent-space-1) var(--lucent-space-1) var(--lucent-space-2)",iconSize:12,dotSize:6,gap:"var(--lucent-space-1)"},md:{fontSize:"var(--lucent-font-size-sm)",height:"calc(var(--lucent-space-6) * 0.5 + 12px)",padding:"var(--lucent-space-1) var(--lucent-space-2)",paddingDismiss:"var(--lucent-space-1) var(--lucent-space-1) var(--lucent-space-1) var(--lucent-space-2)",iconSize:14,dotSize:7,gap:"var(--lucent-space-2)"},lg:{fontSize:"var(--lucent-font-size-md)",height:"calc(var(--lucent-space-8) * 0.5 + 14px)",padding:"var(--lucent-space-1) var(--lucent-space-3)",paddingDismiss:"var(--lucent-space-1) var(--lucent-space-2) var(--lucent-space-1) var(--lucent-space-3)",iconSize:16,dotSize:8,gap:"var(--lucent-space-2)"}},Et=`
2
2
  @keyframes lucent-chip-pulse {
3
3
  0% { transform: scale(1); opacity: 0.6; }
4
4
  100% { transform: scale(2.8); opacity: 0; }
@@ -233,7 +233,7 @@ const [results, setResults] = useState([]);
233
233
  onChange={(v) => { setQuery(v); setResults(filter(v)); }}
234
234
  results={results}
235
235
  onResultSelect={(r) => console.log(r)}
236
- />`},{title:"Filter mode (no dropdown)",code:'<SearchInput mode="filter" value={filter} onChange={setFilter} placeholder="Filter items…" />'},{title:"Loading state",code:"<SearchInput value={query} onChange={setQuery} isLoading={isFetching} results={[]} />"}],compositionGraph:[{componentId:"input",componentName:"Input",role:"Search text field with icon slots",required:!0},{componentId:"spinner",componentName:"Spinner",role:"Loading indicator in the right slot",required:!1}],accessibility:{role:"combobox",ariaAttributes:["aria-expanded","aria-haspopup","aria-label"],keyboardInteractions:["Enter to select focused result","Escape to close dropdown"],notes:'The results list uses role="listbox" with role="option" items. For full keyboard navigation (arrow keys to move between results), wire up onKeyDown on the Input and manage an activeIndex state.'}},ye=c.createContext({px:"0",py:"0"}),mr={none:{py:"0",px:"0"},sm:{py:"var(--lucent-space-2)",px:"var(--lucent-space-3)"},md:{py:"var(--lucent-space-4)",px:"var(--lucent-space-5)"},lg:{py:"var(--lucent-space-6)",px:"var(--lucent-space-8)"}},ue={none:"var(--lucent-shadow-none)",sm:"var(--lucent-shadow-sm)",md:"var(--lucent-shadow-md)",lg:"var(--lucent-shadow-lg)"},gr={none:"var(--lucent-radius-none)",sm:"var(--lucent-radius-sm)",md:"var(--lucent-radius-md)",lg:"var(--lucent-radius-lg)"},vr={success:"var(--lucent-success-default)",warning:"var(--lucent-warning-default)",danger:"var(--lucent-danger-default)",info:"var(--lucent-info-default)"},br={ghost:{background:"transparent",border:"none",shadowDefault:"none",dividers:!0},outline:{background:"transparent",border:"1px solid var(--lucent-border-default)",shadowDefault:"none",dividers:!0},filled:{background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",border:"none",shadowDefault:"none",dividers:!0},elevated:{background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-default)",shadowDefault:"md",dividers:!0},combo:{background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",border:"none",shadowDefault:"none",dividers:!1}},yr=["transform 80ms var(--lucent-easing-default)","box-shadow var(--lucent-duration-fast) var(--lucent-easing-default)","border-color var(--lucent-duration-fast) var(--lucent-easing-default)","background var(--lucent-duration-fast) var(--lucent-easing-default)"].join(", "),xr="0 4px 14px -2px var(--lucent-accent-subtle)",wr="0 4px 14px -2px color-mix(in srgb, var(--lucent-text-primary) 12%, transparent)",kr="0 0 0 3px var(--lucent-accent-subtle)",Sr="0 0 0 2px var(--lucent-surface), 0 0 0 4px var(--lucent-accent-default)",Tr="0 0 0 3px var(--lucent-accent-subtle)";function ee(...t){const r=t.filter(n=>n!=null&&n!=="none"&&n!=="var(--lucent-shadow-none)");return r.length>0?r.join(", "):void 0}function Cr(t,r,n){return!r||n?t.background:"var(--lucent-accent-subtle)"}function Ir({variant:t="outline",header:r,footer:n,children:o,padding:a="md",shadow:i,radius:s="lg",style:l,onClick:d,href:p,target:m,rel:f,disabled:w,status:y,selected:g,hoverable:h,media:u}){const b=br[t],x=t==="combo",k=i??(x?"md":b.shadowDefault),{py:S,px:C}=mr[a],v=`${S} ${C}`,z=gr[s],T=p!=null,E=d!=null||T,F=E||(h??!1),R=(w??!1)&&E,j=T?"a":E?"button":"div",[P,D]=c.useState(!1),[W,q]=c.useState(!1),[N,H]=c.useState(!1),A=(g??!1)&&!R,B=A?Tr:void 0,M=Cr(b,g??!1,R),$=y!=null?`inset 3px 0 0 ${vr[y]}`:void 0;let V;x?V=ee(B,$):F&&!R?N?V=ee(Sr,B,$):W?V=ee(kr,B,$):P?V=ee(E?xr:wr,ue[k],B,$):V=ee(ue[k],B,$):V=ee(ue[k],B,$);const L={display:"flex",flexDirection:"column",background:M,border:b.border,borderRadius:z,overflow:u!=null&&!(A||F&&W)?"hidden":"visible",boxSizing:"border-box",position:"relative",...V!==void 0&&{boxShadow:V},...F&&!R&&N&&{transform:"translateY(1px)"},...F&&!R&&P&&!N&&{transform:"translateY(-1px)"},...F&&{cursor:R?"not-allowed":"pointer",transition:yr},...E&&!T&&{padding:0,font:"inherit",textAlign:"inherit",width:"100%",background:M},...T&&{textDecoration:"none",color:"inherit"},...R&&{opacity:.6,pointerEvents:"none"},...l},G=F&&!R?{onMouseEnter:()=>D(!0),onMouseLeave:()=>{D(!1),H(!1)},onMouseDown:()=>H(!0),onMouseUp:()=>H(!1),onFocus:()=>q(!0),onBlur:()=>{q(!1),H(!1)}}:{};return e.jsxs(j,{style:L,...G,...T&&{href:R?void 0:p,...m!==void 0&&{target:m},...f!==void 0&&{rel:f}},...!T&&E&&{type:"button",...R&&{disabled:!0}},...d!==void 0&&!R&&{onClick:d},...E&&g!==void 0&&{"aria-pressed":g},...T&&R&&{"aria-disabled":!0},children:[u!=null&&e.jsx("div",{style:{lineHeight:0,overflow:"hidden",borderRadius:`${z} ${z} 0 0`},children:u}),r!=null&&e.jsx("div",{style:{padding:v,...b.dividers?{borderBottom:"1px solid var(--lucent-border-default)"}:{}},children:r}),e.jsx(ye.Provider,{value:{px:C,py:S},children:e.jsx("div",{style:{padding:v,flex:1,...x?{background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-default)",borderRadius:z,boxShadow:ue[k],marginLeft:`calc(${C} / 3)`,marginRight:`calc(${C} / 3)`}:{}},children:o})}),n!=null&&e.jsx("div",{style:{padding:v,...b.dividers?{borderTop:"1px solid var(--lucent-border-default)"}:{}},children:n})]})}function jr({children:t,style:r}){const{px:n}=c.useContext(ye);return e.jsx("div",{style:{marginLeft:`calc(-1 * ${n})`,marginRight:`calc(-1 * ${n})`,paddingLeft:n,paddingRight:n,...r},children:t})}const Mr={id:"card",name:"Card",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"A surface container with five elevation variants that form a visual importance hierarchy. Supports optional header, body, and footer slots with configurable padding, shadow, and radius. Includes a CardBleed sub-component for edge-to-edge content.",designIntent:'Card provides a configurable surface with an explicit elevation hierarchy. Each variant maps to a distinct level of visual prominence, giving consumers a single prop to express how much attention a surface should command relative to its surroundings.\n\n## Elevation hierarchy (lowest → highest)\n\n1. **ghost** — transparent background, no border, no shadow.\n The card is invisible as a container — content floats directly against the page or parent surface. Use for logical groupings that shouldn\'t compete visually: sidebar sections, form regions, or layout slots where structure exists conceptually but not visually. Header/footer dividers still render if those slots are used, providing minimal internal structure.\n\n2. **outline** (default) — transparent background with `border-default` border, no shadow.\n Like ghost, the card inherits its container\'s background — the border alone defines the boundary. This is the workhorse variant for lists of items, form sections, data panels, and any content that needs a visible container without drawing excessive attention. Header and footer slots are separated from the body by matching `border-default` dividers.\n\n3. **filled** — semi-transparent tinted background, no border, no shadow.\n Differentiates the card from its surroundings by darkening (light mode) or lightening (dark mode) whatever surface it sits on. Uses `color-mix(in srgb, textPrimary 6%, transparent)` so the tint is always relative to the container — never a fixed color. Use for secondary content areas, inset panels, summary blocks, or anywhere a border would feel heavy but the card needs to be visually distinct. Effective for nesting (e.g., a filled card inside an elevated card creates a recessed region).\n\n4. **elevated** — `surface` background with medium shadow, no border.\n The card lifts off the page through depth. The shadow creates a physical metaphor: this content sits above the surface it rests on. Use for primary content areas, feature highlights, pricing cards, or any surface that should feel physically elevated. The lack of border keeps the silhouette soft — the shadow alone defines the boundary.\n\n5. **combo** — transparent wrapper with an elevated body inset.\n The header and footer are flat — they blend into the page background as if they were part of it. Only the body section is elevated: it gets `surface` background, border-radius, and shadow, appearing as a raised card sitting between the flat header/footer regions. This draws the eye to the primary content while keeping supporting info (header) and actions (footer) visually subordinate — they frame the elevated body without competing with it. No dividers are rendered — the elevation change IS the visual separator. Use for detail panels, profile cards, or settings forms where the body content is the focal point.\n\n## Shadow override\nThe `shadow` prop overrides whatever shadow the variant implies. This allows fine-tuning without changing the variant. For example, `variant="elevated" shadow="lg"` gives an elevated card with extra depth, while `variant="outline" shadow="none"` gives a flat bordered card.\n\n## Token rules\n- `elevated` and `combo` body use `surface` (the primary component surface — white in light mode).\n- `filled` uses a semi-transparent tint of `textPrimary` — contextually darker/lighter than its container.\n- `combo` wrapper is transparent (header/footer blend with page); only the body is elevated with `surface`.\n- `ghost` and `outline` use `transparent` — they inherit from whatever they\'re placed on. The border is the only visual differentiator for `outline`.\n- Never use `bgBase` or `bgSubtle` on a Card — those tokens are reserved for the page canvas.\n- Content nested inside a Card that needs a tinted fill should use `color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)` for accent-neutral insets.',props:[{name:"variant",type:"enum",required:!1,default:"outline",description:"The elevation variant. Controls background color, border, and default shadow. Ordered from lowest to highest visual prominence: ghost → outline → filled → elevated → combo.",enumValues:["ghost","outline","filled","elevated","combo"]},{name:"children",type:"ReactNode",required:!0,description:"The card body content."},{name:"header",type:"ReactNode",required:!1,description:"Content rendered in the header slot. Separated from the body by a divider in all variants except combo, where the background-color change provides the separation."},{name:"footer",type:"ReactNode",required:!1,description:"Content rendered in the footer slot. Separated from the body by a divider in all variants except combo, where the background-color change provides the separation."},{name:"padding",type:"enum",required:!1,default:"md",description:"Inner padding applied equally to header, body, and footer.",enumValues:["none","sm","md","lg"]},{name:"shadow",type:"enum",required:!1,description:"Box shadow elevation. When omitted, uses the variant's default: ghost=none, outline=none, filled=none, elevated=md, combo=md. When set explicitly, overrides the variant's default.",enumValues:["none","sm","md","lg"]},{name:"radius",type:"enum",required:!1,default:"lg",description:"Border radius of the card.",enumValues:["none","sm","md","lg"]},{name:"style",type:"object",required:!1,description:"Inline style overrides for the card wrapper."},{name:"onClick",type:"function",required:!1,description:"Click handler. When provided, the card renders as a <button> with hover lift, focus ring, and active press states matching the Button component."},{name:"href",type:"string",required:!1,description:"Link URL. When provided, the card renders as an <a>. Takes precedence over onClick for the element type, but onClick is still attached as a handler."},{name:"target",type:"string",required:!1,description:'Passed to <a> when href is set (e.g. "_blank").'},{name:"rel",type:"string",required:!1,description:'Passed to <a> when href is set (e.g. "noopener noreferrer").'},{name:"disabled",type:"boolean",required:!1,description:"Disables interactive behavior. Reduces opacity, removes hover/focus/active states, and sets cursor to not-allowed. Only applies when onClick or href is set."},{name:"status",type:"enum",required:!1,description:"Adds a 3px colored inset box-shadow on the left edge of the card. Rendered as an inset shadow (same technique as NavMenu inverse highlight) so it naturally follows the card's border-radius. Uses the corresponding status token (successDefault, warningDefault, dangerDefault, infoDefault). Works with all variants.",enumValues:["success","warning","danger","info"]},{name:"selected",type:"boolean",required:!1,description:"Adds an inset accent ring and subtle background tint to indicate selection. Used for card grids where cards act as radio/checkbox options. Pairs with onClick for toggle behavior. Sets aria-pressed on interactive cards. Disabled takes precedence — ring is hidden when disabled."},{name:"hoverable",type:"boolean",required:!1,description:"Enables hover lift (translateY -1px) and neutral glow shadow without making the card a button or link. Use when the card contains its own interactive content (e.g. a Collapsible trigger) and the whole card surface should hint at interactivity. Interactive cards (onClick/href) get accent-colored hover glow; hoverable-only cards get a neutral glow (12% text-primary)."},{name:"media",type:"ReactNode",required:!1,description:"Full-bleed content rendered at the top of the card (before header). No padding is applied. Use for hero images, illustrations, or any edge-to-edge top content. The media slot self-clips to the card's top border-radius. Cards without media default to overflow:visible so nested child shadows (e.g. an elevated Card inside a Collapsible) are never cut off."}],usageExamples:[{title:"Ghost — invisible container",code:`<Card variant="ghost">
236
+ />`},{title:"Filter mode (no dropdown)",code:'<SearchInput mode="filter" value={filter} onChange={setFilter} placeholder="Filter items…" />'},{title:"Loading state",code:"<SearchInput value={query} onChange={setQuery} isLoading={isFetching} results={[]} />"}],compositionGraph:[{componentId:"input",componentName:"Input",role:"Search text field with icon slots",required:!0},{componentId:"spinner",componentName:"Spinner",role:"Loading indicator in the right slot",required:!1}],accessibility:{role:"combobox",ariaAttributes:["aria-expanded","aria-haspopup","aria-label"],keyboardInteractions:["Enter to select focused result","Escape to close dropdown"],notes:'The results list uses role="listbox" with role="option" items. For full keyboard navigation (arrow keys to move between results), wire up onKeyDown on the Input and manage an activeIndex state.'}},ye=c.createContext({px:"0",py:"0"}),mr={none:{py:"0",px:"0"},sm:{py:"var(--lucent-space-2)",px:"var(--lucent-space-3)"},md:{py:"var(--lucent-space-4)",px:"var(--lucent-space-5)"},lg:{py:"var(--lucent-space-6)",px:"var(--lucent-space-8)"}},ue={none:"var(--lucent-shadow-none)",sm:"var(--lucent-shadow-sm)",md:"var(--lucent-shadow-md)",lg:"var(--lucent-shadow-lg)"},gr={none:"var(--lucent-radius-none)",sm:"var(--lucent-radius-sm)",md:"var(--lucent-radius-md)",lg:"var(--lucent-radius-lg)"},vr={success:"var(--lucent-success-default)",warning:"var(--lucent-warning-default)",danger:"var(--lucent-danger-default)",info:"var(--lucent-info-default)"},br={ghost:{background:"transparent",border:"none",shadowDefault:"none",dividers:!0},outline:{background:"transparent",border:"1px solid var(--lucent-border-default)",shadowDefault:"none",dividers:!0},filled:{background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",border:"none",shadowDefault:"none",dividers:!0},elevated:{background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-default)",shadowDefault:"md",dividers:!0},combo:{background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",border:"none",shadowDefault:"none",dividers:!1}},yr=["transform 80ms var(--lucent-easing-default)","box-shadow var(--lucent-duration-fast) var(--lucent-easing-default)","border-color var(--lucent-duration-fast) var(--lucent-easing-default)","background var(--lucent-duration-fast) var(--lucent-easing-default)"].join(", "),xr="0 4px 14px -2px var(--lucent-accent-subtle)",wr="0 4px 14px -2px color-mix(in srgb, var(--lucent-text-primary) 12%, transparent)",kr="0 0 0 3px var(--lucent-accent-subtle)",Sr="0 0 0 2px var(--lucent-surface), 0 0 0 4px var(--lucent-accent-default)",Tr="0 0 0 3px var(--lucent-accent-subtle)";function ee(...t){const r=t.filter(n=>n!=null&&n!=="none"&&n!=="var(--lucent-shadow-none)");return r.length>0?r.join(", "):void 0}function Cr(t,r,n){return!r||n?t.background:"var(--lucent-accent-subtle)"}function Ir({variant:t="outline",header:r,footer:n,children:o,padding:a="md",shadow:i,radius:s="lg",style:l,onClick:d,href:p,target:m,rel:f,disabled:w,status:y,selected:g,hoverable:h,media:u}){const b=br[t],x=t==="combo",k=i??(x?"md":b.shadowDefault),{py:S,px:C}=mr[a],v=`${S} ${C}`,z=gr[s],T=p!=null,E=d!=null||T,F=E||(h??!1),R=(w??!1)&&E,j=T?"a":E?"button":"div",[P,D]=c.useState(!1),[W,q]=c.useState(!1),[N,H]=c.useState(!1),A=(g??!1)&&!R,B=A?Tr:void 0,M=Cr(b,g??!1,R),$=y!=null?`inset 3px 0 0 ${vr[y]}`:void 0;let V;x?V=ee(B,$):F&&!R?N?V=ee(Sr,B,$):W?V=ee(kr,B,$):P?V=ee(E?xr:wr,ue[k],B,$):V=ee(ue[k],B,$):V=ee(ue[k],B,$);const L={display:"flex",flexDirection:"column",background:M,border:b.border,borderRadius:z,overflow:u!=null&&!(A||F&&W)?"hidden":"visible",boxSizing:"border-box",position:"relative",...V!==void 0&&{boxShadow:V},...F&&!R&&N&&{transform:"translateY(1px)"},...F&&!R&&P&&!N&&{transform:"translateY(-1px)"},...F&&{cursor:R?"not-allowed":"pointer",transition:yr},...E&&!T&&{padding:0,font:"inherit",textAlign:"inherit",width:"100%",background:M},...T&&{textDecoration:"none",color:"inherit"},...R&&{opacity:.6,pointerEvents:"none"},...l},G=F&&!R?{onMouseEnter:()=>D(!0),onMouseLeave:()=>{D(!1),H(!1)},onMouseDown:()=>H(!0),onMouseUp:()=>H(!1),onFocus:()=>q(!0),onBlur:()=>{q(!1),H(!1)}}:{};return e.jsxs(j,{style:L,...G,...T&&{href:R?void 0:p,...m!==void 0&&{target:m},...f!==void 0&&{rel:f}},...!T&&E&&{type:"button",...R&&{disabled:!0}},...d!==void 0&&!R&&{onClick:d},...E&&g!==void 0&&{"aria-pressed":g},...T&&R&&{"aria-disabled":!0},children:[u!=null&&e.jsx("div",{style:{lineHeight:0,overflow:"hidden",borderRadius:`${z} ${z} 0 0`},children:u}),r!=null&&e.jsx("div",{style:{padding:v,...b.dividers?{borderBottom:"1px solid var(--lucent-border-default)"}:{}},children:r}),e.jsx(ye.Provider,{value:{px:C,py:S},children:e.jsx("div",{style:{padding:v,flex:1,...x?{background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-default)",borderRadius:z,boxShadow:ue[k],marginLeft:`calc(${C} / 3)`,marginRight:`calc(${C} / 3)`}:{}},children:o})}),n!=null&&e.jsx("div",{style:{padding:v,...b.dividers?{borderTop:"1px solid var(--lucent-border-default)"}:{}},children:n})]})}function jr({children:t,style:r}){const{px:n}=c.useContext(ye);return e.jsx("div",{style:{marginLeft:`calc(-1 * ${n})`,marginRight:`calc(-1 * ${n})`,paddingLeft:n,paddingRight:n,...r},children:t})}const Mr={id:"card",name:"Card",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"A surface container with five elevation variants that form a visual importance hierarchy. Supports optional header, body, and footer slots with configurable padding, shadow, and radius. Includes a CardBleed sub-component for edge-to-edge content.",designIntent:'Card provides a configurable surface with an explicit elevation hierarchy. Each variant maps to a distinct level of visual prominence, giving consumers a single prop to express how much attention a surface should command relative to its surroundings.\n\n## Elevation hierarchy (lowest → highest)\n\n1. **ghost** — transparent background, no border, no shadow.\n The card is invisible as a container — content floats directly against the page or parent surface. Use for logical groupings that shouldn\'t compete visually: sidebar sections, form regions, or layout slots where structure exists conceptually but not visually. Header/footer dividers still render if those slots are used, providing minimal internal structure.\n\n2. **outline** (default) — transparent background with `border-default` border, no shadow.\n Like ghost, the card inherits its container\'s background — the border alone defines the boundary. This is the workhorse variant for lists of items, form sections, data panels, and any content that needs a visible container without drawing excessive attention. Header and footer slots are separated from the body by matching `border-default` dividers.\n\n3. **filled** — semi-transparent tinted background, no border, no shadow.\n Differentiates the card from its surroundings by darkening (light mode) or lightening (dark mode) whatever surface it sits on. Uses `color-mix(in srgb, textPrimary 6%, transparent)` so the tint is always relative to the container — never a fixed color. Use for secondary content areas, inset panels, summary blocks, or anywhere a border would feel heavy but the card needs to be visually distinct. Effective for nesting (e.g., a filled card inside an elevated card creates a recessed region).\n\n4. **elevated** — `surface` background with medium shadow, no border.\n The card lifts off the page through depth. The shadow creates a physical metaphor: this content sits above the surface it rests on. Use for primary content areas, feature highlights, pricing cards, or any surface that should feel physically elevated. The lack of border keeps the silhouette soft — the shadow alone defines the boundary.\n\n5. **combo** — transparent wrapper with an elevated body inset.\n The header and footer are flat — they blend into the page background as if they were part of it. Only the body section is elevated: it gets `surface` background, border-radius, and shadow, appearing as a raised card sitting between the flat header/footer regions. This draws the eye to the primary content while keeping supporting info (header) and actions (footer) visually subordinate — they frame the elevated body without competing with it. No dividers are rendered — the elevation change IS the visual separator. Use for detail panels, profile cards, or settings forms where the body content is the focal point.\n\n## Shadow override\nThe `shadow` prop overrides whatever shadow the variant implies. This allows fine-tuning without changing the variant. For example, `variant="elevated" shadow="lg"` gives an elevated card with extra depth, while `variant="outline" shadow="none"` gives a flat bordered card.\n\n## Token rules\n- `elevated` and `combo` body use `surface` (the primary component surface — white in light mode).\n- `filled` uses a semi-transparent tint of `textPrimary` — contextually darker/lighter than its container.\n- `combo` wrapper is transparent (header/footer blend with page); only the body is elevated with `surface`.\n- `ghost` and `outline` use `transparent` — they inherit from whatever they\'re placed on. The border is the only visual differentiator for `outline`.\n- Never use `navigation`, `bgBase`, or `bgSubtle` on a Card — those tokens are reserved for the page chrome and content area.\n- Content nested inside a Card that needs a tinted fill should use `color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)` for accent-neutral insets.',props:[{name:"variant",type:"enum",required:!1,default:"outline",description:"The elevation variant. Controls background color, border, and default shadow. Ordered from lowest to highest visual prominence: ghost → outline → filled → elevated → combo.",enumValues:["ghost","outline","filled","elevated","combo"]},{name:"children",type:"ReactNode",required:!0,description:"The card body content."},{name:"header",type:"ReactNode",required:!1,description:"Content rendered in the header slot. Separated from the body by a divider in all variants except combo, where the background-color change provides the separation."},{name:"footer",type:"ReactNode",required:!1,description:"Content rendered in the footer slot. Separated from the body by a divider in all variants except combo, where the background-color change provides the separation."},{name:"padding",type:"enum",required:!1,default:"md",description:"Inner padding applied equally to header, body, and footer.",enumValues:["none","sm","md","lg"]},{name:"shadow",type:"enum",required:!1,description:"Box shadow elevation. When omitted, uses the variant's default: ghost=none, outline=none, filled=none, elevated=md, combo=md. When set explicitly, overrides the variant's default.",enumValues:["none","sm","md","lg"]},{name:"radius",type:"enum",required:!1,default:"lg",description:"Border radius of the card.",enumValues:["none","sm","md","lg"]},{name:"style",type:"object",required:!1,description:"Inline style overrides for the card wrapper."},{name:"onClick",type:"function",required:!1,description:"Click handler. When provided, the card renders as a <button> with hover lift, focus ring, and active press states matching the Button component."},{name:"href",type:"string",required:!1,description:"Link URL. When provided, the card renders as an <a>. Takes precedence over onClick for the element type, but onClick is still attached as a handler."},{name:"target",type:"string",required:!1,description:'Passed to <a> when href is set (e.g. "_blank").'},{name:"rel",type:"string",required:!1,description:'Passed to <a> when href is set (e.g. "noopener noreferrer").'},{name:"disabled",type:"boolean",required:!1,description:"Disables interactive behavior. Reduces opacity, removes hover/focus/active states, and sets cursor to not-allowed. Only applies when onClick or href is set."},{name:"status",type:"enum",required:!1,description:"Adds a 3px colored inset box-shadow on the left edge of the card. Rendered as an inset shadow (same technique as NavMenu inverse highlight) so it naturally follows the card's border-radius. Uses the corresponding status token (successDefault, warningDefault, dangerDefault, infoDefault). Works with all variants.",enumValues:["success","warning","danger","info"]},{name:"selected",type:"boolean",required:!1,description:"Adds an inset accent ring and subtle background tint to indicate selection. Used for card grids where cards act as radio/checkbox options. Pairs with onClick for toggle behavior. Sets aria-pressed on interactive cards. Disabled takes precedence — ring is hidden when disabled."},{name:"hoverable",type:"boolean",required:!1,description:"Enables hover lift (translateY -1px) and neutral glow shadow without making the card a button or link. Use when the card contains its own interactive content (e.g. a Collapsible trigger) and the whole card surface should hint at interactivity. Interactive cards (onClick/href) get accent-colored hover glow; hoverable-only cards get a neutral glow (12% text-primary)."},{name:"media",type:"ReactNode",required:!1,description:"Full-bleed content rendered at the top of the card (before header). No padding is applied. Use for hero images, illustrations, or any edge-to-edge top content. The media slot self-clips to the card's top border-radius. Cards without media default to overflow:visible so nested child shadows (e.g. an elevated Card inside a Collapsible) are never cut off."}],usageExamples:[{title:"Ghost — invisible container",code:`<Card variant="ghost">
237
237
  <Text>Content sits directly on the page background.</Text>
238
238
  </Card>`},{title:"Outline — bordered card (default)",code:`<Card
239
239
  header={<Text weight="semibold">Card title</Text>}
@@ -325,7 +325,7 @@ const [results, setResults] = useState([]);
325
325
  [data-lucent-collapsible-trigger]:focus-visible {
326
326
  box-shadow: 0 0 0 2px var(--lucent-surface), 0 0 0 4px var(--lucent-accent-default) !important;
327
327
  }
328
- `;let _e=!1;function Ur(){if(_e||typeof document>"u")return;const t=document.createElement("style");t.setAttribute("data-lucent-collapsible",""),t.textContent=Gr,document.head.appendChild(t),_e=!0}function _r({trigger:t,children:r,defaultOpen:n=!1,open:o,onOpenChange:a,padded:i=!0,disabled:s=!1,style:l}){const d=c.useContext(ye),p=d.px!=="0"||d.py!=="0",m=o!==void 0,[f,w]=c.useState(n),y=m?o:f,g=c.useRef(null),h=c.useRef(!1),u=c.useRef();c.useEffect(Ur,[]),c.useEffect(()=>{h.current=!0},[]),c.useLayoutEffect(()=>{const x=g.current;if(x)if(y){x.style.overflow="hidden";const k=x.scrollHeight;x.style.height=`${k}px`,x.style.transition=Ue,clearTimeout(u.current),u.current=setTimeout(()=>{x.style.height="auto",x.style.overflow="visible",x.style.transition=""},dt+20)}else h.current&&(clearTimeout(u.current),x.style.overflow="hidden",x.style.transition="",x.style.height=`${x.scrollHeight}px`,x.getBoundingClientRect(),x.style.transition=Ue,x.style.height="0px")},[y]),c.useEffect(()=>{const x=g.current;x&&!y&&!h.current&&(x.style.height="0px")},[]),c.useEffect(()=>()=>clearTimeout(u.current),[]);const b=()=>{if(s)return;const x=!y;m||w(x),a==null||a(x)};return e.jsxs("div",{style:{display:"flex",flexDirection:"column",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-md)",...p&&{margin:`calc(-1 * ${d.py}) calc(-1 * ${d.px})`},...l},children:[e.jsxs("button",{"data-lucent-collapsible-trigger":!0,onClick:b,disabled:s,"aria-expanded":y,style:{display:"flex",alignItems:"center",justifyContent:"space-between",width:"100%",background:"none",border:"none",borderRadius:"var(--lucent-radius-md)",padding:"var(--lucent-space-4)",cursor:s?"not-allowed":"pointer",textAlign:"left",outline:"none",color:"inherit",fontFamily:"inherit",fontSize:"inherit",opacity:s?.5:1,transition:`background ${ze} ${te}`},children:[e.jsx("span",{style:{flex:1},children:t}),e.jsx(Yr,{open:y})]}),e.jsx("div",{ref:g,"aria-hidden":!y,style:{overflow:y?"visible":"hidden"},children:e.jsx("div",{style:{...i?{padding:"var(--lucent-space-3) var(--lucent-space-4) var(--lucent-space-4)"}:{},opacity:y?1:0,transform:y?"translateY(0)":"translateY(-4px)",transition:`opacity ${Ge}ms ${te}, transform ${Ge}ms ${te}`},children:r})})]})}function Yr({open:t}){return e.jsx("svg",{"data-lucent-collapsible-chevron":!0,width:16,height:16,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,style:{flexShrink:0,color:"var(--lucent-text-secondary)",transform:t?"rotate(180deg)":"rotate(0deg)",transition:`transform ${ze} ${te}, color ${ze} ${te}`},children:e.jsx("polyline",{points:"6 9 12 15 18 9"})})}const oe="lucent-pl-no-scrollbar",Kr=`.${oe}{scrollbar-width:none}.${oe}::-webkit-scrollbar{display:none}`;function pe(t){return typeof t=="number"?`${t}px`:t}function Xr({children:t,header:r,sidebar:n,sidebarWidth:o=240,headerHeight:a=48,sidebarCollapsed:i=!1,rightSidebar:s,rightSidebarWidth:l=240,rightSidebarCollapsed:d=!1,footer:p,footerHeight:m=28,chromeBackground:f="bgBase",mainStyle:w,style:y}){const g=pe(a),h=pe(o),u=pe(l),b=pe(m),k={bgBase:"var(--lucent-bg-base)",bgSubtle:"var(--lucent-bg-subtle)",surface:"var(--lucent-surface)",surfaceSecondary:"var(--lucent-surface-secondary)"}[f]??"var(--lucent-bg-base)";return e.jsxs("div",{style:{display:"flex",flexDirection:"column",height:"100vh",overflow:"hidden",background:k,fontFamily:"var(--lucent-font-family-base)",...y},children:[e.jsx("style",{children:Kr}),r!=null&&e.jsx("div",{style:{flexShrink:0,height:g,zIndex:10,background:k},children:r}),e.jsxs("div",{style:{display:"flex",flex:1,overflow:"hidden"},children:[n!=null&&e.jsx("div",{className:oe,style:{width:i?0:h,flexShrink:0,overflow:"hidden",overflowY:i?"hidden":"auto",background:k,transition:"width 200ms var(--lucent-easing-default)"},children:n}),e.jsx("main",{className:oe,style:{flex:1,overflowY:"auto",minWidth:0,margin:s!=null?"0 0 var(--lucent-space-3) 0":"0 var(--lucent-space-3) var(--lucent-space-3) 0",border:"1px solid var(--lucent-border-default)",borderRadius:"var(--lucent-radius-lg)",boxShadow:"var(--lucent-shadow-sm)",background:"var(--lucent-surface)",...w},children:t}),s!=null&&e.jsx("aside",{className:oe,style:{width:d?0:u,flexShrink:0,overflow:"hidden",overflowY:d?"hidden":"auto",background:k,transition:"width 200ms var(--lucent-easing-default)"},children:s})]}),p!=null&&e.jsx("div",{style:{flexShrink:0,height:b,zIndex:10,background:k},children:p})]})}function Jr({state:t}){return e.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 12 12",fill:"none","aria-hidden":!0,style:{flexShrink:0,opacity:t==="none"?.35:1},children:[e.jsx("path",{d:"M6 2L9 5H3L6 2Z",fill:"currentColor",opacity:t==="desc"?.35:1}),e.jsx("path",{d:"M6 10L3 7H9L6 10Z",fill:"currentColor",opacity:t==="asc"?.35:1})]})}function Ye({dir:t}){return e.jsx("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":!0,children:e.jsx("path",{d:t==="left"?"M10 12L6 8l4-4":"M6 4l4 4-4 4",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})}function Zr({columns:t,rows:r,pageSize:n=10,page:o,onPageChange:a,onFilterChange:i,emptyState:s,style:l}){const[d,p]=c.useState(null),[m,f]=c.useState(0),[w,y]=c.useState(null),[g,h]=c.useState({}),u=o!==void 0,b=u?o:m,x=t.some(j=>j.filterable),k=x?r.filter(j=>t.every(P=>{if(!P.filterable)return!0;const D=g[P.key];if(!D||D.length===0)return!0;const W=String(j[P.key]??"");return D.includes(W)})):r,S=d?[...k].sort((j,P)=>{const D=j[d.key],W=P[d.key],q=String(D??"").localeCompare(String(W??""),void 0,{numeric:!0});return d.dir==="asc"?q:-q}):k,C=n>0?S.slice(b*n,(b+1)*n):S,v=n>0?Math.max(1,Math.ceil(S.length/n)):1,z=j=>{u||f(j),a==null||a(j)},T=j=>{p(P=>!P||P.key!==j?{key:j,dir:"asc"}:P.dir==="asc"?{key:j,dir:"desc"}:null),u||f(0),a==null||a(0)},E=(j,P)=>{const D={...g,[j]:P};P.length===0&&delete D[j],h(D),u||f(0),a==null||a(0),i==null||i(D)},F=()=>{h({}),u||f(0),a==null||a(0),i==null||i({})},R=[];if(v<=7)for(let j=0;j<v;j++)R.push(j);else{R.push(0),b>2&&R.push("…");for(let j=Math.max(1,b-1);j<=Math.min(v-2,b+1);j++)R.push(j);b<v-3&&R.push("…"),R.push(v-1)}return e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-3)",...l},children:[x&&e.jsx("div",{style:{position:"relative",zIndex:1},children:e.jsx(Qr,{columns:t,rows:r,filters:g,onFilter:E,onClearAll:F})}),e.jsx("div",{style:{overflowX:"auto",borderRadius:"var(--lucent-radius-lg)",border:"1px solid var(--lucent-border-default)"},children:e.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-sm)"},children:[e.jsx("thead",{children:e.jsx("tr",{style:{borderBottom:"1px solid var(--lucent-border-default)"},children:t.map(j=>{const P=(d==null?void 0:d.key)===j.key?d.dir:"none";return e.jsx("th",{onClick:j.sortable?()=>T(j.key):void 0,style:{padding:"var(--lucent-space-3) var(--lucent-space-4)",textAlign:j.align??"left",fontWeight:"var(--lucent-font-weight-medium)",color:"var(--lucent-text-secondary)",background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",borderBottom:"1px solid var(--lucent-border-default)",cursor:j.sortable?"pointer":"default",userSelect:"none",whiteSpace:"nowrap",...j.width?{width:j.width}:{}},children:e.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:"var(--lucent-space-1)"},children:[j.header,j.sortable&&e.jsx(Jr,{state:P}),j.headerFilter&&e.jsx("span",{onClick:D=>D.stopPropagation(),style:{display:"inline-flex",marginLeft:"var(--lucent-space-1)"},children:j.headerFilter})]})},j.key)})})}),e.jsx("tbody",{children:C.length===0?e.jsx("tr",{children:e.jsx("td",{colSpan:t.length,style:{padding:"var(--lucent-space-12)",textAlign:"center"},children:s??e.jsx(I.Text,{color:"secondary",children:"No data"})})}):C.map((j,P)=>e.jsx("tr",{onMouseEnter:()=>y(P),onMouseLeave:()=>y(null),style:{borderBottom:P<C.length-1?"1px solid var(--lucent-border-subtle)":"none",background:w===P?"color-mix(in srgb, var(--lucent-text-primary) 4%, transparent)":"transparent",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)"},children:t.map(D=>e.jsx("td",{style:{padding:"var(--lucent-space-3) var(--lucent-space-4)",color:"var(--lucent-text-primary)",textAlign:D.align??"left",verticalAlign:"middle"},children:D.render?D.render(j,P):String(j[D.key]??"")},D.key))},P))})]})}),n>0&&S.length>0&&e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:"var(--lucent-space-3)",flexWrap:"wrap"},children:[e.jsx(I.Text,{color:"secondary",size:"sm",children:S.length===0?`0 rows${r.length>0?` (filtered from ${r.length})`:""}`:S.length===1?`1 row${S.length<r.length?` (filtered from ${r.length})`:""}`:`${b*n+1}–${Math.min((b+1)*n,S.length)} of ${S.length} rows${S.length<r.length?` (filtered from ${r.length})`:""}`}),e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-1)"},children:[e.jsx(ke,{onClick:()=>z(b-1),disabled:b===0,"aria-label":"Previous page",children:e.jsx(Ye,{dir:"left"})}),R.map((j,P)=>j==="…"?e.jsx("span",{style:{padding:"0 var(--lucent-space-1)",color:"var(--lucent-text-disabled)",fontSize:"var(--lucent-font-size-sm)"},children:"…"},`ellipsis-${P}`):e.jsx(ke,{onClick:()=>z(j),active:j===b,"aria-label":`Page ${j+1}`,"aria-current":j===b?"page":void 0,children:j+1},j)),e.jsx(ke,{onClick:()=>z(b+1),disabled:b>=v-1,"aria-label":"Next page",children:e.jsx(Ye,{dir:"right"})})]})]})]})}function Qr({columns:t,rows:r,filters:n,onFilter:o,onClearAll:a}){const i=t.filter(l=>l.filterable),s=Object.keys(n).length;return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-2)",flexWrap:"wrap"},children:[i.map(l=>{const d=Array.from(new Set(r.map(p=>String(p[l.key]??"")))).sort();return e.jsx(ea,{label:l.header,values:d,value:n[l.key]??[],onChange:p=>o(l.key,p)},l.key)}),s>0&&e.jsx("button",{onClick:a,style:{display:"inline-flex",alignItems:"center",height:30,padding:"0 var(--lucent-space-2)",border:"none",borderRadius:"var(--lucent-radius-md)",background:"transparent",color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",cursor:"pointer"},children:"Clear all"})]})}function ea({label:t,values:r,value:n,onChange:o}){const[a,i]=c.useState(!1),[s,l]=c.useState(!1),[d,p]=c.useState(""),m=c.useRef(null),f=c.useRef(null),w=n.length>0;c.useEffect(()=>{if(!a){p("");return}setTimeout(()=>{var x;return(x=f.current)==null?void 0:x.focus()},0);const u=x=>{m.current&&!m.current.contains(x.target)&&i(!1)},b=x=>{x.key==="Escape"&&i(!1)};return document.addEventListener("mousedown",u),document.addEventListener("keydown",b),()=>{document.removeEventListener("mousedown",u),document.removeEventListener("keydown",b)}},[a]);const y=d?r.filter(u=>u.toLowerCase().includes(d.toLowerCase())):r,g=u=>o(n.includes(u)?n.filter(b=>b!==u):[...n,u]),h=n.length===0?null:n.length===1?e.jsxs("span",{style:{color:"var(--lucent-text-secondary)",fontWeight:"var(--lucent-font-weight-regular)"},children:[": ",n[0]]}):e.jsxs("span",{style:{color:"var(--lucent-accent-default)"},children:["(",n.length,")"]});return e.jsxs("div",{ref:m,style:{position:"relative"},children:[e.jsxs("button",{onClick:()=>i(u=>!u),onMouseEnter:()=>l(!0),onMouseLeave:()=>l(!1),style:{display:"inline-flex",alignItems:"center",gap:"var(--lucent-space-1)",height:30,padding:"0 var(--lucent-space-3)",borderRadius:"var(--lucent-radius-md)",border:`1px solid ${w?"var(--lucent-accent-default)":s?"var(--lucent-border-strong)":"var(--lucent-border-default)"}`,background:w?"var(--lucent-accent-subtle)":"var(--lucent-surface)",color:w?"var(--lucent-accent-default)":"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:w?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",cursor:"pointer",outline:"none",whiteSpace:"nowrap",transition:"border-color var(--lucent-duration-fast) var(--lucent-easing-default), background var(--lucent-duration-fast) var(--lucent-easing-default)"},children:[t,h,e.jsx("svg",{width:"10",height:"10",viewBox:"0 0 10 10",fill:"none","aria-hidden":!0,style:{transform:a?"rotate(180deg)":"none",transition:"transform var(--lucent-duration-fast) var(--lucent-easing-default)"},children:e.jsx("path",{d:"M2 3.5L5 6.5L8 3.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})]}),a&&e.jsxs("div",{style:{position:"absolute",top:"calc(100% + 4px)",left:0,minWidth:180,maxHeight:280,display:"flex",flexDirection:"column",background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-default)",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 4px 16px color-mix(in srgb, var(--lucent-text-primary) 8%, transparent)",zIndex:50},children:[e.jsx("div",{style:{padding:"var(--lucent-space-2)",paddingBottom:0},children:e.jsx("input",{ref:f,type:"text",value:d,onChange:u=>p(u.target.value),placeholder:"Search…",style:{width:"100%",boxSizing:"border-box",height:26,padding:"0 var(--lucent-space-2)",borderRadius:"var(--lucent-radius-md)",border:"1px solid var(--lucent-border-default)",background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",color:"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",outline:"none"}})}),n.length>0&&e.jsx("div",{style:{padding:"var(--lucent-space-1) var(--lucent-space-2) 0"},children:e.jsx("button",{onClick:()=>o([]),style:{display:"inline-flex",padding:"2px var(--lucent-space-2)",border:"none",borderRadius:"var(--lucent-radius-sm)",background:"transparent",color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",cursor:"pointer",textDecoration:"underline"},children:"Clear selection"})}),e.jsx("div",{style:{overflowY:"auto",padding:"var(--lucent-space-1)",borderTop:"1px solid var(--lucent-border-subtle)",marginTop:"var(--lucent-space-2)"},children:y.length===0?e.jsx("div",{style:{padding:"var(--lucent-space-3)",color:"var(--lucent-text-disabled)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",textAlign:"center"},children:"No results"}):y.map(u=>e.jsx(ta,{label:u,isSelected:n.includes(u),onClick:()=>g(u)},u))})]})]})}function ta({label:t,isSelected:r,onClick:n}){const[o,a]=c.useState(!1);return e.jsxs("button",{onClick:n,onMouseEnter:()=>a(!0),onMouseLeave:()=>a(!1),style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-2)",width:"100%",textAlign:"left",padding:"var(--lucent-space-2) var(--lucent-space-3)",borderRadius:"var(--lucent-radius-md)",border:"none",background:o?"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)":"transparent",color:"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:r?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",cursor:"pointer",outline:"none",whiteSpace:"nowrap"},children:[e.jsx("span",{style:{flexShrink:0,width:14,height:14,borderRadius:"var(--lucent-radius-sm)",border:`1.5px solid ${r?"var(--lucent-accent-default)":"var(--lucent-border-strong)"}`,background:r?"var(--lucent-accent-default)":"transparent",display:"flex",alignItems:"center",justifyContent:"center",transition:"border-color var(--lucent-duration-fast), background var(--lucent-duration-fast)"},children:r&&e.jsx("svg",{width:"8",height:"8",viewBox:"0 0 8 8",fill:"none","aria-hidden":!0,children:e.jsx("path",{d:"M1 4L3 6L7 2",stroke:"var(--lucent-accent-fg)",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})}),t]})}function ke({children:t,onClick:r,disabled:n,active:o,...a}){const[i,s]=c.useState(!1);return e.jsx("button",{...a,onClick:r,disabled:n,onMouseEnter:()=>s(!0),onMouseLeave:()=>s(!1),style:{display:"inline-flex",alignItems:"center",justifyContent:"center",minWidth:32,height:32,padding:"0 var(--lucent-space-2)",borderRadius:"var(--lucent-radius-md)",border:o?"1px solid var(--lucent-accent-default)":"1px solid transparent",background:o?"var(--lucent-accent-default)":i&&!n?"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)":"transparent",color:o?"var(--lucent-accent-fg)":n?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontSize:"var(--lucent-font-size-sm)",fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-regular)",cursor:n?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)"},children:t})}const na={id:"data-table",name:"DataTable",tier:"molecule",domain:"neutral",specVersion:"1.0",description:"A sortable, filterable, paginated data table with configurable columns, custom cell renderers, and keyboard-accessible pagination controls.",designIntent:'DataTable is generic over row type T so TypeScript consumers get full type safety on column keys and renderers. Sorting is client-side and composable — each column opts in via sortable:true; clicking a sorted column cycles asc → desc → unsorted. Filtering is per-column — each column opts in via filterable:true, which adds a dropdown button above the table. Each dropdown is searchable and multi-select: a search input filters the option list, and each option is a checkbox that toggles membership in the active set. Filtering uses set-membership: a row passes if its column value is included in the selected values array. A "Clear selection" link inside each dropdown clears that column; a "Clear all" button in the bar appears when any filter is active. Filter → sort → paginate is the fixed pipeline order; any filter change resets the page to 0. Pagination is either controlled (page prop + onPageChange) or uncontrolled (internal state). A pageSize of 0 disables pagination entirely, useful when the parent manages windowing. Row hover uses bg-subtle, not a border change, so the visual weight stays low for dense data views.',props:[{name:"columns",type:"array",required:!0,description:"Column definitions. Each column has a key, header, optional render function, optional sortable flag, optional filterable flag (renders a text filter input below the header), optional width, and optional text align."},{name:"rows",type:"array",required:!0,description:"Array of data objects to display. The generic type T is inferred from this prop."},{name:"pageSize",type:"number",required:!1,default:"10",description:"Number of rows per page. Set to 0 to disable pagination."},{name:"page",type:"number",required:!1,description:"Controlled current page (0-indexed). When provided, the component is fully controlled."},{name:"onPageChange",type:"function",required:!1,description:"Called with the new page index whenever the page changes (from pagination controls or after a sort/filter reset)."},{name:"onFilterChange",type:"function",required:!1,description:"Called with the current filter map (Record<string, string[]>) whenever any column filter changes. Keys are column keys; columns with no selection are omitted from the map."},{name:"emptyState",type:"ReactNode",required:!1,description:'Content to render when rows is empty. Defaults to a "No data" text.'},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer wrapper."}],usageExamples:[{title:"Basic sortable table",code:`<DataTable
328
+ `;let _e=!1;function Ur(){if(_e||typeof document>"u")return;const t=document.createElement("style");t.setAttribute("data-lucent-collapsible",""),t.textContent=Gr,document.head.appendChild(t),_e=!0}function _r({trigger:t,children:r,defaultOpen:n=!1,open:o,onOpenChange:a,padded:i=!0,disabled:s=!1,style:l}){const d=c.useContext(ye),p=d.px!=="0"||d.py!=="0",m=o!==void 0,[f,w]=c.useState(n),y=m?o:f,g=c.useRef(null),h=c.useRef(!1),u=c.useRef();c.useEffect(Ur,[]),c.useEffect(()=>{h.current=!0},[]),c.useLayoutEffect(()=>{const x=g.current;if(x)if(y){x.style.overflow="hidden";const k=x.scrollHeight;x.style.height=`${k}px`,x.style.transition=Ue,clearTimeout(u.current),u.current=setTimeout(()=>{x.style.height="auto",x.style.overflow="visible",x.style.transition=""},dt+20)}else h.current&&(clearTimeout(u.current),x.style.overflow="hidden",x.style.transition="",x.style.height=`${x.scrollHeight}px`,x.getBoundingClientRect(),x.style.transition=Ue,x.style.height="0px")},[y]),c.useEffect(()=>{const x=g.current;x&&!y&&!h.current&&(x.style.height="0px")},[]),c.useEffect(()=>()=>clearTimeout(u.current),[]);const b=()=>{if(s)return;const x=!y;m||w(x),a==null||a(x)};return e.jsxs("div",{style:{display:"flex",flexDirection:"column",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-md)",...p&&{margin:`calc(-1 * ${d.py}) calc(-1 * ${d.px})`},...l},children:[e.jsxs("button",{"data-lucent-collapsible-trigger":!0,onClick:b,disabled:s,"aria-expanded":y,style:{display:"flex",alignItems:"center",justifyContent:"space-between",width:"100%",background:"none",border:"none",borderRadius:"var(--lucent-radius-md)",padding:"var(--lucent-space-4)",cursor:s?"not-allowed":"pointer",textAlign:"left",outline:"none",color:"inherit",fontFamily:"inherit",fontSize:"inherit",opacity:s?.5:1,transition:`background ${ze} ${te}`},children:[e.jsx("span",{style:{flex:1},children:t}),e.jsx(Yr,{open:y})]}),e.jsx("div",{ref:g,"aria-hidden":!y,style:{overflow:y?"visible":"hidden"},children:e.jsx("div",{style:{...i?{padding:"var(--lucent-space-3) var(--lucent-space-4) var(--lucent-space-4)"}:{},opacity:y?1:0,transform:y?"translateY(0)":"translateY(-4px)",transition:`opacity ${Ge}ms ${te}, transform ${Ge}ms ${te}`},children:r})})]})}function Yr({open:t}){return e.jsx("svg",{"data-lucent-collapsible-chevron":!0,width:16,height:16,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,style:{flexShrink:0,color:"var(--lucent-text-secondary)",transform:t?"rotate(180deg)":"rotate(0deg)",transition:`transform ${ze} ${te}, color ${ze} ${te}`},children:e.jsx("polyline",{points:"6 9 12 15 18 9"})})}const oe="lucent-pl-no-scrollbar",Kr=`.${oe}{scrollbar-width:none}.${oe}::-webkit-scrollbar{display:none}`;function pe(t){return typeof t=="number"?`${t}px`:t}function Xr({children:t,header:r,sidebar:n,sidebarWidth:o=240,headerHeight:a=48,sidebarCollapsed:i=!1,rightSidebar:s,rightSidebarWidth:l=240,rightSidebarCollapsed:d=!1,footer:p,footerHeight:m=28,chromeBackground:f="navigation",mainStyle:w,style:y}){const g=pe(a),h=pe(o),u=pe(l),b=pe(m),k={navigation:"var(--lucent-navigation)",bgBase:"var(--lucent-bg-base)",bgSubtle:"var(--lucent-bg-subtle)",surface:"var(--lucent-surface)",surfaceSecondary:"var(--lucent-surface-secondary)"}[f]??"var(--lucent-navigation)";return e.jsxs("div",{style:{display:"flex",flexDirection:"column",height:"100vh",overflow:"hidden",background:k,fontFamily:"var(--lucent-font-family-base)",...y},children:[e.jsx("style",{children:Kr}),r!=null&&e.jsx("div",{style:{flexShrink:0,height:g,zIndex:10,background:k},children:r}),e.jsxs("div",{style:{display:"flex",flex:1,overflow:"hidden"},children:[n!=null&&e.jsx("div",{className:oe,style:{width:i?0:h,flexShrink:0,overflow:"hidden",overflowY:i?"hidden":"auto",background:k,transition:"width 200ms var(--lucent-easing-default)"},children:n}),e.jsx("main",{className:oe,style:{flex:1,overflowY:"auto",minWidth:0,margin:s!=null?"0 0 var(--lucent-space-3) 0":"0 var(--lucent-space-3) var(--lucent-space-3) 0",border:"1px solid var(--lucent-border-default)",borderRadius:"var(--lucent-radius-lg)",boxShadow:"var(--lucent-shadow-sm)",background:"var(--lucent-bg-base)",...w},children:t}),s!=null&&e.jsx("aside",{className:oe,style:{width:d?0:u,flexShrink:0,overflow:"hidden",overflowY:d?"hidden":"auto",background:k,transition:"width 200ms var(--lucent-easing-default)"},children:s})]}),p!=null&&e.jsx("div",{style:{flexShrink:0,height:b,zIndex:10,background:k},children:p})]})}function Jr({state:t}){return e.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 12 12",fill:"none","aria-hidden":!0,style:{flexShrink:0,opacity:t==="none"?.35:1},children:[e.jsx("path",{d:"M6 2L9 5H3L6 2Z",fill:"currentColor",opacity:t==="desc"?.35:1}),e.jsx("path",{d:"M6 10L3 7H9L6 10Z",fill:"currentColor",opacity:t==="asc"?.35:1})]})}function Ye({dir:t}){return e.jsx("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":!0,children:e.jsx("path",{d:t==="left"?"M10 12L6 8l4-4":"M6 4l4 4-4 4",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})}function Zr({columns:t,rows:r,pageSize:n=10,page:o,onPageChange:a,onFilterChange:i,emptyState:s,style:l}){const[d,p]=c.useState(null),[m,f]=c.useState(0),[w,y]=c.useState(null),[g,h]=c.useState({}),u=o!==void 0,b=u?o:m,x=t.some(j=>j.filterable),k=x?r.filter(j=>t.every(P=>{if(!P.filterable)return!0;const D=g[P.key];if(!D||D.length===0)return!0;const W=String(j[P.key]??"");return D.includes(W)})):r,S=d?[...k].sort((j,P)=>{const D=j[d.key],W=P[d.key],q=String(D??"").localeCompare(String(W??""),void 0,{numeric:!0});return d.dir==="asc"?q:-q}):k,C=n>0?S.slice(b*n,(b+1)*n):S,v=n>0?Math.max(1,Math.ceil(S.length/n)):1,z=j=>{u||f(j),a==null||a(j)},T=j=>{p(P=>!P||P.key!==j?{key:j,dir:"asc"}:P.dir==="asc"?{key:j,dir:"desc"}:null),u||f(0),a==null||a(0)},E=(j,P)=>{const D={...g,[j]:P};P.length===0&&delete D[j],h(D),u||f(0),a==null||a(0),i==null||i(D)},F=()=>{h({}),u||f(0),a==null||a(0),i==null||i({})},R=[];if(v<=7)for(let j=0;j<v;j++)R.push(j);else{R.push(0),b>2&&R.push("…");for(let j=Math.max(1,b-1);j<=Math.min(v-2,b+1);j++)R.push(j);b<v-3&&R.push("…"),R.push(v-1)}return e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-3)",...l},children:[x&&e.jsx("div",{style:{position:"relative",zIndex:1},children:e.jsx(Qr,{columns:t,rows:r,filters:g,onFilter:E,onClearAll:F})}),e.jsx("div",{style:{overflowX:"auto",borderRadius:"var(--lucent-radius-lg)",border:"1px solid var(--lucent-border-default)"},children:e.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-sm)"},children:[e.jsx("thead",{children:e.jsx("tr",{style:{borderBottom:"1px solid var(--lucent-border-default)"},children:t.map(j=>{const P=(d==null?void 0:d.key)===j.key?d.dir:"none";return e.jsx("th",{onClick:j.sortable?()=>T(j.key):void 0,style:{padding:"var(--lucent-space-3) var(--lucent-space-4)",textAlign:j.align??"left",fontWeight:"var(--lucent-font-weight-medium)",color:"var(--lucent-text-secondary)",background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",borderBottom:"1px solid var(--lucent-border-default)",cursor:j.sortable?"pointer":"default",userSelect:"none",whiteSpace:"nowrap",...j.width?{width:j.width}:{}},children:e.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:"var(--lucent-space-1)"},children:[j.header,j.sortable&&e.jsx(Jr,{state:P}),j.headerFilter&&e.jsx("span",{onClick:D=>D.stopPropagation(),style:{display:"inline-flex",marginLeft:"var(--lucent-space-1)"},children:j.headerFilter})]})},j.key)})})}),e.jsx("tbody",{children:C.length===0?e.jsx("tr",{children:e.jsx("td",{colSpan:t.length,style:{padding:"var(--lucent-space-12)",textAlign:"center"},children:s??e.jsx(I.Text,{color:"secondary",children:"No data"})})}):C.map((j,P)=>e.jsx("tr",{onMouseEnter:()=>y(P),onMouseLeave:()=>y(null),style:{borderBottom:P<C.length-1?"1px solid var(--lucent-border-subtle)":"none",background:w===P?"color-mix(in srgb, var(--lucent-text-primary) 4%, transparent)":"transparent",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)"},children:t.map(D=>e.jsx("td",{style:{padding:"var(--lucent-space-3) var(--lucent-space-4)",color:"var(--lucent-text-primary)",textAlign:D.align??"left",verticalAlign:"middle"},children:D.render?D.render(j,P):String(j[D.key]??"")},D.key))},P))})]})}),n>0&&S.length>0&&e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:"var(--lucent-space-3)",flexWrap:"wrap"},children:[e.jsx(I.Text,{color:"secondary",size:"sm",children:S.length===0?`0 rows${r.length>0?` (filtered from ${r.length})`:""}`:S.length===1?`1 row${S.length<r.length?` (filtered from ${r.length})`:""}`:`${b*n+1}–${Math.min((b+1)*n,S.length)} of ${S.length} rows${S.length<r.length?` (filtered from ${r.length})`:""}`}),e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-1)"},children:[e.jsx(ke,{onClick:()=>z(b-1),disabled:b===0,"aria-label":"Previous page",children:e.jsx(Ye,{dir:"left"})}),R.map((j,P)=>j==="…"?e.jsx("span",{style:{padding:"0 var(--lucent-space-1)",color:"var(--lucent-text-disabled)",fontSize:"var(--lucent-font-size-sm)"},children:"…"},`ellipsis-${P}`):e.jsx(ke,{onClick:()=>z(j),active:j===b,"aria-label":`Page ${j+1}`,"aria-current":j===b?"page":void 0,children:j+1},j)),e.jsx(ke,{onClick:()=>z(b+1),disabled:b>=v-1,"aria-label":"Next page",children:e.jsx(Ye,{dir:"right"})})]})]})]})}function Qr({columns:t,rows:r,filters:n,onFilter:o,onClearAll:a}){const i=t.filter(l=>l.filterable),s=Object.keys(n).length;return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-2)",flexWrap:"wrap"},children:[i.map(l=>{const d=Array.from(new Set(r.map(p=>String(p[l.key]??"")))).sort();return e.jsx(ea,{label:l.header,values:d,value:n[l.key]??[],onChange:p=>o(l.key,p)},l.key)}),s>0&&e.jsx("button",{onClick:a,style:{display:"inline-flex",alignItems:"center",height:30,padding:"0 var(--lucent-space-2)",border:"none",borderRadius:"var(--lucent-radius-md)",background:"transparent",color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",cursor:"pointer"},children:"Clear all"})]})}function ea({label:t,values:r,value:n,onChange:o}){const[a,i]=c.useState(!1),[s,l]=c.useState(!1),[d,p]=c.useState(""),m=c.useRef(null),f=c.useRef(null),w=n.length>0;c.useEffect(()=>{if(!a){p("");return}setTimeout(()=>{var x;return(x=f.current)==null?void 0:x.focus()},0);const u=x=>{m.current&&!m.current.contains(x.target)&&i(!1)},b=x=>{x.key==="Escape"&&i(!1)};return document.addEventListener("mousedown",u),document.addEventListener("keydown",b),()=>{document.removeEventListener("mousedown",u),document.removeEventListener("keydown",b)}},[a]);const y=d?r.filter(u=>u.toLowerCase().includes(d.toLowerCase())):r,g=u=>o(n.includes(u)?n.filter(b=>b!==u):[...n,u]),h=n.length===0?null:n.length===1?e.jsxs("span",{style:{color:"var(--lucent-text-secondary)",fontWeight:"var(--lucent-font-weight-regular)"},children:[": ",n[0]]}):e.jsxs("span",{style:{color:"var(--lucent-accent-default)"},children:["(",n.length,")"]});return e.jsxs("div",{ref:m,style:{position:"relative"},children:[e.jsxs("button",{onClick:()=>i(u=>!u),onMouseEnter:()=>l(!0),onMouseLeave:()=>l(!1),style:{display:"inline-flex",alignItems:"center",gap:"var(--lucent-space-1)",height:30,padding:"0 var(--lucent-space-3)",borderRadius:"var(--lucent-radius-md)",border:`1px solid ${w?"var(--lucent-accent-default)":s?"var(--lucent-border-strong)":"var(--lucent-border-default)"}`,background:w?"var(--lucent-accent-subtle)":"var(--lucent-surface)",color:w?"var(--lucent-accent-default)":"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:w?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",cursor:"pointer",outline:"none",whiteSpace:"nowrap",transition:"border-color var(--lucent-duration-fast) var(--lucent-easing-default), background var(--lucent-duration-fast) var(--lucent-easing-default)"},children:[t,h,e.jsx("svg",{width:"10",height:"10",viewBox:"0 0 10 10",fill:"none","aria-hidden":!0,style:{transform:a?"rotate(180deg)":"none",transition:"transform var(--lucent-duration-fast) var(--lucent-easing-default)"},children:e.jsx("path",{d:"M2 3.5L5 6.5L8 3.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})]}),a&&e.jsxs("div",{style:{position:"absolute",top:"calc(100% + 4px)",left:0,minWidth:180,maxHeight:280,display:"flex",flexDirection:"column",background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-default)",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 4px 16px color-mix(in srgb, var(--lucent-text-primary) 8%, transparent)",zIndex:50},children:[e.jsx("div",{style:{padding:"var(--lucent-space-2)",paddingBottom:0},children:e.jsx("input",{ref:f,type:"text",value:d,onChange:u=>p(u.target.value),placeholder:"Search…",style:{width:"100%",boxSizing:"border-box",height:26,padding:"0 var(--lucent-space-2)",borderRadius:"var(--lucent-radius-md)",border:"1px solid var(--lucent-border-default)",background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",color:"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",outline:"none"}})}),n.length>0&&e.jsx("div",{style:{padding:"var(--lucent-space-1) var(--lucent-space-2) 0"},children:e.jsx("button",{onClick:()=>o([]),style:{display:"inline-flex",padding:"2px var(--lucent-space-2)",border:"none",borderRadius:"var(--lucent-radius-sm)",background:"transparent",color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",cursor:"pointer",textDecoration:"underline"},children:"Clear selection"})}),e.jsx("div",{style:{overflowY:"auto",padding:"var(--lucent-space-1)",borderTop:"1px solid var(--lucent-border-subtle)",marginTop:"var(--lucent-space-2)"},children:y.length===0?e.jsx("div",{style:{padding:"var(--lucent-space-3)",color:"var(--lucent-text-disabled)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",textAlign:"center"},children:"No results"}):y.map(u=>e.jsx(ta,{label:u,isSelected:n.includes(u),onClick:()=>g(u)},u))})]})]})}function ta({label:t,isSelected:r,onClick:n}){const[o,a]=c.useState(!1);return e.jsxs("button",{onClick:n,onMouseEnter:()=>a(!0),onMouseLeave:()=>a(!1),style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-2)",width:"100%",textAlign:"left",padding:"var(--lucent-space-2) var(--lucent-space-3)",borderRadius:"var(--lucent-radius-md)",border:"none",background:o?"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)":"transparent",color:"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:r?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",cursor:"pointer",outline:"none",whiteSpace:"nowrap"},children:[e.jsx("span",{style:{flexShrink:0,width:14,height:14,borderRadius:"var(--lucent-radius-sm)",border:`1.5px solid ${r?"var(--lucent-accent-default)":"var(--lucent-border-strong)"}`,background:r?"var(--lucent-accent-default)":"transparent",display:"flex",alignItems:"center",justifyContent:"center",transition:"border-color var(--lucent-duration-fast), background var(--lucent-duration-fast)"},children:r&&e.jsx("svg",{width:"8",height:"8",viewBox:"0 0 8 8",fill:"none","aria-hidden":!0,children:e.jsx("path",{d:"M1 4L3 6L7 2",stroke:"var(--lucent-accent-fg)",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})}),t]})}function ke({children:t,onClick:r,disabled:n,active:o,...a}){const[i,s]=c.useState(!1);return e.jsx("button",{...a,onClick:r,disabled:n,onMouseEnter:()=>s(!0),onMouseLeave:()=>s(!1),style:{display:"inline-flex",alignItems:"center",justifyContent:"center",minWidth:32,height:32,padding:"0 var(--lucent-space-2)",borderRadius:"var(--lucent-radius-md)",border:o?"1px solid var(--lucent-accent-default)":"1px solid transparent",background:o?"var(--lucent-accent-default)":i&&!n?"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)":"transparent",color:o?"var(--lucent-accent-fg)":n?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontSize:"var(--lucent-font-size-sm)",fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-regular)",cursor:n?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)"},children:t})}const na={id:"data-table",name:"DataTable",tier:"molecule",domain:"neutral",specVersion:"1.0",description:"A sortable, filterable, paginated data table with configurable columns, custom cell renderers, and keyboard-accessible pagination controls.",designIntent:'DataTable is generic over row type T so TypeScript consumers get full type safety on column keys and renderers. Sorting is client-side and composable — each column opts in via sortable:true; clicking a sorted column cycles asc → desc → unsorted. Filtering is per-column — each column opts in via filterable:true, which adds a dropdown button above the table. Each dropdown is searchable and multi-select: a search input filters the option list, and each option is a checkbox that toggles membership in the active set. Filtering uses set-membership: a row passes if its column value is included in the selected values array. A "Clear selection" link inside each dropdown clears that column; a "Clear all" button in the bar appears when any filter is active. Filter → sort → paginate is the fixed pipeline order; any filter change resets the page to 0. Pagination is either controlled (page prop + onPageChange) or uncontrolled (internal state). A pageSize of 0 disables pagination entirely, useful when the parent manages windowing. Row hover uses bg-subtle, not a border change, so the visual weight stays low for dense data views.',props:[{name:"columns",type:"array",required:!0,description:"Column definitions. Each column has a key, header, optional render function, optional sortable flag, optional filterable flag (renders a text filter input below the header), optional width, and optional text align."},{name:"rows",type:"array",required:!0,description:"Array of data objects to display. The generic type T is inferred from this prop."},{name:"pageSize",type:"number",required:!1,default:"10",description:"Number of rows per page. Set to 0 to disable pagination."},{name:"page",type:"number",required:!1,description:"Controlled current page (0-indexed). When provided, the component is fully controlled."},{name:"onPageChange",type:"function",required:!1,description:"Called with the new page index whenever the page changes (from pagination controls or after a sort/filter reset)."},{name:"onFilterChange",type:"function",required:!1,description:"Called with the current filter map (Record<string, string[]>) whenever any column filter changes. Keys are column keys; columns with no selection are omitted from the map."},{name:"emptyState",type:"ReactNode",required:!1,description:'Content to render when rows is empty. Defaults to a "No data" text.'},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer wrapper."}],usageExamples:[{title:"Basic sortable table",code:`<DataTable
329
329
  columns={[
330
330
  { key: 'name', header: 'Name', sortable: true },
331
331
  { key: 'role', header: 'Role', sortable: true },
@@ -551,7 +551,7 @@ dismiss(id);`},{title:"Custom icon",description:"Override the built-in variant i
551
551
  <Button variant="primary" size="sm" onClick={() => setStep(s => s + 1)}>Continue</Button>
552
552
  </Row>
553
553
  </Stack>
554
- </Card>`,description:"Stepper paired with step content and navigation buttons inside a Card."}],compositionGraph:[{componentId:"chip",componentName:"Chip",role:"Status badge for each step (Completed/In Progress/Pending)",required:!1}],accessibility:{role:"group",ariaAttributes:["aria-label","aria-current"],keyboardInteractions:[],notes:'Root element has role="group" with aria-label="Progress steps". The current step circle receives aria-current="step" so screen readers can identify which step is active. Step labels are visible text, not aria-only. The checkmark animation uses prefers-reduced-motion: the spring scale is purely decorative and does not affect content comprehension.'}},No={bgBase:{description:"Main page/canvas background. The lowest elevation layer — everything sits on top of this. When bgBase is customized, `surface` and `surfaceTint` are auto-derived so the entire card elevation hierarchy (ghost → outline → filled → elevated → combo) adapts automatically.",lightGuidance:"Near-white. Typically #ffffff or a very faint tint (L > 0.96).",darkGuidance:"Near-black with a subtle cool or warm tint. Typically L 0.07–0.10 (e.g. #0f0f11, #111318).",derives:["bgSubtle","surfaceTint","surface","surfaceSecondary","surfaceRaised","surfaceOverlay"]},surface:{description:"Component surface color for elevated cards, input backgrounds, table rows, list items. Optional — when omitted, auto-derived from bgBase (pushed 85% toward white in light mode, +0.04 lightness in dark mode, with 30% saturation retention). Only set explicitly when you need a surface color that doesn't match the bgBase hue.",lightGuidance:"Very slightly off-white — just enough to read as distinct from bgBase. Typically L 0.96–0.98 (e.g. #f9fafb, #f8f8f8).",darkGuidance:"Slightly lighter than bgBase. Typically L 0.11–0.15 (e.g. #1a1a1e, #18181b).",derives:["surfaceSecondary","surfaceRaised","surfaceOverlay"]},borderDefault:{description:"Default border and divider color. Used on input outlines, card borders, table cell borders, section dividers.",lightGuidance:"A mid-light gray. Enough contrast to be legible on bgBase, not so dark it draws attention. Typically L 0.85–0.92 (e.g. #e5e7eb, #d1d5db).",darkGuidance:"A dim gray that reads against dark surfaces. Typically L 0.18–0.26 (e.g. #2d2d35, #374151).",derives:["borderSubtle","borderStrong"]},textPrimary:{description:"Primary body text. High contrast against bgBase — the default color for headings, labels, and body copy.",lightGuidance:"Very dark — near-black. Typically L 0.04–0.15 (e.g. #111827, #0f172a). Must pass WCAG AA against bgBase.",darkGuidance:"Very light — near-white. Typically L 0.88–0.96 (e.g. #f9fafb, #e2e8f0). Must pass WCAG AA against bgBase.",derives:["textSecondary","textDisabled"]},accentDefault:{description:"Primary brand/action color. Drives the visual identity — used on primary buttons, active nav links, focus rings, badges, and progress indicators.",lightGuidance:"Pick a color with enough contrast against both bgBase (white) and accentFg (computed automatically). Saturated mid-tones work well. E.g. indigo #6366f1, violet #8b5cf6, emerald #10b981. Monochrome default is #111827 (near-black).",darkGuidance:"In dark mode the accent typically lightens so it stays visible. This is handled automatically via deriveDarkFromLight() — you can supply the same light-mode accent and the dark variant is computed.",derives:["accentHover","accentSubtle","accentBorder","accentFg"]},successDefault:{description:'Positive/success state color. Used on success alerts, completed step indicators, "online" status badges, and confirmation toasts.',lightGuidance:"A readable green in the mid-to-dark range. E.g. #22c55e (Tailwind green-500), #16a34a (green-600). Avoid very pale greens — they fail WCAG against white.",darkGuidance:"Slightly lighter than the light-mode value so it reads against dark surfaces. createTheme() handles this automatically.",derives:["successSubtle","successText"]},warningDefault:{description:'Cautionary/warning state color. Used on warning banners, pending/in-review badges, and "needs attention" indicators.',lightGuidance:"An amber or orange-yellow. E.g. #f59e0b (Tailwind amber-500), #d97706 (amber-600). Avoid pure yellow — insufficient contrast on white.",darkGuidance:"Slightly lighter than light-mode. createTheme() handles this automatically.",derives:["warningSubtle","warningText"]},dangerDefault:{description:'Error/destructive state color. Used on error messages, delete-confirmation buttons, form field errors, and "offline" or "failed" status.',lightGuidance:"A mid-to-dark red. E.g. #ef4444 (Tailwind red-500), #dc2626 (red-600).",darkGuidance:"Slightly lighter than light-mode. createTheme() handles this automatically.",derives:["dangerHover","dangerSubtle","dangerText"]},infoDefault:{description:'Neutral informational state color. Used on info banners, help tooltips, "new feature" callouts, and link colors in some contexts.',lightGuidance:"A mid blue. E.g. #3b82f6 (Tailwind blue-500), #2563eb (blue-600).",darkGuidance:"Slightly lighter than light-mode. createTheme() handles this automatically.",derives:["infoSubtle","infoText"]}},$o={id:"lucent-provider",name:"LucentProvider",tier:"provider",domain:"neutral",specVersion:"1.0",description:"Root configuration wrapper that injects Lucent design tokens as CSS custom properties. Accepts a design preset for instant theming, 9 anchor colors (via `anchors`) for zero-config theming, or granular token overrides (via `tokens`) for full control.",designIntent:'LucentProvider is the single source of truth for the visual identity of any Lucent-powered UI. Its primary design goal is to make custom theming accessible to both humans and LLMs with minimal effort.\n\nPRESET MODE (fastest path): Pass `preset` with a named preset ("modern", "enterprise", "playful") or an object mixing dimensions: `{ palette: "indigo", shape: "pill", density: "compact", shadow: "elevated" }`. Presets bundle palette colors, border radius, spacing, and shadows into one cohesive design. Available palettes: default, brand, indigo, emerald, rose, ocean. Shapes: sharp, rounded, pill. Densities: compact, default, spacious. Shadows: flat, subtle, elevated. Presets work with both light and dark themes automatically.\n\nANCHOR MODE (recommended for LLMs building custom themes): Pass `anchors` with 9 semantic colors and the provider automatically derives all 30+ variant tokens — hover/active/subtle states, WCAG-compliant contrast text, status subtle tints, surface elevation steps, and more. An LLM generating a themed UI only needs to reason about 9 colors, not the full token surface.\n\nTOKEN MODE (for granular control): Pass `tokens` as a partial override object. Any token not supplied falls back to the base light or dark theme. Derivation still runs — e.g. if you supply only `accentDefault`, the provider auto-derives `accentHover`, `accentSubtle`, `accentBorder`, and `accentFg`.\n\nPRECEDENCE (later wins): base theme → preset → anchors → tokens. You can combine preset with tokens to start from a preset and tweak individual values.\n\nAPCA auto-computation: `accentFg` is always computed from the resolved accent color via APCA contrast — it will be #000000 or #ffffff, whichever has higher perceived contrast. Consumers can override it if they have brand-specific requirements.\n\nDARK MODE: Pass `theme="dark"` alongside any prop. Presets include both light and dark palettes. Anchor colors are applied to the dark base palette; derivation runs with dark-calibrated lightness deltas.\n\nDO NOT nest multiple LucentProviders unless intentionally theming a sub-tree differently (e.g. an inverted sidebar). The outermost provider wins for `:root` CSS vars.',props:[{name:"preset",type:"PresetProp",required:!1,description:'Design preset for instant theming. Pass a combined name ("modern", "enterprise", "playful") or an object to mix dimensions: { palette?: "default"|"brand"|"indigo"|"emerald"|"rose"|"ocean", shape?: "sharp"|"rounded"|"pill", density?: "compact"|"default"|"spacious", shadow?: "flat"|"subtle"|"elevated" }. You can also pass imported preset objects directly for tree-shaking. Precedence: base theme → preset → anchors → tokens.'},{name:"anchors",type:"ThemeAnchors",required:!1,description:"Minimal theming API: supply 9 semantic anchor colors and all variant tokens are derived automatically. See ThemeAnchorsSpec for guidance on each key. When provided alongside a preset, anchors override the preset's palette colors but preset shape/density/shadow tokens are preserved. When provided without a preset, the `tokens` prop is ignored. Required keys: bgBase, borderDefault, accentDefault, successDefault, warningDefault, dangerDefault, infoDefault. Optional keys: textPrimary (defaults to near-black/white), surface (auto-derived from bgBase)."},{name:"theme",type:"enum",required:!1,default:'"light"',enumValues:["light","dark"],description:'Selects the base token palette. "light" uses lightTokens as the starting point; "dark" uses darkTokens. Applies to both anchor-mode and token-mode.'},{name:"tokens",type:"Partial<LucentTokens>",required:!1,description:"Granular token overrides. Any token not supplied falls back to the base theme. Variant derivation still runs for anchor tokens found in this object (e.g. supplying `accentDefault` auto-derives hover/active/subtle variants). Ignored when `anchors` is provided."},{name:"children",type:"ReactNode",required:!0,description:"The subtree to theme. Typically your entire app, or a section that needs independent theming."}],usageExamples:[{title:"Preset mode — combined preset (simplest)",description:"Pick a curated preset that bundles palette, shape, density, and shadow tokens. Works with both light and dark themes.",code:`import { LucentProvider } from 'lucent-ui';
554
+ </Card>`,description:"Stepper paired with step content and navigation buttons inside a Card."}],compositionGraph:[{componentId:"chip",componentName:"Chip",role:"Status badge for each step (Completed/In Progress/Pending)",required:!1}],accessibility:{role:"group",ariaAttributes:["aria-label","aria-current"],keyboardInteractions:[],notes:'Root element has role="group" with aria-label="Progress steps". The current step circle receives aria-current="step" so screen readers can identify which step is active. Step labels are visible text, not aria-only. The checkmark animation uses prefers-reduced-motion: the spring scale is purely decorative and does not affect content comprehension.'}},No={navigation:{description:"Chrome/shell background for sidebars, headers, footers, and navigation rails. The outermost structural layer of the UI. Independent of bgBase — set it explicitly when the chrome needs a different tint than the content area.",lightGuidance:"A very faint cool or warm tint that distinguishes the chrome from the content area. Default #f4f6f8. Typically L 0.95–0.98.",darkGuidance:"The darkest layer — near-black with a subtle cool or warm tint. Typically L 0.06–0.09 (e.g. #0f0f11, #111318).",derives:[]},bgBase:{description:"Main content area background. The canvas on which surface elements (cards, tables, panels) sit. Slightly distinct from `navigation` to create a visual content/chrome boundary. When bgBase is customized, `surface` and `surfaceTint` are auto-derived so the entire card elevation hierarchy adapts automatically.",lightGuidance:"White or near-white. Default #ffffff. The content canvas that surface elements (cards) sit on.",darkGuidance:"Slightly lighter than navigation. Typically L 0.08–0.11 (e.g. #14161c, #15171e).",derives:["bgSubtle","surfaceTint","surface","surfaceSecondary","surfaceRaised","surfaceOverlay"]},surface:{description:"Component surface color for elevated cards, input backgrounds, table rows, list items. Optional — when omitted, auto-derived from bgBase (pushed 85% toward white in light mode, +0.04 lightness in dark mode, with 30% saturation retention). Only set explicitly when you need a surface color that doesn't match the bgBase hue.",lightGuidance:"White or near-white. Default #ffffff. Cards and panels sit on this surface above the content area background.",darkGuidance:"Slightly lighter than bgBase. Typically L 0.11–0.15 (e.g. #1a1a1e, #18181b).",derives:["surfaceSecondary","surfaceRaised","surfaceOverlay"]},borderDefault:{description:"Default border and divider color. Used on input outlines, card borders, table cell borders, section dividers.",lightGuidance:"A mid-light gray. Enough contrast to be legible on bgBase, not so dark it draws attention. Typically L 0.85–0.92 (e.g. #e5e7eb, #d1d5db).",darkGuidance:"A dim gray that reads against dark surfaces. Typically L 0.18–0.26 (e.g. #2d2d35, #374151).",derives:["borderSubtle","borderStrong"]},textPrimary:{description:"Primary body text. High contrast against bgBase — the default color for headings, labels, and body copy.",lightGuidance:"Very dark — near-black. Typically L 0.04–0.15 (e.g. #111827, #0f172a). Must pass WCAG AA against bgBase.",darkGuidance:"Very light — near-white. Typically L 0.88–0.96 (e.g. #f9fafb, #e2e8f0). Must pass WCAG AA against bgBase.",derives:["textSecondary","textDisabled"]},accentDefault:{description:"Primary brand/action color. Drives the visual identity — used on primary buttons, active nav links, focus rings, badges, and progress indicators.",lightGuidance:"Pick a color with enough contrast against both bgBase (white) and accentFg (computed automatically). Saturated mid-tones work well. E.g. indigo #6366f1, violet #8b5cf6, emerald #10b981. Monochrome default is #111827 (near-black).",darkGuidance:"In dark mode the accent typically lightens so it stays visible. This is handled automatically via deriveDarkFromLight() — you can supply the same light-mode accent and the dark variant is computed.",derives:["accentHover","accentSubtle","accentBorder","accentFg"]},successDefault:{description:'Positive/success state color. Used on success alerts, completed step indicators, "online" status badges, and confirmation toasts.',lightGuidance:"A readable green in the mid-to-dark range. E.g. #22c55e (Tailwind green-500), #16a34a (green-600). Avoid very pale greens — they fail WCAG against white.",darkGuidance:"Slightly lighter than the light-mode value so it reads against dark surfaces. createTheme() handles this automatically.",derives:["successSubtle","successText"]},warningDefault:{description:'Cautionary/warning state color. Used on warning banners, pending/in-review badges, and "needs attention" indicators.',lightGuidance:"An amber or orange-yellow. E.g. #f59e0b (Tailwind amber-500), #d97706 (amber-600). Avoid pure yellow — insufficient contrast on white.",darkGuidance:"Slightly lighter than light-mode. createTheme() handles this automatically.",derives:["warningSubtle","warningText"]},dangerDefault:{description:'Error/destructive state color. Used on error messages, delete-confirmation buttons, form field errors, and "offline" or "failed" status.',lightGuidance:"A mid-to-dark red. E.g. #ef4444 (Tailwind red-500), #dc2626 (red-600).",darkGuidance:"Slightly lighter than light-mode. createTheme() handles this automatically.",derives:["dangerHover","dangerSubtle","dangerText"]},infoDefault:{description:'Neutral informational state color. Used on info banners, help tooltips, "new feature" callouts, and link colors in some contexts.',lightGuidance:"A mid blue. E.g. #3b82f6 (Tailwind blue-500), #2563eb (blue-600).",darkGuidance:"Slightly lighter than light-mode. createTheme() handles this automatically.",derives:["infoSubtle","infoText"]}},$o={id:"lucent-provider",name:"LucentProvider",tier:"provider",domain:"neutral",specVersion:"1.0",description:"Root configuration wrapper that injects Lucent design tokens as CSS custom properties. Accepts a design preset for instant theming, 9 anchor colors (via `anchors`) for zero-config theming, or granular token overrides (via `tokens`) for full control.",designIntent:'LucentProvider is the single source of truth for the visual identity of any Lucent-powered UI. Its primary design goal is to make custom theming accessible to both humans and LLMs with minimal effort.\n\nPRESET MODE (fastest path): Pass `preset` with a named preset ("modern", "enterprise", "playful") or an object mixing dimensions: `{ palette: "indigo", shape: "pill", density: "compact", shadow: "elevated" }`. Presets bundle palette colors, border radius, spacing, and shadows into one cohesive design. Available palettes: default, brand, indigo, emerald, rose, ocean. Shapes: sharp, rounded, pill. Densities: compact, default, spacious. Shadows: flat, subtle, elevated. Presets work with both light and dark themes automatically.\n\nANCHOR MODE (recommended for LLMs building custom themes): Pass `anchors` with 9 semantic colors and the provider automatically derives all 30+ variant tokens — hover/active/subtle states, WCAG-compliant contrast text, status subtle tints, surface elevation steps, and more. An LLM generating a themed UI only needs to reason about 9 colors, not the full token surface.\n\nTOKEN MODE (for granular control): Pass `tokens` as a partial override object. Any token not supplied falls back to the base light or dark theme. Derivation still runs — e.g. if you supply only `accentDefault`, the provider auto-derives `accentHover`, `accentSubtle`, `accentBorder`, and `accentFg`.\n\nPRECEDENCE (later wins): base theme → preset → anchors → tokens. You can combine preset with tokens to start from a preset and tweak individual values.\n\nAPCA auto-computation: `accentFg` is always computed from the resolved accent color via APCA contrast — it will be #000000 or #ffffff, whichever has higher perceived contrast. Consumers can override it if they have brand-specific requirements.\n\nDARK MODE: Pass `theme="dark"` alongside any prop. Presets include both light and dark palettes. Anchor colors are applied to the dark base palette; derivation runs with dark-calibrated lightness deltas.\n\nDO NOT nest multiple LucentProviders unless intentionally theming a sub-tree differently (e.g. an inverted sidebar). The outermost provider wins for `:root` CSS vars.',props:[{name:"preset",type:"PresetProp",required:!1,description:'Design preset for instant theming. Pass a combined name ("modern", "enterprise", "playful") or an object to mix dimensions: { palette?: "default"|"brand"|"indigo"|"emerald"|"rose"|"ocean", shape?: "sharp"|"rounded"|"pill", density?: "compact"|"default"|"spacious", shadow?: "flat"|"subtle"|"elevated" }. You can also pass imported preset objects directly for tree-shaking. Precedence: base theme → preset → anchors → tokens.'},{name:"anchors",type:"ThemeAnchors",required:!1,description:"Minimal theming API: supply 9 semantic anchor colors and all variant tokens are derived automatically. See ThemeAnchorsSpec for guidance on each key. When provided alongside a preset, anchors override the preset's palette colors but preset shape/density/shadow tokens are preserved. When provided without a preset, the `tokens` prop is ignored. Required keys: bgBase, borderDefault, accentDefault, successDefault, warningDefault, dangerDefault, infoDefault. Optional keys: textPrimary (defaults to near-black/white), surface (auto-derived from bgBase), navigation (chrome background — defaults to base theme value)."},{name:"theme",type:"enum",required:!1,default:'"light"',enumValues:["light","dark"],description:'Selects the base token palette. "light" uses lightTokens as the starting point; "dark" uses darkTokens. Applies to both anchor-mode and token-mode.'},{name:"tokens",type:"Partial<LucentTokens>",required:!1,description:"Granular token overrides. Any token not supplied falls back to the base theme. Variant derivation still runs for anchor tokens found in this object (e.g. supplying `accentDefault` auto-derives hover/active/subtle variants). Ignored when `anchors` is provided."},{name:"children",type:"ReactNode",required:!0,description:"The subtree to theme. Typically your entire app, or a section that needs independent theming."}],usageExamples:[{title:"Preset mode — combined preset (simplest)",description:"Pick a curated preset that bundles palette, shape, density, and shadow tokens. Works with both light and dark themes.",code:`import { LucentProvider } from 'lucent-ui';
555
555
 
556
556
  <LucentProvider preset="modern">
557
557
  <App />
@@ -586,6 +586,7 @@ dismiss(id);`},{title:"Custom icon",description:"Override the built-in variant i
586
586
 
587
587
  <LucentProvider
588
588
  anchors={{
589
+ navigation: '#f4f6f8',
589
590
  bgBase: '#ffffff',
590
591
  borderDefault: '#e5e7eb',
591
592
  accentDefault: '#6366f1',
@@ -618,6 +619,7 @@ dismiss(id);`},{title:"Custom icon",description:"Override the built-in variant i
618
619
  </LucentProvider>`},{title:"createTheme() then pass as tokens",description:"Use the pure createTheme() helper when you need the resolved token object for other purposes (e.g. passing to a charting library or Storybook decorator).",code:`import { LucentProvider, createTheme } from 'lucent-ui';
619
620
 
620
621
  const myTheme = createTheme({
622
+ navigation: '#f4f6f8',
621
623
  bgBase: '#ffffff',
622
624
  borderDefault: '#e7e5e4',
623
625
  accentDefault: '#f97316',
package/dist/index.d.ts CHANGED
@@ -536,13 +536,14 @@ export declare interface CompositionNode {
536
536
  *
537
537
  * @example
538
538
  * const tokens = createTheme({
539
- * bgBase: '#ffffff',
539
+ * bgBase: '#f9fafb',
540
540
  * borderDefault: '#e5e7eb',
541
541
  * accentDefault: '#6366f1',
542
542
  * successDefault: '#22c55e',
543
543
  * warningDefault: '#f59e0b',
544
544
  * dangerDefault: '#ef4444',
545
545
  * infoDefault: '#3b82f6',
546
+ * navigation: '#ffffff', // optional
546
547
  * });
547
548
  *
548
549
  * // <LucentProvider tokens={tokens}>...</LucentProvider>
@@ -1243,14 +1244,10 @@ export declare interface NavMenuSubProps {
1243
1244
  }
1244
1245
 
1245
1246
  /**
1246
- * Neumorphic — light mode uses classic dual light/dark shadows
1247
- * for the extruded soft-UI look.
1247
+ * Neumorphic — bold, dramatic drop shadows that create strong
1248
+ * visual lift. Single-layer for clean interaction with borders.
1248
1249
  *
1249
- * Dark mode shifts to "chromatic" offset split-color accent
1250
- * glow (warm-shifted on one side, cool-shifted on the other).
1251
- * Creates a CRT/screen-like depth that reads as physical without
1252
- * relying on darkening. Uses `color-mix()` so it follows the
1253
- * active accent automatically.
1250
+ * Dark mode uses the same approach with higher opacity.
1254
1251
  */
1255
1252
  export declare const neumorphicShadow: ShadowPreset;
1256
1253
 
@@ -1278,8 +1275,8 @@ export declare interface PageLayoutProps {
1278
1275
  footer?: ReactNode;
1279
1276
  /** Footer height in px or any CSS value. Default: 48 */
1280
1277
  footerHeight?: number | string;
1281
- /** Background token for chrome regions (header, sidebar, footer). Default: "bgBase" */
1282
- chromeBackground?: 'bgBase' | 'bgSubtle' | 'surface' | 'surfaceSecondary';
1278
+ /** Background token for chrome regions (header, sidebar, footer). Default: "navigation" */
1279
+ chromeBackground?: 'navigation' | 'bgBase' | 'bgSubtle' | 'surface' | 'surfaceSecondary';
1283
1280
  /** Style overrides for the main content card (border, borderRadius, boxShadow, etc.) */
1284
1281
  mainStyle?: CSSProperties;
1285
1282
  style?: CSSProperties;
@@ -1502,6 +1499,7 @@ export declare interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelec
1502
1499
  export declare type SelectSize = 'sm' | 'md' | 'lg';
1503
1500
 
1504
1501
  declare interface SemanticColorTokens {
1502
+ navigation: string;
1505
1503
  bgBase: string;
1506
1504
  bgSubtle: string;
1507
1505
  bgOverlay: string;
@@ -1879,16 +1877,17 @@ export declare type Theme = 'light' | 'dark';
1879
1877
  *
1880
1878
  * @example
1881
1879
  * const myTheme = createTheme({
1882
- * bgBase: '#ffffff',
1880
+ * bgBase: '#f9fafb',
1883
1881
  * borderDefault: '#e5e7eb',
1884
1882
  * accentDefault: '#6366f1',
1885
1883
  * successDefault: '#22c55e',
1886
1884
  * warningDefault: '#f59e0b',
1887
1885
  * dangerDefault: '#ef4444',
1888
1886
  * infoDefault: '#3b82f6',
1887
+ * navigation: '#ffffff', // optional — chrome background
1889
1888
  * });
1890
1889
  */
1891
- export declare type ThemeAnchors = Pick<LucentTokens, 'bgBase' | 'borderDefault' | 'accentDefault' | 'successDefault' | 'warningDefault' | 'dangerDefault' | 'infoDefault'> & Partial<Pick<LucentTokens, 'textPrimary' | 'surface'>>;
1890
+ export declare type ThemeAnchors = Pick<LucentTokens, 'bgBase' | 'borderDefault' | 'accentDefault' | 'successDefault' | 'warningDefault' | 'dangerDefault' | 'infoDefault'> & Partial<Pick<LucentTokens, 'textPrimary' | 'surface' | 'navigation'>>;
1892
1891
 
1893
1892
  /**
1894
1893
  * Semantic guidance for each ThemeAnchors key.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsxs as g, jsx as e, Fragment as Q } from "react/jsx-runtime";
2
- import { T as V, I as ut, B as ce, g as St, a as Me } from "./LucentProvider-BoiXYqA4.js";
3
- import { b as gi, C as vi, c as bi, L as yi, S as xi, d as wi, e as ki, f as Si, h as Ti, i as Ci, j as Ii, k as Mi, l as zi, m as Ei, n as Ai, o as qi, p as Di, q as Bi, r as Ri, s as Li, t as Pi, u as Fi, v as Ni, w as $i, x as Wi, y as Oi, z as Vi, A as Hi, D as Gi, E as Ui, F as ji, G as _i, H as Yi, J as Ki, K as Xi, M as Ji, N as Zi, O as Qi, P as es, Q as ts, R as ns, U as rs, V as as, W as os, X as is, Y as ss, Z as ls, _ as cs } from "./LucentProvider-BoiXYqA4.js";
2
+ import { T as V, I as ut, B as ce, g as St, a as Me } from "./LucentProvider-F0EN_7TD.js";
3
+ import { b as gi, C as vi, c as bi, L as yi, S as xi, d as wi, e as ki, f as Si, h as Ti, i as Ci, j as Ii, k as Mi, l as zi, m as Ei, n as Ai, o as qi, p as Di, q as Bi, r as Ri, s as Li, t as Pi, u as Fi, v as Ni, w as $i, x as Wi, y as Oi, z as Vi, A as Hi, D as Gi, E as Ui, F as ji, G as _i, H as Yi, J as Ki, K as Xi, M as Ji, N as Zi, O as Qi, P as es, Q as ts, R as ns, U as rs, V as as, W as os, X as is, Y as ss, Z as ls, _ as cs } from "./LucentProvider-F0EN_7TD.js";
4
4
  import { forwardRef as pt, useRef as $, useEffect as H, useState as z, useCallback as J, useContext as ee, createContext as fe, useLayoutEffect as te, useId as Pe, Children as Tt, isValidElement as Ct, cloneElement as It } from "react";
5
5
  import { createPortal as ae } from "react-dom";
6
6
  const Ra = {
@@ -4790,7 +4790,7 @@ const zo = {
4790
4790
  domain: "neutral",
4791
4791
  specVersion: "0.1",
4792
4792
  description: "A surface container with five elevation variants that form a visual importance hierarchy. Supports optional header, body, and footer slots with configurable padding, shadow, and radius. Includes a CardBleed sub-component for edge-to-edge content.",
4793
- designIntent: 'Card provides a configurable surface with an explicit elevation hierarchy. Each variant maps to a distinct level of visual prominence, giving consumers a single prop to express how much attention a surface should command relative to its surroundings.\n\n## Elevation hierarchy (lowest → highest)\n\n1. **ghost** — transparent background, no border, no shadow.\n The card is invisible as a container — content floats directly against the page or parent surface. Use for logical groupings that shouldn\'t compete visually: sidebar sections, form regions, or layout slots where structure exists conceptually but not visually. Header/footer dividers still render if those slots are used, providing minimal internal structure.\n\n2. **outline** (default) — transparent background with `border-default` border, no shadow.\n Like ghost, the card inherits its container\'s background — the border alone defines the boundary. This is the workhorse variant for lists of items, form sections, data panels, and any content that needs a visible container without drawing excessive attention. Header and footer slots are separated from the body by matching `border-default` dividers.\n\n3. **filled** — semi-transparent tinted background, no border, no shadow.\n Differentiates the card from its surroundings by darkening (light mode) or lightening (dark mode) whatever surface it sits on. Uses `color-mix(in srgb, textPrimary 6%, transparent)` so the tint is always relative to the container — never a fixed color. Use for secondary content areas, inset panels, summary blocks, or anywhere a border would feel heavy but the card needs to be visually distinct. Effective for nesting (e.g., a filled card inside an elevated card creates a recessed region).\n\n4. **elevated** — `surface` background with medium shadow, no border.\n The card lifts off the page through depth. The shadow creates a physical metaphor: this content sits above the surface it rests on. Use for primary content areas, feature highlights, pricing cards, or any surface that should feel physically elevated. The lack of border keeps the silhouette soft — the shadow alone defines the boundary.\n\n5. **combo** — transparent wrapper with an elevated body inset.\n The header and footer are flat — they blend into the page background as if they were part of it. Only the body section is elevated: it gets `surface` background, border-radius, and shadow, appearing as a raised card sitting between the flat header/footer regions. This draws the eye to the primary content while keeping supporting info (header) and actions (footer) visually subordinate — they frame the elevated body without competing with it. No dividers are rendered — the elevation change IS the visual separator. Use for detail panels, profile cards, or settings forms where the body content is the focal point.\n\n## Shadow override\nThe `shadow` prop overrides whatever shadow the variant implies. This allows fine-tuning without changing the variant. For example, `variant="elevated" shadow="lg"` gives an elevated card with extra depth, while `variant="outline" shadow="none"` gives a flat bordered card.\n\n## Token rules\n- `elevated` and `combo` body use `surface` (the primary component surface — white in light mode).\n- `filled` uses a semi-transparent tint of `textPrimary` — contextually darker/lighter than its container.\n- `combo` wrapper is transparent (header/footer blend with page); only the body is elevated with `surface`.\n- `ghost` and `outline` use `transparent` — they inherit from whatever they\'re placed on. The border is the only visual differentiator for `outline`.\n- Never use `bgBase` or `bgSubtle` on a Card — those tokens are reserved for the page canvas.\n- Content nested inside a Card that needs a tinted fill should use `color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)` for accent-neutral insets.',
4793
+ designIntent: 'Card provides a configurable surface with an explicit elevation hierarchy. Each variant maps to a distinct level of visual prominence, giving consumers a single prop to express how much attention a surface should command relative to its surroundings.\n\n## Elevation hierarchy (lowest → highest)\n\n1. **ghost** — transparent background, no border, no shadow.\n The card is invisible as a container — content floats directly against the page or parent surface. Use for logical groupings that shouldn\'t compete visually: sidebar sections, form regions, or layout slots where structure exists conceptually but not visually. Header/footer dividers still render if those slots are used, providing minimal internal structure.\n\n2. **outline** (default) — transparent background with `border-default` border, no shadow.\n Like ghost, the card inherits its container\'s background — the border alone defines the boundary. This is the workhorse variant for lists of items, form sections, data panels, and any content that needs a visible container without drawing excessive attention. Header and footer slots are separated from the body by matching `border-default` dividers.\n\n3. **filled** — semi-transparent tinted background, no border, no shadow.\n Differentiates the card from its surroundings by darkening (light mode) or lightening (dark mode) whatever surface it sits on. Uses `color-mix(in srgb, textPrimary 6%, transparent)` so the tint is always relative to the container — never a fixed color. Use for secondary content areas, inset panels, summary blocks, or anywhere a border would feel heavy but the card needs to be visually distinct. Effective for nesting (e.g., a filled card inside an elevated card creates a recessed region).\n\n4. **elevated** — `surface` background with medium shadow, no border.\n The card lifts off the page through depth. The shadow creates a physical metaphor: this content sits above the surface it rests on. Use for primary content areas, feature highlights, pricing cards, or any surface that should feel physically elevated. The lack of border keeps the silhouette soft — the shadow alone defines the boundary.\n\n5. **combo** — transparent wrapper with an elevated body inset.\n The header and footer are flat — they blend into the page background as if they were part of it. Only the body section is elevated: it gets `surface` background, border-radius, and shadow, appearing as a raised card sitting between the flat header/footer regions. This draws the eye to the primary content while keeping supporting info (header) and actions (footer) visually subordinate — they frame the elevated body without competing with it. No dividers are rendered — the elevation change IS the visual separator. Use for detail panels, profile cards, or settings forms where the body content is the focal point.\n\n## Shadow override\nThe `shadow` prop overrides whatever shadow the variant implies. This allows fine-tuning without changing the variant. For example, `variant="elevated" shadow="lg"` gives an elevated card with extra depth, while `variant="outline" shadow="none"` gives a flat bordered card.\n\n## Token rules\n- `elevated` and `combo` body use `surface` (the primary component surface — white in light mode).\n- `filled` uses a semi-transparent tint of `textPrimary` — contextually darker/lighter than its container.\n- `combo` wrapper is transparent (header/footer blend with page); only the body is elevated with `surface`.\n- `ghost` and `outline` use `transparent` — they inherit from whatever they\'re placed on. The border is the only visual differentiator for `outline`.\n- Never use `navigation`, `bgBase`, or `bgSubtle` on a Card — those tokens are reserved for the page chrome and content area.\n- Content nested inside a Card that needs a tinted fill should use `color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)` for accent-neutral insets.',
4794
4794
  props: [
4795
4795
  {
4796
4796
  name: "variant",
@@ -5663,16 +5663,17 @@ function Fo({
5663
5663
  rightSidebarCollapsed: c = !1,
5664
5664
  footer: u,
5665
5665
  footerHeight: f = 28,
5666
- chromeBackground: p = "bgBase",
5666
+ chromeBackground: p = "navigation",
5667
5667
  mainStyle: w,
5668
5668
  style: y
5669
5669
  }) {
5670
5670
  const m = ye(a), h = ye(o), d = ye(l), b = ye(f), k = {
5671
+ navigation: "var(--lucent-navigation)",
5671
5672
  bgBase: "var(--lucent-bg-base)",
5672
5673
  bgSubtle: "var(--lucent-bg-subtle)",
5673
5674
  surface: "var(--lucent-surface)",
5674
5675
  surfaceSecondary: "var(--lucent-surface-secondary)"
5675
- }[p] ?? "var(--lucent-bg-base)";
5676
+ }[p] ?? "var(--lucent-navigation)";
5676
5677
  return /* @__PURE__ */ g(
5677
5678
  "div",
5678
5679
  {
@@ -5727,7 +5728,7 @@ function Fo({
5727
5728
  border: "1px solid var(--lucent-border-default)",
5728
5729
  borderRadius: "var(--lucent-radius-lg)",
5729
5730
  boxShadow: "var(--lucent-shadow-sm)",
5730
- background: "var(--lucent-surface)",
5731
+ background: "var(--lucent-bg-base)",
5731
5732
  ...w
5732
5733
  },
5733
5734
  children: t
@@ -10014,15 +10015,21 @@ const oi = {
10014
10015
  notes: 'Root element has role="group" with aria-label="Progress steps". The current step circle receives aria-current="step" so screen readers can identify which step is active. Step labels are visible text, not aria-only. The checkmark animation uses prefers-reduced-motion: the spring scale is purely decorative and does not affect content comprehension.'
10015
10016
  }
10016
10017
  }, ii = {
10018
+ navigation: {
10019
+ description: "Chrome/shell background for sidebars, headers, footers, and navigation rails. The outermost structural layer of the UI. Independent of bgBase — set it explicitly when the chrome needs a different tint than the content area.",
10020
+ lightGuidance: "A very faint cool or warm tint that distinguishes the chrome from the content area. Default #f4f6f8. Typically L 0.95–0.98.",
10021
+ darkGuidance: "The darkest layer — near-black with a subtle cool or warm tint. Typically L 0.06–0.09 (e.g. #0f0f11, #111318).",
10022
+ derives: []
10023
+ },
10017
10024
  bgBase: {
10018
- description: "Main page/canvas background. The lowest elevation layer everything sits on top of this. When bgBase is customized, `surface` and `surfaceTint` are auto-derived so the entire card elevation hierarchy (ghost → outline → filled → elevated → combo) adapts automatically.",
10019
- lightGuidance: "Near-white. Typically #ffffff or a very faint tint (L > 0.96).",
10020
- darkGuidance: "Near-black with a subtle cool or warm tint. Typically L 0.07–0.10 (e.g. #0f0f11, #111318).",
10025
+ description: "Main content area background. The canvas on which surface elements (cards, tables, panels) sit. Slightly distinct from `navigation` to create a visual content/chrome boundary. When bgBase is customized, `surface` and `surfaceTint` are auto-derived so the entire card elevation hierarchy adapts automatically.",
10026
+ lightGuidance: "White or near-white. Default #ffffff. The content canvas that surface elements (cards) sit on.",
10027
+ darkGuidance: "Slightly lighter than navigation. Typically L 0.08–0.11 (e.g. #14161c, #15171e).",
10021
10028
  derives: ["bgSubtle", "surfaceTint", "surface", "surfaceSecondary", "surfaceRaised", "surfaceOverlay"]
10022
10029
  },
10023
10030
  surface: {
10024
10031
  description: "Component surface color for elevated cards, input backgrounds, table rows, list items. Optional — when omitted, auto-derived from bgBase (pushed 85% toward white in light mode, +0.04 lightness in dark mode, with 30% saturation retention). Only set explicitly when you need a surface color that doesn't match the bgBase hue.",
10025
- lightGuidance: "Very slightly off-white just enough to read as distinct from bgBase. Typically L 0.96–0.98 (e.g. #f9fafb, #f8f8f8).",
10032
+ lightGuidance: "White or near-white. Default #ffffff. Cards and panels sit on this surface above the content area background.",
10026
10033
  darkGuidance: "Slightly lighter than bgBase. Typically L 0.11–0.15 (e.g. #1a1a1e, #18181b).",
10027
10034
  derives: ["surfaceSecondary", "surfaceRaised", "surfaceOverlay"]
10028
10035
  },
@@ -10087,7 +10094,7 @@ const oi = {
10087
10094
  name: "anchors",
10088
10095
  type: "ThemeAnchors",
10089
10096
  required: !1,
10090
- description: "Minimal theming API: supply 9 semantic anchor colors and all variant tokens are derived automatically. See ThemeAnchorsSpec for guidance on each key. When provided alongside a preset, anchors override the preset's palette colors but preset shape/density/shadow tokens are preserved. When provided without a preset, the `tokens` prop is ignored. Required keys: bgBase, borderDefault, accentDefault, successDefault, warningDefault, dangerDefault, infoDefault. Optional keys: textPrimary (defaults to near-black/white), surface (auto-derived from bgBase)."
10097
+ description: "Minimal theming API: supply 9 semantic anchor colors and all variant tokens are derived automatically. See ThemeAnchorsSpec for guidance on each key. When provided alongside a preset, anchors override the preset's palette colors but preset shape/density/shadow tokens are preserved. When provided without a preset, the `tokens` prop is ignored. Required keys: bgBase, borderDefault, accentDefault, successDefault, warningDefault, dangerDefault, infoDefault. Optional keys: textPrimary (defaults to near-black/white), surface (auto-derived from bgBase), navigation (chrome background — defaults to base theme value)."
10091
10098
  },
10092
10099
  {
10093
10100
  name: "theme",
@@ -10169,6 +10176,7 @@ const oi = {
10169
10176
 
10170
10177
  <LucentProvider
10171
10178
  anchors={{
10179
+ navigation: '#f4f6f8',
10172
10180
  bgBase: '#ffffff',
10173
10181
  borderDefault: '#e5e7eb',
10174
10182
  accentDefault: '#6366f1',
@@ -10216,6 +10224,7 @@ const oi = {
10216
10224
  code: `import { LucentProvider, createTheme } from 'lucent-ui';
10217
10225
 
10218
10226
  const myTheme = createTheme({
10227
+ navigation: '#f4f6f8',
10219
10228
  bgBase: '#ffffff',
10220
10229
  borderDefault: '#e7e5e4',
10221
10230
  accentDefault: '#f97316',
@@ -50,7 +50,7 @@ export const COMPONENT_MANIFEST = {
50
50
  '- `combo` wrapper is transparent (header/footer blend with page); only the body is elevated with `surface`.\n' +
51
51
  '- `ghost` and `outline` use `transparent` — they inherit from whatever they\'re placed on. ' +
52
52
  'The border is the only visual differentiator for `outline`.\n' +
53
- '- Never use `bgBase` or `bgSubtle` on a Card — those tokens are reserved for the page canvas.\n' +
53
+ '- Never use `navigation`, `bgBase`, or `bgSubtle` on a Card — those tokens are reserved for the page chrome and content area.\n' +
54
54
  '- Content nested inside a Card that needs a tinted fill should use `color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)` for accent-neutral insets.',
55
55
  props: [
56
56
  {