lucent-ui 0.40.0 → 0.41.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/LucentProvider-DluNb5H9.cjs +109 -0
- package/dist/{LucentProvider-Bm39MMvv.js → LucentProvider-lGSitrJV.js} +1019 -1001
- package/dist/devtools.cjs +1 -1
- package/dist/devtools.js +1 -1
- package/dist/index.cjs +47 -42
- package/dist/index.d.ts +33 -4
- package/dist/index.js +2252 -1998
- package/dist-server/server/pattern-registry.js +6 -0
- package/dist-server/server/recipe-registry.js +18 -0
- package/dist-server/src/components/molecules/PageLayout/PageLayout.manifest.js +65 -8
- package/dist-server/src/manifest/patterns/index.js +2 -0
- package/dist-server/src/manifest/patterns/multi-step-wizard.pattern.js +180 -0
- package/dist-server/src/manifest/patterns/search-filter-panel.pattern.js +188 -0
- package/dist-server/src/manifest/patterns/tab-page.pattern.js +152 -0
- package/dist-server/src/manifest/recipes/action-bar.recipe.js +91 -0
- package/dist-server/src/manifest/recipes/collapsible-card.recipe.js +100 -0
- package/dist-server/src/manifest/recipes/empty-state-card.recipe.js +72 -0
- package/dist-server/src/manifest/recipes/form-layout.recipe.js +98 -0
- package/dist-server/src/manifest/recipes/index.js +8 -0
- package/dist-server/src/manifest/recipes/profile-card.recipe.js +101 -0
- package/dist-server/src/manifest/recipes/search-filter-bar.recipe.js +122 -0
- package/dist-server/src/manifest/recipes/settings-panel.recipe.js +167 -0
- package/dist-server/src/manifest/recipes/stats-row.recipe.js +106 -0
- package/dist-server/src/manifest/validate.test.js +28 -0
- package/package.json +1 -1
- package/dist/LucentProvider-CzEDW5SL.cjs +0 -109
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),E=require("./LucentProvider-CzEDW5SL.cjs"),d=require("react"),oe=require("react-dom"),tn={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. Ignored when `href` is set.",enumValues:["button","submit","reset"]},{name:"href",type:"string",required:!1,description:'When set, renders the Button as an `<a href={href}>` instead of a native `<button>`. Preserves native anchor affordances (middle-click, cmd/ctrl-click, right-click "copy link address", open in new tab) that an onClick handler cannot. Use for `mailto:` / `tel:` quick actions, external links styled as buttons, or in-app routes where users legitimately expect anchor semantics. When combined with `disabled`, the anchor renders with `aria-disabled="true"` and its `href` is stripped so navigation is neutralised.'},{name:"target",type:"string",required:!1,description:'Forwarded to the rendered `<a>` when `href` is set (e.g. `"_blank"` to open in a new tab). Ignored when rendering as a button.'},{name:"rel",type:"string",required:!1,description:'Forwarded to the rendered `<a>` when `href` is set (e.g. `"noopener noreferrer"` for external links). Ignored when rendering as a button.'}],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."},{title:"Mailto quick action",code:'<Button variant="ghost" size="sm" href="mailto:foo@example.com" leftIcon={<MailIcon />} aria-label="Email" />',description:'Renders as <a href="mailto:..."> so middle-click, cmd/ctrl-click, and right-click "copy link" all work.'},{title:"External link as button",code:'<Button variant="primary" href="https://example.com" target="_blank" rel="noopener noreferrer" rightIcon={<ExternalIcon />}>View docs</Button>',description:"Use href + target + rel for external links that should look like a primary call-to-action."},{title:"Disabled link",code:'<Button variant="outline" href="/settings" disabled>Settings</Button>',description:'When disabled, the anchor is rendered with aria-disabled="true" and its href is stripped.'}],compositionGraph:[],accessibility:{role:"button",ariaAttributes:["aria-disabled","aria-busy"],keyboardInteractions:["Enter — activates the button","Space — activates the button"]}},nn={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"]}},an={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-md)",lg:"var(--lucent-font-size-md)"},rn={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-md)"},on={sm:"var(--lucent-space-3)",md:"var(--lucent-space-4)",lg:"var(--lucent-space-4)"},At=d.forwardRef(({label:t,helperText:a,errorText:n,autoResize:i=!1,maxLength:r,showCount:o=!1,size:s="md",id:l,value:c,onChange:u,disabled:h,style:f,...S},w)=>{const g=d.useRef(null),m=w??g,p=l??`lucent-textarea-${Math.random().toString(36).slice(2,7)}`,b=!!n,y=!!h,x=typeof c=="string"?c.length:0;d.useEffect(()=>{if(!i)return;const k=m.current;k&&(k.style.height="auto",k.style.height=`${k.scrollHeight}px`)},[c,i,m]);const T=y?"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:p,style:{fontSize:rn[s],fontWeight:"var(--lucent-font-weight-medium)",color:y?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)"},children:t}),e.jsx("textarea",{ref:m,id:p,maxLength:r,value:c,onChange:u,disabled:h,"aria-invalid":b,"aria-describedby":b?`${p}-error`:a?`${p}-helper`:void 0,style:{width:"100%",minHeight:"100px",padding:on[s],fontSize:an[s],fontFamily:"var(--lucent-font-family-base)",color:y?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",background:y?"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)":"var(--lucent-surface)",border:`1px solid ${T}`,borderRadius:"var(--lucent-radius-lg)",outline:"none",resize:i?"none":"vertical",boxSizing:"border-box",lineHeight:"var(--lucent-line-height-base)",cursor:y?"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:k=>{var v;!y&&k.currentTarget!==document.activeElement&&(k.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-border-strong)"),(v=S.onMouseEnter)==null||v.call(S,k)},onMouseLeave:k=>{var v;!y&&k.currentTarget!==document.activeElement&&(k.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-border-default)"),(v=S.onMouseLeave)==null||v.call(S,k)},onFocus:k=>{var v;y||(k.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-accent-border)",k.currentTarget.style.boxShadow=`0 0 0 3px ${b?"var(--lucent-danger-subtle)":"var(--lucent-accent-subtle)"}`,(v=S.onFocus)==null||v.call(S,k))},onBlur:k=>{var v;y||(k.currentTarget.style.borderColor=b?"var(--lucent-danger-default)":"var(--lucent-border-default)",k.currentTarget.style.boxShadow="none",(v=S.onBlur)==null||v.call(S,k))},...S}),e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"flex-start"},children:[e.jsxs("div",{children:[b&&e.jsx("span",{id:`${p}-error`,role:"alert",style:{fontSize:"var(--lucent-font-size-sm)",color:"var(--lucent-danger-text)",fontFamily:"var(--lucent-font-family-base)"},children:n}),!b&&a&&e.jsx("span",{id:`${p}-helper`,style:{fontSize:"var(--lucent-font-size-sm)",color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)"},children:a})]}),(o||r)&&e.jsxs("span",{style:{fontSize:"var(--lucent-font-size-xs)",color:r&&x>=r?"var(--lucent-danger-text)":"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-mono)",flexShrink:0,marginLeft:"var(--lucent-space-2)"},children:[x,r?`/${r}`:""]})]})]})});At.displayName="Textarea";const sn={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"]}},ln={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."}},cn={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)"}},dn={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)"}},un=`
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),E=require("./LucentProvider-DluNb5H9.cjs"),c=require("react"),Q=require("react-dom"),sn={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. Ignored when `href` is set.",enumValues:["button","submit","reset"]},{name:"href",type:"string",required:!1,description:'When set, renders the Button as an `<a href={href}>` instead of a native `<button>`. Preserves native anchor affordances (middle-click, cmd/ctrl-click, right-click "copy link address", open in new tab) that an onClick handler cannot. Use for `mailto:` / `tel:` quick actions, external links styled as buttons, or in-app routes where users legitimately expect anchor semantics. When combined with `disabled`, the anchor renders with `aria-disabled="true"` and its `href` is stripped so navigation is neutralised.'},{name:"target",type:"string",required:!1,description:'Forwarded to the rendered `<a>` when `href` is set (e.g. `"_blank"` to open in a new tab). Ignored when rendering as a button.'},{name:"rel",type:"string",required:!1,description:'Forwarded to the rendered `<a>` when `href` is set (e.g. `"noopener noreferrer"` for external links). Ignored when rendering as a button.'}],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."},{title:"Mailto quick action",code:'<Button variant="ghost" size="sm" href="mailto:foo@example.com" leftIcon={<MailIcon />} aria-label="Email" />',description:'Renders as <a href="mailto:..."> so middle-click, cmd/ctrl-click, and right-click "copy link" all work.'},{title:"External link as button",code:'<Button variant="primary" href="https://example.com" target="_blank" rel="noopener noreferrer" rightIcon={<ExternalIcon />}>View docs</Button>',description:"Use href + target + rel for external links that should look like a primary call-to-action."},{title:"Disabled link",code:'<Button variant="outline" href="/settings" disabled>Settings</Button>',description:'When disabled, the anchor is rendered with aria-disabled="true" and its href is stripped.'}],compositionGraph:[],accessibility:{role:"button",ariaAttributes:["aria-disabled","aria-busy"],keyboardInteractions:["Enter — activates the button","Space — activates the button"]}},ln={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"]}},cn={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-md)",lg:"var(--lucent-font-size-md)"},dn={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-md)"},un={sm:"var(--lucent-space-3)",md:"var(--lucent-space-4)",lg:"var(--lucent-space-4)"},Pt=c.forwardRef(({label:t,helperText:r,errorText:n,autoResize:i=!1,maxLength:a,showCount:o=!1,size:l="md",id:s,value:d,onChange:p,disabled:h,style:f,...w},b)=>{const g=c.useRef(null),m=b??g,u=s??`lucent-textarea-${Math.random().toString(36).slice(2,7)}`,y=!!n,x=!!h,T=typeof d=="string"?d.length:0;c.useEffect(()=>{if(!i)return;const k=m.current;k&&(k.style.height="auto",k.style.height=`${k.scrollHeight}px`)},[d,i,m]);const S=x?"transparent":y?"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:dn[l],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:m,id:u,maxLength:a,value:d,onChange:p,disabled:h,"aria-invalid":y,"aria-describedby":y?`${u}-error`:r?`${u}-helper`:void 0,style:{width:"100%",minHeight:"100px",padding:un[l],fontSize:cn[l],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:i?"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:k=>{var v;!x&&k.currentTarget!==document.activeElement&&(k.currentTarget.style.borderColor=y?"var(--lucent-danger-default)":"var(--lucent-border-strong)"),(v=w.onMouseEnter)==null||v.call(w,k)},onMouseLeave:k=>{var v;!x&&k.currentTarget!==document.activeElement&&(k.currentTarget.style.borderColor=y?"var(--lucent-danger-default)":"var(--lucent-border-default)"),(v=w.onMouseLeave)==null||v.call(w,k)},onFocus:k=>{var v;x||(k.currentTarget.style.borderColor=y?"var(--lucent-danger-default)":"var(--lucent-accent-border)",k.currentTarget.style.boxShadow=`0 0 0 3px ${y?"var(--lucent-danger-subtle)":"var(--lucent-accent-subtle)"}`,(v=w.onFocus)==null||v.call(w,k))},onBlur:k=>{var v;x||(k.currentTarget.style.borderColor=y?"var(--lucent-danger-default)":"var(--lucent-border-default)",k.currentTarget.style.boxShadow="none",(v=w.onBlur)==null||v.call(w,k))},...w}),e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"flex-start"},children:[e.jsxs("div",{children:[y&&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}),!y&&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})]}),(o||a)&&e.jsxs("span",{style:{fontSize:"var(--lucent-font-size-xs)",color:a&&T>=a?"var(--lucent-danger-text)":"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-mono)",flexShrink:0,marginLeft:"var(--lucent-space-2)"},children:[T,a?`/${a}`:""]})]})]})});Pt.displayName="Textarea";const pn={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"]}},hn={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."}},fn={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)"}},mn={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)"}},gn=`
|
|
2
2
|
@keyframes lucent-chip-pulse {
|
|
3
3
|
0% { transform: scale(1); opacity: 0.6; }
|
|
4
4
|
100% { transform: scale(2.8); opacity: 0; }
|
|
5
|
-
}`;function ve({children:t,variant:a="neutral",size:n="md",onDismiss:i,onClick:r,leftIcon:o,swatch:s,dot:l=!1,pulse:c=!1,borderless:u=!1,ghost:h=!1,disabled:f=!1,style:S}){const w=cn[a],g=dn[n],[m,p]=d.useState(!1),b=l&&c,y=l&&!t,x=!f&&(i||r),T=`color-mix(in srgb, ${w.color} 8%, transparent)`,k=g.dotSize*3,v={display:"inline-flex",alignItems:"center",justifyContent:y?"center":void 0,gap:y?void 0:g.gap,height:y?k:g.height,width:y?k:void 0,padding:y?0:i?g.paddingDismiss:g.padding,fontSize:g.fontSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",lineHeight:1,borderRadius:y?"var(--lucent-radius-full)":"var(--lucent-radius-lg)",background:h?m&&x?T:"transparent":m&&x?w.hoverBg:w.bg,color:w.color,border:h||u?"1px solid transparent":`1px solid ${m&&x?w.hoverBorder:w.border}`,whiteSpace:"nowrap",boxSizing:"border-box",opacity:f?.5:1,transform:m&&x?"translateY(-1px)":"none",boxShadow:m&&x&&!h?`0 2px 4px ${w.hoverBorder}22`:"none",transition:["transform var(--lucent-duration-fast) 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(", "),cursor:x?"pointer":"default",...r?{outline:"none"}:{},...S},A=e.jsxs(e.Fragment,{children:[b&&e.jsx("style",{children:un}),s&&e.jsx("span",{style:{width:g.dotSize+2,height:g.dotSize+2,borderRadius:"50%",background:s,border:"1px solid rgba(0,0,0,0.1)",flexShrink:0}}),l&&!s&&e.jsxs("span",{style:{position:"relative",width:g.dotSize,height:g.dotSize,flexShrink:0},children:[e.jsx("span",{style:{position:"absolute",inset:0,borderRadius:"50%",background:"currentColor"}}),b&&e.jsx("span",{style:{position:"absolute",inset:0,borderRadius:"50%",background:"currentColor",animation:"lucent-chip-pulse 1.5s ease-out infinite"}})]}),o&&!s&&!l&&e.jsx("span",{style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:g.iconSize,height:g.iconSize,flexShrink:0},children:o}),t,i&&e.jsx("button",{type:"button",onClick:f?void 0:j=>{j.stopPropagation(),i()},disabled:f,"aria-label":"Dismiss",style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:g.iconSize+2,height:g.iconSize+2,padding:0,border:"none",borderRadius:"var(--lucent-radius-lg)",background:"transparent",color:"inherit",cursor:f?"not-allowed":"pointer",flexShrink:0,lineHeight:1},children:e.jsx("svg",{width:g.iconSize-2,height:g.iconSize-2,viewBox:"0 0 10 10",fill:"none",stroke:"currentColor",strokeWidth:1.5,strokeLinecap:"round",children:e.jsx("path",{d:"M2 2L8 8M8 2L2 8"})})})]}),C={onMouseEnter:()=>{f||p(!0)},onMouseLeave:()=>p(!1)};return r?e.jsx("button",{type:"button",onClick:f?void 0:r,disabled:f,style:v,...C,children:A}):e.jsx("span",{style:v,...C,children:A})}const pn={id:"chip",name:"Chip",tier:"atom",domain:"neutral",specVersion:"1.0",description:"A compact label for filters, tags, statuses, and categories. Combines the roles of Badge and Tag into a single flexible component.",designIntent:"Chip is the universal label primitive — use it anywhere you need a compact visual marker. It replaces both Badge (static status) and Tag (dismissible filter) with a single component. Use `onDismiss` for removable chips (filters, multi-select values). Use `onClick` for clickable/selectable chips (filter toggles, category navigation). Use `dot` for status indicators (online/offline). Use `dot` + `pulse` to show in-progress or live states (deploying, syncing, live incident). Use `ghost` + `dot` for subtle inline status that blends into surrounding text or table rows. Omit `children` with `dot` for a minimal dot-only indicator — great for table cells or avatar badges. Use `swatch` for color-coded categories. Use `leftIcon` for chips with leading icons (folders, file types, flags). Use `borderless` for a softer, filled-only appearance in dense UIs. Variant conveys semantic meaning — default to neutral for user-generated content.",props:[{name:"children",type:"ReactNode",required:!1,description:"Chip label content. When omitted with dot=true, renders as a compact dot-only indicator."},{name:"variant",type:"enum",required:!1,default:"neutral",description:"Colour scheme conveying semantic meaning.",enumValues:["neutral","accent","success","warning","danger","info"]},{name:"size",type:"enum",required:!1,default:"md",description:"Controls height, font size, and icon size.",enumValues:["sm","md","lg"]},{name:"onDismiss",type:"function",required:!1,description:"Renders an x button that calls this handler. Use for removable filters and multi-select values."},{name:"onClick",type:"function",required:!1,description:"Makes the chip clickable (renders as <button>). Use for filter toggles and category links."},{name:"leftIcon",type:"ReactNode",required:!1,description:"Icon or element rendered before the label (emoji, flag, avatar)."},{name:"swatch",type:"string",required:!1,description:"Hex color string. Renders a small color dot before the label."},{name:"dot",type:"boolean",required:!1,default:"false",description:"Renders a status dot using the variant colour. Omit children for a compact dot-only indicator."},{name:"pulse",type:"boolean",required:!1,default:"false",description:"Adds a pulsing ring animation to the status dot. Only applies when dot=true. Use for running, deploying, or live states."},{name:"borderless",type:"boolean",required:!1,default:"false",description:"Removes the border for a filled-only look."},{name:"ghost",type:"boolean",required:!1,default:"false",description:"Transparent background with text color only. Pairs well with dot for subtle inline statuses in tables or lists."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Dims the chip and prevents interaction."},{name:"style",type:"object",required:!1,description:"Inline style overrides."}],usageExamples:[{title:"Status dot",code:'<Chip variant="success" dot>Online</Chip>'},{title:"Pulsing status (in-progress)",code:'<Chip variant="warning" dot pulse>Deploying</Chip>'},{title:"Ghost status (inline/table)",code:'<Chip variant="success" ghost dot>Active</Chip>'},{title:"Ghost pulsing",code:'<Chip variant="danger" ghost dot pulse>Live incident</Chip>'},{title:"Dot only (minimal)",code:'<Chip variant="success" dot />'},{title:"Dot only pulsing",code:'<Chip variant="danger" dot pulse />'},{title:"Dismissible filter",code:"<Chip onDismiss={() => removeFilter('react')}>React</Chip>"},{title:"Color swatch",code:'<Chip swatch="#6366f1" onDismiss={() => {}}>Indigo</Chip>'},{title:"With icon",code:"<Chip leftIcon={<FolderIcon />} onDismiss={() => {}}>Documents</Chip>"},{title:"Clickable category",code:`<Chip variant="accent" onClick={() => navigate('/ux')}>UX</Chip>`},{title:"Borderless",code:'<Chip variant="warning" borderless>Pending</Chip>'},{title:"Static label",code:'<Chip variant="info">Beta</Chip>'}],compositionGraph:[],accessibility:{role:"group",notes:'When onClick is provided, renders as <button> with native button semantics. Dismiss button has aria-label="Dismiss" and stopPropagation to prevent parent click handlers.',keyboardInteractions:["Enter / Space — activates onClick or dismiss button when focused"]}},hn={xs:24,sm:32,md:40,lg:56,xl:80},fn={xs:"var(--lucent-font-size-xs)",sm:"var(--lucent-font-size-xs)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-lg)",xl:"var(--lucent-font-size-xl)"};function mn(t,a){var i,r,o;if(a)return a.slice(0,2).toUpperCase();const n=t.trim().split(/\s+/);return n.length===1?(((i=n[0])==null?void 0:i[0])??"").toUpperCase():((((r=n[0])==null?void 0:r[0])??"")+(((o=n[n.length-1])==null?void 0:o[0])??"")).toUpperCase()}function gn({src:t,alt:a,size:n="md",initials:i,style:r,...o}){const s=hn[n],l=mn(a,i),c={width:s,height:s,borderRadius:"var(--lucent-radius-full)",flexShrink:0,display:"inline-flex",alignItems:"center",justifyContent:"center",overflow:"hidden",boxSizing:"border-box",userSelect:"none",...r};return t?e.jsx("img",{src:t,alt:a,width:s,height:s,style:{...c,objectFit:"cover"},...o}):e.jsx("span",{role:"img","aria-label":a,style:{...c,background:"var(--lucent-accent-default)",color:"var(--lucent-accent-fg)",fontSize:fn[n],fontWeight:"var(--lucent-font-weight-semibold)",fontFamily:"var(--lucent-font-family-base)"},children:l})}const vn={id:"avatar",name:"Avatar",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A circular user image with initials fallback.",designIntent:"Always provide alt for accessibility — it is used to derive initials automatically when src is absent or fails. Use initials prop to override auto-derived initials (e.g. for non-Latin names). Size xs/sm suit table rows and compact lists; md is the default for comment threads; lg/xl for profile headers.",props:[{name:"src",type:"string",required:!1,description:"Image URL. Falls back to initials if omitted or fails to load."},{name:"alt",type:"string",required:!0,description:"Alt text and source for auto-derived initials."},{name:"size",type:"enum",required:!1,default:"md",description:"Diameter of the avatar.",enumValues:["xs","sm","md","lg","xl"]},{name:"initials",type:"string",required:!1,description:"Override auto-derived initials (max 2 characters)."}],usageExamples:[{title:"With image",code:'<Avatar src="/avatars/jane.jpg" alt="Jane Doe" />'},{title:"Initials fallback",code:'<Avatar alt="Jane Doe" />'},{title:"Large profile",code:'<Avatar src={user.avatar} alt={user.name} size="lg" />'},{title:"Custom initials",code:'<Avatar alt="张伟" initials="张" size="md" />'}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label"],notes:'When src is present, renders as <img> with alt. When showing initials, renders as <span role="img" aria-label>.'}},bn={xs:12,sm:16,md:24,lg:36},yn={xs:2.5,sm:2.5,md:2,lg:2};function qt({size:t="md",label:a="Loading…",color:n}){const i=bn[t],r=yn[t];return e.jsxs("span",{role:"status","aria-label":a,style:{display:"inline-flex",alignItems:"center",justifyContent:"center"},children:[e.jsxs("svg",{width:i,height:i,viewBox:"0 0 24 24",fill:"none","aria-hidden":!0,style:{animation:"lucent-spin 0.7s linear infinite",color:n??"currentColor"},children:[e.jsx("style",{children:"@keyframes lucent-spin { to { transform: rotate(360deg); } }"}),e.jsx("circle",{cx:12,cy:12,r:10,stroke:"currentColor",strokeWidth:r,strokeOpacity:.2}),e.jsx("path",{d:"M12 2a10 10 0 0 1 10 10",stroke:"currentColor",strokeWidth:r,strokeLinecap:"round"})]}),e.jsx("span",{style:{position:"absolute",width:1,height:1,overflow:"hidden",clip:"rect(0,0,0,0)",whiteSpace:"nowrap"},children:a})]})}const xn={id:"spinner",name:"Spinner",tier:"atom",domain:"neutral",specVersion:"0.1",description:"An animated loading indicator for async operations.",designIntent:"Use Spinner for indeterminate loading states of short duration (< 3s). For full-page or skeleton-level loading, prefer Skeleton instead. The label prop is visually hidden but read by screen readers — always set it to a meaningful description of what is loading.",props:[{name:"size",type:"enum",required:!1,default:"md",description:"Spinner diameter.",enumValues:["xs","sm","md","lg"]},{name:"label",type:"string",required:!1,default:"Loading…",description:"Visually hidden accessible label."},{name:"color",type:"string",required:!1,description:"Override colour (CSS value). Defaults to currentColor."}],usageExamples:[{title:"Default",code:"<Spinner />"},{title:"Inside button",code:'<Button loading><Spinner size="sm" label="Saving…" /></Button>'},{title:"Full-page overlay",code:`<div style={{ display: 'grid', placeItems: 'center', minHeight: '100vh' }}><Spinner size="lg" label="Loading dashboard…" /></div>`}],compositionGraph:[],accessibility:{role:"status",ariaAttributes:["aria-label"],notes:'The visible SVG is aria-hidden. The label is conveyed via a visually-hidden span inside role="status".'}};function Dt({orientation:t="horizontal",label:a,spacing:n="0",style:i}){return t==="vertical"?e.jsx("span",{role:"separator","aria-orientation":"vertical",style:{display:"inline-block",width:"1px",alignSelf:"stretch",background:"var(--lucent-border-default)",margin:`0 ${n}`,flexShrink:0,...i}}):a?e.jsxs("div",{role:"separator","aria-label":a,style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",margin:`${n} 0`,...i},children:[e.jsx("span",{style:{flex:1,height:"1px",background:"var(--lucent-border-default)"}}),e.jsx("span",{style:{fontSize:"var(--lucent-font-size-xs)",fontFamily:"var(--lucent-font-family-base)",color:"var(--lucent-text-secondary)",whiteSpace:"nowrap",letterSpacing:"var(--lucent-letter-spacing-wide)",textTransform:"uppercase"},children:a}),e.jsx("span",{style:{flex:1,height:"1px",background:"var(--lucent-border-default)"}})]}):e.jsx("hr",{role:"separator",style:{border:"none",borderTop:"1px solid var(--lucent-border-default)",margin:`${n} 0`,width:"100%",...i}})}const wn={id:"divider",name:"Divider",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A visual separator between content sections, horizontal or vertical.",designIntent:'Use horizontal Divider to separate sections in a layout. Use vertical Divider inline between sibling elements (e.g. nav links, toolbar buttons). Use the label prop for "OR" separators in auth flows or form sections — never use a plain text node next to a divider for this.',props:[{name:"orientation",type:"enum",required:!1,default:"horizontal",description:"Direction of the divider line.",enumValues:["horizontal","vertical"]},{name:"label",type:"string",required:!1,description:'Optional centered label (horizontal only). Common use: "OR", "AND", section titles.'},{name:"spacing",type:"string",required:!1,default:"0",description:'Margin on the axis perpendicular to the line. Defaults to 0 so parent gap-based layouts (Stack, Row) control spacing. Pass an explicit value like "var(--lucent-space-4)" for standalone use outside flex/grid containers.'}],usageExamples:[{title:"Section separator",code:"<Divider />"},{title:"With label",code:'<Divider label="OR" />'},{title:"Vertical in nav",code:`<nav style={{ display: 'flex', alignItems: 'center' }}><a>Home</a><Divider orientation="vertical" /><a>About</a></nav>`}],compositionGraph:[],accessibility:{role:"separator",ariaAttributes:["aria-orientation","aria-label"]}},kn={sm:14,md:16,lg:20},Sn={sm:"calc(var(--lucent-space-8) * 0.5 + 16px)",md:"calc(var(--lucent-space-10) * 0.5 + 20px)",lg:"calc(var(--lucent-space-12) * 0.5 + 24px)"},Tn=`
|
|
5
|
+
}`;function be({children:t,variant:r="neutral",size:n="md",onDismiss:i,onClick:a,leftIcon:o,swatch:l,dot:s=!1,pulse:d=!1,borderless:p=!1,ghost:h=!1,disabled:f=!1,style:w}){const b=fn[r],g=mn[n],[m,u]=c.useState(!1),y=s&&d,x=s&&!t,T=!f&&(i||a),S=`color-mix(in srgb, ${b.color} 8%, transparent)`,k=g.dotSize*3,v={display:"inline-flex",alignItems:"center",justifyContent:x?"center":void 0,gap:x?void 0:g.gap,height:x?k:g.height,width:x?k:void 0,padding:x?0:i?g.paddingDismiss:g.padding,fontSize:g.fontSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",lineHeight:1,borderRadius:x?"var(--lucent-radius-full)":"var(--lucent-radius-lg)",background:h?m&&T?S:"transparent":m&&T?b.hoverBg:b.bg,color:b.color,border:h||p?"1px solid transparent":`1px solid ${m&&T?b.hoverBorder:b.border}`,whiteSpace:"nowrap",boxSizing:"border-box",opacity:f?.5:1,transform:m&&T?"translateY(-1px)":"none",boxShadow:m&&T&&!h?`0 2px 4px ${b.hoverBorder}22`:"none",transition:["transform var(--lucent-duration-fast) 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(", "),cursor:T?"pointer":"default",...a?{outline:"none"}:{},...w},A=e.jsxs(e.Fragment,{children:[y&&e.jsx("style",{children:gn}),l&&e.jsx("span",{style:{width:g.dotSize+2,height:g.dotSize+2,borderRadius:"50%",background:l,border:"1px solid rgba(0,0,0,0.1)",flexShrink:0}}),s&&!l&&e.jsxs("span",{style:{position:"relative",width:g.dotSize,height:g.dotSize,flexShrink:0},children:[e.jsx("span",{style:{position:"absolute",inset:0,borderRadius:"50%",background:"currentColor"}}),y&&e.jsx("span",{style:{position:"absolute",inset:0,borderRadius:"50%",background:"currentColor",animation:"lucent-chip-pulse 1.5s ease-out infinite"}})]}),o&&!l&&!s&&e.jsx("span",{style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:g.iconSize,height:g.iconSize,flexShrink:0},children:o}),t,i&&e.jsx("button",{type:"button",onClick:f?void 0:M=>{M.stopPropagation(),i()},disabled:f,"aria-label":"Dismiss",style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:g.iconSize+2,height:g.iconSize+2,padding:0,border:"none",borderRadius:"var(--lucent-radius-lg)",background:"transparent",color:"inherit",cursor:f?"not-allowed":"pointer",flexShrink:0,lineHeight:1},children:e.jsx("svg",{width:g.iconSize-2,height:g.iconSize-2,viewBox:"0 0 10 10",fill:"none",stroke:"currentColor",strokeWidth:1.5,strokeLinecap:"round",children:e.jsx("path",{d:"M2 2L8 8M8 2L2 8"})})})]}),C={onMouseEnter:()=>{f||u(!0)},onMouseLeave:()=>u(!1)};return a?e.jsx("button",{type:"button",onClick:f?void 0:a,disabled:f,style:v,...C,children:A}):e.jsx("span",{style:v,...C,children:A})}const vn={id:"chip",name:"Chip",tier:"atom",domain:"neutral",specVersion:"1.0",description:"A compact label for filters, tags, statuses, and categories. Combines the roles of Badge and Tag into a single flexible component.",designIntent:"Chip is the universal label primitive — use it anywhere you need a compact visual marker. It replaces both Badge (static status) and Tag (dismissible filter) with a single component. Use `onDismiss` for removable chips (filters, multi-select values). Use `onClick` for clickable/selectable chips (filter toggles, category navigation). Use `dot` for status indicators (online/offline). Use `dot` + `pulse` to show in-progress or live states (deploying, syncing, live incident). Use `ghost` + `dot` for subtle inline status that blends into surrounding text or table rows. Omit `children` with `dot` for a minimal dot-only indicator — great for table cells or avatar badges. Use `swatch` for color-coded categories. Use `leftIcon` for chips with leading icons (folders, file types, flags). Use `borderless` for a softer, filled-only appearance in dense UIs. Variant conveys semantic meaning — default to neutral for user-generated content.",props:[{name:"children",type:"ReactNode",required:!1,description:"Chip label content. When omitted with dot=true, renders as a compact dot-only indicator."},{name:"variant",type:"enum",required:!1,default:"neutral",description:"Colour scheme conveying semantic meaning.",enumValues:["neutral","accent","success","warning","danger","info"]},{name:"size",type:"enum",required:!1,default:"md",description:"Controls height, font size, and icon size.",enumValues:["sm","md","lg"]},{name:"onDismiss",type:"function",required:!1,description:"Renders an x button that calls this handler. Use for removable filters and multi-select values."},{name:"onClick",type:"function",required:!1,description:"Makes the chip clickable (renders as <button>). Use for filter toggles and category links."},{name:"leftIcon",type:"ReactNode",required:!1,description:"Icon or element rendered before the label (emoji, flag, avatar)."},{name:"swatch",type:"string",required:!1,description:"Hex color string. Renders a small color dot before the label."},{name:"dot",type:"boolean",required:!1,default:"false",description:"Renders a status dot using the variant colour. Omit children for a compact dot-only indicator."},{name:"pulse",type:"boolean",required:!1,default:"false",description:"Adds a pulsing ring animation to the status dot. Only applies when dot=true. Use for running, deploying, or live states."},{name:"borderless",type:"boolean",required:!1,default:"false",description:"Removes the border for a filled-only look."},{name:"ghost",type:"boolean",required:!1,default:"false",description:"Transparent background with text color only. Pairs well with dot for subtle inline statuses in tables or lists."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Dims the chip and prevents interaction."},{name:"style",type:"object",required:!1,description:"Inline style overrides."}],usageExamples:[{title:"Status dot",code:'<Chip variant="success" dot>Online</Chip>'},{title:"Pulsing status (in-progress)",code:'<Chip variant="warning" dot pulse>Deploying</Chip>'},{title:"Ghost status (inline/table)",code:'<Chip variant="success" ghost dot>Active</Chip>'},{title:"Ghost pulsing",code:'<Chip variant="danger" ghost dot pulse>Live incident</Chip>'},{title:"Dot only (minimal)",code:'<Chip variant="success" dot />'},{title:"Dot only pulsing",code:'<Chip variant="danger" dot pulse />'},{title:"Dismissible filter",code:"<Chip onDismiss={() => removeFilter('react')}>React</Chip>"},{title:"Color swatch",code:'<Chip swatch="#6366f1" onDismiss={() => {}}>Indigo</Chip>'},{title:"With icon",code:"<Chip leftIcon={<FolderIcon />} onDismiss={() => {}}>Documents</Chip>"},{title:"Clickable category",code:`<Chip variant="accent" onClick={() => navigate('/ux')}>UX</Chip>`},{title:"Borderless",code:'<Chip variant="warning" borderless>Pending</Chip>'},{title:"Static label",code:'<Chip variant="info">Beta</Chip>'}],compositionGraph:[],accessibility:{role:"group",notes:'When onClick is provided, renders as <button> with native button semantics. Dismiss button has aria-label="Dismiss" and stopPropagation to prevent parent click handlers.',keyboardInteractions:["Enter / Space — activates onClick or dismiss button when focused"]}},bn={xs:24,sm:32,md:40,lg:56,xl:80},yn={xs:"var(--lucent-font-size-xs)",sm:"var(--lucent-font-size-xs)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-lg)",xl:"var(--lucent-font-size-xl)"};function xn(t,r){var i,a,o;if(r)return r.slice(0,2).toUpperCase();const n=t.trim().split(/\s+/);return n.length===1?(((i=n[0])==null?void 0:i[0])??"").toUpperCase():((((a=n[0])==null?void 0:a[0])??"")+(((o=n[n.length-1])==null?void 0:o[0])??"")).toUpperCase()}function wn({src:t,alt:r,size:n="md",initials:i,style:a,...o}){const l=bn[n],s=xn(r,i),d={width:l,height:l,borderRadius:"var(--lucent-radius-full)",flexShrink:0,display:"inline-flex",alignItems:"center",justifyContent:"center",overflow:"hidden",boxSizing:"border-box",userSelect:"none",...a};return t?e.jsx("img",{src:t,alt:r,width:l,height:l,style:{...d,objectFit:"cover"},...o}):e.jsx("span",{role:"img","aria-label":r,style:{...d,background:"var(--lucent-accent-default)",color:"var(--lucent-accent-fg)",fontSize:yn[n],fontWeight:"var(--lucent-font-weight-semibold)",fontFamily:"var(--lucent-font-family-base)"},children:s})}const kn={id:"avatar",name:"Avatar",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A circular user image with initials fallback.",designIntent:"Always provide alt for accessibility — it is used to derive initials automatically when src is absent or fails. Use initials prop to override auto-derived initials (e.g. for non-Latin names). Size xs/sm suit table rows and compact lists; md is the default for comment threads; lg/xl for profile headers.",props:[{name:"src",type:"string",required:!1,description:"Image URL. Falls back to initials if omitted or fails to load."},{name:"alt",type:"string",required:!0,description:"Alt text and source for auto-derived initials."},{name:"size",type:"enum",required:!1,default:"md",description:"Diameter of the avatar.",enumValues:["xs","sm","md","lg","xl"]},{name:"initials",type:"string",required:!1,description:"Override auto-derived initials (max 2 characters)."}],usageExamples:[{title:"With image",code:'<Avatar src="/avatars/jane.jpg" alt="Jane Doe" />'},{title:"Initials fallback",code:'<Avatar alt="Jane Doe" />'},{title:"Large profile",code:'<Avatar src={user.avatar} alt={user.name} size="lg" />'},{title:"Custom initials",code:'<Avatar alt="张伟" initials="张" size="md" />'}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label"],notes:'When src is present, renders as <img> with alt. When showing initials, renders as <span role="img" aria-label>.'}},Sn={xs:12,sm:16,md:24,lg:36},Tn={xs:2.5,sm:2.5,md:2,lg:2};function Dt({size:t="md",label:r="Loading…",color:n}){const i=Sn[t],a=Tn[t];return e.jsxs("span",{role:"status","aria-label":r,style:{display:"inline-flex",alignItems:"center",justifyContent:"center"},children:[e.jsxs("svg",{width:i,height:i,viewBox:"0 0 24 24",fill:"none","aria-hidden":!0,style:{animation:"lucent-spin 0.7s linear infinite",color:n??"currentColor"},children:[e.jsx("style",{children:"@keyframes lucent-spin { to { transform: rotate(360deg); } }"}),e.jsx("circle",{cx:12,cy:12,r:10,stroke:"currentColor",strokeWidth:a,strokeOpacity:.2}),e.jsx("path",{d:"M12 2a10 10 0 0 1 10 10",stroke:"currentColor",strokeWidth:a,strokeLinecap:"round"})]}),e.jsx("span",{style:{position:"absolute",width:1,height:1,overflow:"hidden",clip:"rect(0,0,0,0)",whiteSpace:"nowrap"},children:r})]})}const Cn={id:"spinner",name:"Spinner",tier:"atom",domain:"neutral",specVersion:"0.1",description:"An animated loading indicator for async operations.",designIntent:"Use Spinner for indeterminate loading states of short duration (< 3s). For full-page or skeleton-level loading, prefer Skeleton instead. The label prop is visually hidden but read by screen readers — always set it to a meaningful description of what is loading.",props:[{name:"size",type:"enum",required:!1,default:"md",description:"Spinner diameter.",enumValues:["xs","sm","md","lg"]},{name:"label",type:"string",required:!1,default:"Loading…",description:"Visually hidden accessible label."},{name:"color",type:"string",required:!1,description:"Override colour (CSS value). Defaults to currentColor."}],usageExamples:[{title:"Default",code:"<Spinner />"},{title:"Inside button",code:'<Button loading><Spinner size="sm" label="Saving…" /></Button>'},{title:"Full-page overlay",code:`<div style={{ display: 'grid', placeItems: 'center', minHeight: '100vh' }}><Spinner size="lg" label="Loading dashboard…" /></div>`}],compositionGraph:[],accessibility:{role:"status",ariaAttributes:["aria-label"],notes:'The visible SVG is aria-hidden. The label is conveyed via a visually-hidden span inside role="status".'}};function Lt({orientation:t="horizontal",label:r,spacing:n="0",style:i}){return t==="vertical"?e.jsx("span",{role:"separator","aria-orientation":"vertical",style:{display:"inline-block",width:"1px",alignSelf:"stretch",background:"var(--lucent-border-default)",margin:`0 ${n}`,flexShrink:0,...i}}):r?e.jsxs("div",{role:"separator","aria-label":r,style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",margin:`${n} 0`,...i},children:[e.jsx("span",{style:{flex:1,height:"1px",background:"var(--lucent-border-default)"}}),e.jsx("span",{style:{fontSize:"var(--lucent-font-size-xs)",fontFamily:"var(--lucent-font-family-base)",color:"var(--lucent-text-secondary)",whiteSpace:"nowrap",letterSpacing:"var(--lucent-letter-spacing-wide)",textTransform:"uppercase"},children:r}),e.jsx("span",{style:{flex:1,height:"1px",background:"var(--lucent-border-default)"}})]}):e.jsx("hr",{role:"separator",style:{border:"none",borderTop:"1px solid var(--lucent-border-default)",margin:`${n} 0`,width:"100%",...i}})}const jn={id:"divider",name:"Divider",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A visual separator between content sections, horizontal or vertical.",designIntent:'Use horizontal Divider to separate sections in a layout. Use vertical Divider inline between sibling elements (e.g. nav links, toolbar buttons). Use the label prop for "OR" separators in auth flows or form sections — never use a plain text node next to a divider for this.',props:[{name:"orientation",type:"enum",required:!1,default:"horizontal",description:"Direction of the divider line.",enumValues:["horizontal","vertical"]},{name:"label",type:"string",required:!1,description:'Optional centered label (horizontal only). Common use: "OR", "AND", section titles.'},{name:"spacing",type:"string",required:!1,default:"0",description:'Margin on the axis perpendicular to the line. Defaults to 0 so parent gap-based layouts (Stack, Row) control spacing. Pass an explicit value like "var(--lucent-space-4)" for standalone use outside flex/grid containers.'}],usageExamples:[{title:"Section separator",code:"<Divider />"},{title:"With label",code:'<Divider label="OR" />'},{title:"Vertical in nav",code:`<nav style={{ display: 'flex', alignItems: 'center' }}><a>Home</a><Divider orientation="vertical" /><a>About</a></nav>`}],compositionGraph:[],accessibility:{role:"separator",ariaAttributes:["aria-orientation","aria-label"]}},In={sm:14,md:16,lg:20},Mn={sm:"calc(var(--lucent-space-8) * 0.5 + 16px)",md:"calc(var(--lucent-space-10) * 0.5 + 20px)",lg:"calc(var(--lucent-space-12) * 0.5 + 24px)"},En=`
|
|
6
6
|
@keyframes lucent-cb-pop {
|
|
7
7
|
0% { transform: scale(1); }
|
|
8
8
|
35% { transform: scale(0.82); }
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
60% { transform: scale(1.15) rotate(2deg); }
|
|
15
15
|
100% { opacity: 1; transform: scale(1) rotate(0deg); }
|
|
16
16
|
}
|
|
17
|
-
`,
|
|
17
|
+
`,Ae=c.forwardRef(({label:t,size:r="md",indeterminate:n=!1,contained:i=!1,helperText:a,checked:o,defaultChecked:l,disabled:s,id:d,onChange:p,style:h,...f},w)=>{const b=c.useRef(null),g=d??`lucent-checkbox-${Math.random().toString(36).slice(2,7)}`,m=In[r],u=o!==void 0,[y,x]=c.useState(l??!1),T=u?!!o:y,S=c.useRef(T),[k,v]=c.useState(0);c.useEffect(()=>{!s&&S.current!==T&&(S.current=T,v(I=>I+1))},[T,s]);const A=c.useCallback(I=>{b.current=I,typeof w=="function"?w(I):w&&(w.current=I)},[w]);c.useEffect(()=>{b.current&&(b.current.indeterminate=n)},[n]);const C=I=>{u||x(I.target.checked),p==null||p(I)},M=s?"var(--lucent-text-disabled)":"var(--lucent-accent-fg)",q={width:m,height:m,borderRadius:"4px",border:`1.5px solid ${s?"transparent":T||n?"var(--lucent-accent-default)":"var(--lucent-border-strong)"}`,background:s?"var(--lucent-surface-secondary)":T||n?"var(--lucent-accent-default)":"var(--lucent-surface)",display:"inline-flex",alignItems:"center",justifyContent:"center",flexShrink:0,transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), border-color var(--lucent-duration-fast) var(--lucent-easing-default)",animation:k>0?"lucent-cb-pop 220ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards":void 0},B=e.jsxs("label",{style:{display:"inline-flex",alignItems:a?"flex-start":"center",gap:"var(--lucent-space-2)",cursor:s?"not-allowed":"pointer",fontFamily:"var(--lucent-font-family-base)",fontSize:r==="sm"?"var(--lucent-font-size-sm)":"var(--lucent-font-size-md)",color:s?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",userSelect:"none",...i?{}:h},children:[e.jsx("input",{ref:A,type:"checkbox",id:g,checked:u?o:y,disabled:s,onChange:C,style:{position:"absolute",opacity:0,width:0,height:0,margin:0,pointerEvents:"none"},...f}),e.jsxs("span",{"aria-hidden":!0,style:q,children:[T&&!n&&e.jsx("svg",{width:m-4,height:m-4,viewBox:"0 0 10 10",fill:"none",style:{animation:"lucent-cb-mark 200ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards"},children:e.jsx("path",{d:"M1.5 5L4 7.5L8.5 2.5",stroke:M,strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round"})}),n&&e.jsx("svg",{width:m-4,height:m-4,viewBox:"0 0 10 10",fill:"none",style:{animation:"lucent-cb-mark 200ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards"},children:e.jsx("path",{d:"M2 5H8",stroke:M,strokeWidth:1.5,strokeLinecap:"round"})})]},k),(t||a)&&e.jsxs("span",{style:{display:"flex",flexDirection:"column"},children:[t&&e.jsx("span",{style:{fontWeight:a?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",lineHeight:a?1.4:1},children:t}),a&&e.jsx("span",{style:{fontSize:"var(--lucent-font-size-xs)",color:s?"var(--lucent-text-disabled)":"var(--lucent-text-secondary)",marginTop:"2px"},children:a})]})]});return e.jsxs(e.Fragment,{children:[e.jsx("style",{children:En}),i?e.jsx("div",{style:{border:"1px solid var(--lucent-border-strong)",borderRadius:"var(--lucent-radius-lg)",...a?{}:{minHeight:Mn[r]},padding:a?"var(--lucent-space-3)":"0 var(--lucent-space-3)",display:"flex",alignItems:a?"flex-start":"center",background:T&&!s?"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)":"transparent",transition:"border-color var(--lucent-duration-fast) var(--lucent-easing-default), background var(--lucent-duration-fast) var(--lucent-easing-default)",cursor:s?"not-allowed":"pointer",...h},onClick:I=>{var L;s||I.target===I.currentTarget&&((L=b.current)==null||L.click())},children:B}):B]})});Ae.displayName="Checkbox";const zn={id:"checkbox",name:"Checkbox",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A binary selection control for boolean values or multi-select lists.",designIntent:'Checkboxes represent independent boolean choices — they do not affect each other. Use a Checkbox for settings that take effect immediately (e.g. "Remember me") or for selecting multiple items from a list. When only one option may be active at a time, use Radio instead. The indeterminate state communicates a "select all" parent whose children are partially checked — never use it for a third logical state. Use the contained variant when you want to add visual emphasis to individual options — for example, plan selection cards, feature toggles, or consent checkboxes. Contained is especially useful when checkboxes are standalone or unrelated to each other, since the border gives each option its own visual weight. Pair with helperText to provide additional context without cluttering the label.',props:[{name:"checked",type:"boolean",required:!1,description:"Controlled checked state. Pair with onChange for controlled usage."},{name:"defaultChecked",type:"boolean",required:!1,default:"false",description:"Initial checked state for uncontrolled usage."},{name:"onChange",type:"function",required:!1,description:"Called when the checked state changes."},{name:"label",type:"string",required:!1,description:"Visible label rendered beside the checkbox."},{name:"indeterminate",type:"boolean",required:!1,default:"false",description:"Displays a dash to indicate a partially-checked parent state."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Prevents interaction and dims the control."},{name:"size",type:"enum",required:!1,default:"md",description:"Size of the checkbox box and label text.",enumValues:["sm","md","lg"]},{name:"contained",type:"boolean",required:!1,default:"false",description:"Wraps the checkbox in a bordered container. Use for standalone choices that need visual emphasis — plan cards, feature toggles, consent items. The border highlights with the accent colour when checked."},{name:"helperText",type:"string",required:!1,description:"Secondary text below the label for additional context. The label becomes medium-weight for visual hierarchy."}],usageExamples:[{title:"Controlled",code:'<Checkbox checked={agreed} onChange={e => setAgreed(e.target.checked)} label="I agree to the terms" />'},{title:"Uncontrolled",code:'<Checkbox defaultChecked label="Send me updates" />'},{title:"Indeterminate",code:'<Checkbox indeterminate label="Select all" />'},{title:"Disabled",code:'<Checkbox disabled label="Unavailable option" />'},{title:"Contained with helper",code:'<Checkbox contained label="Pro plan" helperText="Unlimited projects, 100 GB storage" />'},{title:"Contained standalone",code:'<Checkbox contained label="I accept the terms and conditions" />'}],compositionGraph:[],accessibility:{role:"checkbox",ariaAttributes:["aria-checked","aria-disabled"],keyboardInteractions:["Space — toggles checked state"]}},An=`
|
|
18
18
|
@keyframes lucent-radio-pop {
|
|
19
19
|
0% { transform: scale(1); }
|
|
20
20
|
35% { transform: scale(0.82); }
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
60% { transform: scale(1.2); }
|
|
27
27
|
100% { opacity: 1; transform: scale(1); }
|
|
28
28
|
}
|
|
29
|
-
`,
|
|
29
|
+
`,Ft=c.createContext(null);function $t({name:t,value:r,onChange:n,disabled:i,orientation:a="vertical",label:o,children:l}){return e.jsx(Ft.Provider,{value:{name:t,value:r,onChange:n,disabled:i??!1},children:e.jsx("div",{role:"radiogroup","aria-label":o,style:{display:"flex",flexDirection:a==="vertical"?"column":"row",gap:a==="vertical"?"var(--lucent-space-3)":"var(--lucent-space-4)",flexWrap:"wrap"},children:l})})}const qn={sm:14,md:16,lg:20},Bn={sm:"calc(var(--lucent-space-8) * 0.5 + 16px)",md:"calc(var(--lucent-space-10) * 0.5 + 20px)",lg:"calc(var(--lucent-space-12) * 0.5 + 24px)"},Rn={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-md)",lg:"var(--lucent-font-size-md)"};function Pn({value:t,label:r,size:n="md",contained:i=!1,helperText:a,disabled:o,id:l,onChange:s,checked:d,style:p,...h}){const f=c.useContext(Ft),w=l??`lucent-radio-${Math.random().toString(36).slice(2,7)}`,b=qn[n],g=o??(f==null?void 0:f.disabled)??!1,m=f?f.value===t:!!d,u=c.useRef(m),[y,x]=c.useState(0);c.useEffect(()=>{!g&&u.current!==m&&(u.current=m,x(A=>A+1))},[m,g]);const T=A=>{f==null||f.onChange(t),s==null||s(A)},S={width:b/2,height:b/2,borderRadius:"50%",background:g?"var(--lucent-text-disabled)":"var(--lucent-accent-fg)",animation:m?"lucent-radio-dot 200ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards":void 0,opacity:m?1:0},k={width:b,height:b,borderRadius:"50%",border:`1.5px solid ${g?"transparent":m?"var(--lucent-accent-default)":"var(--lucent-border-strong)"}`,background:g?"var(--lucent-surface-secondary)":m?"var(--lucent-accent-default)":"var(--lucent-surface)",display:"inline-flex",alignItems:"center",justifyContent:"center",flexShrink:0,transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), border-color var(--lucent-duration-fast) var(--lucent-easing-default)",animation:y>0?"lucent-radio-pop 220ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards":void 0},v=e.jsxs("label",{style:{display:"inline-flex",alignItems:a?"flex-start":"center",gap:"var(--lucent-space-2)",cursor:g?"not-allowed":"pointer",fontFamily:"var(--lucent-font-family-base)",fontSize:Rn[n],color:g?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",userSelect:"none",...i?{}:p},children:[e.jsx("input",{type:"radio",id:w,value:t,name:(f==null?void 0:f.name)??h.name,checked:m,disabled:g,onChange:T,style:{position:"absolute",opacity:0,width:0,height:0,margin:0,pointerEvents:"none"},...h}),e.jsx("span",{"aria-hidden":!0,style:k,children:e.jsx("span",{style:S})},y),r||a?e.jsxs("span",{style:{display:"flex",flexDirection:"column"},children:[r&&e.jsx("span",{style:{fontWeight:a?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",lineHeight:a?1.3:1},children:r}),a&&e.jsx("span",{style:{fontSize:"var(--lucent-font-size-xs)",color:g?"var(--lucent-text-disabled)":"var(--lucent-text-secondary)",marginTop:"2px"},children:a})]}):null]});return e.jsxs(e.Fragment,{children:[e.jsx("style",{children:An}),i?e.jsx("div",{style:{border:"1px solid var(--lucent-border-strong)",borderRadius:"var(--lucent-radius-lg)",...a?{padding:"var(--lucent-space-3)"}:{minHeight:Bn[n],padding:"0 var(--lucent-space-3)",display:"flex",alignItems:"center"},background:m&&!g?"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)":"transparent",transition:"border-color var(--lucent-duration-fast) var(--lucent-easing-default), background var(--lucent-duration-fast) var(--lucent-easing-default)",cursor:g?"not-allowed":"pointer",...p},onClick:A=>{if(!g&&A.target===A.currentTarget){const C=A.currentTarget.querySelector("input");C==null||C.click()}},children:v}):v]})}function Dn({defaultValue:t="",onChange:r,...n}){const[i,a]=c.useState(t);return e.jsx($t,{...n,value:i,onChange:o=>{a(o),r==null||r(o)}})}const Ln={id:"radio",name:"Radio",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A mutually exclusive selection control. Use RadioGroup to manage a set of options.",designIntent:"Radio buttons enforce a single selection from a small set of options (typically 2–6). Always wrap Radio in a RadioGroup so name and selection state are shared automatically. For larger option sets (7+) prefer a Select. For independent true/false choices, use a Checkbox. RadioGroup orientation should match how the options relate — vertical for distinct choices, horizontal for brief inline options (e.g. Yes / No).",props:[{name:"value",type:"string",required:!0,description:"The value submitted when this radio is selected."},{name:"label",type:"string",required:!1,description:"Visible label rendered beside the radio button."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Prevents interaction. Inherits group-level disabled when inside RadioGroup."},{name:"size",type:"enum",required:!1,default:"md",description:"Size of the radio button circle and label text.",enumValues:["sm","md","lg"]},{name:"contained",type:"boolean",required:!1,default:"false",description:"Wraps the radio in a bordered container. Highlights with accent border and subtle background when selected. Use for plan/option cards where each choice needs visual emphasis."},{name:"helperText",type:"string",required:!1,description:"Secondary text below the label for additional context. Label becomes medium-weight for visual hierarchy."}],usageExamples:[{title:"Controlled RadioGroup",code:`
|
|
30
30
|
<RadioGroup name="plan" value={plan} onChange={setPlan}>
|
|
31
31
|
<Radio value="free" label="Free" />
|
|
32
32
|
<Radio value="pro" label="Pro" />
|
|
@@ -44,18 +44,18 @@
|
|
|
44
44
|
<RadioGroup name="tier" value="basic" onChange={() => {}} disabled>
|
|
45
45
|
<Radio value="basic" label="Basic" />
|
|
46
46
|
<Radio value="advanced" label="Advanced" />
|
|
47
|
-
</RadioGroup>`.trim()}],compositionGraph:[{componentId:"radio-group",componentName:"RadioGroup",role:"container",required:!0},{componentId:"radio",componentName:"Radio",role:"item",required:!0}],accessibility:{role:"radio",ariaAttributes:["aria-checked","aria-disabled"],keyboardInteractions:["Arrow keys — move selection within the group","Space — selects the focused radio"]}},
|
|
47
|
+
</RadioGroup>`.trim()}],compositionGraph:[{componentId:"radio-group",componentName:"RadioGroup",role:"container",required:!0},{componentId:"radio",componentName:"Radio",role:"item",required:!0}],accessibility:{role:"radio",ariaAttributes:["aria-checked","aria-disabled"],keyboardInteractions:["Arrow keys — move selection within the group","Space — selects the focused radio"]}},Fn={id:"toggle",name:"Toggle",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A sliding switch for immediately-applied binary settings.",designIntent:'Toggles are for settings that take effect the moment they are flipped — no Save button needed. If the action requires a confirmation step or only applies on form submit, use a Checkbox instead. The "on" state is visually represented by the accent colour so the meaning is clear without relying on text alone. Keep the label short (2–4 words) and phrase it as the enabled state (e.g. "Dark mode", not "Enable dark mode").',props:[{name:"checked",type:"boolean",required:!1,description:"Controlled on/off state. Pair with onChange for controlled usage."},{name:"defaultChecked",type:"boolean",required:!1,default:"false",description:"Initial state for uncontrolled usage."},{name:"onChange",type:"function",required:!1,description:"Called when the toggle is flipped."},{name:"label",type:"string",required:!1,description:"Visible label rendered beside the toggle."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls the track and thumb size.",enumValues:["sm","md","lg"]},{name:"contained",type:"boolean",required:!1,default:"false",description:"Wraps the toggle in a bordered container. Highlights with accent border and subtle background when checked."},{name:"helperText",type:"string",required:!1,description:"Secondary text below the label for additional context. Label becomes medium-weight for visual hierarchy."},{name:"align",type:"enum",required:!1,default:"left",description:'Position of the toggle track relative to the label. Use "right" to push the track to the far end of the container.',enumValues:["left","right"]},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Prevents interaction and dims the control."}],usageExamples:[{title:"Controlled",code:'<Toggle checked={darkMode} onChange={e => setDarkMode(e.target.checked)} label="Dark mode" />'},{title:"Uncontrolled",code:'<Toggle defaultChecked label="Email notifications" />'},{title:"Sizes",code:`<Toggle size="sm" label="Compact" />
|
|
48
48
|
<Toggle size="md" label="Default" />
|
|
49
|
-
<Toggle size="lg" label="Large" />`},{title:"Contained with helper",code:'<Toggle contained label="Dark mode" helperText="Use dark colour scheme across the app" />'},{title:"Right-aligned",code:'<Toggle align="right" contained label="Notifications" helperText="Receive email alerts" />'},{title:"Disabled",code:'<Toggle disabled label="Unavailable setting" />'}],compositionGraph:[],accessibility:{role:"switch",ariaAttributes:["aria-checked","aria-disabled"],keyboardInteractions:["Space — toggles the switch state"]}}
|
|
49
|
+
<Toggle size="lg" label="Large" />`},{title:"Contained with helper",code:'<Toggle contained label="Dark mode" helperText="Use dark colour scheme across the app" />'},{title:"Right-aligned",code:'<Toggle align="right" contained label="Notifications" helperText="Receive email alerts" />'},{title:"Disabled",code:'<Toggle disabled label="Unavailable setting" />'}],compositionGraph:[],accessibility:{role:"switch",ariaAttributes:["aria-checked","aria-disabled"],keyboardInteractions:["Space — toggles the switch state"]}},$n={id:"select",name:"Select",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A dropdown for choosing one option from a list.",designIntent:"Select is best for 5–15 options where showing all at once would be too noisy. For fewer than 5 options, prefer RadioGroup (always-visible, faster to scan). For search-filtered or async options, a Combobox (Wave 3) is more appropriate. Always provide a placeholder when no default is sensible — this communicates the field is required and prevents silent zero-state submissions.",props:[{name:"options",type:"array",required:!0,description:"Array of { value, label, disabled? } option objects."},{name:"value",type:"string",required:!1,description:"Controlled selected value. Pair with onChange."},{name:"onChange",type:"function",required:!1,description:"Called when the selected value changes."},{name:"placeholder",type:"string",required:!1,description:"Disabled first option shown when no value is selected."},{name:"label",type:"string",required:!1,description:"Visible label rendered above the select."},{name:"helperText",type:"string",required:!1,description:"Supplementary hint shown below the select."},{name:"errorText",type:"string",required:!1,description:"Validation error message. Replaces helperText and applies error styling."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls the height of the select control.",enumValues:["sm","md","lg"]},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Prevents interaction."}],usageExamples:[{title:"Controlled",code:`
|
|
50
50
|
<Select
|
|
51
51
|
label="Country"
|
|
52
52
|
placeholder="Choose a country"
|
|
53
53
|
options={countries}
|
|
54
54
|
value={country}
|
|
55
55
|
onChange={e => setCountry(e.target.value)}
|
|
56
|
-
/>`.trim()},{title:"With validation error",code:'<Select label="Role" options={roles} errorText="Please select a role" />'},{title:"With helper text",code:'<Select label="Timezone" options={timezones} helperText="Used for scheduling notifications" />'}],compositionGraph:[],accessibility:{role:"combobox",ariaAttributes:["aria-invalid","aria-describedby"],keyboardInteractions:["Enter / Space — opens the option list","Arrow keys — navigate options","Escape — closes the list"]}},Pn={neutral:{bg:"var(--lucent-surface-secondary)",color:"var(--lucent-text-secondary)",border:"var(--lucent-border-default)",dismissHover:"var(--lucent-border-strong)"},accent:{bg:"var(--lucent-accent-subtle)",color:"var(--lucent-accent-default)",border:"var(--lucent-accent-subtle)",dismissHover:"var(--lucent-accent-default)"},success:{bg:"var(--lucent-success-subtle)",color:"var(--lucent-success-text)",border:"var(--lucent-success-subtle)",dismissHover:"var(--lucent-success-default)"},warning:{bg:"var(--lucent-warning-subtle)",color:"var(--lucent-warning-text)",border:"var(--lucent-warning-subtle)",dismissHover:"var(--lucent-warning-default)"},danger:{bg:"var(--lucent-danger-subtle)",color:"var(--lucent-danger-text)",border:"var(--lucent-danger-subtle)",dismissHover:"var(--lucent-danger-default)"},info:{bg:"var(--lucent-info-subtle)",color:"var(--lucent-info-text)",border:"var(--lucent-info-subtle)",dismissHover:"var(--lucent-info-default)"}},Rn={sm:{fontSize:"var(--lucent-font-size-xs)",height:"20px",padding:"0 var(--lucent-space-3)",iconSize:10,gap:"var(--lucent-space-1)"},md:{fontSize:"var(--lucent-font-size-sm)",height:"24px",padding:"0 var(--lucent-space-3)",iconSize:12,gap:"var(--lucent-space-1)"},lg:{fontSize:"var(--lucent-font-size-md)",height:"28px",padding:"0 var(--lucent-space-4)",iconSize:14,gap:"var(--lucent-space-2)"}};function Ln({children:t,variant:a="neutral",size:n="md",onDismiss:i,disabled:r}){const o=Pn[a],s=Rn[n],[l,c]=d.useState(!1),u=!r&&i;return e.jsxs("span",{onMouseEnter:()=>{r||c(!0)},onMouseLeave:()=>c(!1),style:{display:"inline-flex",alignItems:"center",gap:s.gap,height:s.height,padding:i?`0 var(--lucent-space-2) 0 ${s.padding.split(" ")[1]}`:s.padding,fontSize:s.fontSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",lineHeight:1,borderRadius:"var(--lucent-radius-lg)",background:o.bg,color:o.color,border:`1px solid ${l&&!r?o.dismissHover:o.border}`,whiteSpace:"nowrap",boxSizing:"border-box",opacity:r?.5:1,transform:l&&u?"translateY(-1px)":"none",boxShadow:l&&u?`0 2px 4px ${o.dismissHover}22`:"none",transition:"transform var(--lucent-duration-fast) var(--lucent-easing-default), box-shadow var(--lucent-duration-fast) var(--lucent-easing-default), border-color var(--lucent-duration-fast) var(--lucent-easing-default)",cursor:u?"pointer":"default"},children:[t,i&&e.jsx("button",{type:"button",onClick:r?void 0:h=>{h.stopPropagation(),i==null||i()},disabled:r,"aria-label":"Dismiss",style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:s.iconSize+4,height:s.iconSize+4,padding:0,border:"none",borderRadius:"var(--lucent-radius-lg)",background:"transparent",color:"inherit",cursor:r?"not-allowed":"pointer",flexShrink:0,lineHeight:1},onMouseEnter:h=>{r||(h.currentTarget.style.background=o.dismissHover+"33")},onMouseLeave:h=>{h.currentTarget.style.background="transparent"},children:e.jsx("svg",{width:s.iconSize,height:s.iconSize,viewBox:"0 0 10 10",fill:"none",stroke:"currentColor",strokeWidth:1.5,strokeLinecap:"round",children:e.jsx("path",{d:"M2 2L8 8M8 2L2 8"})})})]})}const Fn={id:"tag",name:"Tag",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A dismissible label for applied filters, selected values, or categorisation.",designIntent:"Tags represent user-applied selections that can be removed — filters, multi-select values, categories. They differ from Badge in that they are interactive: provide onDismiss when the user should be able to remove the tag. Without onDismiss they render as a static pill (identical to Badge in purpose). Use semantic variants to match meaning; default to neutral for user-generated content where no semantic applies.",props:[{name:"children",type:"ReactNode",required:!0,description:"Tag label content."},{name:"variant",type:"enum",required:!1,default:"neutral",description:"Colour scheme conveying semantic meaning.",enumValues:["neutral","accent","success","warning","danger","info"]},{name:"size",type:"enum",required:!1,default:"md",description:"Controls height and font size.",enumValues:["sm","md","lg"]},{name:"onDismiss",type:"function",required:!1,description:"When provided, renders an × button that calls this handler on click."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Dims the tag and prevents the dismiss action."}],usageExamples:[{title:"Dismissible filter",code:"<Tag onDismiss={() => removeFilter('react')}>React</Tag>"},{title:"Multi-select value",code:'<Tag variant="accent" onDismiss={() => deselect(id)}>Jane Doe</Tag>'},{title:"Static category",code:'<Tag variant="info">Beta</Tag>'},{title:"Danger (removable)",code:'<Tag variant="danger" onDismiss={handleRemove}>Blocked</Tag>'}],compositionGraph:[],accessibility:{role:"group",notes:'The dismiss button has aria-label="Dismiss" and is keyboard-focusable.',keyboardInteractions:["Enter / Space — activates the dismiss button when focused"]}},_=5,$n={top:{bottom:"100%",left:"50%",transform:"translateX(-50%)",marginBottom:_+3},bottom:{top:"100%",left:"50%",transform:"translateX(-50%)",marginTop:_+3},left:{right:"100%",top:"50%",transform:"translateY(-50%)",marginRight:_+3},right:{left:"100%",top:"50%",transform:"translateY(-50%)",marginLeft:_+3}},Nn={top:{bottom:-_,left:"50%",transform:"translateX(-50%)",borderWidth:`${_}px ${_}px 0 ${_}px`,borderColor:"var(--lucent-text-primary) transparent transparent transparent"},bottom:{top:-_,left:"50%",transform:"translateX(-50%)",borderWidth:`0 ${_}px ${_}px ${_}px`,borderColor:"transparent transparent var(--lucent-text-primary) transparent"},left:{right:-_,top:"50%",transform:"translateY(-50%)",borderWidth:`${_}px 0 ${_}px ${_}px`,borderColor:"transparent transparent transparent var(--lucent-text-primary)"},right:{left:-_,top:"50%",transform:"translateY(-50%)",borderWidth:`${_}px ${_}px ${_}px 0`,borderColor:"transparent var(--lucent-text-primary) transparent transparent"}};function be({content:t,children:a,placement:n="top",delay:i=300,open:r}){const[o,s]=d.useState(!1),l=r!==void 0?r:o,c=d.useRef(null),u=()=>{c.current=setTimeout(()=>s(!0),i)},h=()=>{c.current&&clearTimeout(c.current),s(!1)};return t?e.jsxs("span",{style:{position:"relative",display:"inline-flex"},onMouseEnter:u,onMouseLeave:h,onFocus:u,onBlur:h,children:[a,l&&e.jsxs("span",{role:"tooltip",style:{position:"absolute",...$n[n],background:"var(--lucent-text-primary)",color:"var(--lucent-bg-base)",padding:"5px 10px",borderRadius:"var(--lucent-radius-md)",fontSize:"var(--lucent-font-size-xs)",fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",lineHeight:"var(--lucent-line-height-base)",whiteSpace:"nowrap",zIndex:9999,pointerEvents:"none",boxShadow:"var(--lucent-shadow-md)"},children:[t,e.jsx("span",{"aria-hidden":!0,style:{position:"absolute",width:0,height:0,borderStyle:"solid",...Nn[n]}})]})]}):e.jsx(e.Fragment,{children:a})}const Wn={id:"tooltip",name:"Tooltip",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A transient label that appears on hover or focus to explain an element.",designIntent:'Tooltips surface supplementary context that would clutter the UI if shown permanently — keyboard shortcut hints, icon button labels, truncated text expansions. They must never contain critical information (errors, required actions) because they are invisible until hovered. Keep content to one short phrase; avoid wrapping. Use placement to avoid viewport edges — "top" is the default and works in most cases.',props:[{name:"content",type:"ReactNode",required:!0,description:"Text or element shown inside the tooltip. Keep it short (under 80 chars)."},{name:"children",type:"ReactNode",required:!0,description:"The element that triggers the tooltip on hover/focus."},{name:"placement",type:"enum",required:!1,default:"top",description:"Position of the tooltip relative to the trigger.",enumValues:["top","bottom","left","right"]},{name:"delay",type:"number",required:!1,default:"300",description:"Milliseconds before the tooltip appears. Prevents flicker on fast cursor movement."}],usageExamples:[{title:"Icon button label",code:'<Tooltip content="Copy to clipboard"><IconButton icon={<CopyIcon />} /></Tooltip>'},{title:"Keyboard shortcut hint",code:'<Tooltip content="⌘K"><Button variant="ghost">Search</Button></Tooltip>'},{title:"Bottom placement",code:'<Tooltip content="Opens in a new tab" placement="bottom"><a href="…">Link</a></Tooltip>'},{title:"No delay (instant)",code:'<Tooltip content="Delete" delay={0}><Button variant="ghost">🗑</Button></Tooltip>'}],compositionGraph:[],accessibility:{role:"tooltip",ariaAttributes:['role="tooltip"'],notes:'The tooltip is shown on both hover and focus, making it accessible to keyboard users. Content is exposed via role="tooltip".'}},On={xs:12,sm:14,md:16,lg:20,xl:24};function Vn({children:t,size:a="md",label:n,color:i,style:r}){const o=On[a];return e.jsx("span",{role:n?"img":void 0,"aria-label":n,"aria-hidden":n?void 0:!0,style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:o,height:o,flexShrink:0,color:i??"currentColor",...r},children:t})}const Hn={id:"icon",name:"Icon",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A sized, accessible wrapper for any SVG or icon-set component.",designIntent:"Icon is intentionally icon-set agnostic — pass any SVG or third-party icon as children and it handles sizing and accessibility automatically. Use label for standalone icons that convey meaning without adjacent text (e.g. a close button). Omit label for decorative icons next to text, where the text already provides context. colour inherits from the parent by default via currentColor — override only when the icon must differ from its surrounding text.",props:[{name:"children",type:"ReactNode",required:!0,description:"The icon to render — any SVG element or icon-set component."},{name:"size",type:"enum",required:!1,default:"md",description:"Constrains the icon to a fixed square (xs=12, sm=14, md=16, lg=20, xl=24).",enumValues:["xs","sm","md","lg","xl"]},{name:"label",type:"string",required:!1,description:"Accessible label (aria-label). Provide for meaningful standalone icons; omit for decorative ones."},{name:"color",type:"string",required:!1,description:"CSS colour value. Defaults to currentColor (inherits from parent)."}],usageExamples:[{title:"Decorative (next to text)",code:'<Icon size="md"><SearchIcon /></Icon>'},{title:"Meaningful (standalone)",code:'<Icon size="lg" label="Close dialog"><XIcon /></Icon>'},{title:"Coloured",code:'<Icon color="var(--lucent-danger-default)"><AlertIcon /></Icon>'},{title:"Inside a button",code:'<Button leftIcon={<Icon size="sm"><PlusIcon /></Icon>}>Add item</Button>'}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label","aria-hidden"],notes:'aria-hidden="true" is applied automatically when no label is given, hiding the icon from screen readers.'}},Gn={id:"text",name:"Text",tier:"atom",domain:"neutral",specVersion:"0.1",description:"Polymorphic typography primitive that maps semantic HTML elements to design-system type scales.",designIntent:"Text is the single source of truth for typography in Lucent UI. Rather than hard-coding font sizes and colors directly in components, every piece of rendered text should pass through this atom. The `as` prop controls the HTML element (and therefore semantic meaning), while `size`, `weight`, `color`, and `lineHeight` control appearance independently — decoupling semantics from style. Use `truncate` only in constrained-width containers where overflow must be prevented; it applies overflow: hidden + text-overflow: ellipsis. The `family` prop enables inline code or monospaced content without switching components.",props:[{name:"as",type:"enum",required:!1,default:"p",description:"HTML element to render. Controls semantic meaning independently of visual style.",enumValues:["p","h1","h2","h3","h4","h5","h6","span","div","label","strong","em","code"]},{name:"size",type:"enum",required:!1,default:"md",description:"Font size from the type scale. Maps to the corresponding --lucent-font-size-* token.",enumValues:["xs","sm","md","lg","xl","2xl","3xl"]},{name:"weight",type:"enum",required:!1,default:"regular",description:"Font weight. Maps to --lucent-font-weight-* tokens.",enumValues:["regular","medium","semibold","bold"]},{name:"color",type:"enum",required:!1,default:"primary",description:"Semantic text color. All values adapt automatically in light and dark themes.",enumValues:["primary","secondary","disabled","inverse","onAccent","success","warning","danger","info"]},{name:"align",type:"enum",required:!1,default:"left",description:"Text alignment.",enumValues:["left","center","right"]},{name:"lineHeight",type:"enum",required:!1,default:"base",description:"Line height. tight=1.25, base=1.5, relaxed=1.75.",enumValues:["tight","base","relaxed"]},{name:"family",type:"enum",required:!1,default:"base",description:"Font family. Use mono for code or tabular data. Use display (Unbounded) for headings and marketing copy.",enumValues:["base","mono","display"]},{name:"truncate",type:"boolean",required:!1,default:"false",description:"Clips overflow text with an ellipsis. Requires the container to have a constrained width."},{name:"children",type:"ReactNode",required:!0,description:"The text content to render."},{name:"style",type:"object",required:!1,description:'Inline style overrides. Applied after computed token styles, so any property you set here wins. This is the official escape hatch for one-off styling outside the token system — e.g. a custom color via style={{ color: "var(--my-green)" }}. See docs/style-escape-hatch.md.'}],usageExamples:[{title:"Heading",code:'<Text as="h1" size="3xl" weight="bold">Page title</Text>'},{title:"Body paragraph",code:'<Text size="md" color="secondary">Supporting copy goes here.</Text>'},{title:"Label",code:'<Text as="label" size="sm" weight="medium" htmlFor="email">Email</Text>'},{title:"Inline code",code:'<Text as="code" family="mono" size="sm">const x = 1;</Text>'},{title:"Truncated",code:"<Text truncate style={{ maxWidth: 200 }}>Very long text that will be clipped</Text>"},{title:"Status color",code:'<Text color="danger" size="xs">This field is required</Text>'},{title:"Custom color via style escape hatch",code:"<Text style={{ color: 'var(--chart-series-a)' }}>Revenue</Text>"}],compositionGraph:[],accessibility:{notes:'The rendered element determines the implicit ARIA role. Use heading elements (h1–h6) for document headings so screen readers can navigate the page structure. Use `as="label"` with `htmlFor` to associate labels with form controls. Decorative text needs no additional ARIA.'}};function Un({children:t,href:a,isActive:n=!1,icon:i,disabled:r=!1,inverse:o=!1,onClick:s,as:l,style:c}){const u=l??"a";return e.jsxs(u,{href:r?void 0:a,onClick:r?void 0:s,"aria-current":n?"page":void 0,"aria-disabled":r||void 0,style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-2)",padding:"var(--lucent-space-2) var(--lucent-space-3) var(--lucent-space-2) var(--lucent-space-4)",borderRadius:"var(--lucent-radius-md)",background:r?"transparent":n?o?"var(--lucent-surface)":"var(--lucent-accent-default)":"transparent",color:r?"var(--lucent-text-disabled)":n?o?"var(--lucent-text-primary)":"var(--lucent-accent-fg)":"var(--lucent-text-secondary)",border:n&&o?"1px solid var(--lucent-border-default)":"1px solid transparent",borderRight:n&&o?"3px solid var(--lucent-accent-default)":"3px solid transparent",boxShadow:n&&o?"var(--lucent-shadow-md)":"none",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-md)",fontWeight:n?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",textDecoration:"none",cursor:r?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), color var(--lucent-duration-fast) var(--lucent-easing-default)",userSelect:"none",boxSizing:"border-box",...c},onMouseEnter:h=>{!r&&!n&&(h.currentTarget.style.background="var(--lucent-surface-secondary)")},onMouseLeave:h=>{!r&&!n&&(h.currentTarget.style.background="transparent")},children:[i!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0,color:"inherit"},children:i}),t]})}const _n={id:"slider",name:"Slider",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A range input styled with Lucent tokens for selecting a numeric value within a bounded range.",designIntent:'Use Slider for continuous or stepped numeric inputs where the relative position matters — volume, brightness, border radius, spacing scale. Pair with showValue when the exact number is meaningful to the user. For precise numeric entry, use Input with type="number" instead. Disabled state uses muted colours without opacity hacks.',props:[{name:"min",type:"number",required:!1,default:"0",description:"Minimum value."},{name:"max",type:"number",required:!1,default:"100",description:"Maximum value."},{name:"step",type:"number",required:!1,default:"1",description:"Increment step between values."},{name:"value",type:"number",required:!1,description:"Controlled current value. Pair with onChange."},{name:"defaultValue",type:"number",required:!1,description:"Initial value for uncontrolled usage. Defaults to the midpoint of min/max."},{name:"onChange",type:"function",required:!1,description:"Called on every value change."},{name:"label",type:"string",required:!1,description:"Visible label rendered above the track."},{name:"showValue",type:"boolean",required:!1,default:"false",description:"Displays the current numeric value to the right of the label."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls track thickness and thumb diameter.",enumValues:["sm","md","lg"]},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Prevents interaction and dims the control."}],usageExamples:[{title:"Basic",code:'<Slider label="Volume" showValue />'},{title:"Controlled",code:'<Slider label="Opacity" min={0} max={1} step={0.01} value={opacity} onChange={e => setOpacity(Number(e.target.value))} showValue />'},{title:"Sizes",code:`<Slider size="sm" label="Small" />
|
|
56
|
+
/>`.trim()},{title:"With validation error",code:'<Select label="Role" options={roles} errorText="Please select a role" />'},{title:"With helper text",code:'<Select label="Timezone" options={timezones} helperText="Used for scheduling notifications" />'}],compositionGraph:[],accessibility:{role:"combobox",ariaAttributes:["aria-invalid","aria-describedby"],keyboardInteractions:["Enter / Space — opens the option list","Arrow keys — navigate options","Escape — closes the list"]}},Nn={neutral:{bg:"var(--lucent-surface-secondary)",color:"var(--lucent-text-secondary)",border:"var(--lucent-border-default)",dismissHover:"var(--lucent-border-strong)"},accent:{bg:"var(--lucent-accent-subtle)",color:"var(--lucent-accent-default)",border:"var(--lucent-accent-subtle)",dismissHover:"var(--lucent-accent-default)"},success:{bg:"var(--lucent-success-subtle)",color:"var(--lucent-success-text)",border:"var(--lucent-success-subtle)",dismissHover:"var(--lucent-success-default)"},warning:{bg:"var(--lucent-warning-subtle)",color:"var(--lucent-warning-text)",border:"var(--lucent-warning-subtle)",dismissHover:"var(--lucent-warning-default)"},danger:{bg:"var(--lucent-danger-subtle)",color:"var(--lucent-danger-text)",border:"var(--lucent-danger-subtle)",dismissHover:"var(--lucent-danger-default)"},info:{bg:"var(--lucent-info-subtle)",color:"var(--lucent-info-text)",border:"var(--lucent-info-subtle)",dismissHover:"var(--lucent-info-default)"}},Wn={sm:{fontSize:"var(--lucent-font-size-xs)",height:"20px",padding:"0 var(--lucent-space-3)",iconSize:10,gap:"var(--lucent-space-1)"},md:{fontSize:"var(--lucent-font-size-sm)",height:"24px",padding:"0 var(--lucent-space-3)",iconSize:12,gap:"var(--lucent-space-1)"},lg:{fontSize:"var(--lucent-font-size-md)",height:"28px",padding:"0 var(--lucent-space-4)",iconSize:14,gap:"var(--lucent-space-2)"}};function Vn({children:t,variant:r="neutral",size:n="md",onDismiss:i,disabled:a}){const o=Nn[r],l=Wn[n],[s,d]=c.useState(!1),p=!a&&i;return e.jsxs("span",{onMouseEnter:()=>{a||d(!0)},onMouseLeave:()=>d(!1),style:{display:"inline-flex",alignItems:"center",gap:l.gap,height:l.height,padding:i?`0 var(--lucent-space-2) 0 ${l.padding.split(" ")[1]}`:l.padding,fontSize:l.fontSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",lineHeight:1,borderRadius:"var(--lucent-radius-lg)",background:o.bg,color:o.color,border:`1px solid ${s&&!a?o.dismissHover:o.border}`,whiteSpace:"nowrap",boxSizing:"border-box",opacity:a?.5:1,transform:s&&p?"translateY(-1px)":"none",boxShadow:s&&p?`0 2px 4px ${o.dismissHover}22`:"none",transition:"transform var(--lucent-duration-fast) var(--lucent-easing-default), box-shadow var(--lucent-duration-fast) var(--lucent-easing-default), border-color var(--lucent-duration-fast) var(--lucent-easing-default)",cursor:p?"pointer":"default"},children:[t,i&&e.jsx("button",{type:"button",onClick:a?void 0:h=>{h.stopPropagation(),i==null||i()},disabled:a,"aria-label":"Dismiss",style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:l.iconSize+4,height:l.iconSize+4,padding:0,border:"none",borderRadius:"var(--lucent-radius-lg)",background:"transparent",color:"inherit",cursor:a?"not-allowed":"pointer",flexShrink:0,lineHeight:1},onMouseEnter:h=>{a||(h.currentTarget.style.background=o.dismissHover+"33")},onMouseLeave:h=>{h.currentTarget.style.background="transparent"},children:e.jsx("svg",{width:l.iconSize,height:l.iconSize,viewBox:"0 0 10 10",fill:"none",stroke:"currentColor",strokeWidth:1.5,strokeLinecap:"round",children:e.jsx("path",{d:"M2 2L8 8M8 2L2 8"})})})]})}const On={id:"tag",name:"Tag",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A dismissible label for applied filters, selected values, or categorisation.",designIntent:"Tags represent user-applied selections that can be removed — filters, multi-select values, categories. They differ from Badge in that they are interactive: provide onDismiss when the user should be able to remove the tag. Without onDismiss they render as a static pill (identical to Badge in purpose). Use semantic variants to match meaning; default to neutral for user-generated content where no semantic applies.",props:[{name:"children",type:"ReactNode",required:!0,description:"Tag label content."},{name:"variant",type:"enum",required:!1,default:"neutral",description:"Colour scheme conveying semantic meaning.",enumValues:["neutral","accent","success","warning","danger","info"]},{name:"size",type:"enum",required:!1,default:"md",description:"Controls height and font size.",enumValues:["sm","md","lg"]},{name:"onDismiss",type:"function",required:!1,description:"When provided, renders an × button that calls this handler on click."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Dims the tag and prevents the dismiss action."}],usageExamples:[{title:"Dismissible filter",code:"<Tag onDismiss={() => removeFilter('react')}>React</Tag>"},{title:"Multi-select value",code:'<Tag variant="accent" onDismiss={() => deselect(id)}>Jane Doe</Tag>'},{title:"Static category",code:'<Tag variant="info">Beta</Tag>'},{title:"Danger (removable)",code:'<Tag variant="danger" onDismiss={handleRemove}>Blocked</Tag>'}],compositionGraph:[],accessibility:{role:"group",notes:'The dismiss button has aria-label="Dismiss" and is keyboard-focusable.',keyboardInteractions:["Enter / Space — activates the dismiss button when focused"]}},_=5,ke=_+3,Hn={top:{bottom:-_,left:"50%",transform:"translateX(-50%)",borderWidth:`${_}px ${_}px 0 ${_}px`,borderColor:"var(--lucent-text-primary) transparent transparent transparent"},bottom:{top:-_,left:"50%",transform:"translateX(-50%)",borderWidth:`0 ${_}px ${_}px ${_}px`,borderColor:"transparent transparent var(--lucent-text-primary) transparent"},left:{right:-_,top:"50%",transform:"translateY(-50%)",borderWidth:`${_}px 0 ${_}px ${_}px`,borderColor:"transparent transparent transparent var(--lucent-text-primary)"},right:{left:-_,top:"50%",transform:"translateY(-50%)",borderWidth:`${_}px ${_}px ${_}px 0`,borderColor:"transparent var(--lucent-text-primary) transparent transparent"}};function ye({content:t,children:r,placement:n="top",delay:i=300,open:a,interactive:o=!1}){const[l,s]=c.useState(!1),d=a!==void 0?a:l,p=c.useRef(null),h=c.useRef(null),f=c.useRef(null),[w,b]=c.useState(null),g=()=>{h.current&&(clearTimeout(h.current),h.current=null)},m=()=>{g(),p.current=setTimeout(()=>s(!0),i)},u=()=>{p.current&&clearTimeout(p.current),o?h.current=setTimeout(()=>s(!1),100):s(!1)},y=c.useCallback(()=>{const T=f.current;if(!T)return;const S=T.getBoundingClientRect(),k=S.left+S.width/2,v=S.top+S.height/2;switch(n){case"top":b({top:S.top-ke,left:k});break;case"bottom":b({top:S.bottom+ke,left:k});break;case"left":b({top:v,left:S.left-ke});break;case"right":b({top:v,left:S.right+ke});break}},[n]);if(c.useLayoutEffect(()=>{d&&y()},[d,y]),!t)return e.jsx(e.Fragment,{children:r});const x={top:"translate(-50%, -100%)",bottom:"translate(-50%, 0)",left:"translate(-100%, -50%)",right:"translate(0, -50%)"};return e.jsxs("span",{ref:f,style:{position:"relative",display:"inline-flex"},onMouseEnter:m,onMouseLeave:u,onFocus:m,onBlur:u,children:[r,d&&w&&Q.createPortal(e.jsxs("span",{role:"tooltip",onMouseEnter:o?g:void 0,onMouseLeave:o?u:void 0,style:{position:"fixed",top:w.top,left:w.left,transform:x[n],background:"var(--lucent-text-primary)",color:"var(--lucent-bg-base)",padding:"5px 10px",borderRadius:"var(--lucent-radius-md)",fontSize:"var(--lucent-font-size-xs)",fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",lineHeight:"var(--lucent-line-height-base)",whiteSpace:"nowrap",zIndex:9999,pointerEvents:o?"auto":"none",boxShadow:"var(--lucent-shadow-md)"},children:[t,e.jsx("span",{"aria-hidden":!0,style:{position:"absolute",width:0,height:0,borderStyle:"solid",...Hn[n]}})]}),document.body)]})}const Gn={id:"tooltip",name:"Tooltip",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A transient label that appears on hover or focus to explain an element.",designIntent:'Tooltips surface supplementary context that would clutter the UI if shown permanently — keyboard shortcut hints, icon button labels, truncated text expansions. They must never contain critical information (errors, required actions) because they are invisible until hovered. Keep content to one short phrase; avoid wrapping. Use placement to avoid viewport edges — "top" is the default and works in most cases.',props:[{name:"content",type:"ReactNode",required:!0,description:"Text or element shown inside the tooltip. Keep it short (under 80 chars)."},{name:"children",type:"ReactNode",required:!0,description:"The element that triggers the tooltip on hover/focus."},{name:"placement",type:"enum",required:!1,default:"top",description:"Position of the tooltip relative to the trigger.",enumValues:["top","bottom","left","right"]},{name:"delay",type:"number",required:!1,default:"300",description:"Milliseconds before the tooltip appears. Prevents flicker on fast cursor movement."}],usageExamples:[{title:"Icon button label",code:'<Tooltip content="Copy to clipboard"><IconButton icon={<CopyIcon />} /></Tooltip>'},{title:"Keyboard shortcut hint",code:'<Tooltip content="⌘K"><Button variant="ghost">Search</Button></Tooltip>'},{title:"Bottom placement",code:'<Tooltip content="Opens in a new tab" placement="bottom"><a href="…">Link</a></Tooltip>'},{title:"No delay (instant)",code:'<Tooltip content="Delete" delay={0}><Button variant="ghost">🗑</Button></Tooltip>'}],compositionGraph:[],accessibility:{role:"tooltip",ariaAttributes:['role="tooltip"'],notes:'The tooltip is shown on both hover and focus, making it accessible to keyboard users. Content is exposed via role="tooltip".'}},Un={xs:12,sm:14,md:16,lg:20,xl:24};function _n({children:t,size:r="md",label:n,color:i,style:a}){const o=Un[r];return e.jsx("span",{role:n?"img":void 0,"aria-label":n,"aria-hidden":n?void 0:!0,style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:o,height:o,flexShrink:0,color:i??"currentColor",...a},children:t})}const Yn={id:"icon",name:"Icon",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A sized, accessible wrapper for any SVG or icon-set component.",designIntent:"Icon is intentionally icon-set agnostic — pass any SVG or third-party icon as children and it handles sizing and accessibility automatically. Use label for standalone icons that convey meaning without adjacent text (e.g. a close button). Omit label for decorative icons next to text, where the text already provides context. colour inherits from the parent by default via currentColor — override only when the icon must differ from its surrounding text.",props:[{name:"children",type:"ReactNode",required:!0,description:"The icon to render — any SVG element or icon-set component."},{name:"size",type:"enum",required:!1,default:"md",description:"Constrains the icon to a fixed square (xs=12, sm=14, md=16, lg=20, xl=24).",enumValues:["xs","sm","md","lg","xl"]},{name:"label",type:"string",required:!1,description:"Accessible label (aria-label). Provide for meaningful standalone icons; omit for decorative ones."},{name:"color",type:"string",required:!1,description:"CSS colour value. Defaults to currentColor (inherits from parent)."}],usageExamples:[{title:"Decorative (next to text)",code:'<Icon size="md"><SearchIcon /></Icon>'},{title:"Meaningful (standalone)",code:'<Icon size="lg" label="Close dialog"><XIcon /></Icon>'},{title:"Coloured",code:'<Icon color="var(--lucent-danger-default)"><AlertIcon /></Icon>'},{title:"Inside a button",code:'<Button leftIcon={<Icon size="sm"><PlusIcon /></Icon>}>Add item</Button>'}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label","aria-hidden"],notes:'aria-hidden="true" is applied automatically when no label is given, hiding the icon from screen readers.'}},Kn={id:"text",name:"Text",tier:"atom",domain:"neutral",specVersion:"0.1",description:"Polymorphic typography primitive that maps semantic HTML elements to design-system type scales.",designIntent:"Text is the single source of truth for typography in Lucent UI. Rather than hard-coding font sizes and colors directly in components, every piece of rendered text should pass through this atom. The `as` prop controls the HTML element (and therefore semantic meaning), while `size`, `weight`, `color`, and `lineHeight` control appearance independently — decoupling semantics from style. Use `truncate` only in constrained-width containers where overflow must be prevented; it applies overflow: hidden + text-overflow: ellipsis. The `family` prop enables inline code or monospaced content without switching components.",props:[{name:"as",type:"enum",required:!1,default:"p",description:"HTML element to render. Controls semantic meaning independently of visual style.",enumValues:["p","h1","h2","h3","h4","h5","h6","span","div","label","strong","em","code"]},{name:"size",type:"enum",required:!1,default:"md",description:"Font size from the type scale. Maps to the corresponding --lucent-font-size-* token.",enumValues:["xs","sm","md","lg","xl","2xl","3xl"]},{name:"weight",type:"enum",required:!1,default:"regular",description:"Font weight. Maps to --lucent-font-weight-* tokens.",enumValues:["regular","medium","semibold","bold"]},{name:"color",type:"enum",required:!1,default:"primary",description:"Semantic text color. All values adapt automatically in light and dark themes.",enumValues:["primary","secondary","disabled","inverse","onAccent","success","warning","danger","info"]},{name:"align",type:"enum",required:!1,default:"left",description:"Text alignment.",enumValues:["left","center","right"]},{name:"lineHeight",type:"enum",required:!1,default:"base",description:"Line height. tight=1.25, base=1.5, relaxed=1.75.",enumValues:["tight","base","relaxed"]},{name:"family",type:"enum",required:!1,default:"base",description:"Font family. Use mono for code or tabular data. Use display (Unbounded) for headings and marketing copy.",enumValues:["base","mono","display"]},{name:"truncate",type:"boolean",required:!1,default:"false",description:"Clips overflow text with an ellipsis. Requires the container to have a constrained width."},{name:"children",type:"ReactNode",required:!0,description:"The text content to render."},{name:"style",type:"object",required:!1,description:'Inline style overrides. Applied after computed token styles, so any property you set here wins. This is the official escape hatch for one-off styling outside the token system — e.g. a custom color via style={{ color: "var(--my-green)" }}. See docs/style-escape-hatch.md.'}],usageExamples:[{title:"Heading",code:'<Text as="h1" size="3xl" weight="bold">Page title</Text>'},{title:"Body paragraph",code:'<Text size="md" color="secondary">Supporting copy goes here.</Text>'},{title:"Label",code:'<Text as="label" size="sm" weight="medium" htmlFor="email">Email</Text>'},{title:"Inline code",code:'<Text as="code" family="mono" size="sm">const x = 1;</Text>'},{title:"Truncated",code:"<Text truncate style={{ maxWidth: 200 }}>Very long text that will be clipped</Text>"},{title:"Status color",code:'<Text color="danger" size="xs">This field is required</Text>'},{title:"Custom color via style escape hatch",code:"<Text style={{ color: 'var(--chart-series-a)' }}>Revenue</Text>"}],compositionGraph:[],accessibility:{notes:'The rendered element determines the implicit ARIA role. Use heading elements (h1–h6) for document headings so screen readers can navigate the page structure. Use `as="label"` with `htmlFor` to associate labels with form controls. Decorative text needs no additional ARIA.'}};function Xn({children:t,href:r,isActive:n=!1,icon:i,disabled:a=!1,inverse:o=!1,onClick:l,as:s,style:d}){const p=s??"a";return e.jsxs(p,{href:a?void 0:r,onClick:a?void 0:l,"aria-current":n?"page":void 0,"aria-disabled":a||void 0,style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-2)",padding:"var(--lucent-space-2) var(--lucent-space-3) var(--lucent-space-2) var(--lucent-space-4)",borderRadius:"var(--lucent-radius-md)",background:a?"transparent":n?o?"var(--lucent-surface)":"var(--lucent-accent-default)":"transparent",color:a?"var(--lucent-text-disabled)":n?o?"var(--lucent-text-primary)":"var(--lucent-accent-fg)":"var(--lucent-text-secondary)",border:n&&o?"1px solid var(--lucent-border-default)":"1px solid transparent",borderRight:n&&o?"3px solid var(--lucent-accent-default)":"3px solid transparent",boxShadow:n&&o?"var(--lucent-shadow-md)":"none",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-md)",fontWeight:n?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",textDecoration:"none",cursor:a?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), color var(--lucent-duration-fast) var(--lucent-easing-default)",userSelect:"none",boxSizing:"border-box",...d},onMouseEnter:h=>{!a&&!n&&(h.currentTarget.style.background="var(--lucent-surface-secondary)")},onMouseLeave:h=>{!a&&!n&&(h.currentTarget.style.background="transparent")},children:[i!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0,color:"inherit"},children:i}),t]})}const Jn={id:"slider",name:"Slider",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A range input styled with Lucent tokens for selecting a numeric value within a bounded range.",designIntent:'Use Slider for continuous or stepped numeric inputs where the relative position matters — volume, brightness, border radius, spacing scale. Pair with showValue when the exact number is meaningful to the user. For precise numeric entry, use Input with type="number" instead. Disabled state uses muted colours without opacity hacks.',props:[{name:"min",type:"number",required:!1,default:"0",description:"Minimum value."},{name:"max",type:"number",required:!1,default:"100",description:"Maximum value."},{name:"step",type:"number",required:!1,default:"1",description:"Increment step between values."},{name:"value",type:"number",required:!1,description:"Controlled current value. Pair with onChange."},{name:"defaultValue",type:"number",required:!1,description:"Initial value for uncontrolled usage. Defaults to the midpoint of min/max."},{name:"onChange",type:"function",required:!1,description:"Called on every value change."},{name:"label",type:"string",required:!1,description:"Visible label rendered above the track."},{name:"showValue",type:"boolean",required:!1,default:"false",description:"Displays the current numeric value to the right of the label."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls track thickness and thumb diameter.",enumValues:["sm","md","lg"]},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Prevents interaction and dims the control."}],usageExamples:[{title:"Basic",code:'<Slider label="Volume" showValue />'},{title:"Controlled",code:'<Slider label="Opacity" min={0} max={1} step={0.01} value={opacity} onChange={e => setOpacity(Number(e.target.value))} showValue />'},{title:"Sizes",code:`<Slider size="sm" label="Small" />
|
|
57
57
|
<Slider size="md" label="Medium" />
|
|
58
|
-
<Slider size="lg" label="Large" />`},{title:"Disabled",code:'<Slider label="Locked" disabled defaultValue={40} />'}],compositionGraph:[],accessibility:{role:"slider",ariaAttributes:["aria-valuemin","aria-valuemax","aria-valuenow","aria-disabled"],keyboardInteractions:["ArrowRight / ArrowUp — increase value by step","ArrowLeft / ArrowDown — decrease value by step","Home — jump to min","End — jump to max"]}},
|
|
58
|
+
<Slider size="lg" label="Large" />`},{title:"Disabled",code:'<Slider label="Locked" disabled defaultValue={40} />'}],compositionGraph:[],accessibility:{role:"slider",ariaAttributes:["aria-valuemin","aria-valuemax","aria-valuenow","aria-disabled"],keyboardInteractions:["ArrowRight / ArrowUp — increase value by step","ArrowLeft / ArrowDown — decrease value by step","Home — jump to min","End — jump to max"]}},Zn=2e3;function Qn(){return e.jsxs("svg",{width:13,height:13,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,children:[e.jsx("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),e.jsx("path",{d:"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"})]})}function er(){return e.jsx("svg",{width:13,height:13,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2.5,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,children:e.jsx("path",{d:"M20 6L9 17l-5-5"})})}function tr({code:t,language:r,tabs:n,variant:i="code",wrap:a,minimal:o,helperText:l,showCopyButton:s=!0,style:d}){var T,S;const p=!!(n!=null&&n.length),[h,f]=c.useState(0),[w,b]=c.useState(!1),g=p?((T=n[h])==null?void 0:T.code)??"":t??"",m=p?(S=n[h])==null?void 0:S.language:r,u=async()=>{try{await navigator.clipboard.writeText(g),b(!0),setTimeout(()=>b(!1),Zn)}catch{}},y={display:"inline-flex",alignItems:"center",gap:"var(--lucent-space-1)",padding:"3px var(--lucent-space-2)",border:"1px solid transparent",borderRadius:"var(--lucent-radius-md)",background:"transparent",color:w?"var(--lucent-success-default)":"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",cursor:"pointer",flexShrink:0,transition:"color var(--lucent-duration-fast) var(--lucent-easing-default), background var(--lucent-duration-fast) var(--lucent-easing-default)"},x=()=>e.jsxs("button",{onClick:u,"aria-label":w?"Copied!":"Copy code",style:y,onMouseEnter:k=>{w||(k.currentTarget.style.background="var(--lucent-surface-secondary)",k.currentTarget.style.color="var(--lucent-text-primary)")},onMouseLeave:k=>{w||(k.currentTarget.style.background="transparent",k.currentTarget.style.color="var(--lucent-text-secondary)")},children:[w?e.jsx(er,{}):e.jsx(Qn,{}),w?"Copied!":"Copy"]});return e.jsxs("div",{style:{borderRadius:"var(--lucent-radius-lg)",border:"1px solid var(--lucent-border-default)",overflow:"hidden",...d},children:[p&&e.jsx("div",{style:{display:"flex",alignItems:"flex-end",background:"var(--lucent-surface)",borderBottom:"1px solid var(--lucent-border-default)",padding:"0 var(--lucent-space-2)"},children:n.map((k,v)=>{const A=v===h;return e.jsxs("button",{onClick:()=>{f(v),b(!1)},style:{display:"inline-flex",alignItems:"center",gap:"var(--lucent-space-1)",padding:"var(--lucent-space-2) var(--lucent-space-3)",border:"none",borderBottom:A?"2px solid var(--lucent-accent-default)":"2px solid transparent",marginBottom:-1,background:"transparent",color:A?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-sm)",fontWeight:A?"var(--lucent-font-weight-semibold)":"var(--lucent-font-weight-regular)",cursor:"pointer",transition:"color var(--lucent-duration-fast) var(--lucent-easing-default)",whiteSpace:"nowrap"},onMouseEnter:C=>{A||(C.currentTarget.style.color="var(--lucent-text-primary)")},onMouseLeave:C=>{A||(C.currentTarget.style.color="var(--lucent-text-secondary)")},children:[k.icon!==void 0&&e.jsx("span",{style:{display:"inline-flex",alignItems:"center"},children:k.icon}),k.label]},k.label)})}),!o&&!p&&(!!m||s)&&e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:m?"space-between":"flex-end",padding:"0 var(--lucent-space-3)",height:36,background:"var(--lucent-surface)",borderBottom:"1px solid var(--lucent-border-default)"},children:[m&&e.jsx("span",{style:{fontSize:"var(--lucent-font-size-xs)",fontFamily:"var(--lucent-font-family-mono)",color:"var(--lucent-text-secondary)",letterSpacing:"var(--lucent-letter-spacing-wide)"},children:m}),s&&e.jsx(x,{})]}),l&&e.jsx("div",{style:{padding:"var(--lucent-space-2) var(--lucent-space-4)",fontSize:"var(--lucent-font-size-xs)",color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",borderBottom:"1px solid var(--lucent-border-default)"},children:l}),i==="code"?e.jsxs("div",{style:{position:"relative"},children:[e.jsx("pre",{style:{margin:0,padding:"var(--lucent-space-4)",paddingRight:(p||o)&&s?"var(--lucent-space-16)":"var(--lucent-space-4)",background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",overflowX:a?"hidden":"auto",lineHeight:1.65,...a&&{whiteSpace:"pre-wrap",wordBreak:"break-word"}},children:e.jsx("code",{style:{fontFamily:"var(--lucent-font-family-mono)",fontSize:"var(--lucent-font-size-sm)",color:"var(--lucent-text-primary)"},children:g})}),(p||o)&&s&&e.jsx("div",{style:{position:"absolute",top:o?0:"var(--lucent-space-2)",bottom:o?0:void 0,right:"var(--lucent-space-3)",display:"flex",alignItems:"center"},children:e.jsx(x,{})})]}):e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",padding:"var(--lucent-space-3) var(--lucent-space-4)",background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)"},children:[e.jsx("span",{style:{flex:1,overflow:"hidden",...a?{whiteSpace:"pre-wrap",wordBreak:"break-word"}:{whiteSpace:"nowrap",textOverflow:"ellipsis"},fontFamily:"var(--lucent-font-family-mono)",fontSize:"var(--lucent-font-size-sm)",color:"var(--lucent-text-primary)"},children:g}),s&&e.jsx(x,{})]})]})}const nr={id:"code-block",name:"CodeBlock",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A styled code display with optional tabs, a language label, copy-to-clipboard, and an AI prompt variant.",designIntent:"Use CodeBlock for static code snippets, install commands, API examples, and AI prompt sharing. The tabs prop switches between related snippets (e.g. pnpm / npm / yarn). The prompt variant renders a single-line truncated display suited to AI tool prompts — the full text is always copied even when visually clipped. Zero-dependency — no syntax highlighting library is bundled.",props:[{name:"code",type:"string",required:!1,description:"Code string — used in single (non-tabbed) mode."},{name:"language",type:"string",required:!1,description:"Language label shown in the header (non-tabbed mode only). Purely cosmetic."},{name:"tabs",type:"array",required:!1,description:"Tabbed mode. Each entry has { label, code, language?, icon? }. Switching tabs resets the copied state."},{name:"variant",type:"enum",required:!1,default:"code",enumValues:["code","prompt"],description:'"code" renders a <pre><code> block with horizontal scroll. "prompt" renders a single-line truncated span suited to AI prompts.'},{name:"minimal",type:"boolean",required:!1,default:"false",description:"Hides the header bar and shows only a corner copy button overlay."},{name:"wrap",type:"boolean",required:!1,default:"false",description:"When true, wraps long lines instead of scrolling horizontally."},{name:"helperText",type:"string",required:!1,description:"Descriptive text rendered between the tab bar and the code area."},{name:"showCopyButton",type:"boolean",required:!1,default:"true",description:'Renders a copy-to-clipboard button. Shows a "Copied!" confirmation for 2 s on success.'}],usageExamples:[{title:"Single snippet",code:'<CodeBlock language="tsx" code={`<Button variant="primary">Save</Button>`} />'},{title:"Tabbed install commands",code:`<CodeBlock tabs={[
|
|
59
59
|
{ label: 'pnpm', code: 'pnpm add lucent-ui', language: 'bash' },
|
|
60
60
|
{ label: 'npm', code: 'npm install lucent-ui', language: 'bash' },
|
|
61
61
|
]} />`},{title:"AI prompt",code:`<CodeBlock
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
{ label: 'Claude', icon: '♦', code: '"Add a Button with variant=\\"primary\\"."' },
|
|
66
66
|
{ label: 'Cursor', icon: '↖', code: '@lucent-ui Add a primary Button.' },
|
|
67
67
|
]}
|
|
68
|
-
/>`}],compositionGraph:[],accessibility:{role:"region",ariaAttributes:["aria-label (copy button)"],keyboardInteractions:["Tab — focuses the copy button","Enter / Space — copies the code"]}},
|
|
68
|
+
/>`}],compositionGraph:[],accessibility:{role:"region",ariaAttributes:["aria-label (copy button)"],keyboardInteractions:["Tab — focuses the copy button","Enter / Space — copies the code"]}},rr=`
|
|
69
69
|
.lucent-table-row:hover > td,
|
|
70
70
|
.lucent-table-row:hover > th {
|
|
71
71
|
background: color-mix(in srgb, var(--lucent-text-primary) 5%, transparent) !important;
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
.lucent-table-striped tbody .lucent-table-row:nth-child(even) > th {
|
|
75
75
|
background: color-mix(in srgb, var(--lucent-text-primary) 4%, transparent);
|
|
76
76
|
}
|
|
77
|
-
`;function
|
|
77
|
+
`;function ar({children:t,style:r,...n}){return e.jsx("thead",{style:{background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",...r},...n,children:t})}function or({children:t,...r}){return e.jsx("tbody",{...r,children:t})}function ir({children:t,style:r,...n}){return e.jsx("tfoot",{style:{background:"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)",...r},...n,children:t})}function sr({children:t,className:r,...n}){return e.jsx("tr",{className:["lucent-table-row",r].filter(Boolean).join(" "),...n,children:t})}function lr({as:t,children:r,style:n,...i}){const a=t==="th",o={padding:"var(--lucent-space-3) var(--lucent-space-4)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-sm)",borderBottom:"1px solid var(--lucent-border-default)",textAlign:"left",verticalAlign:"middle",color:a?"var(--lucent-text-secondary)":"var(--lucent-text-primary)",fontWeight:a?"var(--lucent-font-weight-semibold)":"var(--lucent-font-weight-regular)",whiteSpace:a?"nowrap":void 0,...n};return a?e.jsx("th",{scope:"col",style:o,...i,children:r}):e.jsx("td",{style:o,...i,children:r})}function pe({striped:t=!1,children:r,className:n,style:i,...a}){const o=["lucent-table",t&&"lucent-table-striped",n].filter(Boolean).join(" ");return e.jsxs(e.Fragment,{children:[e.jsx("style",{children:rr}),e.jsx("div",{style:{overflowX:"auto",width:"100%",background:"var(--lucent-surface)"},children:e.jsx("table",{className:o,style:{width:"100%",borderCollapse:"collapse",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-sm)",...i},...a,children:r})})]})}pe.Head=ar;pe.Body=or;pe.Foot=ir;pe.Row=sr;pe.Cell=lr;const cr={id:"table",name:"Table",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A lightweight, token-styled HTML table primitive with compound sub-components. Distinct from DataTable — no sorting, filtering, or pagination.",designIntent:"Use Table for static or lightly dynamic tabular data where full DataTable features are not needed — props tables, changelog entries, comparison grids, reference docs. The compound API (Table.Head, Table.Body, Table.Row, Table.Cell) maps directly to semantic HTML so screen readers get the full table structure. Horizontal overflow is handled automatically by a scroll wrapper. The wrapper paints var(--lucent-surface) as its background so the table always sits on a solid panel, regardless of parent page color; thead/tfoot/striped tints are translucent color-mix overlays on top of that surface so they adapt to both light and dark modes.",props:[{name:"striped",type:"boolean",required:!1,default:"false",description:"Applies alternating tinted backgrounds to even tbody rows via color-mix(transparent)."},{name:"Table.Head",type:"component",required:!1,description:"Renders <thead> with a subtle tinted background. Accepts Table.Row children."},{name:"Table.Body",type:"component",required:!1,description:"Renders <tbody>. Accepts Table.Row children."},{name:"Table.Foot",type:"component",required:!1,description:"Renders <tfoot> with a subtle tinted background."},{name:"Table.Row",type:"component",required:!1,description:"Renders <tr> with a hover highlight. Accepts Table.Cell children."},{name:"Table.Cell",type:"component",required:!1,description:'Renders <td> by default or <th scope="col"> when as="th". Header cells are semibold + secondary colour; data cells are regular + primary.'}],usageExamples:[{title:"Basic",code:`<Table>
|
|
78
78
|
<Table.Head>
|
|
79
79
|
<Table.Row>
|
|
80
80
|
<Table.Cell as="th">Name</Table.Cell>
|
|
@@ -92,15 +92,15 @@
|
|
|
92
92
|
<Table.Body>…</Table.Body>
|
|
93
93
|
</Table>`},{title:"Custom cell content",code:`<Table.Cell>
|
|
94
94
|
<Badge variant="success">Active</Badge>
|
|
95
|
-
</Table.Cell>`}],compositionGraph:[{componentId:"table-head",componentName:"Table.Head",role:"head",required:!1},{componentId:"table-body",componentName:"Table.Body",role:"body",required:!1},{componentId:"table-foot",componentName:"Table.Foot",role:"foot",required:!1},{componentId:"table-row",componentName:"Table.Row",role:"row",required:!1},{componentId:"table-cell",componentName:"Table.Cell",role:"cell",required:!1}],accessibility:{role:"table",ariaAttributes:['scope="col" on th cells'],keyboardInteractions:["Standard browser table navigation"]}},
|
|
95
|
+
</Table.Cell>`}],compositionGraph:[{componentId:"table-head",componentName:"Table.Head",role:"head",required:!1},{componentId:"table-body",componentName:"Table.Body",role:"body",required:!1},{componentId:"table-foot",componentName:"Table.Foot",role:"foot",required:!1},{componentId:"table-row",componentName:"Table.Row",role:"row",required:!1},{componentId:"table-cell",componentName:"Table.Cell",role:"cell",required:!1}],accessibility:{role:"table",ariaAttributes:['scope="col" on th cells'],keyboardInteractions:["Standard browser table navigation"]}},dr={id:"color-picker",name:"ColorPicker",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A color swatch button that opens a popover with preset swatches and HEX / RGB / RGBA / HSL inputs.",designIntent:"ColorPicker surfaces color selection without navigating away. The swatch button shows the current color at a glance; the popover provides precise input through four format modes. Presets accelerate common choices — provide a curated palette relevant to the product. Always pair with a visible label so the purpose of the picker is clear.",props:[{name:"value",type:"string",required:!1,description:"Controlled color value. Accepts hex (#rrggbb / #rrggbbaa), rgb(), rgba(), or hsl() strings."},{name:"onChange",type:"function",required:!1,description:"Called with the new hex string (#rrggbb, or #rrggbbaa when alpha < 1) whenever the color changes."},{name:"label",type:"string",required:!1,description:"Visible label rendered above the swatch button."},{name:"presets",type:"string[]",required:!1,description:"Array of hex color strings shown as clickable swatches. Defaults to a 16-color palette."},{name:"size",type:"string",required:!1,default:'"md"',description:'Swatch trigger size. "sm" renders a 24px swatch, "md" renders 40px.'},{name:"inline",type:"boolean",required:!1,default:"false",description:"Places the label beside the swatch instead of above it. Useful for compact layouts."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Prevents interaction and dims the label."},{name:"portalContainer",type:"HTMLElement | null",required:!1,description:"DOM element to portal the popover into. Defaults to document.body. Set this to a wrapper element to preserve CSS custom property inheritance for per-section theming."},{name:"id",type:"string",required:!1,description:"HTML id for the swatch button. Auto-generated if omitted."},{name:"style",type:"CSSProperties",required:!1,description:"Inline styles applied to the outer wrapper."}],usageExamples:[{title:"Basic controlled",code:`const [color, setColor] = useState('#3b82f6');
|
|
96
96
|
<ColorPicker value={color} onChange={setColor} label="Brand color" />`},{title:"Custom presets",code:`<ColorPicker
|
|
97
97
|
value={accent}
|
|
98
98
|
onChange={setAccent}
|
|
99
99
|
label="Accent"
|
|
100
100
|
presets={['#111827', '#3b82f6', '#8b5cf6', '#ec4899', '#ef4444', '#f97316', '#22c55e']}
|
|
101
|
-
/>`},{title:"No presets",code:'<ColorPicker value={color} onChange={setColor} presets={[]} label="Custom color" />'},{title:"Disabled",code:'<ColorPicker value="#6b7280" disabled label="Theme color" />'}],compositionGraph:[],accessibility:{role:"button (trigger) + dialog (popover)",ariaAttributes:["aria-expanded","aria-haspopup","aria-label"],keyboardInteractions:["Enter / Space — opens the color picker popover","Escape / click outside — closes the popover","Tab — moves through format tabs and input fields within the popover"]}},
|
|
101
|
+
/>`},{title:"No presets",code:'<ColorPicker value={color} onChange={setColor} presets={[]} label="Custom color" />'},{title:"Disabled",code:'<ColorPicker value="#6b7280" disabled label="Theme color" />'}],compositionGraph:[],accessibility:{role:"button (trigger) + dialog (popover)",ariaAttributes:["aria-expanded","aria-haspopup","aria-label"],keyboardInteractions:["Enter / Space — opens the color picker popover","Escape / click outside — closes the popover","Tab — moves through format tabs and input fields within the popover"]}},ur={id:"color-swatch",name:"ColorSwatch",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A circular color swatch that can be static or interactive.",designIntent:"ColorSwatch is the smallest unit of color representation in the system. Use it wherever a color needs to be shown at a glance — palette pickers, token lists, or inline color indicators. The selected state shows an accent-colored ring to indicate the active choice.",props:[{name:"color",type:"string",required:!0,description:"CSS color value to display."},{name:"size",type:"'sm' | 'md' | 'lg'",required:!1,default:"'md'",description:"Swatch diameter: sm=16px, md=22px, lg=28px."},{name:"selected",type:"boolean",required:!1,default:"false",description:"Shows an accent ring when true."},{name:"onClick",type:"function",required:!1,description:"Click handler. Omit for a non-interactive display swatch."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Dims the swatch and prevents interaction."},{name:"title",type:"string",required:!1,description:"Tooltip text. Defaults to the color value."},{name:"style",type:"CSSProperties",required:!1,description:"Inline styles applied to the swatch button."}],usageExamples:[{title:"Static palette",code:`['#ef4444', '#f97316', '#22c55e'].map(c => (
|
|
102
102
|
<ColorSwatch key={c} color={c} />
|
|
103
|
-
))`},{title:"Selectable swatch",code:`<ColorSwatch color="#3b82f6" selected={active === '#3b82f6'} onClick={() => setActive('#3b82f6')} />`}],compositionGraph:[],accessibility:{role:"button",ariaAttributes:["title (tooltip)"],keyboardInteractions:["Enter / Space — triggers onClick"]}},
|
|
103
|
+
))`},{title:"Selectable swatch",code:`<ColorSwatch color="#3b82f6" selected={active === '#3b82f6'} onClick={() => setActive('#3b82f6')} />`}],compositionGraph:[],accessibility:{role:"button",ariaAttributes:["title (tooltip)"],keyboardInteractions:["Enter / Space — triggers onClick"]}},pr={id:"segmented-control",name:"SegmentedControl",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A pill-group selector where exactly one option is always active, with a sliding indicator.",designIntent:"Use SegmentedControl for mutually exclusive view switches or mode selectors that have 2–5 options and where all options should be visible simultaneously (unlike Select). Common uses: view mode (Grid / List / Table), time range (Day / Week / Month), format picker (Hex / RGB / HSL). Keep labels short (1–2 words) so the control stays compact. For more than 5 options or dynamic option lists, prefer Select.",props:[{name:"options",type:"SegmentedOption[]",required:!0,description:"Array of { value, label, disabled? }. 2–5 options recommended."},{name:"value",type:"string",required:!1,description:"Controlled selected value. Pair with onChange."},{name:"defaultValue",type:"string",required:!1,description:"Initial value for uncontrolled usage. Defaults to the first option."},{name:"onChange",type:"function",required:!1,description:"Called with the newly selected value."},{name:"size",type:"enum",required:!1,default:"md",description:"Height and font size of the control.",enumValues:["sm","md","lg"]},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables all options."},{name:"fullWidth",type:"boolean",required:!1,default:"false",description:"Stretches the control to fill its container, distributing options equally."}],usageExamples:[{title:"Controlled",code:`const [view, setView] = useState('grid');
|
|
104
104
|
<SegmentedControl
|
|
105
105
|
value={view}
|
|
106
106
|
onChange={setView}
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
]}
|
|
119
119
|
/>`},{title:"Full-width, sizes",code:`<SegmentedControl fullWidth size="sm" defaultValue="a" options={[{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }]} />
|
|
120
120
|
<SegmentedControl fullWidth size="md" defaultValue="a" options={[{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }]} />
|
|
121
|
-
<SegmentedControl fullWidth size="lg" defaultValue="a" options={[{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }]} />`}],compositionGraph:[],accessibility:{role:"group (radiogroup)",ariaAttributes:["aria-checked",'role="radio"',"disabled"],keyboardInteractions:["Tab — focuses the group","Click — selects an option"]}},
|
|
121
|
+
<SegmentedControl fullWidth size="lg" defaultValue="a" options={[{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }]} />`}],compositionGraph:[],accessibility:{role:"group (radiogroup)",ariaAttributes:["aria-checked",'role="radio"',"disabled"],keyboardInteractions:["Tab — focuses the group","Click — selects an option"]}},hr={start:"flex-start",center:"center",end:"flex-end",stretch:"stretch",baseline:"baseline"},fr={start:"flex-start",center:"center",end:"flex-end",between:"space-between",around:"space-around"};function Xe({gap:t="4",align:r="stretch",justify:n="start",as:i="div",wrap:a=!1,children:o,style:l,...s}){const d=i==="ul"||i==="ol",p={display:"flex",flexDirection:"column",gap:`var(--lucent-space-${t})`,alignItems:hr[r],justifyContent:fr[n],...a&&{flexWrap:"wrap"},...d&&{listStyle:"none",margin:0,padding:0},...l},h=i;return e.jsx(h,{style:p,...s,children:o})}const mr={id:"stack",name:"Stack",tier:"atom",domain:"neutral",specVersion:"0.1",description:"Vertical flex layout primitive that spaces children using design-system gap tokens.",designIntent:"Stack is the primary vertical layout container. Use it any time you need to arrange elements in a column with consistent spacing — form fields, card content, page sections, sidebar navigation. The gap prop maps to spacing tokens (--lucent-space-*), enforcing consistent vertical rhythm without manual margin management. Prefer Stack over raw inline flex styles for maintainability and readability. Use Row (the horizontal counterpart) when items should flow left-to-right. Stack does not add padding — wrap it in a Card or apply padding on a parent container instead.",props:[{name:"gap",type:"enum",required:!1,default:"4",description:"Spacing between children. Maps to --lucent-space-{n} tokens.",enumValues:["0","1","2","3","4","5","6","8","10","12","16","20","24"]},{name:"align",type:"enum",required:!1,default:"stretch",description:'Cross-axis alignment (alignItems). "stretch" fills the container width.',enumValues:["start","center","end","stretch","baseline"]},{name:"justify",type:"enum",required:!1,default:"start",description:"Main-axis distribution (justifyContent).",enumValues:["start","center","end","between","around"]},{name:"as",type:"enum",required:!1,default:"div",description:"HTML element to render. Use semantic elements when appropriate (nav for navigation, ul/ol for lists, section/header/footer/main/aside/article for landmarks). When rendering as ul/ol, list-style/margin/padding are auto-reset; consumers pass <li> children.",enumValues:["div","section","nav","header","footer","main","aside","article","form","fieldset","ul","ol"]},{name:"wrap",type:"boolean",required:!1,default:"false",description:"Whether children should wrap to the next line when they exceed the container width."},{name:"children",type:"ReactNode",required:!0,description:"The elements to arrange vertically."},{name:"style",type:"object",required:!1,description:"Inline style overrides. Applied after computed layout styles."},{name:"className",type:"string",required:!1,description:"CSS class name passthrough."}],usageExamples:[{title:"Form layout",code:`<Stack gap="4">
|
|
122
122
|
<FormField label="Name"><Input /></FormField>
|
|
123
123
|
<FormField label="Email"><Input type="email" /></FormField>
|
|
124
124
|
<Button variant="primary">Submit</Button>
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
<Text size="sm">{note.body}</Text>
|
|
144
144
|
</li>
|
|
145
145
|
))}
|
|
146
|
-
</Stack>`}],compositionGraph:[],accessibility:{notes:"Stack renders a <div> by default, which has no implicit ARIA role. Use the `as` prop to render semantic elements (nav, section, form) when the content has a specific structural purpose. Add aria-label or aria-labelledby when using landmark elements."}},
|
|
146
|
+
</Stack>`}],compositionGraph:[],accessibility:{notes:"Stack renders a <div> by default, which has no implicit ARIA role. Use the `as` prop to render semantic elements (nav, section, form) when the content has a specific structural purpose. Add aria-label or aria-labelledby when using landmark elements."}},gr={start:"flex-start",center:"center",end:"flex-end",stretch:"stretch",baseline:"baseline"},vr={start:"flex-start",center:"center",end:"flex-end",between:"space-between",around:"space-around"};function Je({gap:t="3",align:r="center",justify:n="start",as:i="div",wrap:a=!1,children:o,style:l,...s}){const d=i==="ul"||i==="ol",p={display:"flex",flexDirection:"row",gap:`var(--lucent-space-${t})`,alignItems:gr[r],justifyContent:vr[n],...a&&{flexWrap:"wrap"},...d&&{listStyle:"none",margin:0,padding:0},...l},h=i;return e.jsx(h,{style:p,...s,children:o})}const br={id:"row",name:"Row",tier:"atom",domain:"neutral",specVersion:"0.1",description:"Horizontal flex layout primitive that spaces children using design-system gap tokens.",designIntent:'Row is the primary horizontal layout container. Use it any time you need to arrange elements side by side — button groups, label-value pairs, icon + text combos, toolbar actions. Default align is "center" (vertical centering), which is the most common horizontal layout need. Use justify="between" for space-between patterns like a header with title on the left and actions on the right. Set wrap=true when items should flow to the next line on narrow screens. Row does not add padding — wrap it in a Card or apply padding on a parent container instead. For vertical arrangement, use Stack instead.',props:[{name:"gap",type:"enum",required:!1,default:"3",description:"Spacing between children. Maps to --lucent-space-{n} tokens.",enumValues:["0","1","2","3","4","5","6","8","10","12","16","20","24"]},{name:"align",type:"enum",required:!1,default:"center",description:'Cross-axis alignment (alignItems). "center" vertically centers items, which is the most common need.',enumValues:["start","center","end","stretch","baseline"]},{name:"justify",type:"enum",required:!1,default:"start",description:'Main-axis distribution (justifyContent). Use "between" for label/action pairs.',enumValues:["start","center","end","between","around"]},{name:"as",type:"enum",required:!1,default:"div",description:"HTML element to render. Use semantic elements when appropriate (nav for navigation, ul/ol for lists, section/header/footer/main/aside/article for landmarks). When rendering as ul/ol, list-style/margin/padding are auto-reset; consumers pass <li> children.",enumValues:["div","section","nav","header","footer","main","aside","article","form","fieldset","ul","ol"]},{name:"wrap",type:"boolean",required:!1,default:"false",description:"Whether children should wrap to the next line when they exceed the container width."},{name:"children",type:"ReactNode",required:!0,description:"The elements to arrange horizontally."},{name:"style",type:"object",required:!1,description:"Inline style overrides. Applied after computed layout styles."},{name:"className",type:"string",required:!1,description:"CSS class name passthrough."}],usageExamples:[{title:"Settings toggle",code:`<Row gap="3" justify="between">
|
|
147
147
|
<Text size="sm">Push notifications</Text>
|
|
148
148
|
<Toggle />
|
|
149
149
|
</Row>`},{title:"Button group",code:`<Row gap="2">
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
{tags.map(tag => (
|
|
176
176
|
<li key={tag}><Tag>{tag}</Tag></li>
|
|
177
177
|
))}
|
|
178
|
-
</Row>`}],compositionGraph:[],accessibility:{notes:'Row renders a <div> by default, which has no implicit ARIA role. Use the `as` prop to render semantic elements (nav, section) when the content has a specific structural purpose. For toolbar patterns, consider adding role="toolbar" via the role prop.'}},
|
|
178
|
+
</Row>`}],compositionGraph:[],accessibility:{notes:'Row renders a <div> by default, which has no implicit ARIA role. Use the `as` prop to render semantic elements (nav, section) when the content has a specific structural purpose. For toolbar patterns, consider adding role="toolbar" via the role prop.'}},yr={sm:{height:4,radius:"var(--lucent-radius-sm)"},md:{height:8,radius:"var(--lucent-radius-md)"},lg:{height:12,radius:"var(--lucent-radius-md)"}},xr={accent:"var(--lucent-accent-default)",success:"var(--lucent-success-default)",warning:"var(--lucent-warning-default)",danger:"var(--lucent-danger-default)"};function wr(t,r,n,i,a){return i!==void 0&&a!==void 0?i<=a?t>=a?"danger":t>=i?"warning":"success":t<=a?"danger":t<=i?"warning":"success":i!==void 0?i<=r/2?t<=i?"warning":n??"accent":t>=i?"warning":n??"accent":a!==void 0?a<=r/2?t<=a?"danger":n??"accent":t>=a?"danger":n??"accent":n??"accent"}function kr({value:t,max:r=100,variant:n,size:i="md",warnAt:a,dangerAt:o,label:l,style:s}){const d=Math.max(0,Math.min(t,r)),p=r>0?d/r*100:0,h=wr(d,r,n,a,o),f=yr[i],w=xr[h],b=l===!0?`${Math.round(p)}%`:l||null;return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",fontFamily:"var(--lucent-font-family-base)",...s},children:[e.jsx("div",{role:"progressbar","aria-valuenow":d,"aria-valuemin":0,"aria-valuemax":r,style:{flex:1,height:f.height,borderRadius:f.radius,background:"color-mix(in srgb, var(--lucent-text-primary) 8%, transparent)",overflow:"hidden"},children:e.jsx("div",{style:{width:`${p}%`,height:"100%",borderRadius:f.radius,background:w,transition:"width var(--lucent-duration-base) var(--lucent-easing-default), background var(--lucent-duration-base) var(--lucent-easing-default)"}})}),b!=null&&e.jsx("span",{style:{fontSize:"var(--lucent-font-size-sm)",fontWeight:"var(--lucent-font-weight-medium)",color:"var(--lucent-text-secondary)",whiteSpace:"nowrap",flexShrink:0},children:b})]})}const Sr={id:"progress",name:"Progress",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A horizontal bar indicating completion, progress, or resource usage.",designIntent:'Use Progress to visualise a bounded numeric value — task completion, disk usage, health bars, etc. Set warnAt/dangerAt thresholds for automatic colour shifts instead of hardcoding variants. Ascending thresholds (warnAt < dangerAt) suit "high is bad" metrics like CPU; descending thresholds (warnAt > dangerAt) suit "low is bad" metrics like battery.',props:[{name:"value",type:"number",required:!0,description:"Current progress value."},{name:"max",type:"number",required:!1,default:"100",description:"Maximum value."},{name:"variant",type:"enum",required:!1,default:"accent",description:"Colour variant. Overridden when thresholds are set.",enumValues:["accent","success","warning","danger"]},{name:"size",type:"enum",required:!1,default:"md",description:"Bar height.",enumValues:["sm","md","lg"]},{name:"warnAt",type:"number",required:!1,description:"Value at which variant auto-switches to warning."},{name:"dangerAt",type:"number",required:!1,description:"Value at which variant auto-switches to danger."},{name:"label",type:"union",required:!1,description:"true shows percentage; ReactNode shows custom label."}],usageExamples:[{title:"Basic",code:"<Progress value={60} />"},{title:"With label",code:"<Progress value={42} label />"},{title:"Thresholds",code:"<Progress value={85} warnAt={70} dangerAt={90} label />"},{title:"Danger variant",code:'<Progress value={95} variant="danger" size="lg" label />'}],compositionGraph:[],accessibility:{role:"progressbar",ariaAttributes:["aria-valuenow","aria-valuemin","aria-valuemax"],notes:"Uses native progressbar role. Add aria-label on the wrapping element for screen-reader context."}},Pe=120,Tr=`
|
|
179
179
|
@keyframes lucent-menu-in {
|
|
180
180
|
from { opacity: 0; transform: scale(0.97) translateY(-2px); }
|
|
181
181
|
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
from { opacity: 1; transform: scale(1) translateY(0); }
|
|
185
185
|
to { opacity: 0; transform: scale(0.97) translateY(-2px); }
|
|
186
186
|
}
|
|
187
|
-
`,nt={sm:"var(--lucent-space-2)",md:"var(--lucent-space-2)",lg:"var(--lucent-space-3)"},wa={sm:"sm",md:"md",lg:"lg"},ka={sm:"xs",md:"sm",lg:"md"},Sa={sm:12,md:14,lg:16},at=d.createContext(null),we=4;function Ta(t,a,n){const i=a.offsetWidth,r=a.offsetHeight,o=window.innerWidth,s=window.innerHeight;let l=0,c=0,u=n;const h={top:t.top-r-we,bottom:t.bottom+we,left:t.left-i-we,right:t.right+we};u.startsWith("bottom")?(l=h.bottom,l+r>s&&h.top>=0&&(l=h.top,u=u.replace("bottom","top"))):u.startsWith("top")?(l=h.top,l<0&&h.bottom+r<=s&&(l=h.bottom,u=u.replace("top","bottom"))):u==="left"?(l=t.top+t.height/2-r/2,c=h.left,c<0&&h.right+i<=o&&(c=h.right,u="right")):u==="right"&&(l=t.top+t.height/2-r/2,c=h.right,c+i>o&&h.left>=0&&(c=h.left,u="left")),(u.startsWith("top")||u.startsWith("bottom"))&&(u.endsWith("-start")?c=t.left:u.endsWith("-end")?c=t.right-i:c=t.left+t.width/2-i/2),c+i>o&&(c=o-i-8),c<0&&(c=8),l+r>s&&(l=s-r-8),l<0&&(l=8);let f="top left";return u.startsWith("top")?f=u.endsWith("-end")?"bottom right":u.endsWith("-start")?"bottom left":"bottom center":u.startsWith("bottom")?f=u.endsWith("-end")?"top right":u.endsWith("-start")?"top left":"top center":u==="left"?f="center right":u==="right"&&(f="center left"),{top:l,left:c,transformOrigin:f}}function je({onSelect:t,children:a,icon:n,shortcut:i,disabled:r=!1,danger:o=!1,selected:s=!1,style:l}){const c=d.useContext(at),u=d.useRef(-1);u.current===-1&&c&&(u.current=c.getItemIndex(),c.registerItem(u.current,r));const h=c?c.activeIndex===u.current:!1,f=(c==null?void 0:c.size)??"md",S=nt[f],w=wa[f],g=Sa[f],m=()=>{r||(t(),c==null||c.close())},p=()=>{!r&&c&&c.setActiveIndex(u.current)},b=r?"var(--lucent-text-disabled)":o?"var(--lucent-danger-text)":"var(--lucent-text-primary)";return e.jsxs("div",{role:"menuitemcheckbox","aria-checked":s,"aria-disabled":r||void 0,"data-active":h||void 0,tabIndex:-1,onClick:m,onMouseEnter:p,style:{display:"flex",alignItems:"center",gap:S,padding:S,borderRadius:"var(--lucent-radius-md)",cursor:r?"not-allowed":"pointer",background:s?"color-mix(in srgb, var(--lucent-accent-default) 12%, var(--lucent-surface-overlay))":h?"var(--lucent-surface-secondary)":"transparent",boxShadow:s?"var(--lucent-shadow-sm)":"none",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), box-shadow var(--lucent-duration-fast) var(--lucent-easing-default)",opacity:r?.5:1,outline:"none",...l},children:[n&&e.jsx("span",{style:{flexShrink:0,display:"flex",color:r?"var(--lucent-text-disabled)":o?"var(--lucent-danger-text)":"var(--lucent-text-secondary)"},children:n}),e.jsx("span",{style:{flex:1,minWidth:0},children:e.jsx(E.Text,{size:w,style:{color:b},children:a})}),i&&e.jsx(E.Text,{size:f==="lg"?"sm":"xs",style:{color:"var(--lucent-text-secondary)",flexShrink:0},children:i}),s&&e.jsx("span",{style:{flexShrink:0,display:"flex",color:"var(--lucent-accent-default)"},children:e.jsx("svg",{width:g,height:g,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2.5,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,children:e.jsx("polyline",{points:"20 6 9 17 4 12"})})})]})}function Rt({style:t}){return e.jsx("div",{role:"separator",style:{height:1,margin:"var(--lucent-space-1) 0",background:"var(--lucent-border-subtle)",...t}})}function Ca({label:t,children:a,style:n}){const i=d.useContext(at),r=(i==null?void 0:i.size)??"md",o=nt[r];return e.jsxs("div",{role:"group","aria-label":t,style:n,children:[e.jsx("div",{style:{padding:`${o} ${o} var(--lucent-space-1)`},children:e.jsx(E.Text,{size:ka[r],color:"secondary",weight:"medium",children:t})}),a]})}function rt({trigger:t,children:a,placement:n="bottom-start",size:i="md",open:r,onOpenChange:o,portalContainer:s,style:l}){const c=r!==void 0,[u,h]=d.useState(!1),f=c?r:u,[S,w]=d.useState(!1),[g,m]=d.useState("idle"),p=d.useRef(null),b=d.useRef(null),y=d.useRef(null),x=d.useRef(!1),[T,k]=d.useState(null),v=d.useRef(0),A=d.useRef(new Map),[C,j]=d.useState(-1);if(!x.current&&typeof document<"u"){const P=document.createElement("style");P.textContent=xa,document.head.appendChild(P),x.current=!0}d.useEffect(()=>{if(f)v.current=0,A.current.clear(),w(!0),m("entering");else if(S){m("exiting");const P=setTimeout(()=>{w(!1),m("idle")},Be);return()=>clearTimeout(P)}},[f]);const q=d.useCallback(P=>{c||h(P),o==null||o(P)},[c,o]),D=d.useCallback(()=>{const P=y.current;P&&(P.querySelector("button, [tabindex]")??P).focus()},[]),I=d.useCallback(()=>{q(!1),D()},[q,D]);d.useLayoutEffect(()=>{if(!S||g!=="entering"||!p.current)return;const P=requestAnimationFrame(()=>{const z=b.current,M=p.current;if(!z||!M)return;const $=M.getBoundingClientRect();k(Ta($,z,n))});return()=>cancelAnimationFrame(P)},[S,g,n]),d.useEffect(()=>{if(!S||g!=="entering")return;const P=setTimeout(()=>{const M=Array.from(A.current.entries()).sort(($,W)=>$[0]-W[0]).find(([,$])=>!$.disabled);j(M?M[0]:-1)},0);return()=>clearTimeout(P)},[S,g]),d.useEffect(()=>{if(!f)return;const P=M=>{var W,L;const $=M.target;(W=p.current)!=null&&W.contains($)||(L=b.current)!=null&&L.contains($)||I()},z=requestAnimationFrame(()=>{document.addEventListener("mousedown",P)});return()=>{cancelAnimationFrame(z),document.removeEventListener("mousedown",P)}},[f,I]),d.useEffect(()=>{if(!f)return;let P=!1;const z=setTimeout(()=>{P=!0},50),M=()=>{P&&I()};return window.addEventListener("scroll",M,{passive:!0,capture:!0}),()=>{clearTimeout(z),window.removeEventListener("scroll",M,{capture:!0})}},[f,I]);const F=d.useCallback(()=>Array.from(A.current.entries()).filter(([,P])=>!P.disabled).map(([P])=>P).sort((P,z)=>P-z),[]),R=d.useCallback(P=>{if(!f)return;const z=F();if(z.length!==0)switch(P.key){case"Escape":P.preventDefault(),I();break;case"ArrowDown":{P.preventDefault(),j(M=>{const $=z.indexOf(M);return $<z.length-1?z[$+1]:z[0]});break}case"ArrowUp":{P.preventDefault(),j(M=>{const $=z.indexOf(M);return $>0?z[$-1]:z[z.length-1]});break}case"Home":{P.preventDefault(),j(z[0]);break}case"End":{P.preventDefault(),j(z[z.length-1]);break}case"Enter":case" ":{P.preventDefault();const M=b.current;if(M){const $=M.querySelector('[data-active="true"]');$==null||$.click()}break}case"Tab":{I();break}}},[f,I,F]);d.useEffect(()=>{if(f)return document.addEventListener("keydown",R),()=>document.removeEventListener("keydown",R)},[f,R]),d.useEffect(()=>{if(!f)return;const P=b.current;if(!P)return;const z=P.querySelector('[data-active="true"]');z==null||z.scrollIntoView({block:"nearest"})},[C,f]);const O=()=>{q(!f)},B=P=>{(P.key==="Enter"||P.key===" "||P.key==="ArrowDown")&&(P.preventDefault(),f||q(!0)),P.key==="ArrowUp"&&(P.preventDefault(),f||(q(!0),setTimeout(()=>{const z=F();z.length>0&&j(z[z.length-1])},0)))},N={activeIndex:C,setActiveIndex:j,registerItem:(P,z)=>{A.current.set(P,{disabled:z})},close:I,getItemIndex:()=>v.current++,size:i},G=d.useId();return e.jsxs(e.Fragment,{children:[e.jsx("span",{ref:p,style:{display:"inline-flex"},children:e.jsx("span",{ref:y,onClick:O,onKeyDown:B,"aria-haspopup":"menu","aria-expanded":f,"aria-controls":f?G:void 0,style:{display:"inline-flex",cursor:"pointer"},children:t})}),S&&oe.createPortal(e.jsx(at.Provider,{value:N,children:e.jsx("div",{ref:b,id:G,role:"menu",style:{position:"fixed",top:(T==null?void 0:T.top)??-9999,left:(T==null?void 0:T.left)??-9999,zIndex:1e3,minWidth:180,background:"color-mix(in srgb, var(--lucent-surface-overlay) 85%, transparent)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 15%, var(--lucent-border-default))",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 0 24px -4px color-mix(in srgb, var(--lucent-accent-default) 12%, transparent), var(--lucent-shadow-md)",padding:nt[i],maxHeight:"min(320px, calc(100vh - 32px))",overflowY:"auto",animation:g==="exiting"?`lucent-menu-out ${Be}ms var(--lucent-easing-default) forwards`:`lucent-menu-in ${Be}ms var(--lucent-easing-decelerate)`,transformOrigin:(T==null?void 0:T.transformOrigin)??"top left",outline:"none",pointerEvents:g==="exiting"?"none":"auto",...l},children:a})}),s??document.body)]})}const Ia={id:"menu",name:"Menu",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"A dropdown menu triggered by clicking a host element. Renders in a portal to avoid overflow clipping, supports placement with auto-flip, keyboard navigation (arrow keys, Enter, Escape), and outside-click dismissal.",designIntent:'Menu provides a contextual action list that appears from a trigger element. It is a foundational overlay primitive — Select dropdowns, notification feeds, and context menus can all be composed from this building block.\n\n## Compound component API\nMenu uses a compound component pattern (`Menu`, `MenuItem`, `MenuSeparator`, `MenuGroup`) rather than a flat data array. Menu items have divergent structures — actions, separators, groups, icons, shortcut hints, danger state — that compose naturally as JSX children rather than discriminated union objects.\n\n## Sizing\nThe `size` prop (`sm` | `md` | `lg`) flows from the root `Menu` through context to all sub-components. Font sizes are aligned with Button: sm → `font-size-sm`, md → `font-size-md`, lg → `font-size-lg`. Item padding and gap use `space-2` for sm/md and `space-3` for lg, matching MultiSelect dropdown spacing. Group labels stay one step smaller than item text. Checkmark icons scale from 12px to 16px.\n\n## Placement & auto-flip\nThe `placement` prop sets the preferred position (default `bottom-start`). When the popover would overflow the viewport, it automatically flips to the opposite side. Horizontal alignment (`-start`, `-end`, or centered) is preserved during the flip. Position is computed with `getBoundingClientRect` on mount and rendered via `position: fixed` in a portal.\n\n## Portal rendering\nThe popover is portaled via `createPortal` (default: `document.body`). This prevents overflow clipping from parent containers with `overflow: hidden`. The trigger wrapper stays inline in the DOM tree so it participates in layout normally. The `portalContainer` prop lets consumers render the portal inside a wrapper element that sets `--lucent-*` CSS custom property overrides, preserving per-section theming.\n\n## Keyboard navigation\nFollows WAI-ARIA Menu Button pattern. Arrow keys cycle through enabled items (wrapping). Enter/Space selects the active item and closes the menu. Escape closes without selection. Tab closes and lets focus move naturally. Home/End jump to first/last enabled item.\n\n## Dismissal\nMenus close on outside click (mousedown, deferred via `requestAnimationFrame` to avoid catching the opening click), Escape, Tab, and scroll (armed after 50ms to skip mount-triggered scroll events). After close, focus returns to the trigger element.\n\n## Selected state\nMenuItem accepts a `selected` prop that renders a trailing accent-colored checkmark. The selected item gets a `color-mix(in srgb, accent-default 12%, surface-overlay)` background with `shadow-sm` elevation, making it visually stronger than the hover state (subtle `color-mix` tint). Uses `role="menuitemcheckbox"` with `aria-checked` for accessibility.\n\n## Animation\nBoth entrance and exit use a subtle scale + fade (`scale(0.97) ↔ 1`, `opacity 0 ↔ 1`) over 120ms. Entrance uses `easing-decelerate`, exit uses `easing-default`. `transform-origin` is set based on the actual placement (after auto-flip). The portal stays mounted during the exit animation via a `visible` state with `pointerEvents: none` to prevent interaction while fading out.',props:[{name:"trigger",type:"ReactNode",required:!0,description:"The element that toggles the menu on click. Typically a Button with a chevron. Wrapped in a <span> that receives click, keyboard, and ARIA attributes."},{name:"children",type:"ReactNode",required:!0,description:"MenuItem, MenuSeparator, and/or MenuGroup elements."},{name:"size",type:"enum",required:!1,default:"md",description:"Size of the menu panel. Controls item padding, gap, font size, and checkmark icon size. Font sizes match Button at each tier: sm → font-size-sm, md → font-size-md, lg → font-size-lg.",enumValues:["sm","md","lg"]},{name:"placement",type:"enum",required:!1,default:"bottom-start",description:"Preferred placement relative to the trigger. Auto-flips when near viewport edges.",enumValues:["top","top-start","top-end","bottom","bottom-start","bottom-end","left","right"]},{name:"open",type:"boolean",required:!1,description:"Controlled open state. When provided, the menu becomes controlled."},{name:"onOpenChange",type:"function",required:!1,description:"Callback fired when the menu opens or closes. Receives the new open state."},{name:"portalContainer",type:"HTMLElement | null",required:!1,description:"DOM element to portal the popover into. Defaults to document.body. Set this to a wrapper element to preserve CSS custom property inheritance for per-section theming."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the popover panel."},{name:"MenuItem.onSelect",type:"function",required:!0,description:"Fires when the item is selected via click or Enter/Space."},{name:"MenuItem.children",type:"ReactNode",required:!0,description:"Label content. Rendered via the Text atom at the size determined by the parent Menu."},{name:"MenuItem.icon",type:"ReactNode",required:!1,description:"Leading icon rendered before the label."},{name:"MenuItem.shortcut",type:"string",required:!1,description:'Trailing shortcut hint (e.g. "Cmd+D"). Displayed in secondary text.'},{name:"MenuItem.disabled",type:"boolean",required:!1,default:"false",description:"Disables selection, grays out the item, and skips it during keyboard navigation."},{name:"MenuItem.danger",type:"boolean",required:!1,default:"false",description:"Renders the item in danger color for destructive actions."},{name:"MenuItem.selected",type:"boolean",required:!1,default:"false",description:"Marks the item as currently selected. Shows a trailing checkmark in accent color, accent-tinted background via color-mix, and shadow-sm elevation. Visually stronger than hover."},{name:"MenuItem.style",type:"object",required:!1,description:"Inline style overrides for the item."},{name:"MenuSeparator.style",type:"object",required:!1,description:"Inline style overrides for the separator line."},{name:"MenuGroup.label",type:"string",required:!0,description:"Label shown above the group. Rendered one font step smaller than item text."},{name:"MenuGroup.children",type:"ReactNode",required:!0,description:"MenuItem elements within the group."},{name:"MenuGroup.style",type:"object",required:!1,description:"Inline style overrides for the group wrapper."}],usageExamples:[{title:"Basic menu",code:`<Menu trigger={<Button chevron>Actions</Button>}>
|
|
187
|
+
`,at={sm:"var(--lucent-space-2)",md:"var(--lucent-space-2)",lg:"var(--lucent-space-3)"},Cr={sm:"sm",md:"md",lg:"lg"},jr={sm:"xs",md:"sm",lg:"md"},Ir={sm:12,md:14,lg:16},ot=c.createContext(null),Se=4;function Mr(t,r,n){const i=r.offsetWidth,a=r.offsetHeight,o=window.innerWidth,l=window.innerHeight;let s=0,d=0,p=n;const h={top:t.top-a-Se,bottom:t.bottom+Se,left:t.left-i-Se,right:t.right+Se};p.startsWith("bottom")?(s=h.bottom,s+a>l&&h.top>=0&&(s=h.top,p=p.replace("bottom","top"))):p.startsWith("top")?(s=h.top,s<0&&h.bottom+a<=l&&(s=h.bottom,p=p.replace("top","bottom"))):p==="left"?(s=t.top+t.height/2-a/2,d=h.left,d<0&&h.right+i<=o&&(d=h.right,p="right")):p==="right"&&(s=t.top+t.height/2-a/2,d=h.right,d+i>o&&h.left>=0&&(d=h.left,p="left")),(p.startsWith("top")||p.startsWith("bottom"))&&(p.endsWith("-start")?d=t.left:p.endsWith("-end")?d=t.right-i:d=t.left+t.width/2-i/2),d+i>o&&(d=o-i-8),d<0&&(d=8),s+a>l&&(s=l-a-8),s<0&&(s=8);let f="top left";return p.startsWith("top")?f=p.endsWith("-end")?"bottom right":p.endsWith("-start")?"bottom left":"bottom center":p.startsWith("bottom")?f=p.endsWith("-end")?"top right":p.endsWith("-start")?"top left":"top center":p==="left"?f="center right":p==="right"&&(f="center left"),{top:s,left:d,transformOrigin:f}}function Me({onSelect:t,children:r,icon:n,shortcut:i,disabled:a=!1,danger:o=!1,selected:l=!1,style:s}){const d=c.useContext(ot),p=c.useRef(-1);p.current===-1&&d&&(p.current=d.getItemIndex(),d.registerItem(p.current,a));const h=d?d.activeIndex===p.current:!1,f=(d==null?void 0:d.size)??"md",w=at[f],b=Cr[f],g=Ir[f],m=()=>{a||(t(),d==null||d.close())},u=()=>{!a&&d&&d.setActiveIndex(p.current)},y=a?"var(--lucent-text-disabled)":o?"var(--lucent-danger-text)":"var(--lucent-text-primary)";return e.jsxs("div",{role:"menuitemcheckbox","aria-checked":l,"aria-disabled":a||void 0,"data-active":h||void 0,tabIndex:-1,onClick:m,onMouseEnter:u,style:{display:"flex",alignItems:"center",gap:w,padding:w,borderRadius:"var(--lucent-radius-md)",cursor:a?"not-allowed":"pointer",background:l?"color-mix(in srgb, var(--lucent-accent-default) 12%, var(--lucent-surface-overlay))":h?"var(--lucent-surface-secondary)":"transparent",boxShadow:l?"var(--lucent-shadow-sm)":"none",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), box-shadow var(--lucent-duration-fast) var(--lucent-easing-default)",opacity:a?.5:1,outline:"none",...s},children:[n&&e.jsx("span",{style:{flexShrink:0,display:"flex",color:a?"var(--lucent-text-disabled)":o?"var(--lucent-danger-text)":"var(--lucent-text-secondary)"},children:n}),e.jsx("span",{style:{flex:1,minWidth:0},children:e.jsx(E.Text,{size:b,style:{color:y},children:r})}),i&&e.jsx(E.Text,{size:f==="lg"?"sm":"xs",style:{color:"var(--lucent-text-secondary)",flexShrink:0},children:i}),l&&e.jsx("span",{style:{flexShrink:0,display:"flex",color:"var(--lucent-accent-default)"},children:e.jsx("svg",{width:g,height:g,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2.5,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,children:e.jsx("polyline",{points:"20 6 9 17 4 12"})})})]})}function Nt({style:t}){return e.jsx("div",{role:"separator",style:{height:1,margin:"var(--lucent-space-1) 0",background:"var(--lucent-border-subtle)",...t}})}function Er({label:t,children:r,style:n}){const i=c.useContext(ot),a=(i==null?void 0:i.size)??"md",o=at[a];return e.jsxs("div",{role:"group","aria-label":t,style:n,children:[e.jsx("div",{style:{padding:`${o} ${o} var(--lucent-space-1)`},children:e.jsx(E.Text,{size:jr[a],color:"secondary",weight:"medium",children:t})}),r]})}function it({trigger:t,children:r,placement:n="bottom-start",size:i="md",open:a,onOpenChange:o,portalContainer:l,style:s}){const d=a!==void 0,[p,h]=c.useState(!1),f=d?a:p,[w,b]=c.useState(!1),[g,m]=c.useState("idle"),u=c.useRef(null),y=c.useRef(null),x=c.useRef(null),T=c.useRef(!1),[S,k]=c.useState(null),v=c.useRef(0),A=c.useRef(new Map),[C,M]=c.useState(-1);if(!T.current&&typeof document<"u"){const P=document.createElement("style");P.textContent=Tr,document.head.appendChild(P),T.current=!0}c.useEffect(()=>{if(f)v.current=0,A.current.clear(),b(!0),m("entering");else if(w){m("exiting");const P=setTimeout(()=>{b(!1),m("idle")},Pe);return()=>clearTimeout(P)}},[f]);const q=c.useCallback(P=>{d||h(P),o==null||o(P)},[d,o]),B=c.useCallback(()=>{const P=x.current;P&&(P.querySelector("button, [tabindex]")??P).focus()},[]),I=c.useCallback(()=>{q(!1),B()},[q,B]);c.useLayoutEffect(()=>{if(!w||g!=="entering"||!u.current)return;const P=requestAnimationFrame(()=>{const z=y.current,j=u.current;if(!z||!j)return;const $=j.getBoundingClientRect();k(Mr($,z,n))});return()=>cancelAnimationFrame(P)},[w,g,n]),c.useEffect(()=>{if(!w||g!=="entering")return;const P=setTimeout(()=>{const j=Array.from(A.current.entries()).sort(($,V)=>$[0]-V[0]).find(([,$])=>!$.disabled);M(j?j[0]:-1)},0);return()=>clearTimeout(P)},[w,g]),c.useEffect(()=>{if(!f)return;const P=j=>{var V,F;const $=j.target;(V=u.current)!=null&&V.contains($)||(F=y.current)!=null&&F.contains($)||I()},z=requestAnimationFrame(()=>{document.addEventListener("mousedown",P)});return()=>{cancelAnimationFrame(z),document.removeEventListener("mousedown",P)}},[f,I]),c.useEffect(()=>{if(!f)return;let P=!1;const z=setTimeout(()=>{P=!0},50),j=()=>{P&&I()};return window.addEventListener("scroll",j,{passive:!0,capture:!0}),()=>{clearTimeout(z),window.removeEventListener("scroll",j,{capture:!0})}},[f,I]);const L=c.useCallback(()=>Array.from(A.current.entries()).filter(([,P])=>!P.disabled).map(([P])=>P).sort((P,z)=>P-z),[]),D=c.useCallback(P=>{if(!f)return;const z=L();if(z.length!==0)switch(P.key){case"Escape":P.preventDefault(),I();break;case"ArrowDown":{P.preventDefault(),M(j=>{const $=z.indexOf(j);return $<z.length-1?z[$+1]:z[0]});break}case"ArrowUp":{P.preventDefault(),M(j=>{const $=z.indexOf(j);return $>0?z[$-1]:z[z.length-1]});break}case"Home":{P.preventDefault(),M(z[0]);break}case"End":{P.preventDefault(),M(z[z.length-1]);break}case"Enter":case" ":{P.preventDefault();const j=y.current;if(j){const $=j.querySelector('[data-active="true"]');$==null||$.click()}break}case"Tab":{I();break}}},[f,I,L]);c.useEffect(()=>{if(f)return document.addEventListener("keydown",D),()=>document.removeEventListener("keydown",D)},[f,D]),c.useEffect(()=>{if(!f)return;const P=y.current;if(!P)return;const z=P.querySelector('[data-active="true"]');z==null||z.scrollIntoView({block:"nearest"})},[C,f]);const N=()=>{q(!f)},R=P=>{(P.key==="Enter"||P.key===" "||P.key==="ArrowDown")&&(P.preventDefault(),f||q(!0)),P.key==="ArrowUp"&&(P.preventDefault(),f||(q(!0),setTimeout(()=>{const z=L();z.length>0&&M(z[z.length-1])},0)))},W={activeIndex:C,setActiveIndex:M,registerItem:(P,z)=>{A.current.set(P,{disabled:z})},close:I,getItemIndex:()=>v.current++,size:i},G=c.useId();return e.jsxs(e.Fragment,{children:[e.jsx("span",{ref:u,style:{display:"inline-flex"},children:e.jsx("span",{ref:x,onClick:N,onKeyDown:R,"aria-haspopup":"menu","aria-expanded":f,"aria-controls":f?G:void 0,style:{display:"inline-flex",cursor:"pointer"},children:t})}),w&&Q.createPortal(e.jsx(ot.Provider,{value:W,children:e.jsx("div",{ref:y,id:G,role:"menu",style:{position:"fixed",top:(S==null?void 0:S.top)??-9999,left:(S==null?void 0:S.left)??-9999,zIndex:1e3,minWidth:180,background:"color-mix(in srgb, var(--lucent-surface-overlay) 85%, transparent)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 15%, var(--lucent-border-default))",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 0 24px -4px color-mix(in srgb, var(--lucent-accent-default) 12%, transparent), var(--lucent-shadow-md)",padding:at[i],maxHeight:"min(320px, calc(100vh - 32px))",overflowY:"auto",animation:g==="exiting"?`lucent-menu-out ${Pe}ms var(--lucent-easing-default) forwards`:`lucent-menu-in ${Pe}ms var(--lucent-easing-decelerate)`,transformOrigin:(S==null?void 0:S.transformOrigin)??"top left",outline:"none",pointerEvents:g==="exiting"?"none":"auto",...s},children:r})}),l??document.body)]})}const zr={id:"menu",name:"Menu",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"A dropdown menu triggered by clicking a host element. Renders in a portal to avoid overflow clipping, supports placement with auto-flip, keyboard navigation (arrow keys, Enter, Escape), and outside-click dismissal.",designIntent:'Menu provides a contextual action list that appears from a trigger element. It is a foundational overlay primitive — Select dropdowns, notification feeds, and context menus can all be composed from this building block.\n\n## Compound component API\nMenu uses a compound component pattern (`Menu`, `MenuItem`, `MenuSeparator`, `MenuGroup`) rather than a flat data array. Menu items have divergent structures — actions, separators, groups, icons, shortcut hints, danger state — that compose naturally as JSX children rather than discriminated union objects.\n\n## Sizing\nThe `size` prop (`sm` | `md` | `lg`) flows from the root `Menu` through context to all sub-components. Font sizes are aligned with Button: sm → `font-size-sm`, md → `font-size-md`, lg → `font-size-lg`. Item padding and gap use `space-2` for sm/md and `space-3` for lg, matching MultiSelect dropdown spacing. Group labels stay one step smaller than item text. Checkmark icons scale from 12px to 16px.\n\n## Placement & auto-flip\nThe `placement` prop sets the preferred position (default `bottom-start`). When the popover would overflow the viewport, it automatically flips to the opposite side. Horizontal alignment (`-start`, `-end`, or centered) is preserved during the flip. Position is computed with `getBoundingClientRect` on mount and rendered via `position: fixed` in a portal.\n\n## Portal rendering\nThe popover is portaled via `createPortal` (default: `document.body`). This prevents overflow clipping from parent containers with `overflow: hidden`. The trigger wrapper stays inline in the DOM tree so it participates in layout normally. The `portalContainer` prop lets consumers render the portal inside a wrapper element that sets `--lucent-*` CSS custom property overrides, preserving per-section theming.\n\n## Keyboard navigation\nFollows WAI-ARIA Menu Button pattern. Arrow keys cycle through enabled items (wrapping). Enter/Space selects the active item and closes the menu. Escape closes without selection. Tab closes and lets focus move naturally. Home/End jump to first/last enabled item.\n\n## Dismissal\nMenus close on outside click (mousedown, deferred via `requestAnimationFrame` to avoid catching the opening click), Escape, Tab, and scroll (armed after 50ms to skip mount-triggered scroll events). After close, focus returns to the trigger element.\n\n## Selected state\nMenuItem accepts a `selected` prop that renders a trailing accent-colored checkmark. The selected item gets a `color-mix(in srgb, accent-default 12%, surface-overlay)` background with `shadow-sm` elevation, making it visually stronger than the hover state (subtle `color-mix` tint). Uses `role="menuitemcheckbox"` with `aria-checked` for accessibility.\n\n## Animation\nBoth entrance and exit use a subtle scale + fade (`scale(0.97) ↔ 1`, `opacity 0 ↔ 1`) over 120ms. Entrance uses `easing-decelerate`, exit uses `easing-default`. `transform-origin` is set based on the actual placement (after auto-flip). The portal stays mounted during the exit animation via a `visible` state with `pointerEvents: none` to prevent interaction while fading out.',props:[{name:"trigger",type:"ReactNode",required:!0,description:"The element that toggles the menu on click. Typically a Button with a chevron. Wrapped in a <span> that receives click, keyboard, and ARIA attributes."},{name:"children",type:"ReactNode",required:!0,description:"MenuItem, MenuSeparator, and/or MenuGroup elements."},{name:"size",type:"enum",required:!1,default:"md",description:"Size of the menu panel. Controls item padding, gap, font size, and checkmark icon size. Font sizes match Button at each tier: sm → font-size-sm, md → font-size-md, lg → font-size-lg.",enumValues:["sm","md","lg"]},{name:"placement",type:"enum",required:!1,default:"bottom-start",description:"Preferred placement relative to the trigger. Auto-flips when near viewport edges.",enumValues:["top","top-start","top-end","bottom","bottom-start","bottom-end","left","right"]},{name:"open",type:"boolean",required:!1,description:"Controlled open state. When provided, the menu becomes controlled."},{name:"onOpenChange",type:"function",required:!1,description:"Callback fired when the menu opens or closes. Receives the new open state."},{name:"portalContainer",type:"HTMLElement | null",required:!1,description:"DOM element to portal the popover into. Defaults to document.body. Set this to a wrapper element to preserve CSS custom property inheritance for per-section theming."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the popover panel."},{name:"MenuItem.onSelect",type:"function",required:!0,description:"Fires when the item is selected via click or Enter/Space."},{name:"MenuItem.children",type:"ReactNode",required:!0,description:"Label content. Rendered via the Text atom at the size determined by the parent Menu."},{name:"MenuItem.icon",type:"ReactNode",required:!1,description:"Leading icon rendered before the label."},{name:"MenuItem.shortcut",type:"string",required:!1,description:'Trailing shortcut hint (e.g. "Cmd+D"). Displayed in secondary text.'},{name:"MenuItem.disabled",type:"boolean",required:!1,default:"false",description:"Disables selection, grays out the item, and skips it during keyboard navigation."},{name:"MenuItem.danger",type:"boolean",required:!1,default:"false",description:"Renders the item in danger color for destructive actions."},{name:"MenuItem.selected",type:"boolean",required:!1,default:"false",description:"Marks the item as currently selected. Shows a trailing checkmark in accent color, accent-tinted background via color-mix, and shadow-sm elevation. Visually stronger than hover."},{name:"MenuItem.style",type:"object",required:!1,description:"Inline style overrides for the item."},{name:"MenuSeparator.style",type:"object",required:!1,description:"Inline style overrides for the separator line."},{name:"MenuGroup.label",type:"string",required:!0,description:"Label shown above the group. Rendered one font step smaller than item text."},{name:"MenuGroup.children",type:"ReactNode",required:!0,description:"MenuItem elements within the group."},{name:"MenuGroup.style",type:"object",required:!1,description:"Inline style overrides for the group wrapper."}],usageExamples:[{title:"Basic menu",code:`<Menu trigger={<Button chevron>Actions</Button>}>
|
|
188
188
|
<MenuItem onSelect={() => console.log('edit')}>Edit</MenuItem>
|
|
189
189
|
<MenuItem onSelect={() => console.log('duplicate')}>Duplicate</MenuItem>
|
|
190
190
|
<MenuSeparator />
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
<Menu trigger={<Button>Menu</Button>} open={open} onOpenChange={setOpen}>
|
|
222
222
|
<MenuItem onSelect={() => {}}>Option A</MenuItem>
|
|
223
223
|
<MenuItem onSelect={() => {}}>Option B</MenuItem>
|
|
224
|
-
</Menu>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Item labels and group headers",required:!0}],accessibility:{role:"menu",ariaAttributes:['role="menu" on the popover panel','role="menuitemcheckbox" with aria-checked on each MenuItem','role="separator" on MenuSeparator','role="group" with aria-label on MenuGroup','aria-haspopup="menu" on the trigger wrapper',"aria-expanded on the trigger wrapper","aria-controls linking trigger to popover via id","aria-disabled on disabled MenuItems"],keyboardInteractions:["Enter/Space on trigger — toggle menu","ArrowDown on trigger — open menu, focus first item","ArrowUp on trigger — open menu, focus last item","ArrowDown — focus next enabled item (wraps)","ArrowUp — focus previous enabled item (wraps)","Enter/Space — select focused item, close menu","Escape — close menu, return focus to trigger","Tab — close menu, let focus move naturally","Home — focus first enabled item","End — focus last enabled item"],notes:'Focus returns to the trigger element after the menu is dismissed via Escape or selection. Disabled items are skipped during keyboard navigation and have aria-disabled. Selected items use role="menuitemcheckbox" with aria-checked for screen reader announcement. The popover is portaled to document.body (or portalContainer) but remains semantically linked to the trigger via aria-controls.'}},Lt={primary:{background:"var(--lucent-accent-default)",color:"var(--lucent-accent-fg)",border:"1px solid transparent"},secondary:{background:"color-mix(in srgb, var(--lucent-accent-default) 16%, transparent)",color:"var(--lucent-text-primary)",border:"1px solid transparent"},outline:{background:"transparent",color:"var(--lucent-text-primary)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 35%, var(--lucent-border-default))"},ghost:{background:"transparent",color:"var(--lucent-text-primary)",border:"1px solid transparent"},danger:{background:"var(--lucent-danger-default)",color:"#ffffff",border:"1px solid var(--lucent-danger-default)"},"danger-outline":{background:"transparent",color:"var(--lucent-danger-text)",border:"1px solid var(--lucent-danger-default)"},"danger-ghost":{background:"transparent",color:"var(--lucent-danger-text)",border:"1px solid transparent"}},ja={"2xs":{height:"22px",paddingOuter:"var(--lucent-space-2)",paddingInner:"var(--lucent-space-1)",fontSize:"var(--lucent-font-size-xs)",borderRadius:"var(--lucent-radius-md)"},xs:{height:"26px",paddingOuter:"var(--lucent-space-3)",paddingInner:"var(--lucent-space-1)",fontSize:"var(--lucent-font-size-xs)"},sm:{height:"calc(var(--lucent-space-8) * 0.5 + 18px)",paddingOuter:"var(--lucent-space-4)",paddingInner:"var(--lucent-space-2)",fontSize:"var(--lucent-font-size-sm)"},md:{height:"calc(var(--lucent-space-10) * 0.5 + 22px)",paddingOuter:"var(--lucent-space-5)",paddingInner:"var(--lucent-space-3)",fontSize:"var(--lucent-font-size-md)"},lg:{height:"calc(var(--lucent-space-12) * 0.5 + 26px)",paddingOuter:"var(--lucent-space-6)",paddingInner:"var(--lucent-space-3)",fontSize:"var(--lucent-font-size-lg)"}},Ma={"2xs":8,xs:10,sm:12,md:14,lg:16},Ea={"2xs":"24px",xs:"30px",sm:"34px",md:"36px",lg:"42px"},za={"2xs":"sm",xs:"sm",sm:"sm",md:"md",lg:"lg"},Aa={primary:"var(--lucent-accent-hover)",secondary:"color-mix(in srgb, var(--lucent-accent-default) 22%, transparent)",outline:"color-mix(in srgb, var(--lucent-accent-default) 10%, transparent)",ghost:"color-mix(in srgb, var(--lucent-accent-default) 8%, transparent)",danger:"var(--lucent-danger-hover)","danger-outline":"color-mix(in srgb, var(--lucent-danger-default) 10%, transparent)","danger-ghost":"color-mix(in srgb, var(--lucent-danger-default) 8%, transparent)"},qa={primary:"0 4px 14px -2px color-mix(in srgb, var(--lucent-accent-default) 25%, transparent)",secondary:"0 4px 14px -2px color-mix(in srgb, var(--lucent-accent-default) 20%, transparent)",outline:"0 4px 14px -2px color-mix(in srgb, var(--lucent-accent-default) 20%, transparent)",ghost:"0 4px 14px -2px color-mix(in srgb, var(--lucent-accent-default) 15%, transparent)",danger:"0 4px 14px -2px color-mix(in srgb, var(--lucent-danger-default) 25%, transparent)","danger-outline":"0 4px 14px -2px color-mix(in srgb, var(--lucent-danger-default) 20%, transparent)","danger-ghost":"0 4px 14px -2px color-mix(in srgb, var(--lucent-danger-default) 15%, transparent)"};function ct(t,a,n){t.style.transform="translateY(-1px)",t.style.boxShadow=qa[a],t.style.background=Aa[a],(a==="danger"&&n!==!1||a==="danger-outline"&&n!==!1)&&(t.style.borderColor="var(--lucent-danger-hover)")}function dt(t,a,n){t.style.transform="",t.style.boxShadow="",t.style.background=Lt[a].background,(a==="danger"&&n!==!1||a==="danger-outline"&&n!==!1)&&(t.style.borderColor="var(--lucent-danger-default)")}function ut(t,a){const i=a==="danger"||a==="danger-outline"||a==="danger-ghost"?"color-mix(in srgb, var(--lucent-danger-default) 40%, transparent)":"color-mix(in srgb, var(--lucent-accent-default) 40%, transparent)";t.style.transform="translateY(1px)",t.style.boxShadow=`0 0 0 4px ${i}`,t.dataset.pressed="1"}function pt(t){t.style.transform="",t.style.boxShadow="",delete t.dataset.pressed}function Ft({children:t,onClick:a,menuItems:n,variant:i="primary",size:r="md",bordered:o=!0,menuPlacement:s="bottom-end",disabled:l,loading:c=!1,leftIcon:u,style:h}){const f=l??c,[S,w]=d.useState(!1),g=ja[r],m=g.borderRadius??"var(--lucent-radius-lg)",p=Ma[r],b=f?{background:"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)",color:"var(--lucent-text-disabled)",borderColor:"transparent"}:{},y={display:"inline-flex",alignItems:"center",justifyContent:"center",gap:"var(--lucent-space-2)",fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",lineHeight:1,letterSpacing:"0.01em",cursor:f?"not-allowed":"pointer",outline:"none",margin:0,boxSizing:"border-box",whiteSpace:"nowrap",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), border-color var(--lucent-duration-fast) var(--lucent-easing-default), box-shadow var(--lucent-duration-fast) var(--lucent-easing-default), transform 80ms var(--lucent-easing-default)",height:g.height,fontSize:g.fontSize,...Lt[i],...o===!1&&{border:"none"},...b},x="var(--lucent-radius-sm)",T=`${m} ${x} ${x} ${m}`,k=`${x} ${m} ${m} ${x}`;return e.jsxs("div",{role:"group","aria-label":typeof t=="string"?t:"Split button",style:{display:"inline-flex",alignItems:"center",gap:"calc(var(--lucent-space-1) / 2)",...h},children:[e.jsxs("button",{type:"button",disabled:f,"aria-busy":c,onClick:a,onMouseEnter:v=>{f||ct(v.currentTarget,i,o)},onMouseLeave:v=>{f||dt(v.currentTarget,i,o)},onMouseDown:v=>{f||ut(v.currentTarget,i)},onMouseUp:v=>{pt(v.currentTarget)},onFocus:v=>{v.currentTarget.dataset.pressed||(v.currentTarget.style.boxShadow="0 0 0 3px var(--lucent-accent-subtle)")},onBlur:v=>{v.currentTarget.style.boxShadow=""},style:{...y,borderRadius:T,padding:i==="ghost"||i==="danger-ghost"?`0 ${g.paddingInner} 0 ${g.paddingOuter}`:`0 ${g.paddingOuter}`},children:[u,c?e.jsx(Da,{}):t]}),e.jsx(rt,{open:S,onOpenChange:w,placement:s,size:za[r],trigger:e.jsx("button",{type:"button",disabled:f,"aria-label":"More actions",onMouseEnter:v=>{f||ct(v.currentTarget,i,o)},onMouseLeave:v=>{f||dt(v.currentTarget,i,o)},onMouseDown:v=>{f||ut(v.currentTarget,i)},onMouseUp:v=>{pt(v.currentTarget)},onFocus:v=>{v.currentTarget.dataset.pressed||(v.currentTarget.style.boxShadow="0 0 0 3px var(--lucent-accent-subtle)")},onBlur:v=>{v.currentTarget.style.boxShadow=""},style:{...y,borderRadius:k,padding:0,width:Ea[r]},children:e.jsx("svg",{width:p,height:p,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2.5,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,style:{flexShrink:0,transition:"transform var(--lucent-duration-fast) var(--lucent-easing-default)",...S&&{transform:"rotate(180deg)"}},children:e.jsx("polyline",{points:"6 9 12 15 18 9"})})}),children:n.map((v,A)=>e.jsx(je,{onSelect:v.onSelect,...v.disabled!==void 0&&{disabled:v.disabled},...v.danger!==void 0&&{danger:v.danger},...v.icon!==void 0&&{icon:v.icon},children:v.label},A))})]})}Ft.displayName="SplitButton";function Da(){return e.jsxs("svg",{width:14,height:14,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2.5,strokeLinecap:"round","aria-hidden":!0,style:{animation:"lucent-spin 0.7s linear infinite",flexShrink:0},children:[e.jsx("style",{children:"@keyframes lucent-spin { to { transform: rotate(360deg); } }"}),e.jsx("path",{d:"M12 2a10 10 0 0 1 10 10"})]})}const Ba={id:"split-button",name:"SplitButton",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A compound button pairing a primary action with a chevron dropdown for secondary actions.",designIntent:'Use SplitButton when there is one dominant action alongside a small set of related alternatives (e.g. "Save" + "Save as draft", "Deploy" + "Deploy to staging"). Each half is a fully independent button separated by a small token-based gap, with a subtle inner corner radius (radius-sm) so the pair reads as a unit without sharing a border. Hover lift and press ring apply independently per half. Ghost variants use tighter inner padding to keep the halves visually close. The chevron half opens a Menu molecule; all dropdown keyboard navigation is inherited.',props:[{name:"children",type:"ReactNode",required:!0,description:"Label content for the primary action button."},{name:"onClick",type:"function",required:!0,description:"Handler fired when the primary (left) half is clicked."},{name:"menuItems",type:"array",required:!0,description:"Array of { label, onSelect, disabled?, danger?, icon? } for the dropdown."},{name:"variant",type:"enum",required:!1,default:"primary",description:"Visual variant applied to both halves.",enumValues:["primary","secondary","outline","ghost","danger","danger-outline","danger-ghost"]},{name:"size",type:"enum",required:!1,default:"md",description:"Size applied to both halves.",enumValues:["2xs","xs","sm","md","lg"]},{name:"bordered",type:"boolean",required:!1,default:"true",description:"If false, removes the border on both halves."},{name:"menuPlacement",type:"enum",required:!1,default:"bottom-end",description:"Dropdown placement relative to the chevron trigger.",enumValues:["top","top-start","top-end","bottom","bottom-start","bottom-end","left","right"]},{name:"disabled",type:"boolean",required:!1,description:"Disables both halves."},{name:"loading",type:"boolean",required:!1,default:"false",description:"Shows a spinner in the primary half and disables both."},{name:"leftIcon",type:"ReactNode",required:!1,description:"Icon rendered before the label in the primary half."},{name:"style",type:"object",required:!1,description:"Style overrides for the wrapper div."}],usageExamples:[{title:"Basic",code:"<SplitButton onClick={handleSave} menuItems={[{ label: 'Save as draft', onSelect: handleDraft }]}>Save</SplitButton>"},{title:"Outline with leftIcon",code:`<SplitButton variant="outline" leftIcon={<Icon name="deploy" />} onClick={deploy} menuItems={[{ label: 'Deploy to staging', onSelect: deployStaging }, { label: 'Rollback', onSelect: rollback, danger: true }]}>Deploy</SplitButton>`},{title:"Menu items with icons",code:`<SplitButton onClick={handleSave} menuItems={[{ label: 'Save as draft', onSelect: handleDraft, icon: <Icon name="file" /> }, { label: 'Export', onSelect: handleExport, icon: <Icon name="download" /> }]}>Save</SplitButton>`},{title:"Small ghost bordered (issue #90 use case)",code:`<SplitButton variant="ghost" size="2xs" bordered onClick={toggle} menuItems={[{ label: 'Reset layout', onSelect: reset }]}>Collapse all</SplitButton>`},{title:"Danger outline with disabled item",code:`<SplitButton variant="danger-outline" onClick={handleDelete} menuItems={[{ label: 'Move to trash', onSelect: trash }, { label: 'Delete forever', onSelect: nuke, danger: true }, { label: 'Archive (unavailable)', onSelect: archive, disabled: true }]}>Delete</SplitButton>`},{title:"Disabled",code:"<SplitButton disabled onClick={handleSave} menuItems={[{ label: 'Option', onSelect: fn }]}>Save</SplitButton>"},{title:"Loading",code:"<SplitButton loading onClick={handleSave} menuItems={[{ label: 'Option', onSelect: fn }]}>Saving...</SplitButton>"}],compositionGraph:[{componentId:"menu",componentName:"Menu",role:"Dropdown container for secondary actions",required:!0},{componentId:"menu-item",componentName:"MenuItem",role:"Individual dropdown action",required:!0}],accessibility:{role:"group",ariaAttributes:["aria-label","aria-haspopup","aria-expanded","aria-busy"],keyboardInteractions:["Enter/Space on primary button fires onClick","Enter/Space/ArrowDown on chevron opens dropdown","ArrowDown/ArrowUp cycles dropdown items","Home/End jumps to first/last item","Escape closes dropdown and returns focus to chevron","Tab closes dropdown"],notes:'The wrapper uses role="group" with aria-label derived from children. The chevron button carries aria-haspopup="menu" and aria-expanded via Menu. Primary button has aria-busy when loading.'}},he="var(--lucent-radius-sm)";function $t({children:t,style:a}){const n=d.Children.toArray(t).filter(d.isValidElement),i=n.length;return e.jsx("div",{role:"group",style:{display:"inline-flex",alignItems:"center",gap:"calc(var(--lucent-space-1) / 2)",...a},children:n.map((r,o)=>{const s=o===0,l=o===i-1;if(i===1)return r;const u=r.props.style??{},h=u.borderRadius??"var(--lucent-radius-lg)";let f;return s?f=`${h} ${he} ${he} ${h}`:l?f=`${he} ${h} ${h} ${he}`:f=he,d.cloneElement(r,{key:o,style:{...u,borderRadius:f}})})})}$t.displayName="ButtonGroup";const Pa={id:"button-group",name:"ButtonGroup",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A layout wrapper that visually groups multiple Button or SplitButton children into a related strip.",designIntent:"Use ButtonGroup for peer actions that belong together visually — toolbars, action bars, toggle groups. Unlike SegmentedControl (single-select with indicator), ButtonGroup has no built-in selection state; each child handles its own behaviour. Items are separated by a small token-based gap with subtle inner corner radius so the group reads as a unit while each button retains its own border and hover states.",props:[{name:"children",type:"ReactNode",required:!0,description:"Button or SplitButton elements to group."},{name:"style",type:"object",required:!1,description:"Style overrides for the wrapper div."}],usageExamples:[{title:"Toolbar",code:`<ButtonGroup>
|
|
224
|
+
</Menu>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Item labels and group headers",required:!0}],accessibility:{role:"menu",ariaAttributes:['role="menu" on the popover panel','role="menuitemcheckbox" with aria-checked on each MenuItem','role="separator" on MenuSeparator','role="group" with aria-label on MenuGroup','aria-haspopup="menu" on the trigger wrapper',"aria-expanded on the trigger wrapper","aria-controls linking trigger to popover via id","aria-disabled on disabled MenuItems"],keyboardInteractions:["Enter/Space on trigger — toggle menu","ArrowDown on trigger — open menu, focus first item","ArrowUp on trigger — open menu, focus last item","ArrowDown — focus next enabled item (wraps)","ArrowUp — focus previous enabled item (wraps)","Enter/Space — select focused item, close menu","Escape — close menu, return focus to trigger","Tab — close menu, let focus move naturally","Home — focus first enabled item","End — focus last enabled item"],notes:'Focus returns to the trigger element after the menu is dismissed via Escape or selection. Disabled items are skipped during keyboard navigation and have aria-disabled. Selected items use role="menuitemcheckbox" with aria-checked for screen reader announcement. The popover is portaled to document.body (or portalContainer) but remains semantically linked to the trigger via aria-controls.'}},Wt={primary:{background:"var(--lucent-accent-default)",color:"var(--lucent-accent-fg)",border:"1px solid transparent"},secondary:{background:"color-mix(in srgb, var(--lucent-accent-default) 16%, transparent)",color:"var(--lucent-text-primary)",border:"1px solid transparent"},outline:{background:"transparent",color:"var(--lucent-text-primary)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 35%, var(--lucent-border-default))"},ghost:{background:"transparent",color:"var(--lucent-text-primary)",border:"1px solid transparent"},danger:{background:"var(--lucent-danger-default)",color:"#ffffff",border:"1px solid var(--lucent-danger-default)"},"danger-outline":{background:"transparent",color:"var(--lucent-danger-text)",border:"1px solid var(--lucent-danger-default)"},"danger-ghost":{background:"transparent",color:"var(--lucent-danger-text)",border:"1px solid transparent"}},Ar={"2xs":{height:"22px",paddingOuter:"var(--lucent-space-2)",paddingInner:"var(--lucent-space-1)",fontSize:"var(--lucent-font-size-xs)",borderRadius:"var(--lucent-radius-md)"},xs:{height:"26px",paddingOuter:"var(--lucent-space-3)",paddingInner:"var(--lucent-space-1)",fontSize:"var(--lucent-font-size-xs)"},sm:{height:"calc(var(--lucent-space-8) * 0.5 + 18px)",paddingOuter:"var(--lucent-space-4)",paddingInner:"var(--lucent-space-2)",fontSize:"var(--lucent-font-size-sm)"},md:{height:"calc(var(--lucent-space-10) * 0.5 + 22px)",paddingOuter:"var(--lucent-space-5)",paddingInner:"var(--lucent-space-3)",fontSize:"var(--lucent-font-size-md)"},lg:{height:"calc(var(--lucent-space-12) * 0.5 + 26px)",paddingOuter:"var(--lucent-space-6)",paddingInner:"var(--lucent-space-3)",fontSize:"var(--lucent-font-size-lg)"}},qr={"2xs":8,xs:10,sm:12,md:14,lg:16},Br={"2xs":"24px",xs:"30px",sm:"34px",md:"36px",lg:"42px"},Rr={"2xs":"sm",xs:"sm",sm:"sm",md:"md",lg:"lg"},Pr={primary:"var(--lucent-accent-hover)",secondary:"color-mix(in srgb, var(--lucent-accent-default) 22%, transparent)",outline:"color-mix(in srgb, var(--lucent-accent-default) 10%, transparent)",ghost:"color-mix(in srgb, var(--lucent-accent-default) 8%, transparent)",danger:"var(--lucent-danger-hover)","danger-outline":"color-mix(in srgb, var(--lucent-danger-default) 10%, transparent)","danger-ghost":"color-mix(in srgb, var(--lucent-danger-default) 8%, transparent)"},Dr={primary:"0 4px 14px -2px color-mix(in srgb, var(--lucent-accent-default) 25%, transparent)",secondary:"0 4px 14px -2px color-mix(in srgb, var(--lucent-accent-default) 20%, transparent)",outline:"0 4px 14px -2px color-mix(in srgb, var(--lucent-accent-default) 20%, transparent)",ghost:"0 4px 14px -2px color-mix(in srgb, var(--lucent-accent-default) 15%, transparent)",danger:"0 4px 14px -2px color-mix(in srgb, var(--lucent-danger-default) 25%, transparent)","danger-outline":"0 4px 14px -2px color-mix(in srgb, var(--lucent-danger-default) 20%, transparent)","danger-ghost":"0 4px 14px -2px color-mix(in srgb, var(--lucent-danger-default) 15%, transparent)"};function ut(t,r,n){t.style.transform="translateY(-1px)",t.style.boxShadow=Dr[r],t.style.background=Pr[r],(r==="danger"&&n!==!1||r==="danger-outline"&&n!==!1)&&(t.style.borderColor="var(--lucent-danger-hover)")}function pt(t,r,n){t.style.transform="",t.style.boxShadow="",t.style.background=Wt[r].background,(r==="danger"&&n!==!1||r==="danger-outline"&&n!==!1)&&(t.style.borderColor="var(--lucent-danger-default)")}function ht(t,r){const i=r==="danger"||r==="danger-outline"||r==="danger-ghost"?"color-mix(in srgb, var(--lucent-danger-default) 40%, transparent)":"color-mix(in srgb, var(--lucent-accent-default) 40%, transparent)";t.style.transform="translateY(1px)",t.style.boxShadow=`0 0 0 4px ${i}`,t.dataset.pressed="1"}function ft(t){t.style.transform="",t.style.boxShadow="",delete t.dataset.pressed}function Vt({children:t,onClick:r,menuItems:n,variant:i="primary",size:a="md",bordered:o=!0,menuPlacement:l="bottom-end",disabled:s,loading:d=!1,leftIcon:p,style:h}){const f=s??d,[w,b]=c.useState(!1),g=Ar[a],m=g.borderRadius??"var(--lucent-radius-lg)",u=qr[a],y=f?{background:"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)",color:"var(--lucent-text-disabled)",borderColor:"transparent"}:{},x={display:"inline-flex",alignItems:"center",justifyContent:"center",gap:"var(--lucent-space-2)",fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",lineHeight:1,letterSpacing:"0.01em",cursor:f?"not-allowed":"pointer",outline:"none",margin:0,boxSizing:"border-box",whiteSpace:"nowrap",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), border-color var(--lucent-duration-fast) var(--lucent-easing-default), box-shadow var(--lucent-duration-fast) var(--lucent-easing-default), transform 80ms var(--lucent-easing-default)",height:g.height,fontSize:g.fontSize,...Wt[i],...o===!1&&{border:"none"},...y},T="var(--lucent-radius-sm)",S=`${m} ${T} ${T} ${m}`,k=`${T} ${m} ${m} ${T}`;return e.jsxs("div",{role:"group","aria-label":typeof t=="string"?t:"Split button",style:{display:"inline-flex",alignItems:"center",gap:"calc(var(--lucent-space-1) / 2)",...h},children:[e.jsxs("button",{type:"button",disabled:f,"aria-busy":d,onClick:r,onMouseEnter:v=>{f||ut(v.currentTarget,i,o)},onMouseLeave:v=>{f||pt(v.currentTarget,i,o)},onMouseDown:v=>{f||ht(v.currentTarget,i)},onMouseUp:v=>{ft(v.currentTarget)},onFocus:v=>{v.currentTarget.dataset.pressed||(v.currentTarget.style.boxShadow="0 0 0 3px var(--lucent-accent-subtle)")},onBlur:v=>{v.currentTarget.style.boxShadow=""},style:{...x,borderRadius:S,padding:i==="ghost"||i==="danger-ghost"?`0 ${g.paddingInner} 0 ${g.paddingOuter}`:`0 ${g.paddingOuter}`},children:[p,d?e.jsx(Lr,{}):t]}),e.jsx(it,{open:w,onOpenChange:b,placement:l,size:Rr[a],trigger:e.jsx("button",{type:"button",disabled:f,"aria-label":"More actions",onMouseEnter:v=>{f||ut(v.currentTarget,i,o)},onMouseLeave:v=>{f||pt(v.currentTarget,i,o)},onMouseDown:v=>{f||ht(v.currentTarget,i)},onMouseUp:v=>{ft(v.currentTarget)},onFocus:v=>{v.currentTarget.dataset.pressed||(v.currentTarget.style.boxShadow="0 0 0 3px var(--lucent-accent-subtle)")},onBlur:v=>{v.currentTarget.style.boxShadow=""},style:{...x,borderRadius:k,padding:0,width:Br[a]},children:e.jsx("svg",{width:u,height:u,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2.5,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,style:{flexShrink:0,transition:"transform var(--lucent-duration-fast) var(--lucent-easing-default)",...w&&{transform:"rotate(180deg)"}},children:e.jsx("polyline",{points:"6 9 12 15 18 9"})})}),children:n.map((v,A)=>e.jsx(Me,{onSelect:v.onSelect,...v.disabled!==void 0&&{disabled:v.disabled},...v.danger!==void 0&&{danger:v.danger},...v.icon!==void 0&&{icon:v.icon},children:v.label},A))})]})}Vt.displayName="SplitButton";function Lr(){return e.jsxs("svg",{width:14,height:14,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2.5,strokeLinecap:"round","aria-hidden":!0,style:{animation:"lucent-spin 0.7s linear infinite",flexShrink:0},children:[e.jsx("style",{children:"@keyframes lucent-spin { to { transform: rotate(360deg); } }"}),e.jsx("path",{d:"M12 2a10 10 0 0 1 10 10"})]})}const Fr={id:"split-button",name:"SplitButton",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A compound button pairing a primary action with a chevron dropdown for secondary actions.",designIntent:'Use SplitButton when there is one dominant action alongside a small set of related alternatives (e.g. "Save" + "Save as draft", "Deploy" + "Deploy to staging"). Each half is a fully independent button separated by a small token-based gap, with a subtle inner corner radius (radius-sm) so the pair reads as a unit without sharing a border. Hover lift and press ring apply independently per half. Ghost variants use tighter inner padding to keep the halves visually close. The chevron half opens a Menu molecule; all dropdown keyboard navigation is inherited.',props:[{name:"children",type:"ReactNode",required:!0,description:"Label content for the primary action button."},{name:"onClick",type:"function",required:!0,description:"Handler fired when the primary (left) half is clicked."},{name:"menuItems",type:"array",required:!0,description:"Array of { label, onSelect, disabled?, danger?, icon? } for the dropdown."},{name:"variant",type:"enum",required:!1,default:"primary",description:"Visual variant applied to both halves.",enumValues:["primary","secondary","outline","ghost","danger","danger-outline","danger-ghost"]},{name:"size",type:"enum",required:!1,default:"md",description:"Size applied to both halves.",enumValues:["2xs","xs","sm","md","lg"]},{name:"bordered",type:"boolean",required:!1,default:"true",description:"If false, removes the border on both halves."},{name:"menuPlacement",type:"enum",required:!1,default:"bottom-end",description:"Dropdown placement relative to the chevron trigger.",enumValues:["top","top-start","top-end","bottom","bottom-start","bottom-end","left","right"]},{name:"disabled",type:"boolean",required:!1,description:"Disables both halves."},{name:"loading",type:"boolean",required:!1,default:"false",description:"Shows a spinner in the primary half and disables both."},{name:"leftIcon",type:"ReactNode",required:!1,description:"Icon rendered before the label in the primary half."},{name:"style",type:"object",required:!1,description:"Style overrides for the wrapper div."}],usageExamples:[{title:"Basic",code:"<SplitButton onClick={handleSave} menuItems={[{ label: 'Save as draft', onSelect: handleDraft }]}>Save</SplitButton>"},{title:"Outline with leftIcon",code:`<SplitButton variant="outline" leftIcon={<Icon name="deploy" />} onClick={deploy} menuItems={[{ label: 'Deploy to staging', onSelect: deployStaging }, { label: 'Rollback', onSelect: rollback, danger: true }]}>Deploy</SplitButton>`},{title:"Menu items with icons",code:`<SplitButton onClick={handleSave} menuItems={[{ label: 'Save as draft', onSelect: handleDraft, icon: <Icon name="file" /> }, { label: 'Export', onSelect: handleExport, icon: <Icon name="download" /> }]}>Save</SplitButton>`},{title:"Small ghost bordered (issue #90 use case)",code:`<SplitButton variant="ghost" size="2xs" bordered onClick={toggle} menuItems={[{ label: 'Reset layout', onSelect: reset }]}>Collapse all</SplitButton>`},{title:"Danger outline with disabled item",code:`<SplitButton variant="danger-outline" onClick={handleDelete} menuItems={[{ label: 'Move to trash', onSelect: trash }, { label: 'Delete forever', onSelect: nuke, danger: true }, { label: 'Archive (unavailable)', onSelect: archive, disabled: true }]}>Delete</SplitButton>`},{title:"Disabled",code:"<SplitButton disabled onClick={handleSave} menuItems={[{ label: 'Option', onSelect: fn }]}>Save</SplitButton>"},{title:"Loading",code:"<SplitButton loading onClick={handleSave} menuItems={[{ label: 'Option', onSelect: fn }]}>Saving...</SplitButton>"}],compositionGraph:[{componentId:"menu",componentName:"Menu",role:"Dropdown container for secondary actions",required:!0},{componentId:"menu-item",componentName:"MenuItem",role:"Individual dropdown action",required:!0}],accessibility:{role:"group",ariaAttributes:["aria-label","aria-haspopup","aria-expanded","aria-busy"],keyboardInteractions:["Enter/Space on primary button fires onClick","Enter/Space/ArrowDown on chevron opens dropdown","ArrowDown/ArrowUp cycles dropdown items","Home/End jumps to first/last item","Escape closes dropdown and returns focus to chevron","Tab closes dropdown"],notes:'The wrapper uses role="group" with aria-label derived from children. The chevron button carries aria-haspopup="menu" and aria-expanded via Menu. Primary button has aria-busy when loading.'}},fe="var(--lucent-radius-sm)";function Ot({children:t,style:r}){const n=c.Children.toArray(t).filter(c.isValidElement),i=n.length;return e.jsx("div",{role:"group",style:{display:"inline-flex",alignItems:"center",gap:"calc(var(--lucent-space-1) / 2)",...r},children:n.map((a,o)=>{const l=o===0,s=o===i-1;if(i===1)return a;const p=a.props.style??{},h=p.borderRadius??"var(--lucent-radius-lg)";let f;return l?f=`${h} ${fe} ${fe} ${h}`:s?f=`${fe} ${h} ${h} ${fe}`:f=fe,c.cloneElement(a,{key:o,style:{...p,borderRadius:f}})})})}Ot.displayName="ButtonGroup";const $r={id:"button-group",name:"ButtonGroup",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A layout wrapper that visually groups multiple Button or SplitButton children into a related strip.",designIntent:"Use ButtonGroup for peer actions that belong together visually — toolbars, action bars, toggle groups. Unlike SegmentedControl (single-select with indicator), ButtonGroup has no built-in selection state; each child handles its own behaviour. Items are separated by a small token-based gap with subtle inner corner radius so the group reads as a unit while each button retains its own border and hover states.",props:[{name:"children",type:"ReactNode",required:!0,description:"Button or SplitButton elements to group."},{name:"style",type:"object",required:!1,description:"Style overrides for the wrapper div."}],usageExamples:[{title:"Toolbar",code:`<ButtonGroup>
|
|
225
225
|
<Button variant="outline" leftIcon={<Icon name="bold" />} />
|
|
226
226
|
<Button variant="outline" leftIcon={<Icon name="italic" />} />
|
|
227
227
|
<Button variant="outline" leftIcon={<Icon name="underline" />} />
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
</ButtonGroup>`},{title:"With SplitButton",code:`<ButtonGroup>
|
|
232
232
|
<SplitButton onClick={deploy} menuItems={[{ label: 'Staging', onSelect: staging }]}>Deploy</SplitButton>
|
|
233
233
|
<Button variant="outline">Logs</Button>
|
|
234
|
-
</ButtonGroup>`}],compositionGraph:[{componentId:"button",componentName:"Button",role:"Grouped action item",required:!1},{componentId:"split-button",componentName:"SplitButton",role:"Grouped split action item",required:!1}],accessibility:{role:"group",ariaAttributes:[],keyboardInteractions:["Tab moves focus between buttons in the group"],notes:'Uses role="group" on the wrapper. Individual button accessibility is inherited from the children.'}};function
|
|
234
|
+
</ButtonGroup>`}],compositionGraph:[{componentId:"button",componentName:"Button",role:"Grouped action item",required:!1},{componentId:"split-button",componentName:"SplitButton",role:"Grouped split action item",required:!1}],accessibility:{role:"group",ariaAttributes:[],keyboardInteractions:["Tab moves focus between buttons in the group"],notes:'Uses role="group" on the wrapper. Individual button accessibility is inherited from the children.'}};function Nr({label:t,htmlFor:r,required:n=!1,helperText:i,errorMessage:a,children:o,style:l}){const s=a??i,d=a?"danger":"secondary";return e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-2)",...l},children:[t&&e.jsxs("div",{style:{display:"flex",alignItems:"baseline",gap:"var(--lucent-space-1)"},children:[e.jsx(E.Text,{as:"label",size:"sm",weight:"medium",lineHeight:"tight",...r!==void 0&&{htmlFor:r},children:t}),n&&e.jsx(E.Text,{as:"span",size:"sm",color:"danger",lineHeight:"tight","aria-hidden":"true",children:"*"})]}),o,s&&e.jsx(E.Text,{size:"xs",color:d,lineHeight:"tight",children:s})]})}const Wr={id:"form-field",name:"FormField",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"Wraps any form control (Input, Select, Textarea) with a label, helper text, and validation message.",designIntent:"FormField standardises the vertical rhythm around form controls. A label is linked to the control via htmlFor so screen readers announce it correctly. The required asterisk is decorative (aria-hidden) because the actual required state should be communicated on the input via aria-required. Helper text provides proactive guidance; errorMessage replaces it when validation fails, using danger color to draw attention. The gap between elements uses space-2 to create a tight but breathable stack.",props:[{name:"label",type:"string",required:!1,description:"Label text rendered above the control as a <label> element."},{name:"htmlFor",type:"string",required:!1,description:"ID of the form control this label describes. Forwarded to the label htmlFor attribute."},{name:"required",type:"boolean",required:!1,default:"false",description:"Appends a danger-colored asterisk after the label text."},{name:"helperText",type:"string",required:!1,description:"Secondary text below the control providing guidance. Hidden when errorMessage is set."},{name:"errorMessage",type:"string",required:!1,description:"Validation error shown in danger color below the control. Replaces helperText when set."},{name:"children",type:"ReactNode",required:!0,description:"The form control to wrap — typically Input, Select, or Textarea."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer wrapper."}],usageExamples:[{title:"Basic field",code:`<FormField label="Email" htmlFor="email">
|
|
235
235
|
<Input id="email" placeholder="you@example.com" />
|
|
236
236
|
</FormField>`},{title:"Required with helper",code:`<FormField label="Username" htmlFor="username" required helperText="Letters and numbers only">
|
|
237
237
|
<Input id="username" />
|
|
@@ -239,7 +239,7 @@
|
|
|
239
239
|
<Input id="pw" type="password" />
|
|
240
240
|
</FormField>`},{title:"Wrapping a Select",code:`<FormField label="Country" htmlFor="country">
|
|
241
241
|
<Select id="country" options={countryOptions} />
|
|
242
|
-
</FormField>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Label, helper text, and error message",required:!1}],accessibility:{ariaAttributes:["aria-required","aria-describedby"],notes:'Link the wrapped control to an error message using aria-describedby on the control and a matching id on the error element for full screen reader support. The required asterisk is aria-hidden; set aria-required="true" on the control itself.'}},
|
|
242
|
+
</FormField>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Label, helper text, and error message",required:!1}],accessibility:{ariaAttributes:["aria-required","aria-describedby"],notes:'Link the wrapped control to an error message using aria-describedby on the control and a matching id on the error element for full screen reader support. The required asterisk is aria-hidden; set aria-required="true" on the control itself.'}},mt={sm:14,md:18,lg:20},Vr={sm:"sm",md:"md",lg:"md"},Or={sm:"var(--lucent-space-1)",md:"var(--lucent-space-2)",lg:"var(--lucent-space-2)"},Hr=({size:t=16})=>e.jsxs("svg",{width:t,height:t,viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"6.5",cy:"6.5",r:"4",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M9.5 9.5L13 13",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),Gr=({size:t=16})=>e.jsx("svg",{width:t,height:t,viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:e.jsx("path",{d:"M2 3h12M4 8h8M6 13h4",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})}),Ur=()=>e.jsx("svg",{width:"14",height:"14",viewBox:"0 0 14 14",fill:"none","aria-hidden":"true",children:e.jsx("path",{d:"M3 3L11 11M11 3L3 11",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})});function _r({value:t,onChange:r,mode:n="search",placeholder:i,size:a="md",label:o,helperText:l,errorText:s,results:d=[],onResultSelect:p,isLoading:h=!1,disabled:f=!1,id:w,style:b}){const g=i??(n==="filter"?"Filter…":"Search…"),[m,u]=c.useState(!1),[y,x]=c.useState(null),T=c.useRef(null),S=c.useRef(null),k=c.useRef(null),[v,A]=c.useState({top:0,left:0,width:0}),C=n==="search"&&m&&d.length>0;c.useLayoutEffect(()=>{if(!C||!S.current)return;const D=S.current.getBoundingClientRect();A({top:D.bottom+4,left:D.left,width:D.width})},[C]);const M=()=>{r("")},q=D=>{p==null||p(D),u(!1)},B=()=>{T.current=setTimeout(()=>u(!1),150)},I=()=>{T.current&&clearTimeout(T.current),u(!0)},L=h?e.jsx(Dt,{size:"sm"}):t?e.jsx("button",{type:"button","aria-label":n==="filter"?"Clear filter":"Clear search",onClick:M,style:{display:"flex",alignItems:"center",background:"none",border:"none",cursor:"pointer",padding:2,borderRadius:"var(--lucent-radius-sm)",color:"var(--lucent-text-secondary)"},onMouseEnter:D=>{D.currentTarget.style.color="var(--lucent-text-primary)"},onMouseLeave:D=>{D.currentTarget.style.color="var(--lucent-text-secondary)"},children:e.jsx(Ur,{})}):null;return e.jsxs("div",{ref:S,style:{...b},children:[e.jsx(E.Input,{id:w,type:"text",size:a,value:t,onChange:D=>r(D.target.value),placeholder:g,disabled:f,leftElement:n==="filter"?e.jsx(Gr,{size:mt[a]}):e.jsx(Hr,{size:mt[a]}),rightElement:L??void 0,onFocus:I,onBlur:B,...o!==void 0&&{label:o},...l!==void 0&&{helperText:l},...s!==void 0&&{errorText:s}}),C&&Q.createPortal(e.jsx("div",{ref:k,role:"listbox",style:{position:"fixed",top:v.top,left:v.left,width:v.width,zIndex:1e3,background:"color-mix(in srgb, var(--lucent-surface-overlay) 85%, transparent)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 15%, var(--lucent-border-default))",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 0 24px -4px color-mix(in srgb, var(--lucent-accent-default) 12%, transparent), var(--lucent-shadow-md)",padding:Or[a]},children:d.map((D,N)=>e.jsx("div",{role:"option","aria-selected":!1,onMouseDown:()=>q(D),onMouseEnter:()=>x(N),onMouseLeave:()=>x(null),style:{padding:"var(--lucent-space-2)",borderRadius:"var(--lucent-radius-md)",cursor:"pointer",background:y===N?"color-mix(in srgb, var(--lucent-accent-default) 14%, transparent)":"transparent",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)"},children:e.jsx(E.Text,{as:"span",size:Vr[a],children:D.label})},D.id))}),document.body)]})}const Yr={id:"search-input",name:"SearchInput",tier:"molecule",domain:"neutral",specVersion:"0.1",description:'A search field with a built-in magnifier icon, clear button, and an optional results dropdown. Use mode="filter" for a simple filter input with a funnel icon and no dropdown.',designIntent:'SearchInput is intentionally dumb about filtering — the consumer passes already-filtered results so the component stays stateless and flexible. The clear button appears only when the input has a value, keeping the right side clean at rest. The results dropdown is rendered absolutely below the input and closes after a 150ms delay on blur to allow result clicks to register before focus is lost. Spinner replaces the clear button during loading to communicate async state without layout shift. Use mode="filter" when you only need a text input with a funnel icon and clear button — no dropdown, no results array, just a clean filter field.',props:[{name:"value",type:"string",required:!0,description:"Controlled input value."},{name:"onChange",type:"function",required:!0,description:"Called with the new string value whenever the input changes."},{name:"mode",type:"enum",required:!1,default:"search",description:'Controls variant. "filter" swaps the magnifier for a funnel icon and disables the results dropdown.',enumValues:["search","filter"]},{name:"size",type:"enum",required:!1,default:"md",description:"Controls height and font size. Passed to the underlying Input.",enumValues:["sm","md","lg"]},{name:"label",type:"string",required:!1,description:"Visible label above the search field. Passed to the underlying Input."},{name:"helperText",type:"string",required:!1,description:"Hint text below the field. Passed to the underlying Input."},{name:"errorText",type:"string",required:!1,description:"Validation error text. Triggers error styling on the underlying Input."},{name:"placeholder",type:"string",required:!1,default:'"Search…" (or "Filter…" in filter mode)',description:'Placeholder text for the input. Defaults to "Search…" in search mode and "Filter…" in filter mode.'},{name:"results",type:"array",required:!1,default:"[]",description:"Pre-filtered list of SearchResult objects ({ id, label }) to display in the dropdown."},{name:"onResultSelect",type:"function",required:!1,description:"Called with the selected SearchResult when a dropdown item is clicked."},{name:"isLoading",type:"boolean",required:!1,default:"false",description:"Shows a spinner in the right slot to indicate async search in progress."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables the input."},{name:"id",type:"string",required:!1,description:"HTML id forwarded to the underlying input element."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer wrapper."}],usageExamples:[{title:"Basic controlled search",code:`const [query, setQuery] = useState('');
|
|
243
243
|
const [results, setResults] = useState([]);
|
|
244
244
|
|
|
245
245
|
<SearchInput
|
|
@@ -247,7 +247,7 @@ const [results, setResults] = useState([]);
|
|
|
247
247
|
onChange={(v) => { setQuery(v); setResults(filter(v)); }}
|
|
248
248
|
results={results}
|
|
249
249
|
onResultSelect={(r) => console.log(r)}
|
|
250
|
-
/>`},{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.'}},Ae=d.createContext({px:"0",py:"0"}),Ga={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)"}},ke={none:"var(--lucent-shadow-none)",sm:"var(--lucent-shadow-sm)",md:"var(--lucent-shadow-md)",lg:"var(--lucent-shadow-lg)"},Ua={none:"var(--lucent-radius-none)",sm:"var(--lucent-radius-sm)",md:"var(--lucent-radius-md)",lg:"var(--lucent-radius-lg)"},_a={success:"var(--lucent-success-default)",warning:"var(--lucent-warning-default)",danger:"var(--lucent-danger-default)",info:"var(--lucent-info-default)"},Ya={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}},Ka=["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(", "),Xa="0 4px 14px -2px var(--lucent-accent-subtle)",Ja="0 4px 14px -2px color-mix(in srgb, var(--lucent-text-primary) 12%, transparent)",Za="0 0 0 3px var(--lucent-accent-subtle)",Qa="0 0 0 2px var(--lucent-surface), 0 0 0 4px var(--lucent-accent-default)",er="0 0 0 3px var(--lucent-accent-subtle)";function se(...t){const a=t.filter(n=>n!=null&&n!=="none"&&n!=="var(--lucent-shadow-none)");return a.length>0?a.join(", "):void 0}function tr(t,a,n){return!a||n?t.background:"var(--lucent-accent-subtle)"}function nr({variant:t="outline",header:a,footer:n,children:i,padding:r="md",shadow:o,radius:s="lg",style:l,onClick:c,href:u,target:h,rel:f,disabled:S,status:w,selected:g,hoverable:m,media:p}){const b=Ya[t],y=t==="combo",x=o??(y?"md":b.shadowDefault),{py:T,px:k}=Ga[r],v=`${T} ${k}`,A=Ua[s],C=u!=null,j=c!=null||C,q=j||(m??!1),D=(S??!1)&&j,I=C?"a":j?"button":"div",[F,R]=d.useState(!1),[O,B]=d.useState(!1),[N,G]=d.useState(!1),P=(g??!1)&&!D,z=P?er:void 0,M=tr(b,g??!1,D),$=w!=null?`inset 3px 0 0 ${_a[w]}`:void 0;let W;y?W=se(z,$):q&&!D?N?W=se(Qa,z,$):O?W=se(Za,z,$):F?W=se(j?Xa:Ja,ke[x],z,$):W=se(ke[x],z,$):W=se(ke[x],z,$);const L={display:"flex",flexDirection:"column",background:M,border:b.border,borderRadius:A,overflow:p!=null&&!(P||q&&O)?"hidden":"visible",boxSizing:"border-box",position:"relative",...W!==void 0&&{boxShadow:W},...q&&!D&&N&&{transform:"translateY(1px)"},...q&&!D&&F&&!N&&{transform:"translateY(-1px)"},...q&&{cursor:D?"not-allowed":"pointer",transition:Ka},...j&&!C&&{padding:0,font:"inherit",textAlign:"inherit",width:"100%",background:M},...C&&{textDecoration:"none",color:"inherit"},...D&&{opacity:.6,pointerEvents:"none"},...l},V=q&&!D?{onMouseEnter:()=>R(!0),onMouseLeave:()=>{R(!1),G(!1)},onMouseDown:()=>G(!0),onMouseUp:()=>G(!1),onFocus:()=>B(!0),onBlur:()=>{B(!1),G(!1)}}:{};return e.jsxs(I,{style:L,...V,...C&&{href:D?void 0:u,...h!==void 0&&{target:h},...f!==void 0&&{rel:f}},...!C&&j&&{type:"button",...D&&{disabled:!0}},...c!==void 0&&!D&&{onClick:c},...j&&g!==void 0&&{"aria-pressed":g},...C&&D&&{"aria-disabled":!0},children:[p!=null&&e.jsx("div",{style:{lineHeight:0,overflow:"hidden",borderRadius:`${A} ${A} 0 0`},children:p}),a!=null&&e.jsx("div",{style:{padding:v,...b.dividers?{borderBottom:"1px solid var(--lucent-border-default)"}:{}},children:a}),e.jsx(Ae.Provider,{value:{px:k,py:T},children:e.jsx("div",{style:{padding:v,flex:1,...y?{background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-default)",borderRadius:A,boxShadow:ke[x],marginLeft:`calc(${k} / 3)`,marginRight:`calc(${k} / 3)`,...a==null&&{marginTop:`calc(${T} / 3)`},...n==null&&{marginBottom:`calc(${T} / 3)`}}:{}},children:i})}),n!=null&&e.jsx("div",{style:{padding:v,...b.dividers?{borderTop:"1px solid var(--lucent-border-default)"}:{}},children:n})]})}function ar({children:t,style:a}){const{px:n}=d.useContext(Ae);return e.jsx("div",{style:{marginLeft:`calc(-1 * ${n})`,marginRight:`calc(-1 * ${n})`,paddingLeft:n,paddingRight:n,...a},children:t})}const rr={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">
|
|
250
|
+
/>`},{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.'}},qe=c.createContext({px:"0",py:"0"}),Kr={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)"}},Te={none:"var(--lucent-shadow-none)",sm:"var(--lucent-shadow-sm)",md:"var(--lucent-shadow-md)",lg:"var(--lucent-shadow-lg)"},Xr={none:"var(--lucent-radius-none)",sm:"var(--lucent-radius-sm)",md:"var(--lucent-radius-md)",lg:"var(--lucent-radius-lg)"},Jr={success:"var(--lucent-success-default)",warning:"var(--lucent-warning-default)",danger:"var(--lucent-danger-default)",info:"var(--lucent-info-default)"},Zr={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}},Qr=["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(", "),ea="0 4px 14px -2px var(--lucent-accent-subtle)",ta="0 4px 14px -2px color-mix(in srgb, var(--lucent-text-primary) 12%, transparent)",na="0 0 0 3px var(--lucent-accent-subtle)",ra="0 0 0 2px var(--lucent-surface), 0 0 0 4px var(--lucent-accent-default)",aa="0 0 0 3px var(--lucent-accent-subtle)";function le(...t){const r=t.filter(n=>n!=null&&n!=="none"&&n!=="var(--lucent-shadow-none)");return r.length>0?r.join(", "):void 0}function oa(t,r,n){return!r||n?t.background:"var(--lucent-accent-subtle)"}function ia({variant:t="outline",header:r,footer:n,children:i,padding:a="md",shadow:o,radius:l="lg",style:s,onClick:d,href:p,target:h,rel:f,disabled:w,status:b,selected:g,hoverable:m,media:u}){const y=Zr[t],x=t==="combo",T=o??(x?"md":y.shadowDefault),{py:S,px:k}=Kr[a],v=`${S} ${k}`,A=Xr[l],C=p!=null,M=d!=null||C,q=M||(m??!1),B=(w??!1)&&M,I=C?"a":M?"button":"div",[L,D]=c.useState(!1),[N,R]=c.useState(!1),[W,G]=c.useState(!1),P=(g??!1)&&!B,z=P?aa:void 0,j=oa(y,g??!1,B),$=b!=null?`inset 3px 0 0 ${Jr[b]}`:void 0;let V;x?V=le(z,$):q&&!B?W?V=le(ra,z,$):N?V=le(na,z,$):L?V=le(M?ea:ta,Te[T],z,$):V=le(Te[T],z,$):V=le(Te[T],z,$);const F={display:"flex",flexDirection:"column",background:j,border:y.border,borderRadius:A,overflow:u!=null&&!(P||q&&N)?"hidden":"visible",boxSizing:"border-box",position:"relative",...V!==void 0&&{boxShadow:V},...q&&!B&&W&&{transform:"translateY(1px)"},...q&&!B&&L&&!W&&{transform:"translateY(-1px)"},...q&&{cursor:B?"not-allowed":"pointer",transition:Qr},...M&&!C&&{padding:0,font:"inherit",textAlign:"inherit",width:"100%",background:j},...C&&{textDecoration:"none",color:"inherit"},...B&&{opacity:.6,pointerEvents:"none"},...s},O=q&&!B?{onMouseEnter:()=>D(!0),onMouseLeave:()=>{D(!1),G(!1)},onMouseDown:()=>G(!0),onMouseUp:()=>G(!1),onFocus:()=>R(!0),onBlur:()=>{R(!1),G(!1)}}:{};return e.jsxs(I,{style:F,...O,...C&&{href:B?void 0:p,...h!==void 0&&{target:h},...f!==void 0&&{rel:f}},...!C&&M&&{type:"button",...B&&{disabled:!0}},...d!==void 0&&!B&&{onClick:d},...M&&g!==void 0&&{"aria-pressed":g},...C&&B&&{"aria-disabled":!0},children:[u!=null&&e.jsx("div",{style:{lineHeight:0,overflow:"hidden",borderRadius:`${A} ${A} 0 0`},children:u}),r!=null&&e.jsx("div",{style:{padding:v,...y.dividers?{borderBottom:"1px solid var(--lucent-border-default)"}:{}},children:r}),e.jsx(qe.Provider,{value:{px:k,py:S},children:e.jsx("div",{style:{padding:v,flex:1,...x?{background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-default)",borderRadius:A,boxShadow:Te[T],marginLeft:`calc(${k} / 3)`,marginRight:`calc(${k} / 3)`,...r==null&&{marginTop:`calc(${S} / 3)`},...n==null&&{marginBottom:`calc(${S} / 3)`}}:{}},children:i})}),n!=null&&e.jsx("div",{style:{padding:v,...y.dividers?{borderTop:"1px solid var(--lucent-border-default)"}:{}},children:n})]})}function sa({children:t,style:r}){const{px:n}=c.useContext(qe);return e.jsx("div",{style:{marginLeft:`calc(-1 * ${n})`,marginRight:`calc(-1 * ${n})`,paddingLeft:n,paddingRight:n,...r},children:t})}const la={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">
|
|
251
251
|
<Text>Content sits directly on the page background.</Text>
|
|
252
252
|
</Card>`},{title:"Outline — bordered card (default)",code:`<Card
|
|
253
253
|
header={<Text weight="semibold">Card title</Text>}
|
|
@@ -303,11 +303,11 @@ const [results, setResults] = useState([]);
|
|
|
303
303
|
<Text size="sm" color="secondary">Two-tone: flat trigger on filled surface, content in elevated body.</Text>
|
|
304
304
|
</Card>
|
|
305
305
|
</Collapsible>
|
|
306
|
-
</Card>`}],compositionGraph:[],accessibility:{notes:"Non-interactive cards have no implicit ARIA role — wrap in <section> or <article> if needed. Interactive cards with onClick render as <button> with focus ring. Interactive cards with href render as <a> with focus ring. Selected cards set aria-pressed on the interactive element. The status accent bar is aria-hidden (decorative). Media slot images should include alt text."}},
|
|
306
|
+
</Card>`}],compositionGraph:[],accessibility:{notes:"Non-interactive cards have no implicit ARIA role — wrap in <section> or <article> if needed. Interactive cards with onClick render as <button> with focus ring. Interactive cards with href render as <a> with focus ring. Selected cards set aria-pressed on the interactive element. The status accent bar is aria-hidden (decorative). Media slot images should include alt text."}},ca={info:{bg:"var(--lucent-info-subtle)",border:"var(--lucent-info-default)",iconColor:"var(--lucent-info-text)",textColor:"info"},success:{bg:"var(--lucent-success-subtle)",border:"var(--lucent-success-default)",iconColor:"var(--lucent-success-text)",textColor:"success"},warning:{bg:"var(--lucent-warning-subtle)",border:"var(--lucent-warning-default)",iconColor:"var(--lucent-warning-text)",textColor:"warning"},danger:{bg:"var(--lucent-danger-subtle)",border:"var(--lucent-danger-default)",iconColor:"var(--lucent-danger-text)",textColor:"danger"}},da=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"8",cy:"8",r:"6.5",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M8 5.5V8.5M8 10.5V11",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),ua=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"8",cy:"8",r:"6.5",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M5 8L7 10L11 6",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})]}),pa=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("path",{d:"M8 2L14.5 13H1.5L8 2Z",stroke:"currentColor",strokeWidth:"1.5",strokeLinejoin:"round"}),e.jsx("path",{d:"M8 6V9M8 11V11.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),ha=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"8",cy:"8",r:"6.5",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M5.5 5.5L10.5 10.5M10.5 5.5L5.5 10.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),fa={info:e.jsx(da,{}),success:e.jsx(ua,{}),warning:e.jsx(pa,{}),danger:e.jsx(ha,{})},ma=()=>e.jsx("svg",{width:"14",height:"14",viewBox:"0 0 14 14",fill:"none","aria-hidden":"true",children:e.jsx("path",{d:"M3 3L11 11M11 3L3 11",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})});function ga({variant:t="info",title:r,children:n,onDismiss:i,icon:a,style:o}){const l=ca[t],s=a??fa[t];return e.jsxs("div",{role:"alert",style:{display:"flex",alignItems:"flex-start",gap:"var(--lucent-space-3)",padding:"var(--lucent-space-3) var(--lucent-space-4)",background:l.bg,border:`1px solid ${l.border}`,borderRadius:"var(--lucent-radius-md)",boxSizing:"border-box",...o},children:[e.jsx("span",{style:{flexShrink:0,color:l.iconColor,display:"flex",alignItems:"center",paddingTop:2},children:s}),e.jsxs("div",{style:{flex:1,display:"flex",flexDirection:"column",gap:"var(--lucent-space-1)"},children:[r&&e.jsx(E.Text,{as:"span",size:"sm",weight:"semibold",color:l.textColor,lineHeight:"tight",children:r}),n&&e.jsx(E.Text,{as:"span",size:"sm",color:l.textColor,lineHeight:"base",children:n})]}),i&&e.jsx("button",{type:"button","aria-label":"Dismiss",onClick:i,style:{flexShrink:0,display:"flex",alignItems:"center",background:"none",border:"none",cursor:"pointer",padding:2,borderRadius:"var(--lucent-radius-sm)",color:l.iconColor,opacity:.7},onMouseEnter:d=>{d.currentTarget.style.opacity="1"},onMouseLeave:d=>{d.currentTarget.style.opacity="0.7"},children:e.jsx(ma,{})})]})}const va={id:"alert",name:"Alert",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"An inline feedback banner with info, success, warning, and danger variants, optional title, and dismiss button.",designIntent:'Alert uses role="alert" so screen readers announce the message immediately when it appears. Each variant has a built-in icon that communicates intent visually; the icon can be overridden for custom scenarios. Title and body are both optional — you can show either, both, or just an icon with a body. The dismiss button is only rendered when onDismiss is provided, keeping the layout clean for non-dismissible alerts. All colors use status semantic tokens so they adapt correctly between light and dark themes.',props:[{name:"variant",type:"enum",required:!1,default:"info",description:"Visual and semantic variant of the alert.",enumValues:["info","success","warning","danger"]},{name:"title",type:"string",required:!1,description:"Bold title line rendered above the body."},{name:"children",type:"ReactNode",required:!1,description:"Alert body content — typically a short sentence or ReactNode."},{name:"onDismiss",type:"function",required:!1,description:"When provided, renders a dismiss (×) button and calls this handler on click."},{name:"icon",type:"ReactNode",required:!1,description:"Custom icon to replace the built-in variant icon."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the alert wrapper."}],usageExamples:[{title:"Info with body",code:'<Alert variant="info">Your changes have been saved as a draft.</Alert>'},{title:"With title and dismiss",code:`<Alert variant="danger" title="Payment failed" onDismiss={() => setVisible(false)}>
|
|
307
307
|
Check your card details and try again.
|
|
308
308
|
</Alert>`},{title:"Success confirmation",code:`<Alert variant="success" title="Order placed!">
|
|
309
309
|
You'll receive a confirmation email shortly.
|
|
310
|
-
</Alert>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Title and body content",required:!1}],accessibility:{role:"alert",ariaAttributes:["aria-label"],notes:'role="alert" causes screen readers to announce the content immediately when rendered. For non-urgent status messages, consider using role="status" instead by overriding via style/wrapper.'}};function
|
|
310
|
+
</Alert>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Title and body content",required:!1}],accessibility:{role:"alert",ariaAttributes:["aria-label"],notes:'role="alert" causes screen readers to announce the content immediately when rendered. For non-urgent status messages, consider using role="status" instead by overriding via style/wrapper.'}};function ba({illustration:t,title:r,description:n,action:i,style:a}){return e.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"var(--lucent-space-4)",padding:"var(--lucent-space-8)",textAlign:"center",...a},children:[t!=null&&e.jsx("div",{style:{width:64,height:64,display:"flex",alignItems:"center",justifyContent:"center",color:"var(--lucent-text-secondary)"},children:t}),e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-2)"},children:[e.jsx(E.Text,{as:"h3",size:"lg",weight:"semibold",align:"center",lineHeight:"tight",children:r}),n&&e.jsx(E.Text,{size:"sm",color:"secondary",align:"center",lineHeight:"relaxed",children:n})]}),i!=null&&e.jsx("div",{children:i})]})}const ya={id:"empty-state",name:"EmptyState",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"A centered placeholder shown when a list or page has no content, with an optional illustration, title, description, and CTA.",designIntent:"EmptyState communicates the absence of data in a constructive way. The illustration slot accepts any ReactNode — an Icon atom, a custom SVG, or an image — and constrains it to a 64px square to maintain visual consistency. Title is required to ensure the state is always named; description is optional for additional context. The action slot accepts any ReactNode (typically a Button) so the consumer controls variant and label without prescribing them. The entire layout is center-aligned and padded to sit naturally inside a Card or page section.",props:[{name:"title",type:"string",required:!0,description:'Short headline naming the empty state (e.g. "No results found").'},{name:"illustration",type:"ReactNode",required:!1,description:"Icon, SVG, or image rendered above the title. Constrained to a 64px container."},{name:"description",type:"string",required:!1,description:"Secondary text below the title providing context or next steps."},{name:"action",type:"ReactNode",required:!1,description:"Call-to-action rendered below the description — typically a Button."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer wrapper."}],usageExamples:[{title:"No search results",code:`<EmptyState
|
|
311
311
|
illustration={<Icon size="xl"><SearchIcon /></Icon>}
|
|
312
312
|
title="No results found"
|
|
313
313
|
description="Try adjusting your search or filter to find what you're looking for."
|
|
@@ -318,18 +318,18 @@ const [results, setResults] = useState([]);
|
|
|
318
318
|
action={<Button variant="primary">New project</Button>}
|
|
319
319
|
/>`},{title:"Inside a Card",code:`<Card>
|
|
320
320
|
<EmptyState title="Nothing here" description="Add items to see them listed." />
|
|
321
|
-
</Card>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Title and description",required:!0}],accessibility:{notes:"The title renders as an h3 by default. If EmptyState appears inside a section with its own heading hierarchy, override by passing a Text component as part of a custom layout. Ensure the action element has a descriptive label for screen readers."}},
|
|
321
|
+
</Card>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Title and description",required:!0}],accessibility:{notes:"The title renders as an h3 by default. If EmptyState appears inside a section with its own heading hierarchy, override by passing a Text component as part of a custom layout. Ensure the action element has a descriptive label for screen readers."}},xa={text:"1em",circle:40,rectangle:40},wa={text:"var(--lucent-radius-sm)",circle:"var(--lucent-radius-full)",rectangle:"var(--lucent-radius-md)"};function gt({width:t,height:r,radius:n,animate:i,style:a}){return e.jsx("span",{style:{display:"block",width:typeof t=="number"?`${t}px`:t,height:typeof r=="number"?`${r}px`:r,borderRadius:n,background:i?"linear-gradient(90deg, var(--lucent-surface-secondary) 25%, var(--lucent-surface) 50%, var(--lucent-surface-secondary) 75%)":"var(--lucent-surface-secondary)",backgroundSize:i?"200% 100%":void 0,animation:i?"lucent-skeleton-shimmer 1.6s ease-in-out infinite":void 0,flexShrink:0,...a}})}function ka({variant:t="rectangle",width:r="100%",height:n,lines:i=1,animate:a=!0,radius:o,style:l}){const s=n??xa[t],d=o??wa[t],p=a?e.jsx("style",{children:`
|
|
322
322
|
@keyframes lucent-skeleton-shimmer {
|
|
323
323
|
0% { background-position: 200% 0; }
|
|
324
324
|
100% { background-position: -200% 0; }
|
|
325
325
|
}
|
|
326
|
-
`}):null;return t==="text"&&i>1?e.jsxs(e.Fragment,{children:[
|
|
326
|
+
`}):null;return t==="text"&&i>1?e.jsxs(e.Fragment,{children:[p,e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-2)",...l},children:Array.from({length:i}).map((h,f)=>e.jsx(gt,{width:f===i-1?"70%":r,height:s,radius:d,animate:a},f))})]}):e.jsxs(e.Fragment,{children:[p,e.jsx(gt,{width:t==="circle"?n??40:r,height:s,radius:d,animate:a,...l!==void 0&&{style:l}})]})}const Sa={id:"skeleton",name:"Skeleton",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"Animated placeholder that matches the shape of content while it loads.",designIntent:"Skeleton uses a shimmer animation to communicate that content is loading without showing a spinner. The three variants (text, circle, rectangle) cover the most common content shapes: inline text, avatars/thumbnails, and generic content blocks. The text variant with lines > 1 mimics a paragraph by stacking multiple text skeletons and shortening the last line to 70%, which is a widely recognised convention for body copy placeholders. The shimmer gradient uses bg-muted and bg-subtle so it adapts correctly in both light and dark themes without hard-coded colors.",props:[{name:"variant",type:"enum",required:!1,default:"rectangle",description:"Shape of the skeleton — text (1em tall), circle (equal width/height), or rectangle.",enumValues:["text","circle","rectangle"]},{name:"width",type:"string",required:!1,default:'"100%"',description:"Width of the skeleton. Accepts any CSS value or a number (interpreted as px)."},{name:"height",type:"string",required:!1,description:"Height of the skeleton. Defaults: text=1em, circle=40px, rectangle=40px."},{name:"lines",type:"number",required:!1,default:"1",description:"Number of stacked text lines to render. Only applies to the text variant."},{name:"animate",type:"boolean",required:!1,default:"true",description:"Enables the shimmer animation. Set to false to render a static placeholder."},{name:"radius",type:"string",required:!1,description:"Override the default border-radius for the variant."},{name:"style",type:"object",required:!1,description:"Inline style overrides."}],usageExamples:[{title:"Paragraph placeholder",code:'<Skeleton variant="text" lines={3} />'},{title:"Avatar placeholder",code:'<Skeleton variant="circle" width={40} height={40} />'},{title:"Card placeholder",code:`<Card>
|
|
327
327
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
328
328
|
<Skeleton variant="rectangle" height={160} />
|
|
329
329
|
<Skeleton variant="text" lines={2} />
|
|
330
330
|
<Skeleton variant="text" width="40%" />
|
|
331
331
|
</div>
|
|
332
|
-
</Card>`},{title:"Static (no animation)",code:'<Skeleton variant="rectangle" width={200} height={32} animate={false} />'}],compositionGraph:[],accessibility:{ariaAttributes:["aria-busy","aria-label"],notes:'Wrap loading regions with aria-busy="true" on the container so screen readers know content is loading. Individual Skeleton elements are presentational and do not need ARIA attributes themselves.'}};function
|
|
332
|
+
</Card>`},{title:"Static (no animation)",code:'<Skeleton variant="rectangle" width={200} height={32} animate={false} />'}],compositionGraph:[],accessibility:{ariaAttributes:["aria-busy","aria-label"],notes:'Wrap loading regions with aria-busy="true" on the container so screen readers know content is loading. Individual Skeleton elements are presentational and do not need ARIA attributes themselves.'}};function Ht({items:t,separator:r="/",style:n,LinkComponent:i}){return e.jsx("nav",{"aria-label":"Breadcrumb",style:n,children:e.jsx("ol",{style:{display:"flex",alignItems:"center",flexWrap:"wrap",gap:"var(--lucent-space-1)",listStyle:"none",margin:0,padding:0,fontFamily:"var(--lucent-font-family-base)"},children:t.map((a,o)=>{const l=o===t.length-1,s={fontSize:"var(--lucent-font-size-sm)",color:"var(--lucent-text-secondary)",textDecoration:"none",fontFamily:"var(--lucent-font-family-base)",transition:"color var(--lucent-duration-fast) var(--lucent-easing-default)"},d=h=>{h.currentTarget.style.color="var(--lucent-text-primary)"},p=h=>{h.currentTarget.style.color="var(--lucent-text-secondary)"};return e.jsxs("li",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-1)"},children:[l?e.jsx(E.Text,{size:"sm",color:"primary",as:"span","aria-current":"page",children:a.label}):a.href!=null?i?e.jsx(i,{href:a.href,style:s,onMouseEnter:d,onMouseLeave:p,...a.onClick&&{onClick:a.onClick},children:a.label}):e.jsx("a",{href:a.href,onClick:a.onClick,style:s,onMouseEnter:d,onMouseLeave:p,children:a.label}):e.jsx("button",{onClick:a.onClick,style:{fontSize:"var(--lucent-font-size-sm)",color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",background:"none",border:"none",padding:0,cursor:"pointer",transition:"color var(--lucent-duration-fast) var(--lucent-easing-default)"},onMouseEnter:h=>{h.currentTarget.style.color="var(--lucent-text-primary)"},onMouseLeave:h=>{h.currentTarget.style.color="var(--lucent-text-secondary)"},children:a.label}),!l&&e.jsx("span",{"aria-hidden":!0,style:{color:"var(--lucent-text-disabled)",fontSize:"var(--lucent-font-size-sm)",userSelect:"none"},children:r})]},o)})})})}const vt=80,Gt=180,ue="var(--lucent-easing-default)",Ze="var(--lucent-duration-fast)",bt=`height ${Gt}ms ${ue}`,Ta=`
|
|
333
333
|
[data-lucent-collapsible-trigger]:hover:not(:disabled) {
|
|
334
334
|
background: color-mix(in srgb, var(--lucent-text-primary) 5%, transparent) !important;
|
|
335
335
|
}
|
|
@@ -339,7 +339,12 @@ const [results, setResults] = useState([]);
|
|
|
339
339
|
[data-lucent-collapsible-trigger]:focus-visible {
|
|
340
340
|
box-shadow: 0 0 0 2px var(--lucent-surface), 0 0 0 4px var(--lucent-accent-default) !important;
|
|
341
341
|
}
|
|
342
|
-
`;let vt=!1;function wr(){if(vt||typeof document>"u")return;const t=document.createElement("style");t.setAttribute("data-lucent-collapsible",""),t.textContent=xr,document.head.appendChild(t),vt=!0}function kr({trigger:t,children:a,defaultOpen:n=!1,open:i,onOpenChange:r,padded:o=!0,disabled:s=!1,style:l}){const c=d.useContext(Ae),u=c.px!=="0"||c.py!=="0",h=i!==void 0,[f,S]=d.useState(n),w=h?i:f,g=d.useRef(null),m=d.useRef(!1),p=d.useRef();d.useEffect(wr,[]),d.useEffect(()=>{m.current=!0},[]),d.useLayoutEffect(()=>{const y=g.current;if(y)if(w){y.style.overflow="hidden";const x=y.scrollHeight;y.style.height=`${x}px`,y.style.transition=gt,clearTimeout(p.current),p.current=setTimeout(()=>{y.style.height="auto",y.style.overflow="visible",y.style.transition=""},Wt+20)}else m.current&&(clearTimeout(p.current),y.style.overflow="hidden",y.style.transition="",y.style.height=`${y.scrollHeight}px`,y.getBoundingClientRect(),y.style.transition=gt,y.style.height="0px")},[w]),d.useEffect(()=>{const y=g.current;y&&!w&&!m.current&&(y.style.height="0px")},[]),d.useEffect(()=>()=>clearTimeout(p.current),[]);const b=()=>{if(s)return;const y=!w;h||S(y),r==null||r(y)};return e.jsxs("div",{style:{display:"flex",flexDirection:"column",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-md)",...u&&{margin:`calc(-1 * ${c.py}) calc(-1 * ${c.px})`},...l},children:[e.jsxs("button",{"data-lucent-collapsible-trigger":!0,onClick:b,disabled:s,"aria-expanded":w,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 ${Xe} ${de}`},children:[e.jsx("span",{style:{flex:1},children:t}),e.jsx(Sr,{open:w})]}),e.jsx("div",{ref:g,"aria-hidden":!w,style:{overflow:w?"visible":"hidden"},children:e.jsx("div",{style:{...o?{padding:"var(--lucent-space-3) var(--lucent-space-4) var(--lucent-space-4)"}:{},opacity:w?1:0,transform:w?"translateY(0)":"translateY(-4px)",transition:`opacity ${mt}ms ${de}, transform ${mt}ms ${de}`},children:a})})]})}function Sr({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 ${Xe} ${de}, color ${Xe} ${de}`},children:e.jsx("polyline",{points:"6 9 12 15 18 9"})})}const me="lucent-pl-no-scrollbar",Tr=`.${me}{scrollbar-width:none}.${me}::-webkit-scrollbar{display:none}`;function Se(t){return typeof t=="number"?`${t}px`:t}function Cr({children:t,header:a,sidebar:n,sidebarWidth:i=240,headerHeight:r=48,sidebarCollapsed:o=!1,rightSidebar:s,rightSidebarWidth:l=240,rightSidebarCollapsed:c=!1,footer:u,footerHeight:h=28,chromeBackground:f="navigation",mainStyle:S,style:w}){const g=Se(r),m=Se(i),p=Se(l),b=Se(h),x={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:x,fontFamily:"var(--lucent-font-family-base)",...w},children:[e.jsx("style",{children:Tr}),a!=null&&e.jsx("div",{style:{flexShrink:0,height:g,zIndex:10,background:x},children:a}),e.jsxs("div",{style:{display:"flex",flex:1,overflow:"hidden"},children:[n!=null&&e.jsx("div",{className:me,style:{width:o?0:m,flexShrink:0,overflow:"hidden",overflowY:o?"hidden":"auto",background:x,transition:"width 200ms var(--lucent-easing-default)"},children:n}),e.jsx("main",{className:me,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)",...S},children:t}),s!=null&&e.jsx("aside",{className:me,style:{width:c?0:p,flexShrink:0,overflow:"hidden",overflowY:c?"hidden":"auto",background:x,transition:"width 200ms var(--lucent-easing-default)"},children:s})]}),u!=null&&e.jsx("div",{style:{flexShrink:0,height:b,zIndex:10,background:x},children:u})]})}function Ir({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 bt({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 jr({columns:t,rows:a,pageSize:n=10,page:i,onPageChange:r,onFilterChange:o,emptyState:s,style:l}){const[c,u]=d.useState(null),[h,f]=d.useState(0),[S,w]=d.useState(null),[g,m]=d.useState({}),p=i!==void 0,b=p?i:h,y=t.some(I=>I.filterable),x=y?a.filter(I=>t.every(F=>{if(!F.filterable)return!0;const R=g[F.key];if(!R||R.length===0)return!0;const O=String(I[F.key]??"");return R.includes(O)})):a,T=c?[...x].sort((I,F)=>{const R=I[c.key],O=F[c.key],B=String(R??"").localeCompare(String(O??""),void 0,{numeric:!0});return c.dir==="asc"?B:-B}):x,k=n>0?T.slice(b*n,(b+1)*n):T,v=n>0?Math.max(1,Math.ceil(T.length/n)):1,A=I=>{p||f(I),r==null||r(I)},C=I=>{u(F=>!F||F.key!==I?{key:I,dir:"asc"}:F.dir==="asc"?{key:I,dir:"desc"}:null),p||f(0),r==null||r(0)},j=(I,F)=>{const R={...g,[I]:F};F.length===0&&delete R[I],m(R),p||f(0),r==null||r(0),o==null||o(R)},q=()=>{m({}),p||f(0),r==null||r(0),o==null||o({})},D=[];if(v<=7)for(let I=0;I<v;I++)D.push(I);else{D.push(0),b>2&&D.push("…");for(let I=Math.max(1,b-1);I<=Math.min(v-2,b+1);I++)D.push(I);b<v-3&&D.push("…"),D.push(v-1)}return e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-3)",...l},children:[y&&e.jsx("div",{style:{position:"relative",zIndex:1},children:e.jsx(Mr,{columns:t,rows:a,filters:g,onFilter:j,onClearAll:q})}),e.jsx("div",{style:{overflowX:"auto",borderRadius:"var(--lucent-radius-lg)",border:"1px solid var(--lucent-border-default)",background:"var(--lucent-surface)"},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(I=>{const F=(c==null?void 0:c.key)===I.key?c.dir:"none";return e.jsx("th",{onClick:I.sortable?()=>C(I.key):void 0,style:{padding:"var(--lucent-space-3) var(--lucent-space-4)",textAlign:I.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:I.sortable?"pointer":"default",userSelect:"none",whiteSpace:"nowrap",...I.width?{width:I.width}:{}},children:e.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:"var(--lucent-space-1)"},children:[I.header,I.sortable&&e.jsx(Ir,{state:F}),I.headerFilter&&e.jsx("span",{onClick:R=>R.stopPropagation(),style:{display:"inline-flex",marginLeft:"var(--lucent-space-1)"},children:I.headerFilter})]})},I.key)})})}),e.jsx("tbody",{children:k.length===0?e.jsx("tr",{children:e.jsx("td",{colSpan:t.length,style:{padding:"var(--lucent-space-12)",textAlign:"center"},children:s??e.jsx(E.Text,{color:"secondary",children:"No data"})})}):k.map((I,F)=>e.jsx("tr",{onMouseEnter:()=>w(F),onMouseLeave:()=>w(null),style:{borderBottom:F<k.length-1?"1px solid var(--lucent-border-subtle)":"none",background:S===F?"color-mix(in srgb, var(--lucent-text-primary) 4%, transparent)":"transparent",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)"},children:t.map(R=>e.jsx("td",{style:{padding:"var(--lucent-space-3) var(--lucent-space-4)",color:"var(--lucent-text-primary)",textAlign:R.align??"left",verticalAlign:"middle"},children:R.render?R.render(I,F):String(I[R.key]??"")},R.key))},F))})]})}),n>0&&T.length>0&&e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:"var(--lucent-space-3)",flexWrap:"wrap"},children:[e.jsx(E.Text,{color:"secondary",size:"sm",children:T.length===0?`0 rows${a.length>0?` (filtered from ${a.length})`:""}`:T.length===1?`1 row${T.length<a.length?` (filtered from ${a.length})`:""}`:`${b*n+1}–${Math.min((b+1)*n,T.length)} of ${T.length} rows${T.length<a.length?` (filtered from ${a.length})`:""}`}),e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-1)"},children:[e.jsx(Pe,{onClick:()=>A(b-1),disabled:b===0,"aria-label":"Previous page",children:e.jsx(bt,{dir:"left"})}),D.map((I,F)=>I==="…"?e.jsx("span",{style:{padding:"0 var(--lucent-space-1)",color:"var(--lucent-text-disabled)",fontSize:"var(--lucent-font-size-sm)"},children:"…"},`ellipsis-${F}`):e.jsx(Pe,{onClick:()=>A(I),active:I===b,"aria-label":`Page ${I+1}`,"aria-current":I===b?"page":void 0,children:I+1},I)),e.jsx(Pe,{onClick:()=>A(b+1),disabled:b>=v-1,"aria-label":"Next page",children:e.jsx(bt,{dir:"right"})})]})]})]})}function Mr({columns:t,rows:a,filters:n,onFilter:i,onClearAll:r}){const o=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:[o.map(l=>{const c=Array.from(new Set(a.map(u=>String(u[l.key]??"")))).sort();return e.jsx(Er,{label:l.header,values:c,value:n[l.key]??[],onChange:u=>i(l.key,u)},l.key)}),s>0&&e.jsx("button",{onClick:r,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 Er({label:t,values:a,value:n,onChange:i}){const[r,o]=d.useState(!1),[s,l]=d.useState(!1),[c,u]=d.useState(""),h=d.useRef(null),f=d.useRef(null),S=n.length>0;d.useEffect(()=>{if(!r){u("");return}setTimeout(()=>{var y;return(y=f.current)==null?void 0:y.focus()},0);const p=y=>{h.current&&!h.current.contains(y.target)&&o(!1)},b=y=>{y.key==="Escape"&&o(!1)};return document.addEventListener("mousedown",p),document.addEventListener("keydown",b),()=>{document.removeEventListener("mousedown",p),document.removeEventListener("keydown",b)}},[r]);const w=c?a.filter(p=>p.toLowerCase().includes(c.toLowerCase())):a,g=p=>i(n.includes(p)?n.filter(b=>b!==p):[...n,p]),m=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:h,style:{position:"relative"},children:[e.jsxs("button",{onClick:()=>o(p=>!p),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 ${S?"var(--lucent-accent-default)":s?"var(--lucent-border-strong)":"var(--lucent-border-default)"}`,background:S?"var(--lucent-accent-subtle)":"var(--lucent-surface)",color:S?"var(--lucent-accent-default)":"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:S?"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,m,e.jsx("svg",{width:"10",height:"10",viewBox:"0 0 10 10",fill:"none","aria-hidden":!0,style:{transform:r?"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"})})]}),r&&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:c,onChange:p=>u(p.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:()=>i([]),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:w.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"}):w.map(p=>e.jsx(zr,{label:p,isSelected:n.includes(p),onClick:()=>g(p)},p))})]})]})}function zr({label:t,isSelected:a,onClick:n}){const[i,r]=d.useState(!1);return e.jsxs("button",{onClick:n,onMouseEnter:()=>r(!0),onMouseLeave:()=>r(!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:i?"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:a?"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 ${a?"var(--lucent-accent-default)":"var(--lucent-border-strong)"}`,background:a?"var(--lucent-accent-default)":"transparent",display:"flex",alignItems:"center",justifyContent:"center",transition:"border-color var(--lucent-duration-fast), background var(--lucent-duration-fast)"},children:a&&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 Pe({children:t,onClick:a,disabled:n,active:i,...r}){const[o,s]=d.useState(!1);return e.jsx("button",{...r,onClick:a,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:i?"1px solid var(--lucent-accent-default)":"1px solid transparent",background:i?"var(--lucent-accent-default)":o&&!n?"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)":"transparent",color:i?"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 Ar={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. The table wrapper paints var(--lucent-surface) as its background so the component always sits on a solid panel, regardless of parent page color; header tints and row hover are translucent color-mix overlays on top of that surface.',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
|
|
342
|
+
`;let yt=!1;function Ca(){if(yt||typeof document>"u")return;const t=document.createElement("style");t.setAttribute("data-lucent-collapsible",""),t.textContent=Ta,document.head.appendChild(t),yt=!0}function ja({trigger:t,children:r,defaultOpen:n=!1,open:i,onOpenChange:a,padded:o=!0,disabled:l=!1,style:s}){const d=c.useContext(qe),p=d.px!=="0"||d.py!=="0",h=i!==void 0,[f,w]=c.useState(n),b=h?i:f,g=c.useRef(null),m=c.useRef(!1),u=c.useRef();c.useEffect(Ca,[]),c.useEffect(()=>{m.current=!0},[]),c.useLayoutEffect(()=>{const x=g.current;if(x)if(b){x.style.overflow="hidden";const T=x.scrollHeight;x.style.height=`${T}px`,x.style.transition=bt,clearTimeout(u.current),u.current=setTimeout(()=>{x.style.height="auto",x.style.overflow="visible",x.style.transition=""},Gt+20)}else m.current&&(clearTimeout(u.current),x.style.overflow="hidden",x.style.transition="",x.style.height=`${x.scrollHeight}px`,x.getBoundingClientRect(),x.style.transition=bt,x.style.height="0px")},[b]),c.useEffect(()=>{const x=g.current;x&&!b&&!m.current&&(x.style.height="0px")},[]),c.useEffect(()=>()=>clearTimeout(u.current),[]);const y=()=>{if(l)return;const x=!b;h||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})`},...s},children:[e.jsxs("button",{"data-lucent-collapsible-trigger":!0,onClick:y,disabled:l,"aria-expanded":b,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:l?"not-allowed":"pointer",textAlign:"left",outline:"none",color:"inherit",fontFamily:"inherit",fontSize:"inherit",opacity:l?.5:1,transition:`background ${Ze} ${ue}`},children:[e.jsx("span",{style:{flex:1},children:t}),e.jsx(Ia,{open:b})]}),e.jsx("div",{ref:g,"aria-hidden":!b,style:{overflow:b?"visible":"hidden"},children:e.jsx("div",{style:{...o?{padding:"var(--lucent-space-3) var(--lucent-space-4) var(--lucent-space-4)"}:{},opacity:b?1:0,transform:b?"translateY(0)":"translateY(-4px)",transition:`opacity ${vt}ms ${ue}, transform ${vt}ms ${ue}`},children:r})})]})}function Ia({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} ${ue}, color ${Ze} ${ue}`},children:e.jsx("polyline",{points:"6 9 12 15 18 9"})})}function Ut(t){const[r,n]=c.useState(()=>typeof window>"u"?!1:window.matchMedia(t).matches);return c.useEffect(()=>{const i=window.matchMedia(t);n(i.matches);const a=o=>n(o.matches);return i.addEventListener("change",a),()=>i.removeEventListener("change",a)},[t]),r}const oe="lucent-pl-no-scrollbar",Ma=`.${oe}{scrollbar-width:none}.${oe}::-webkit-scrollbar{display:none}`,De=200,Ea=`
|
|
343
|
+
@keyframes lucent-drawer-in{from{transform:translateX(-100%)}to{transform:translateX(0)}}
|
|
344
|
+
@keyframes lucent-drawer-out{from{transform:translateX(0)}to{transform:translateX(-100%)}}
|
|
345
|
+
@keyframes lucent-backdrop-in{from{opacity:0}to{opacity:1}}
|
|
346
|
+
@keyframes lucent-backdrop-out{from{opacity:1}to{opacity:0}}
|
|
347
|
+
`;function me(t){return typeof t=="number"?`${t}px`:t}function za({collapsed:t,sidebarWidth:r,onClick:n}){const[i,a]=c.useState(!1),o=t;let l,s;return i?o?(l="M2,2 L6,8",s="M6,8 L2,14"):(l="M6,2 L2,8",s="M2,8 L6,14"):(l="M1.5,2 L1.5,14",s="M6.5,2 L6.5,14"),e.jsx("div",{onClick:n,onMouseEnter:()=>a(!0),onMouseLeave:()=>a(!1),style:{position:"absolute",left:r,top:0,bottom:0,width:16,transform:"translateX(-50%)",display:"flex",alignItems:"center",justifyContent:"center",cursor:"pointer",zIndex:15},children:e.jsx("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",width:12,height:32,borderRadius:"var(--lucent-radius-sm)",background:i?"var(--lucent-surface-secondary)":"transparent",transition:"background 200ms"},children:e.jsxs("svg",{width:"8",height:"16",viewBox:"0 0 8 16",fill:"none","aria-hidden":!0,children:[e.jsx("path",{d:l,stroke:"var(--lucent-border-strong)",strokeWidth:"1.5",strokeLinecap:"round",style:{transition:"d 200ms var(--lucent-easing-default)"}}),e.jsx("path",{d:s,stroke:"var(--lucent-border-strong)",strokeWidth:"1.5",strokeLinecap:"round",style:{transition:"d 200ms var(--lucent-easing-default)"}})]})})})}function xt({open:t,onClick:r}){return e.jsx("button",{onClick:r,"aria-label":t?"Close navigation":"Open navigation",style:{display:"flex",alignItems:"center",justifyContent:"center",width:36,height:36,border:"none",background:"transparent",cursor:"pointer",borderRadius:"var(--lucent-radius-md)",color:"var(--lucent-text-primary)",flexShrink:0},children:e.jsx("svg",{width:"20",height:"20",viewBox:"0 0 20 20",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round","aria-hidden":!0,children:t?e.jsxs(e.Fragment,{children:[e.jsx("line",{x1:"4",y1:"4",x2:"16",y2:"16"}),e.jsx("line",{x1:"4",y1:"16",x2:"16",y2:"4"})]}):e.jsxs(e.Fragment,{children:[e.jsx("line",{x1:"3",y1:"5",x2:"17",y2:"5"}),e.jsx("line",{x1:"3",y1:"10",x2:"17",y2:"10"}),e.jsx("line",{x1:"3",y1:"15",x2:"17",y2:"15"})]})})})}function wt({sidebarHeader:t,sidebar:r,sidebarFooter:n,scrollClass:i,sidebarCollapsed:a,sidebarCollapsedWidth:o}){return e.jsxs(e.Fragment,{children:[t!=null&&e.jsx("div",{style:{flexShrink:0},children:t}),r!=null&&e.jsx("div",{className:i,style:{flex:1,overflowY:a&&o===0?"hidden":"auto",overflowX:"hidden"},children:r}),n!=null&&e.jsx("div",{style:{flexShrink:0},children:n})]})}function Aa({children:t,header:r,sidebar:n,sidebarHeader:i,sidebarFooter:a,sidebarWidth:o=240,sidebarCollapsedWidth:l=0,headerHeight:s=48,sidebarCollapsed:d=!1,onSidebarCollapsedChange:p,sidebarMode:h="auto",sidebarDrawerBreakpoint:f=768,sidebarDrawerOpen:w=!1,onSidebarDrawerOpenChange:b,rightSidebar:g,rightSidebarWidth:m=240,rightSidebarCollapsed:u=!1,footer:y,footerHeight:x=28,chromeBackground:T="navigation",mainStyle:S,style:k}){const v=me(s),A=me(o),C=me(l),M=me(m),q=me(x),B=n!=null||i!=null||a!=null,L={navigation:"var(--lucent-navigation)",bgBase:"var(--lucent-bg-base)",bgSubtle:"var(--lucent-bg-subtle)",surface:"var(--lucent-surface)",surfaceSecondary:"var(--lucent-surface-secondary)"}[T]??"var(--lucent-navigation)",D=Ut(`(max-width: ${f-1}px)`),N=B&&(h==="drawer"||h==="auto"&&D),[R,W]=c.useState(!1),[G,P]=c.useState("idle");c.useEffect(()=>{if(w&&N)W(!0),P("entering");else if(R){P("exiting");const j=setTimeout(()=>{W(!1),P("idle")},De);return()=>clearTimeout(j)}},[w,N]),c.useEffect(()=>{!N&&w&&(b==null||b(!1))},[N]),c.useEffect(()=>{if(!w||!N)return;const j=$=>{$.key==="Escape"&&(b==null||b(!1))};return document.addEventListener("keydown",j),()=>document.removeEventListener("keydown",j)},[w,N,b]),c.useEffect(()=>{if(w&&N){const j=document.body.style.overflow;return document.body.style.overflow="hidden",()=>{document.body.style.overflow=j}}},[w,N]);const z=N?"0 var(--lucent-space-3) var(--lucent-space-3) var(--lucent-space-3)":g!=null?"0 0 var(--lucent-space-3) 0":"0 var(--lucent-space-3) var(--lucent-space-3) 0";return e.jsxs("div",{style:{display:"flex",flexDirection:"column",height:"100vh",overflow:"hidden",background:L,fontFamily:"var(--lucent-font-family-base)",...k},children:[e.jsxs("style",{children:[Ma,N?Ea:""]}),r!=null&&e.jsxs("div",{style:{flexShrink:0,height:v,zIndex:10,background:L,display:"flex",alignItems:"center"},children:[N&&e.jsx("div",{style:{flexShrink:0,padding:"0 var(--lucent-space-2)"},children:e.jsx(xt,{open:w,onClick:()=>b==null?void 0:b(!w)})}),e.jsx("div",{style:{flex:1,height:"100%",minWidth:0},children:r})]}),e.jsxs("div",{style:{display:"flex",flex:1,overflow:"hidden",position:"relative"},children:[B&&!N&&e.jsx("div",{style:{width:d?C:A,flexShrink:0,display:"flex",flexDirection:"column",overflow:"hidden",background:L,transition:"width 200ms var(--lucent-easing-default)"},children:e.jsx(wt,{sidebarHeader:i,sidebar:n,sidebarFooter:a,scrollClass:oe,sidebarCollapsed:d,sidebarCollapsedWidth:l})}),B&&!N&&p&&e.jsx(za,{collapsed:d,sidebarWidth:d?C:A,onClick:()=>p(!d)}),e.jsx("main",{className:oe,style:{flex:1,overflowY:"auto",minWidth:0,margin:z,border:"1px solid var(--lucent-border-default)",borderRadius:"var(--lucent-radius-lg)",boxShadow:"var(--lucent-shadow-sm)",background:"var(--lucent-bg-base)",...S},children:t}),g!=null&&e.jsx("aside",{className:oe,style:{width:u?0:M,flexShrink:0,overflow:"hidden",overflowY:u?"hidden":"auto",background:L,transition:"width 200ms var(--lucent-easing-default)"},children:g})]}),y!=null&&e.jsx("div",{style:{flexShrink:0,height:q,zIndex:10,background:L},children:y}),N&&R&&Q.createPortal(e.jsxs(e.Fragment,{children:[e.jsx("div",{onClick:()=>b==null?void 0:b(!1),style:{position:"fixed",inset:0,zIndex:1100,background:"var(--lucent-bg-overlay)",animation:`${G==="exiting"?"lucent-backdrop-out":"lucent-backdrop-in"} ${De}ms var(--lucent-easing-default) forwards`,pointerEvents:G==="exiting"?"none":"auto"}}),e.jsxs("div",{className:oe,style:{position:"fixed",top:0,left:0,bottom:0,width:A,maxWidth:"85vw",zIndex:1101,display:"flex",flexDirection:"column",background:L,boxShadow:"var(--lucent-shadow-lg)",animation:`${G==="exiting"?"lucent-drawer-out":"lucent-drawer-in"} ${De}ms var(--lucent-easing-decelerate) forwards`,fontFamily:"var(--lucent-font-family-base)"},children:[e.jsx("div",{style:{flexShrink:0,display:"flex",alignItems:"center",padding:"var(--lucent-space-2)"},children:e.jsx(xt,{open:!0,onClick:()=>b==null?void 0:b(!1)})}),e.jsx(wt,{sidebarHeader:i,sidebar:n,sidebarFooter:a,scrollClass:oe,sidebarCollapsed:!1,sidebarCollapsedWidth:0})]})]}),document.body)]})}function qa({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 kt({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 Ba({columns:t,rows:r,pageSize:n=10,page:i,onPageChange:a,onFilterChange:o,emptyState:l,style:s}){const[d,p]=c.useState(null),[h,f]=c.useState(0),[w,b]=c.useState(null),[g,m]=c.useState({}),u=i!==void 0,y=u?i:h,x=t.some(I=>I.filterable),T=x?r.filter(I=>t.every(L=>{if(!L.filterable)return!0;const D=g[L.key];if(!D||D.length===0)return!0;const N=String(I[L.key]??"");return D.includes(N)})):r,S=d?[...T].sort((I,L)=>{const D=I[d.key],N=L[d.key],R=String(D??"").localeCompare(String(N??""),void 0,{numeric:!0});return d.dir==="asc"?R:-R}):T,k=n>0?S.slice(y*n,(y+1)*n):S,v=n>0?Math.max(1,Math.ceil(S.length/n)):1,A=I=>{u||f(I),a==null||a(I)},C=I=>{p(L=>!L||L.key!==I?{key:I,dir:"asc"}:L.dir==="asc"?{key:I,dir:"desc"}:null),u||f(0),a==null||a(0)},M=(I,L)=>{const D={...g,[I]:L};L.length===0&&delete D[I],m(D),u||f(0),a==null||a(0),o==null||o(D)},q=()=>{m({}),u||f(0),a==null||a(0),o==null||o({})},B=[];if(v<=7)for(let I=0;I<v;I++)B.push(I);else{B.push(0),y>2&&B.push("…");for(let I=Math.max(1,y-1);I<=Math.min(v-2,y+1);I++)B.push(I);y<v-3&&B.push("…"),B.push(v-1)}return e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-3)",...s},children:[x&&e.jsx("div",{style:{position:"relative",zIndex:1},children:e.jsx(Ra,{columns:t,rows:r,filters:g,onFilter:M,onClearAll:q})}),e.jsx("div",{style:{overflowX:"auto",borderRadius:"var(--lucent-radius-lg)",border:"1px solid var(--lucent-border-default)",background:"var(--lucent-surface)"},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(I=>{const L=(d==null?void 0:d.key)===I.key?d.dir:"none";return e.jsx("th",{onClick:I.sortable?()=>C(I.key):void 0,style:{padding:"var(--lucent-space-3) var(--lucent-space-4)",textAlign:I.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:I.sortable?"pointer":"default",userSelect:"none",whiteSpace:"nowrap",...I.width?{width:I.width}:{}},children:e.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:"var(--lucent-space-1)"},children:[I.header,I.sortable&&e.jsx(qa,{state:L}),I.headerFilter&&e.jsx("span",{onClick:D=>D.stopPropagation(),style:{display:"inline-flex",marginLeft:"var(--lucent-space-1)"},children:I.headerFilter})]})},I.key)})})}),e.jsx("tbody",{children:k.length===0?e.jsx("tr",{children:e.jsx("td",{colSpan:t.length,style:{padding:"var(--lucent-space-12)",textAlign:"center"},children:l??e.jsx(E.Text,{color:"secondary",children:"No data"})})}):k.map((I,L)=>e.jsx("tr",{onMouseEnter:()=>b(L),onMouseLeave:()=>b(null),style:{borderBottom:L<k.length-1?"1px solid var(--lucent-border-subtle)":"none",background:w===L?"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(I,L):String(I[D.key]??"")},D.key))},L))})]})}),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(E.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})`:""}`:`${y*n+1}–${Math.min((y+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(Le,{onClick:()=>A(y-1),disabled:y===0,"aria-label":"Previous page",children:e.jsx(kt,{dir:"left"})}),B.map((I,L)=>I==="…"?e.jsx("span",{style:{padding:"0 var(--lucent-space-1)",color:"var(--lucent-text-disabled)",fontSize:"var(--lucent-font-size-sm)"},children:"…"},`ellipsis-${L}`):e.jsx(Le,{onClick:()=>A(I),active:I===y,"aria-label":`Page ${I+1}`,"aria-current":I===y?"page":void 0,children:I+1},I)),e.jsx(Le,{onClick:()=>A(y+1),disabled:y>=v-1,"aria-label":"Next page",children:e.jsx(kt,{dir:"right"})})]})]})]})}function Ra({columns:t,rows:r,filters:n,onFilter:i,onClearAll:a}){const o=t.filter(s=>s.filterable),l=Object.keys(n).length;return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-2)",flexWrap:"wrap"},children:[o.map(s=>{const d=Array.from(new Set(r.map(p=>String(p[s.key]??"")))).sort();return e.jsx(Pa,{label:s.header,values:d,value:n[s.key]??[],onChange:p=>i(s.key,p)},s.key)}),l>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 Pa({label:t,values:r,value:n,onChange:i}){const[a,o]=c.useState(!1),[l,s]=c.useState(!1),[d,p]=c.useState(""),h=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=>{h.current&&!h.current.contains(x.target)&&o(!1)},y=x=>{x.key==="Escape"&&o(!1)};return document.addEventListener("mousedown",u),document.addEventListener("keydown",y),()=>{document.removeEventListener("mousedown",u),document.removeEventListener("keydown",y)}},[a]);const b=d?r.filter(u=>u.toLowerCase().includes(d.toLowerCase())):r,g=u=>i(n.includes(u)?n.filter(y=>y!==u):[...n,u]),m=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:h,style:{position:"relative"},children:[e.jsxs("button",{onClick:()=>o(u=>!u),onMouseEnter:()=>s(!0),onMouseLeave:()=>s(!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)":l?"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,m,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:()=>i([]),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:b.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"}):b.map(u=>e.jsx(Da,{label:u,isSelected:n.includes(u),onClick:()=>g(u)},u))})]})]})}function Da({label:t,isSelected:r,onClick:n}){const[i,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:i?"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 Le({children:t,onClick:r,disabled:n,active:i,...a}){const[o,l]=c.useState(!1);return e.jsx("button",{...a,onClick:r,disabled:n,onMouseEnter:()=>l(!0),onMouseLeave:()=>l(!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:i?"1px solid var(--lucent-accent-default)":"1px solid transparent",background:i?"var(--lucent-accent-default)":o&&!n?"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)":"transparent",color:i?"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 La={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. The table wrapper paints var(--lucent-surface) as its background so the component always sits on a solid panel, regardless of parent page color; header tints and row hover are translucent color-mix overlays on top of that surface.',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
|
|
343
348
|
columns={[
|
|
344
349
|
{ key: 'name', header: 'Name', sortable: true },
|
|
345
350
|
{ key: 'role', header: 'Role', sortable: true },
|
|
@@ -361,12 +366,12 @@ const [results, setResults] = useState([]);
|
|
|
361
366
|
pageSize={20}
|
|
362
367
|
page={page}
|
|
363
368
|
onPageChange={setPage}
|
|
364
|
-
/>`},{title:"No pagination",code:"<DataTable columns={columns} rows={rows} pageSize={0} />"}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Row count and empty state labels",required:!1},{componentId:"badge",componentName:"Badge",role:"Typical cell content for status columns",required:!1}],accessibility:{role:"table",ariaAttributes:["aria-label","aria-sort","aria-current"],keyboardInteractions:["Tab to pagination controls","Enter/Space to activate buttons"],notes:'Column headers with sortable:true are interactive buttons with aria-sort reflecting the current sort direction. Pagination buttons include aria-label and aria-current="page" for the active page.'}},
|
|
369
|
+
/>`},{title:"No pagination",code:"<DataTable columns={columns} rows={rows} pageSize={0} />"}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Row count and empty state labels",required:!1},{componentId:"badge",componentName:"Badge",role:"Typical cell content for status columns",required:!1}],accessibility:{role:"table",ariaAttributes:["aria-label","aria-sort","aria-current"],keyboardInteractions:["Tab to pagination controls","Enter/Space to activate buttons"],notes:'Column headers with sortable:true are interactive buttons with aria-sort reflecting the current sort direction. Pagination buttons include aria-label and aria-current="page" for the active page.'}},Fa=`
|
|
365
370
|
@keyframes lucent-palette-in {
|
|
366
371
|
from { opacity: 0; transform: scale(0.96) translateY(-8px); }
|
|
367
372
|
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
368
373
|
}
|
|
369
|
-
`;function
|
|
374
|
+
`;function $a(t,r){var i;const n=r.toLowerCase();return t.label.toLowerCase().includes(n)||(((i=t.description)==null?void 0:i.toLowerCase().includes(n))??!1)}function Na({commands:t,placeholder:r="Search commands…",shortcutKey:n="k",open:i,onOpenChange:a,style:o}){const l=i!==void 0,[s,d]=c.useState(!1),p=l?i:s,[h,f]=c.useState(""),[w,b]=c.useState(0),g=c.useRef(null),m=c.useRef(null),u=c.useRef(!1);if(!u.current){const C=document.createElement("style");C.textContent=Fa,document.head.appendChild(C),u.current=!0}const y=c.useCallback(C=>{l||d(C),a==null||a(C)},[l,a]);c.useEffect(()=>{const C=M=>{(M.metaKey||M.ctrlKey)&&M.key===n&&(M.preventDefault(),y(!p))};return window.addEventListener("keydown",C),()=>window.removeEventListener("keydown",C)},[p,n,y]),c.useEffect(()=>{p&&(f(""),b(0),setTimeout(()=>{var C;return(C=g.current)==null?void 0:C.focus()},10))},[p]);const x=h?t.filter(C=>$a(C,h)):t,T=x.filter(C=>!C.disabled);c.useEffect(()=>{const C=m.current;if(!C)return;const M=C.querySelector('[data-active="true"]');M==null||M.scrollIntoView({block:"nearest"})},[w]);const S=C=>{C.disabled||(C.onSelect(),y(!1))},k=C=>{if(C.key==="Escape"){y(!1);return}if(C.key==="ArrowDown")C.preventDefault(),T.length>0&&b(M=>(M+1)%T.length);else if(C.key==="ArrowUp")C.preventDefault(),T.length>0&&b(M=>(M-1+T.length)%T.length);else if(C.key==="Enter"){const M=T[w];M&&S(M)}},v=[];for(const C of x){const M=v[v.length-1];M&&M.group===C.group?M.items.push(C):v.push({group:C.group,items:[C]})}if(!p)return null;let A=0;return e.jsx("div",{role:"dialog","aria-label":"Command palette","aria-modal":"true",onClick:()=>y(!1),style:{position:"fixed",inset:0,zIndex:9e3,display:"flex",alignItems:"flex-start",justifyContent:"center",paddingTop:"15vh",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",background:"rgba(0, 0, 0, 0.15)",...o},children:e.jsxs("div",{role:"combobox","aria-haspopup":"listbox","aria-expanded":"true",onClick:C=>C.stopPropagation(),style:{width:"100%",maxWidth:560,margin:"0 var(--lucent-space-4)",background:"color-mix(in srgb, var(--lucent-surface-overlay) 85%, transparent)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",borderRadius:"var(--lucent-radius-xl)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 15%, var(--lucent-border-default))",boxShadow:"0 0 24px -4px color-mix(in srgb, var(--lucent-accent-default) 12%, transparent), var(--lucent-shadow-xl)",overflow:"hidden",animation:"lucent-palette-in 150ms var(--lucent-easing-decelerate)"},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",padding:"var(--lucent-space-3) var(--lucent-space-4)",borderBottom:x.length>0?"1px solid var(--lucent-border-subtle)":"none"},children:[e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":!0,style:{flexShrink:0,color:"var(--lucent-text-secondary)"},children:[e.jsx("circle",{cx:"7",cy:"7",r:"4.5",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M10.5 10.5L13.5 13.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),e.jsx("input",{ref:g,role:"searchbox","aria-autocomplete":"list","aria-controls":"lucent-command-list",value:h,onChange:C=>{f(C.target.value),b(0)},onKeyDown:k,placeholder:r,style:{flex:1,border:"none",outline:"none",background:"transparent",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-md)",color:"var(--lucent-text-primary)",lineHeight:"var(--lucent-line-height-base)"}}),e.jsx(E.Button,{size:"xs",variant:"outline",tabIndex:-1,style:{pointerEvents:"none",flexShrink:0},children:"Esc"})]}),e.jsx("div",{id:"lucent-command-list",role:"listbox",ref:m,style:{maxHeight:360,overflowY:"auto",padding:"var(--lucent-space-1) var(--lucent-space-2)"},children:x.length===0?e.jsx("div",{style:{padding:"var(--lucent-space-8)",textAlign:"center"},children:e.jsxs(E.Text,{color:"secondary",children:['No results for "',h,'"']})}):v.map(({group:C,items:M},q)=>e.jsxs("div",{children:[C&&e.jsx("div",{style:{padding:"var(--lucent-space-2) var(--lucent-space-2) var(--lucent-space-1)",...q>0?{borderTop:"1px solid var(--lucent-border-subtle)",marginTop:"var(--lucent-space-2)"}:{}},children:e.jsx(E.Text,{size:"xs",color:"secondary",weight:"medium",children:C})}),M.map(B=>{const I=B.disabled??!1;let L=!1;return I||(L=A===w,A++),e.jsxs("div",{role:"option","aria-selected":L,"aria-disabled":I,"data-active":L,onClick:()=>S(B),onMouseEnter:()=>{if(!I){const D=T.indexOf(B);D!==-1&&b(D)}},style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",padding:"var(--lucent-space-2) var(--lucent-space-3)",borderRadius:"var(--lucent-radius-md)",cursor:I?"not-allowed":"pointer",background:L?"color-mix(in srgb, var(--lucent-accent-default) 20%, var(--lucent-surface-secondary))":"transparent",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)",opacity:I?.5:1},children:[B.icon&&e.jsx("span",{style:{flexShrink:0,color:"var(--lucent-text-secondary)",display:"flex"},children:B.icon}),e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx(E.Text,{size:"sm",weight:"medium",truncate:!0,children:B.label}),B.description&&e.jsx(E.Text,{size:"xs",color:"secondary",truncate:!0,children:B.description})]})]},B.id)})]},q))}),e.jsx("div",{style:{display:"flex",gap:"var(--lucent-space-4)",padding:"var(--lucent-space-2) var(--lucent-space-4)",borderTop:"1px solid var(--lucent-border-subtle)",background:"var(--lucent-surface-secondary)"},children:[["↑↓","Navigate"],["↵","Select"],["Esc","Close"]].map(([C,M])=>e.jsxs("span",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-1)"},children:[e.jsx(E.Button,{size:"xs",variant:"outline",tabIndex:-1,style:{pointerEvents:"none"},children:C}),e.jsx(E.Text,{size:"xs",color:"secondary",children:M})]},C))})]})})}const Wa={id:"command-palette",name:"CommandPalette",tier:"overlay",domain:"neutral",specVersion:"1.0",description:"A keyboard-driven modal command palette with fuzzy search, grouped results, and a built-in ⌘K / Ctrl+K global shortcut.",designIntent:"CommandPalette renders as a portal-style overlay (fixed, full-viewport) with a centred panel that animates in with a subtle scale+fade. The ⌘K shortcut is registered as a global window listener so it works regardless of focus — consumers can override the key via shortcutKey prop. Clicking the backdrop dismisses the palette; Escape also closes it. Results are filtered client-side against label and description using simple substring match — no fuzzy library needed. Navigation is purely keyboard-driven: ↑↓ move the active index, Enter selects, mouse hover syncs the active index so mouse and keyboard stay in sync. Groups are derived from the group field on each CommandItem; the order of groups follows the order they first appear in the commands array. The component is controlled-or-uncontrolled: pass open + onOpenChange for controlled use, or omit both to use internal state.",props:[{name:"commands",type:"array",required:!0,description:"Array of CommandItem objects. Each has id, label, onSelect, and optional description, icon, group, disabled."},{name:"placeholder",type:"string",required:!1,default:'"Search commands…"',description:"Placeholder text for the search input."},{name:"shortcutKey",type:"string",required:!1,default:'"k"',description:'Key that opens the palette when pressed with Meta (Mac) or Ctrl (Windows). Defaults to "k" for ⌘K.'},{name:"open",type:"boolean",required:!1,description:"Controlled open state. When provided, the component is fully controlled."},{name:"onOpenChange",type:"function",required:!1,description:"Called when the palette requests an open/close state change."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the backdrop element."}],usageExamples:[{title:"Uncontrolled with ⌘K",code:`<CommandPalette
|
|
370
375
|
commands={[
|
|
371
376
|
{ id: 'new', label: 'New document', icon: <PlusIcon />, onSelect: () => router.push('/new') },
|
|
372
377
|
{ id: 'settings', label: 'Settings', description: 'Open app settings', onSelect: () => router.push('/settings') },
|
|
@@ -381,7 +386,7 @@ const [results, setResults] = useState([]);
|
|
|
381
386
|
onOpenChange={setOpen}
|
|
382
387
|
shortcutKey="p"
|
|
383
388
|
/>
|
|
384
|
-
</>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Group labels, item labels, descriptions, and empty state",required:!0}],accessibility:{role:"dialog",ariaAttributes:["aria-label","aria-modal","aria-expanded","aria-selected","aria-disabled","aria-controls","aria-autocomplete"],keyboardInteractions:["⌘K / Ctrl+K to open","↑↓ to navigate","Enter to select","Escape to close"],notes:'The backdrop and panel use role="dialog" with aria-modal="true". The input is role="searchbox". The result list is role="listbox" with role="option" items. Focus is moved to the search input on open.'}},
|
|
389
|
+
</>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Group labels, item labels, descriptions, and empty state",required:!0}],accessibility:{role:"dialog",ariaAttributes:["aria-label","aria-modal","aria-expanded","aria-selected","aria-disabled","aria-controls","aria-autocomplete"],keyboardInteractions:["⌘K / Ctrl+K to open","↑↓ to navigate","Enter to select","Escape to close"],notes:'The backdrop and panel use role="dialog" with aria-modal="true". The input is role="searchbox". The result list is role="listbox" with role="option" items. Focus is moved to the search input on open.'}},Va={sm:"calc(var(--lucent-space-8) * 0.5 + 16px + 2px)",md:"calc(var(--lucent-space-10) * 0.5 + 20px + 2px)",lg:"calc(var(--lucent-space-12) * 0.5 + 24px + 2px)"},Oa={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-md)",lg:"var(--lucent-font-size-lg)"},Ha={sm:"var(--lucent-space-1) var(--lucent-space-2)",md:"var(--lucent-space-1) var(--lucent-space-2)",lg:"var(--lucent-space-2) var(--lucent-space-3)"},Ce={sm:"var(--lucent-space-2)",md:"var(--lucent-space-2)",lg:"var(--lucent-space-3)"},Fe={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-md)"},St={sm:"sm",md:"md",lg:"lg"},Ga={sm:"sm",md:"md",lg:"lg"};function Ua({options:t,value:r,defaultValue:n=[],onChange:i,placeholder:a="Select…",disabled:o=!1,max:l,size:s="md",label:d,helperText:p,errorText:h,style:f}){const w=r!==void 0,[b,g]=c.useState(n),m=w?r:b,[u,y]=c.useState(!1),[x,T]=c.useState(""),[S,k]=c.useState(0),[v,A]=c.useState(!1),C=c.useRef(null),M=c.useRef(null),q=c.useRef(null),B=c.useRef(null),[I,L]=c.useState({top:0,left:0,width:0}),D=c.useId();c.useEffect(()=>{if(!u)return;const F=O=>{var U,ee;!((U=C.current)!=null&&U.contains(O.target))&&!((ee=B.current)!=null&&ee.contains(O.target))&&(y(!1),T(""))};return document.addEventListener("mousedown",F),()=>document.removeEventListener("mousedown",F)},[u]),c.useLayoutEffect(()=>{if(!u||!q.current)return;const F=q.current.getBoundingClientRect();L({top:F.bottom+4,left:F.left,width:F.width})},[u,m,x]);const N=F=>{const O=m.includes(F)?m.filter(U=>U!==F):l!==void 0&&m.length>=l?m:[...m,F];w||g(O),i==null||i(O)},R=F=>{const O=m.filter(U=>U!==F);w||g(O),i==null||i(O)},W=t.filter(F=>F.label.toLowerCase().includes(x.toLowerCase())),G=F=>{if(F.key==="Escape"){y(!1),T("");return}if(F.key==="ArrowDown"&&(F.preventDefault(),y(!0),k(O=>Math.min(O+1,W.length-1))),F.key==="ArrowUp"&&(F.preventDefault(),k(O=>Math.max(O-1,0))),F.key==="Enter"){F.preventDefault();const O=W[S];O&&!O.disabled&&N(O.value)}F.key==="Backspace"&&x===""&&m.length>0&&R(m[m.length-1])},P=l!==void 0&&m.length>=l,z=!!h,j=o?"var(--lucent-border-default)":z?"var(--lucent-danger-default)":v?"var(--lucent-accent-border)":"var(--lucent-border-default)",$=v?`0 0 0 3px ${z?"var(--lucent-danger-subtle)":"var(--lucent-accent-subtle)"}`:"none",V=`lucent-multiselect-${c.useId()}`;return e.jsxs("div",{ref:C,style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-1)",...f},children:[d&&e.jsx("label",{htmlFor:V,style:{fontSize:Fe[s],fontWeight:"var(--lucent-font-weight-medium)",color:o?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)"},children:d}),e.jsxs("div",{ref:q,children:[e.jsxs("div",{onClick:()=>{var F;o||(y(!0),(F=M.current)==null||F.focus())},style:{display:"flex",flexWrap:"wrap",alignItems:"center",gap:Ce[s],minHeight:Va[s],boxSizing:"border-box",padding:Ha[s],borderRadius:"var(--lucent-radius-lg)",border:`1px solid ${j}`,background:o?"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)":"var(--lucent-surface)",cursor:o?"not-allowed":"text",transition:"border-color var(--lucent-duration-fast) var(--lucent-easing-default), box-shadow var(--lucent-duration-fast) var(--lucent-easing-default)",boxShadow:$},children:[m.map(F=>{const O=t.find(U=>U.value===F);return O?e.jsx(be,{size:Ga[s],onDismiss:()=>R(F),disabled:o,children:O.label},F):null}),e.jsx("input",{id:V,ref:M,value:x,onChange:F=>{T(F.target.value),y(!0),k(0)},onKeyDown:G,onFocus:()=>A(!0),onBlur:()=>A(!1),disabled:o,placeholder:m.length===0?a:"","aria-autocomplete":"list","aria-controls":D,"aria-expanded":u,"aria-describedby":z?`${V}-error`:p?`${V}-helper`:void 0,role:"combobox",style:{flex:"1 1 80px",minWidth:80,border:"none",outline:"none",background:"transparent",fontFamily:"var(--lucent-font-family-base)",fontSize:Oa[s],color:o?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",cursor:o?"not-allowed":"text",padding:m.length===0?"2px 0 2px var(--lucent-space-1)":"2px 0"}})]}),u&&!o&&Q.createPortal(e.jsxs("div",{ref:B,id:D,role:"listbox","aria-multiselectable":"true",style:{position:"fixed",top:I.top,left:I.left,width:I.width,zIndex:1e3,background:"color-mix(in srgb, var(--lucent-surface-overlay) 85%, transparent)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 15%, var(--lucent-border-default))",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 0 24px -4px color-mix(in srgb, var(--lucent-accent-default) 12%, transparent), var(--lucent-shadow-md)",maxHeight:240,overflowY:"auto",padding:Ce[s]},children:[W.length===0?e.jsx("div",{style:{padding:"var(--lucent-space-2)"},children:e.jsx(E.Text,{color:"secondary",size:St[s],children:"No options"})}):W.map((F,O)=>{const U=m.includes(F.value),ee=O===S,J=F.disabled??!1,H=P&&!U;return e.jsxs("div",{role:"option","aria-selected":U,"aria-disabled":J||H,onClick:()=>{!J&&!H&&N(F.value)},onMouseEnter:()=>k(O),style:{display:"flex",alignItems:"center",gap:Ce[s],padding:Ce[s],borderRadius:"var(--lucent-radius-md)",cursor:J||H?"not-allowed":"pointer",background:ee?"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)":"transparent",opacity:J||H?.5:1},children:[e.jsx(Ae,{checked:U,disabled:J||H,size:s,style:{margin:0,pointerEvents:"none"},"aria-hidden":!0,readOnly:!0}),e.jsx(E.Text,{size:St[s],children:F.label})]},F.value)}),P&&e.jsx("div",{style:{padding:"var(--lucent-space-2)",borderTop:"1px solid var(--lucent-border-subtle)"},children:e.jsxs(E.Text,{size:s==="lg"?"sm":"xs",color:"secondary",children:["Max ",l," selected"]})})]}),document.body)]}),z&&e.jsx("span",{id:`${V}-error`,role:"alert",style:{fontSize:Fe[s],color:"var(--lucent-danger-text)",fontFamily:"var(--lucent-font-family-base)"},children:h}),!z&&p&&e.jsx("span",{id:`${V}-helper`,style:{fontSize:Fe[s],color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)"},children:p})]})}const _a={id:"multi-select",name:"MultiSelect",tier:"molecule",domain:"neutral",specVersion:"1.0",description:"A tag-based multi-option selector with inline search, keyboard navigation, and an optional selection cap.",designIntent:"MultiSelect renders selected values as removable tags inside the trigger area, giving immediate visual feedback on what is selected. The search input sits inline with the tags so there is no separate search field to discover. Backspace with an empty query removes the last selected tag — a standard UX pattern in multi-select inputs. The max prop caps selection without disabling the entire component: unselected options beyond the cap are grayed out and show a hint. The dropdown closes on outside click, Escape, or when focus leaves. Checkbox indicators in the dropdown make the selected state scannable at a glance without relying solely on background color.",props:[{name:"options",type:"array",required:!0,description:"Array of { value, label, disabled? } option objects."},{name:"value",type:"array",required:!1,description:"Controlled array of selected values."},{name:"defaultValue",type:"array",required:!1,default:"[]",description:"Initial selected values for uncontrolled usage."},{name:"onChange",type:"function",required:!1,description:"Called with the updated array of selected values on each change."},{name:"placeholder",type:"string",required:!1,default:'"Select…"',description:"Placeholder shown when nothing is selected."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables interaction and dims the component."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls trigger height and font size to match Input/Select.",enumValues:["sm","md","lg"]},{name:"max",type:"number",required:!1,description:"Maximum number of selectable options. Unselected options beyond the cap are grayed out."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer wrapper."}],usageExamples:[{title:"Uncontrolled",code:`<MultiSelect
|
|
385
390
|
options={[
|
|
386
391
|
{ value: 'react', label: 'React' },
|
|
387
392
|
{ value: 'vue', label: 'Vue' },
|
|
@@ -395,14 +400,14 @@ const [results, setResults] = useState([]);
|
|
|
395
400
|
onChange={setTags}
|
|
396
401
|
max={3}
|
|
397
402
|
placeholder="Up to 3 tags"
|
|
398
|
-
/>`}],compositionGraph:[{componentId:"chip",componentName:"Chip",role:"Selected value chips with dismiss button",required:!0},{componentId:"checkbox",componentName:"Checkbox",role:"Selection indicator in dropdown options",required:!0},{componentId:"text",componentName:"Text",role:"Option labels, empty state, and max hint",required:!0}],accessibility:{role:"combobox",ariaAttributes:["aria-autocomplete","aria-controls","aria-expanded","aria-multiselectable","aria-selected","aria-disabled"],keyboardInteractions:["↑↓ to navigate options","Enter to toggle selection","Escape to close","Backspace to remove last tag"],notes:'The input carries role="combobox" with aria-expanded and aria-controls pointing to the listbox. Each option has role="option" with aria-selected. Remove buttons on tags have descriptive aria-label.'}};function Or(t,a){return new Date(t,a+1,0).getDate()}function Vr(t,a){return new Date(t,a,1).getDay()}function Me(t,a){return t.getFullYear()===a.getFullYear()&&t.getMonth()===a.getMonth()&&t.getDate()===a.getDate()}function Ee(t,a){return new Date(t.getFullYear(),t.getMonth(),t.getDate())<new Date(a.getFullYear(),a.getMonth(),a.getDate())}function xt(t,a){return new Date(t.getFullYear(),t.getMonth(),t.getDate())>new Date(a.getFullYear(),a.getMonth(),a.getDate())}const Hr=["January","February","March","April","May","June","July","August","September","October","November","December"],Gr=["Su","Mo","Tu","We","Th","Fr","Sa"];function ge(t){return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}const Ur={sm:"calc(var(--lucent-space-8) * 0.5 + 18px)",md:"calc(var(--lucent-space-10) * 0.5 + 22px)",lg:"calc(var(--lucent-space-12) * 0.5 + 26px)"},_r={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-md)",lg:"var(--lucent-font-size-md)"},Le={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-md)"},Yr={sm:"var(--lucent-space-3)",md:"var(--lucent-space-4)",lg:"var(--lucent-space-4)"},Kr={sm:"var(--lucent-space-2)",md:"calc((var(--lucent-space-2) + var(--lucent-space-3)) / 2)",lg:"var(--lucent-space-3)"},Xr={sm:28,md:32,lg:38},Jr={sm:"var(--lucent-font-size-xs)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-md)"},Zr={sm:"xs",md:"sm",lg:"md"},Qr={sm:"xs",md:"xs",lg:"sm"},eo={sm:24,md:28,lg:32},to={sm:14,md:16,lg:18},Ot={sm:"var(--lucent-space-3)",md:"var(--lucent-space-4)",lg:"var(--lucent-space-5)"},Je={sm:220,md:260,lg:300};function wt({dir:t,onClick:a,disabled:n,size:i="md"}){const[r,o]=d.useState(!1),s=eo[i],l=to[i];return e.jsx("button",{type:"button",onClick:a,disabled:n,onMouseEnter:()=>o(!0),onMouseLeave:()=>o(!1),"aria-label":t==="prev"?"Previous month":"Next month",style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:s,height:s,border:"none",borderRadius:"var(--lucent-radius-md)",background:r&&!n?"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)":"transparent",color:n?"var(--lucent-text-disabled)":"var(--lucent-text-secondary)",cursor:n?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast)"},children:e.jsx("svg",{width:l,height:l,viewBox:"0 0 16 16",fill:"none","aria-hidden":!0,children:e.jsx("path",{d:t==="prev"?"M10 12L6 8l4-4":"M6 4l4 4-4 4",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})})}function Ze({year:t,month:a,selected:n,today:i,min:r,max:o,onSelect:s,onPrevMonth:l,onNextMonth:c,highlightRange:u,onDayHover:h,size:f="md"}){const S=Or(t,a),w=Vr(t,a),[g,m]=d.useState(null),p=[...Array(w).fill(null),...Array.from({length:S},(b,y)=>y+1)];for(;p.length%7!==0;)p.push(null);return e.jsxs("div",{children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"var(--lucent-space-3)"},children:[e.jsx(wt,{dir:"prev",onClick:l,size:f}),e.jsxs(E.Text,{weight:"medium",size:Zr[f],children:[Hr[a]," ",t]}),e.jsx(wt,{dir:"next",onClick:c,size:f})]}),e.jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(7, 1fr)",gap:2,marginBottom:"var(--lucent-space-1)"},children:Gr.map(b=>e.jsx("div",{style:{textAlign:"center"},children:e.jsx(E.Text,{size:Qr[f],color:"secondary",children:b})},b))}),e.jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(7, 1fr)",gap:2},children:p.map((b,y)=>{if(!b)return e.jsx("div",{},y);const x=new Date(t,a,b),T=n?Me(x,n):!1,k=Me(x,i),v=(r?Ee(x,r):!1)||(o?xt(x,o):!1);let A=!1;return u!=null&&u.start&&(u!=null&&u.end)&&(A=!Ee(x,u.start)&&!xt(x,u.end)),e.jsx("button",{type:"button",disabled:v,onClick:()=>!v&&s(x),onMouseEnter:()=>{m(b),h==null||h(x)},onMouseLeave:()=>{m(null),h==null||h(null)},"aria-label":ge(x),"aria-pressed":T,style:{display:"flex",alignItems:"center",justifyContent:"center",height:Xr[f],width:"100%",border:k&&!T?"1px solid var(--lucent-border-strong)":"1px solid transparent",borderRadius:"var(--lucent-radius-md)",background:T?"var(--lucent-accent-default)":A?"var(--lucent-accent-subtle)":g===b&&!v?"color-mix(in srgb, var(--lucent-accent-default) 14%, transparent)":"transparent",color:T?"var(--lucent-accent-fg)":v?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontSize:Jr[f],fontFamily:"var(--lucent-font-family-base)",fontWeight:k?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",cursor:v?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast)"},children:b},y)})})]})}function no({value:t,defaultValue:a,onChange:n,placeholder:i="Pick a date",disabled:r=!1,min:o,max:s,size:l="md",label:c,helperText:u,errorText:h,style:f}){const S=t!==void 0,[w,g]=d.useState(a),m=S?t:w,p=!!h,b=r,y=`lucent-datepicker-${Math.random().toString(36).slice(2,7)}`,x=new Date,[T,k]=d.useState((m??x).getFullYear()),[v,A]=d.useState((m??x).getMonth()),[C,j]=d.useState(!1),[q,D]=d.useState(!1),I=d.useRef(null),F=d.useRef(null),[R,O]=d.useState({top:0,left:0});d.useEffect(()=>{if(!C)return;const M=$=>{var W,L;!((W=I.current)!=null&&W.contains($.target))&&!((L=F.current)!=null&&L.contains($.target))&&j(!1)};return document.addEventListener("mousedown",M),()=>document.removeEventListener("mousedown",M)},[C]),d.useLayoutEffect(()=>{if(!C||!I.current)return;const M=I.current.getBoundingClientRect();O({top:M.bottom+4,left:M.left})},[C]);const B=M=>{S||g(M),n==null||n(M),j(!1)},N=()=>{v===0?(A(11),k(M=>M-1)):A(M=>M-1)},G=()=>{v===11?(A(0),k(M=>M+1)):A(M=>M+1)},P=b?"transparent":p?"var(--lucent-danger-default)":q?"var(--lucent-accent-border)":"var(--lucent-border-default)",z=q?`0 0 0 3px ${p?"var(--lucent-danger-subtle)":"var(--lucent-accent-subtle)"}`:"none";return e.jsxs("div",{ref:I,style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-1)",...f},children:[c&&e.jsx("label",{htmlFor:y,style:{fontSize:Le[l],fontWeight:"var(--lucent-font-weight-medium)",color:b?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)"},children:c}),e.jsxs("button",{type:"button",id:y,disabled:r,onClick:()=>!r&&j(M=>!M),onFocus:()=>D(!0),onBlur:()=>D(!1),"aria-haspopup":"dialog","aria-expanded":C,"aria-invalid":p,style:{display:"flex",alignItems:"center",gap:Kr[l],width:"100%",height:Ur[l],boxSizing:"border-box",padding:`0 ${Yr[l]}`,borderRadius:"var(--lucent-radius-lg)",border:`1px solid ${P}`,boxShadow:z,background:b?"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)":"var(--lucent-surface)",color:m?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",fontSize:_r[l],cursor:b?"not-allowed":"pointer",outline:"none",transition:["border-color var(--lucent-duration-fast) var(--lucent-easing-default)","box-shadow var(--lucent-duration-fast) var(--lucent-easing-default)"].join(", ")},children:[e.jsxs("svg",{width:"14",height:"14",viewBox:"0 0 14 14",fill:"none","aria-hidden":!0,style:{flexShrink:0},children:[e.jsx("rect",{x:"1",y:"2",width:"12",height:"11",rx:"2",stroke:"currentColor",strokeWidth:"1.3"}),e.jsx("path",{d:"M1 6h12",stroke:"currentColor",strokeWidth:"1.3"}),e.jsx("path",{d:"M4 1v2M10 1v2",stroke:"currentColor",strokeWidth:"1.3",strokeLinecap:"round"})]}),e.jsx("span",{style:{flex:1,textAlign:"left"},children:m?ge(m):i})]}),p&&e.jsx("span",{role:"alert",style:{fontSize:Le[l],color:"var(--lucent-danger-text)",fontFamily:"var(--lucent-font-family-base)"},children:h}),!p&&u&&e.jsx("span",{style:{fontSize:Le[l],color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)"},children:u}),C&&oe.createPortal(e.jsx("div",{ref:F,role:"dialog","aria-label":"Date picker",style:{position:"fixed",top:R.top,left:R.left,zIndex:1e3,background:"color-mix(in srgb, var(--lucent-surface-overlay) 85%, transparent)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 15%, var(--lucent-border-default))",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 0 24px -4px color-mix(in srgb, var(--lucent-accent-default) 12%, transparent), var(--lucent-shadow-md)",padding:Ot[l],minWidth:Je[l]},children:e.jsx(Ze,{year:T,month:v,...m!==void 0&&{selected:m},today:x,...o!==void 0&&{min:o},...s!==void 0&&{max:s},onSelect:B,onPrevMonth:N,onNextMonth:G,size:l})}),document.body)]})}const ao={id:"date-picker",name:"DatePicker",tier:"molecule",domain:"neutral",specVersion:"1.0",description:"A single-date picker with a calendar popover, month navigation, today highlight, min/max constraints, and controlled or uncontrolled modes.",designIntent:`DatePicker deliberately avoids native <input type="date"> to guarantee consistent cross-browser appearance that matches the Lucent token system. The trigger button shows the selected date in YYYY-MM-DD format (ISO-sortable, unambiguous locale-wise) or a placeholder. The calendar popover renders as a role="dialog" and closes on outside click. Today is outlined rather than filled so it doesn't compete visually with the selected date. Disabled dates (outside min/max) are grayed out and non-interactive. The Calendar primitive is exported separately so DateRangePicker can compose two calendars side by side.`,props:[{name:"value",type:"object",required:!1,description:"Controlled selected Date. When provided the component is fully controlled."},{name:"defaultValue",type:"object",required:!1,description:"Initial selected Date for uncontrolled usage."},{name:"onChange",type:"function",required:!1,description:"Called with the newly selected Date when the user picks a day."},{name:"placeholder",type:"string",required:!1,default:'"Pick a date"',description:"Trigger button text when no date is selected."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls trigger height and font size to match Input/Select.",enumValues:["sm","md","lg"]},{name:"label",type:"string",required:!1,description:"Label text rendered above the trigger, with size-aware font sizing matching Input/Select."},{name:"helperText",type:"string",required:!1,description:"Helper text rendered below the trigger. Hidden when errorText is present."},{name:"errorText",type:"string",required:!1,description:"Error text rendered below the trigger in danger color. Takes precedence over helperText."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables the trigger button and all interaction."},{name:"min",type:"object",required:!1,description:"Earliest selectable Date. Days before this are grayed out."},{name:"max",type:"object",required:!1,description:"Latest selectable Date. Days after this are grayed out."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer wrapper."}],usageExamples:[{title:"Uncontrolled",code:"<DatePicker onChange={(d) => console.log(d)} />"},{title:"Controlled with constraints",code:`const [date, setDate] = useState<Date>();
|
|
403
|
+
/>`}],compositionGraph:[{componentId:"chip",componentName:"Chip",role:"Selected value chips with dismiss button",required:!0},{componentId:"checkbox",componentName:"Checkbox",role:"Selection indicator in dropdown options",required:!0},{componentId:"text",componentName:"Text",role:"Option labels, empty state, and max hint",required:!0}],accessibility:{role:"combobox",ariaAttributes:["aria-autocomplete","aria-controls","aria-expanded","aria-multiselectable","aria-selected","aria-disabled"],keyboardInteractions:["↑↓ to navigate options","Enter to toggle selection","Escape to close","Backspace to remove last tag"],notes:'The input carries role="combobox" with aria-expanded and aria-controls pointing to the listbox. Each option has role="option" with aria-selected. Remove buttons on tags have descriptive aria-label.'}};function Ya(t,r){return new Date(t,r+1,0).getDate()}function Ka(t,r){return new Date(t,r,1).getDay()}function Ee(t,r){return t.getFullYear()===r.getFullYear()&&t.getMonth()===r.getMonth()&&t.getDate()===r.getDate()}function ze(t,r){return new Date(t.getFullYear(),t.getMonth(),t.getDate())<new Date(r.getFullYear(),r.getMonth(),r.getDate())}function Tt(t,r){return new Date(t.getFullYear(),t.getMonth(),t.getDate())>new Date(r.getFullYear(),r.getMonth(),r.getDate())}const Xa=["January","February","March","April","May","June","July","August","September","October","November","December"],Ja=["Su","Mo","Tu","We","Th","Fr","Sa"];function ve(t){return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}const Za={sm:"calc(var(--lucent-space-8) * 0.5 + 18px)",md:"calc(var(--lucent-space-10) * 0.5 + 22px)",lg:"calc(var(--lucent-space-12) * 0.5 + 26px)"},Qa={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-md)",lg:"var(--lucent-font-size-md)"},$e={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-md)"},eo={sm:"var(--lucent-space-3)",md:"var(--lucent-space-4)",lg:"var(--lucent-space-4)"},to={sm:"var(--lucent-space-2)",md:"calc((var(--lucent-space-2) + var(--lucent-space-3)) / 2)",lg:"var(--lucent-space-3)"},no={sm:28,md:32,lg:38},ro={sm:"var(--lucent-font-size-xs)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-md)"},ao={sm:"xs",md:"sm",lg:"md"},oo={sm:"xs",md:"xs",lg:"sm"},io={sm:24,md:28,lg:32},so={sm:14,md:16,lg:18},_t={sm:"var(--lucent-space-3)",md:"var(--lucent-space-4)",lg:"var(--lucent-space-5)"},Qe={sm:220,md:260,lg:300};function Ct({dir:t,onClick:r,disabled:n,size:i="md"}){const[a,o]=c.useState(!1),l=io[i],s=so[i];return e.jsx("button",{type:"button",onClick:r,disabled:n,onMouseEnter:()=>o(!0),onMouseLeave:()=>o(!1),"aria-label":t==="prev"?"Previous month":"Next month",style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:l,height:l,border:"none",borderRadius:"var(--lucent-radius-md)",background:a&&!n?"color-mix(in srgb, var(--lucent-text-primary) 5%, transparent)":"transparent",color:n?"var(--lucent-text-disabled)":"var(--lucent-text-secondary)",cursor:n?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast)"},children:e.jsx("svg",{width:s,height:s,viewBox:"0 0 16 16",fill:"none","aria-hidden":!0,children:e.jsx("path",{d:t==="prev"?"M10 12L6 8l4-4":"M6 4l4 4-4 4",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})})}function et({year:t,month:r,selected:n,today:i,min:a,max:o,onSelect:l,onPrevMonth:s,onNextMonth:d,highlightRange:p,onDayHover:h,size:f="md"}){const w=Ya(t,r),b=Ka(t,r),[g,m]=c.useState(null),u=[...Array(b).fill(null),...Array.from({length:w},(y,x)=>x+1)];for(;u.length%7!==0;)u.push(null);return e.jsxs("div",{children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"var(--lucent-space-3)"},children:[e.jsx(Ct,{dir:"prev",onClick:s,size:f}),e.jsxs(E.Text,{weight:"medium",size:ao[f],children:[Xa[r]," ",t]}),e.jsx(Ct,{dir:"next",onClick:d,size:f})]}),e.jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(7, 1fr)",gap:2,marginBottom:"var(--lucent-space-1)"},children:Ja.map(y=>e.jsx("div",{style:{textAlign:"center"},children:e.jsx(E.Text,{size:oo[f],color:"secondary",children:y})},y))}),e.jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(7, 1fr)",gap:2},children:u.map((y,x)=>{if(!y)return e.jsx("div",{},x);const T=new Date(t,r,y),S=n?Ee(T,n):!1,k=Ee(T,i),v=(a?ze(T,a):!1)||(o?Tt(T,o):!1);let A=!1;return p!=null&&p.start&&(p!=null&&p.end)&&(A=!ze(T,p.start)&&!Tt(T,p.end)),e.jsx("button",{type:"button",disabled:v,onClick:()=>!v&&l(T),onMouseEnter:()=>{m(y),h==null||h(T)},onMouseLeave:()=>{m(null),h==null||h(null)},"aria-label":ve(T),"aria-pressed":S,style:{display:"flex",alignItems:"center",justifyContent:"center",height:no[f],width:"100%",border:k&&!S?"1px solid var(--lucent-border-strong)":"1px solid transparent",borderRadius:"var(--lucent-radius-md)",background:S?"var(--lucent-accent-default)":A?"var(--lucent-accent-subtle)":g===y&&!v?"color-mix(in srgb, var(--lucent-accent-default) 14%, transparent)":"transparent",color:S?"var(--lucent-accent-fg)":v?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontSize:ro[f],fontFamily:"var(--lucent-font-family-base)",fontWeight:k?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",cursor:v?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast)"},children:y},x)})})]})}function lo({value:t,defaultValue:r,onChange:n,placeholder:i="Pick a date",disabled:a=!1,min:o,max:l,size:s="md",label:d,helperText:p,errorText:h,style:f}){const w=t!==void 0,[b,g]=c.useState(r),m=w?t:b,u=!!h,y=a,x=`lucent-datepicker-${Math.random().toString(36).slice(2,7)}`,T=new Date,[S,k]=c.useState((m??T).getFullYear()),[v,A]=c.useState((m??T).getMonth()),[C,M]=c.useState(!1),[q,B]=c.useState(!1),I=c.useRef(null),L=c.useRef(null),[D,N]=c.useState({top:0,left:0});c.useEffect(()=>{if(!C)return;const j=$=>{var V,F;!((V=I.current)!=null&&V.contains($.target))&&!((F=L.current)!=null&&F.contains($.target))&&M(!1)};return document.addEventListener("mousedown",j),()=>document.removeEventListener("mousedown",j)},[C]),c.useLayoutEffect(()=>{if(!C||!I.current)return;const j=I.current.getBoundingClientRect();N({top:j.bottom+4,left:j.left})},[C]);const R=j=>{w||g(j),n==null||n(j),M(!1)},W=()=>{v===0?(A(11),k(j=>j-1)):A(j=>j-1)},G=()=>{v===11?(A(0),k(j=>j+1)):A(j=>j+1)},P=y?"transparent":u?"var(--lucent-danger-default)":q?"var(--lucent-accent-border)":"var(--lucent-border-default)",z=q?`0 0 0 3px ${u?"var(--lucent-danger-subtle)":"var(--lucent-accent-subtle)"}`:"none";return e.jsxs("div",{ref:I,style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-1)",...f},children:[d&&e.jsx("label",{htmlFor:x,style:{fontSize:$e[s],fontWeight:"var(--lucent-font-weight-medium)",color:y?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)"},children:d}),e.jsxs("button",{type:"button",id:x,disabled:a,onClick:()=>!a&&M(j=>!j),onFocus:()=>B(!0),onBlur:()=>B(!1),"aria-haspopup":"dialog","aria-expanded":C,"aria-invalid":u,style:{display:"flex",alignItems:"center",gap:to[s],width:"100%",height:Za[s],boxSizing:"border-box",padding:`0 ${eo[s]}`,borderRadius:"var(--lucent-radius-lg)",border:`1px solid ${P}`,boxShadow:z,background:y?"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)":"var(--lucent-surface)",color:m?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",fontSize:Qa[s],cursor:y?"not-allowed":"pointer",outline:"none",transition:["border-color var(--lucent-duration-fast) var(--lucent-easing-default)","box-shadow var(--lucent-duration-fast) var(--lucent-easing-default)"].join(", ")},children:[e.jsxs("svg",{width:"14",height:"14",viewBox:"0 0 14 14",fill:"none","aria-hidden":!0,style:{flexShrink:0},children:[e.jsx("rect",{x:"1",y:"2",width:"12",height:"11",rx:"2",stroke:"currentColor",strokeWidth:"1.3"}),e.jsx("path",{d:"M1 6h12",stroke:"currentColor",strokeWidth:"1.3"}),e.jsx("path",{d:"M4 1v2M10 1v2",stroke:"currentColor",strokeWidth:"1.3",strokeLinecap:"round"})]}),e.jsx("span",{style:{flex:1,textAlign:"left"},children:m?ve(m):i})]}),u&&e.jsx("span",{role:"alert",style:{fontSize:$e[s],color:"var(--lucent-danger-text)",fontFamily:"var(--lucent-font-family-base)"},children:h}),!u&&p&&e.jsx("span",{style:{fontSize:$e[s],color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)"},children:p}),C&&Q.createPortal(e.jsx("div",{ref:L,role:"dialog","aria-label":"Date picker",style:{position:"fixed",top:D.top,left:D.left,zIndex:1e3,background:"color-mix(in srgb, var(--lucent-surface-overlay) 85%, transparent)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 15%, var(--lucent-border-default))",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 0 24px -4px color-mix(in srgb, var(--lucent-accent-default) 12%, transparent), var(--lucent-shadow-md)",padding:_t[s],minWidth:Qe[s]},children:e.jsx(et,{year:S,month:v,...m!==void 0&&{selected:m},today:T,...o!==void 0&&{min:o},...l!==void 0&&{max:l},onSelect:R,onPrevMonth:W,onNextMonth:G,size:s})}),document.body)]})}const co={id:"date-picker",name:"DatePicker",tier:"molecule",domain:"neutral",specVersion:"1.0",description:"A single-date picker with a calendar popover, month navigation, today highlight, min/max constraints, and controlled or uncontrolled modes.",designIntent:`DatePicker deliberately avoids native <input type="date"> to guarantee consistent cross-browser appearance that matches the Lucent token system. The trigger button shows the selected date in YYYY-MM-DD format (ISO-sortable, unambiguous locale-wise) or a placeholder. The calendar popover renders as a role="dialog" and closes on outside click. Today is outlined rather than filled so it doesn't compete visually with the selected date. Disabled dates (outside min/max) are grayed out and non-interactive. The Calendar primitive is exported separately so DateRangePicker can compose two calendars side by side.`,props:[{name:"value",type:"object",required:!1,description:"Controlled selected Date. When provided the component is fully controlled."},{name:"defaultValue",type:"object",required:!1,description:"Initial selected Date for uncontrolled usage."},{name:"onChange",type:"function",required:!1,description:"Called with the newly selected Date when the user picks a day."},{name:"placeholder",type:"string",required:!1,default:'"Pick a date"',description:"Trigger button text when no date is selected."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls trigger height and font size to match Input/Select.",enumValues:["sm","md","lg"]},{name:"label",type:"string",required:!1,description:"Label text rendered above the trigger, with size-aware font sizing matching Input/Select."},{name:"helperText",type:"string",required:!1,description:"Helper text rendered below the trigger. Hidden when errorText is present."},{name:"errorText",type:"string",required:!1,description:"Error text rendered below the trigger in danger color. Takes precedence over helperText."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables the trigger button and all interaction."},{name:"min",type:"object",required:!1,description:"Earliest selectable Date. Days before this are grayed out."},{name:"max",type:"object",required:!1,description:"Latest selectable Date. Days after this are grayed out."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer wrapper."}],usageExamples:[{title:"Uncontrolled",code:"<DatePicker onChange={(d) => console.log(d)} />"},{title:"Controlled with constraints",code:`const [date, setDate] = useState<Date>();
|
|
399
404
|
<DatePicker
|
|
400
405
|
value={date}
|
|
401
406
|
onChange={setDate}
|
|
402
407
|
min={new Date()}
|
|
403
408
|
placeholder="Select a future date"
|
|
404
|
-
/>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Month/year header and weekday labels",required:!0}],accessibility:{role:"dialog",ariaAttributes:["aria-haspopup","aria-expanded","aria-invalid","aria-label","aria-pressed"],keyboardInteractions:["Enter/Space to open calendar","Click day to select","Escape closes popover (click outside)"],notes:'The calendar popover is role="dialog". Each day button has aria-label with the full date and aria-pressed for selected state. Full arrow-key navigation within the calendar grid is a planned enhancement.'}},
|
|
405
|
-
<DateRangePicker value={range} onChange={setRange} min={new Date()} />`}],compositionGraph:[{componentId:"date-picker",componentName:"DatePicker",role:"Calendar primitive (two instances, left and right)",required:!0},{componentId:"text",componentName:"Text",role:"Mid-selection hint and calendar headers",required:!0}],accessibility:{role:"dialog",ariaAttributes:["aria-haspopup","aria-expanded","aria-invalid","aria-label","aria-pressed"],keyboardInteractions:["Enter/Space to open","Click first day to set start","Click second day to set end","Escape/click outside to cancel"],notes:'Inherits Calendar accessibility from DatePicker. The two-step selection flow is reinforced with a visible "Now pick the end date" hint.'}};function
|
|
409
|
+
/>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Month/year header and weekday labels",required:!0}],accessibility:{role:"dialog",ariaAttributes:["aria-haspopup","aria-expanded","aria-invalid","aria-label","aria-pressed"],keyboardInteractions:["Enter/Space to open calendar","Click day to select","Escape closes popover (click outside)"],notes:'The calendar popover is role="dialog". Each day button has aria-label with the full date and aria-pressed for selected state. Full arrow-key navigation within the calendar grid is a planned enhancement.'}},uo={sm:"calc(var(--lucent-space-8) * 0.5 + 18px)",md:"calc(var(--lucent-space-10) * 0.5 + 22px)",lg:"calc(var(--lucent-space-12) * 0.5 + 26px)"},po={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-md)",lg:"var(--lucent-font-size-md)"},ho={sm:"var(--lucent-space-3)",md:"var(--lucent-space-4)",lg:"var(--lucent-space-4)"},fo={sm:"var(--lucent-space-2)",md:"calc((var(--lucent-space-2) + var(--lucent-space-3)) / 2)",lg:"var(--lucent-space-3)"},Ne={sm:"var(--lucent-font-size-sm)",md:"var(--lucent-font-size-sm)",lg:"var(--lucent-font-size-md)"};function mo(t,r){return t?Ee(t.start,t.end)?ve(t.start):`${ve(t.start)} → ${ve(t.end)}`:r}function Yt({value:t,defaultValue:r,onChange:n,placeholder:i="Pick a date range",disabled:a=!1,min:o,max:l,size:s="md",label:d,helperText:p,errorText:h,trigger:f,style:w}){const b=t!==void 0,[g,m]=c.useState(r),u=b?t:g,y=!!h,x=a,T=`lucent-daterangepicker-${Math.random().toString(36).slice(2,7)}`,[S,k]=c.useState(null),[v,A]=c.useState(null),C=new Date,[M,q]=c.useState(((u==null?void 0:u.start)??C).getFullYear()),[B,I]=c.useState(((u==null?void 0:u.start)??C).getMonth()),L=B===11?0:B+1,D=B===11?M+1:M,[N,R]=c.useState(!1),[W,G]=c.useState(!1),P=c.useRef(null),z=c.useRef(null),[j,$]=c.useState({top:0,left:0});c.useEffect(()=>{if(!N)return;const H=se=>{var we,he;!((we=P.current)!=null&&we.contains(se.target))&&!((he=z.current)!=null&&he.contains(se.target))&&(R(!1),k(null))};return document.addEventListener("mousedown",H),()=>document.removeEventListener("mousedown",H)},[N]),c.useLayoutEffect(()=>{if(!N||!P.current)return;const H=P.current.getBoundingClientRect();$({top:H.bottom+4,left:H.left})},[N]);const V=H=>{if(!S)k(H);else{const[se,we]=ze(H,S)||Ee(H,S)?[H,S]:[S,H],he={start:se,end:we};b||m(he),n==null||n(he),k(null),R(!1)}},F=()=>{B===0?(I(11),q(H=>H-1)):I(H=>H-1)},O=()=>{B===11?(I(0),q(H=>H+1)):I(H=>H+1)};let U;if(S&&v){const[H,se]=ze(v,S)?[v,S]:[S,v];U={start:H,end:se}}else S?U={start:S,end:S}:u&&(U={start:u.start,end:u.end});const ee=x?"transparent":y?"var(--lucent-danger-default)":W?"var(--lucent-accent-border)":"var(--lucent-border-default)",J=W?`0 0 0 3px ${y?"var(--lucent-danger-subtle)":"var(--lucent-accent-subtle)"}`:"none";return e.jsxs("div",{ref:P,style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-1)",...w},children:[d&&e.jsx("label",{htmlFor:T,style:{fontSize:Ne[s],fontWeight:"var(--lucent-font-weight-medium)",color:x?"var(--lucent-text-disabled)":"var(--lucent-text-primary)",fontFamily:"var(--lucent-font-family-base)"},children:d}),f?e.jsx("span",{onClick:()=>!a&&R(H=>!H),"aria-haspopup":"dialog","aria-expanded":N,style:{display:"inline-flex",cursor:a?"not-allowed":"pointer"},children:f}):e.jsxs("button",{type:"button",id:T,disabled:a,onClick:()=>!a&&R(H=>!H),onFocus:()=>G(!0),onBlur:()=>G(!1),"aria-haspopup":"dialog","aria-expanded":N,"aria-invalid":y,style:{display:"flex",alignItems:"center",gap:fo[s],width:"100%",height:uo[s],boxSizing:"border-box",padding:`0 ${ho[s]}`,borderRadius:"var(--lucent-radius-lg)",border:`1px solid ${ee}`,boxShadow:J,background:x?"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)":"var(--lucent-surface)",color:u?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)",fontSize:po[s],cursor:x?"not-allowed":"pointer",outline:"none",transition:["border-color var(--lucent-duration-fast) var(--lucent-easing-default)","box-shadow var(--lucent-duration-fast) var(--lucent-easing-default)"].join(", ")},children:[e.jsxs("svg",{width:"14",height:"14",viewBox:"0 0 14 14",fill:"none","aria-hidden":!0,style:{flexShrink:0},children:[e.jsx("rect",{x:"1",y:"2",width:"12",height:"11",rx:"2",stroke:"currentColor",strokeWidth:"1.3"}),e.jsx("path",{d:"M1 6h12",stroke:"currentColor",strokeWidth:"1.3"}),e.jsx("path",{d:"M4 1v2M10 1v2",stroke:"currentColor",strokeWidth:"1.3",strokeLinecap:"round"})]}),e.jsx("span",{style:{flex:1,textAlign:"left"},children:mo(u,i)})]}),y&&e.jsx("span",{role:"alert",style:{fontSize:Ne[s],color:"var(--lucent-danger-text)",fontFamily:"var(--lucent-font-family-base)"},children:h}),!y&&p&&e.jsx("span",{style:{fontSize:Ne[s],color:"var(--lucent-text-secondary)",fontFamily:"var(--lucent-font-family-base)"},children:p}),N&&Q.createPortal(e.jsxs("div",{ref:z,role:"dialog","aria-label":"Date range picker",style:{position:"fixed",top:j.top,left:j.left,zIndex:1e3,background:"color-mix(in srgb, var(--lucent-surface-overlay) 85%, transparent)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 15%, var(--lucent-border-default))",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 0 24px -4px color-mix(in srgb, var(--lucent-accent-default) 12%, transparent), var(--lucent-shadow-md)",padding:_t[s],display:"flex",gap:"var(--lucent-space-6)"},children:[e.jsx("div",{style:{minWidth:Qe[s]},children:e.jsx(et,{year:M,month:B,...(u==null?void 0:u.start)!==void 0&&{selected:u.start},today:C,...o!==void 0&&{min:o},...l!==void 0&&{max:l},onSelect:V,onPrevMonth:F,onNextMonth:O,...U!==void 0&&{highlightRange:U},...S&&{onDayHover:A},size:s})}),e.jsx("div",{style:{width:1,background:"var(--lucent-border-subtle)",flexShrink:0}}),e.jsx("div",{style:{minWidth:Qe[s]},children:e.jsx(et,{year:D,month:L,...(u==null?void 0:u.end)!==void 0&&{selected:u.end},today:C,...o!==void 0&&{min:o},...l!==void 0&&{max:l},onSelect:V,onPrevMonth:F,onNextMonth:O,...U!==void 0&&{highlightRange:U},...S&&{onDayHover:A},size:s})})]}),document.body),S&&N&&e.jsx("div",{style:{position:"absolute",top:"calc(100% + var(--lucent-space-1))",left:0,zIndex:1001,pointerEvents:"none"}}),S&&N&&e.jsx("div",{style:{position:"absolute",bottom:-24,left:0},children:e.jsx(E.Text,{size:"xs",color:"secondary",children:"Now pick the end date"})})]})}const go={id:"date-range-picker",name:"DateRangePicker",tier:"molecule",domain:"neutral",specVersion:"1.0",description:"A two-calendar date range picker. First click sets the start date, second click sets the end; the selected interval is highlighted across both calendars.",designIntent:'DateRangePicker composes two Calendar primitives from DatePicker side by side, advancing in lockstep (left = current month, right = next month). Selection is a two-click flow: first click anchors the start, a hint appears ("Now pick the end date"), second click resolves the range with automatic start/end ordering so users can click in either direction. The highlight range (accent-subtle background) spans both calendars to give clear visual feedback for the selected interval. Navigation (prev/next month) advances both calendars together to maintain the one-month-apart constraint.',props:[{name:"value",type:"object",required:!1,description:"Controlled DateRange { start: Date; end: Date }. When provided the component is fully controlled."},{name:"defaultValue",type:"object",required:!1,description:"Initial DateRange for uncontrolled usage."},{name:"onChange",type:"function",required:!1,description:"Called with the completed DateRange after the user picks both start and end."},{name:"placeholder",type:"string",required:!1,default:'"Pick a date range"',description:"Trigger button text when no range is selected."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls trigger height and font size to match Input/Select.",enumValues:["sm","md","lg"]},{name:"label",type:"string",required:!1,description:"Label text rendered above the trigger, with size-aware font sizing matching Input/Select."},{name:"helperText",type:"string",required:!1,description:"Helper text rendered below the trigger. Hidden when errorText is present."},{name:"errorText",type:"string",required:!1,description:"Error text rendered below the trigger in danger color. Takes precedence over helperText."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables the trigger button and all interaction."},{name:"min",type:"object",required:!1,description:"Earliest selectable Date."},{name:"max",type:"object",required:!1,description:"Latest selectable Date."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer wrapper."}],usageExamples:[{title:"Uncontrolled",code:"<DateRangePicker onChange={({ start, end }) => console.log(start, end)} />"},{title:"Controlled",code:`const [range, setRange] = useState<DateRange>();
|
|
410
|
+
<DateRangePicker value={range} onChange={setRange} min={new Date()} />`}],compositionGraph:[{componentId:"date-picker",componentName:"DatePicker",role:"Calendar primitive (two instances, left and right)",required:!0},{componentId:"text",componentName:"Text",role:"Mid-selection hint and calendar headers",required:!0}],accessibility:{role:"dialog",ariaAttributes:["aria-haspopup","aria-expanded","aria-invalid","aria-label","aria-pressed"],keyboardInteractions:["Enter/Space to open","Click first day to set start","Click second day to set end","Escape/click outside to cancel"],notes:'Inherits Calendar accessibility from DatePicker. The two-step selection flow is reinforced with a visible "Now pick the end date" hint.'}};function tt(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}function vo(){return Math.random().toString(36).slice(2)}function bo({item:t,onRemove:r}){const[n,i]=c.useState(!1),a=t.progress,o=!!t.error;return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",padding:"var(--lucent-space-2) var(--lucent-space-3)",borderRadius:"var(--lucent-radius-md)",border:`1px solid ${o?"var(--lucent-danger-default)":"var(--lucent-border-default)"}`,background:"var(--lucent-surface)"},children:[e.jsxs("svg",{width:"20",height:"20",viewBox:"0 0 20 20",fill:"none","aria-hidden":!0,style:{flexShrink:0,color:"var(--lucent-text-secondary)"},children:[e.jsx("path",{d:"M5 2h7l4 4v12a1 1 0 01-1 1H5a1 1 0 01-1-1V3a1 1 0 011-1z",stroke:"currentColor",strokeWidth:"1.3"}),e.jsx("path",{d:"M12 2v4h4",stroke:"currentColor",strokeWidth:"1.3"})]}),e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx(E.Text,{size:"sm",truncate:!0,children:t.file.name}),o?e.jsx(E.Text,{size:"xs",color:"danger",children:t.error}):e.jsx(E.Text,{size:"xs",color:"secondary",children:tt(t.file.size)}),a!==void 0&&!o&&e.jsx("div",{style:{marginTop:4,height:3,borderRadius:"var(--lucent-radius-full)",background:"color-mix(in srgb, var(--lucent-text-primary) 8%, transparent)",overflow:"hidden"},children:e.jsx("div",{style:{height:"100%",width:`${a}%`,borderRadius:"var(--lucent-radius-full)",background:a===100?"var(--lucent-success-default)":"var(--lucent-accent-default)",transition:"width 200ms var(--lucent-easing-default)"}})})]}),e.jsx("button",{type:"button",onClick:()=>r(t.id),onMouseEnter:()=>i(!0),onMouseLeave:()=>i(!1),"aria-label":`Remove ${t.file.name}`,style:{flexShrink:0,display:"inline-flex",alignItems:"center",justifyContent:"center",width:24,height:24,border:"none",borderRadius:"var(--lucent-radius-md)",background:n?"color-mix(in srgb, var(--lucent-text-primary) 6%, transparent)":"transparent",color:"var(--lucent-text-secondary)",cursor:"pointer",transition:"background var(--lucent-duration-fast)"},children:e.jsx("svg",{width:"12",height:"12",viewBox:"0 0 12 12",fill:"none","aria-hidden":!0,children:e.jsx("path",{d:"M2 2l8 8M10 2l-8 8",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})})})]})}function yo({accept:t,multiple:r=!1,maxSize:n,value:i,onChange:a,onError:o,disabled:l=!1,style:s}){const d=i!==void 0,[p,h]=c.useState([]),f=d?i:p,[w,b]=c.useState(!1),[g,m]=c.useState(!1),u=c.useRef(null),y=c.useCallback(k=>{if(!k||l)return;const v=[];for(const C of Array.from(k)){if(n&&C.size>n){o==null||o(`"${C.name}" exceeds the ${tt(n)} limit.`);continue}if(!r&&f.length+v.length>=1)break;v.push({id:vo(),file:C})}if(v.length===0)return;const A=r?[...f,...v]:v;d||h(A),a==null||a(A)},[l,f,d,n,r,a,o]),x=k=>{const v=f.filter(A=>A.id!==k);d||h(v),a==null||a(v)},T=k=>{k.preventDefault(),b(!1),y(k.dataTransfer.files)},S=k=>{y(k.target.files),k.target.value=""};return e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-3)",...s},children:[e.jsxs("div",{role:"button",tabIndex:l?-1:0,"aria-label":"Upload files","aria-disabled":l,onClick:()=>{var k;return!l&&((k=u.current)==null?void 0:k.click())},onKeyDown:k=>{var v;(k.key==="Enter"||k.key===" ")&&(k.preventDefault(),(v=u.current)==null||v.click())},onFocus:()=>m(!0),onBlur:()=>m(!1),onDragOver:k=>{k.preventDefault(),l||b(!0)},onDragLeave:()=>b(!1),onDrop:T,style:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:"var(--lucent-space-2)",padding:"var(--lucent-space-8) var(--lucent-space-6)",borderRadius:"var(--lucent-radius-lg)",border:`2px dashed ${l?"color-mix(in srgb, var(--lucent-text-primary) 15%, transparent)":w||g?"var(--lucent-accent-default)":"color-mix(in srgb, var(--lucent-text-primary) 20%, transparent)"}`,background:w?"var(--lucent-accent-subtle)":"color-mix(in srgb, var(--lucent-text-primary) 4%, transparent)",cursor:l?"not-allowed":"pointer",transition:"border-color var(--lucent-duration-fast), background var(--lucent-duration-fast)",outline:"none"},children:[e.jsxs("svg",{width:"32",height:"32",viewBox:"0 0 32 32",fill:"none","aria-hidden":!0,style:{color:l?"var(--lucent-text-disabled)":w?"var(--lucent-accent-default)":"var(--lucent-text-secondary)"},children:[e.jsx("path",{d:"M16 20V10M16 10l-4 4M16 10l4 4",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round",strokeLinejoin:"round"}),e.jsx("path",{d:"M8 24h16",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round"})]}),e.jsxs("div",{style:{textAlign:"center",width:"100%"},children:[e.jsx(E.Text,{color:l?"disabled":"primary",weight:"medium",align:"center",children:w?"Drop to upload":"Drop files here or click to browse"}),(t||n)&&e.jsx(E.Text,{size:"xs",color:"secondary",align:"center",children:[t&&`Accepted: ${t}`,n&&`Max size: ${tt(n)}`].filter(Boolean).join(" · ")})]}),e.jsx("input",{ref:u,type:"file",accept:t,multiple:r,disabled:l,onChange:S,style:{display:"none"},tabIndex:-1})]}),f.length>0&&e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"var(--lucent-space-2)"},children:f.map(k=>e.jsx(bo,{item:k,onRemove:x},k.id))})]})}const xo={id:"file-upload",name:"FileUpload",tier:"molecule",domain:"neutral",specVersion:"1.0",description:"A drag-and-drop file upload zone with a file list, per-file progress bars, error display, and size/type validation.",designIntent:'FileUpload separates concerns between the drop zone (entry point) and the file list (status display). The drop zone uses a dashed border and an upload arrow icon to communicate droppability without words. Its idle background and border are translucent color-mix overlays on top of var(--lucent-text-primary) (not hard-coded surface tokens), so the drop zone always reads as a visible step against whatever parent background it sits on — in any theme, in any palette. Progress is modelled as a field on UploadFile rather than as a callback so the parent controls upload logic — this component is purely presentational for the upload state. The progress bar turns success-green at 100% to give clear completion feedback. Errors are shown inline on each file row (not as a toast) so the user knows exactly which file failed and why. The hidden <input type="file"> is triggered programmatically on click/keyboard so the drop zone can have a fully custom appearance.',props:[{name:"accept",type:"string",required:!1,description:'Accepted MIME types or extensions passed to the file input, e.g. "image/*,.pdf".'},{name:"multiple",type:"boolean",required:!1,default:"false",description:"Allow selecting multiple files at once."},{name:"maxSize",type:"number",required:!1,description:"Maximum file size in bytes. Files exceeding this trigger onError and are not added."},{name:"value",type:"array",required:!1,description:"Controlled array of UploadFile objects. Each has id, file (File), optional progress (0–100), and optional error string."},{name:"onChange",type:"function",required:!1,description:"Called with the updated UploadFile array after files are added or removed."},{name:"onError",type:"function",required:!1,description:"Called with an error message string when a file fails validation (e.g. exceeds maxSize)."},{name:"disabled",type:"boolean",required:!1,default:"false",description:"Disables the drop zone and all file interaction."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer wrapper."}],usageExamples:[{title:"Uncontrolled multi-file upload",code:`<FileUpload
|
|
406
411
|
multiple
|
|
407
412
|
accept="image/*,.pdf"
|
|
408
413
|
maxSize={5 * 1024 * 1024}
|
|
@@ -421,7 +426,7 @@ const handleChange = async (updated: UploadFile[]) => {
|
|
|
421
426
|
}
|
|
422
427
|
};
|
|
423
428
|
|
|
424
|
-
<FileUpload value={files} onChange={handleChange} multiple />`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Drop zone label, file name, file size, and error messages",required:!0}],accessibility:{role:"button",ariaAttributes:["aria-label","aria-disabled"],keyboardInteractions:["Enter/Space to open file picker","Tab to focus drop zone"],notes:'The drop zone has role="button" with tabIndex=0 and responds to Enter/Space. Remove buttons on file rows have aria-label including the filename.'}},
|
|
429
|
+
<FileUpload value={files} onChange={handleChange} multiple />`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Drop zone label, file name, file size, and error messages",required:!0}],accessibility:{role:"button",ariaAttributes:["aria-label","aria-disabled"],keyboardInteractions:["Enter/Space to open file picker","Tab to focus drop zone"],notes:'The drop zone has role="button" with tabIndex=0 and responds to Enter/Space. Remove buttons on file rows have aria-label including the filename.'}},wo={default:"var(--lucent-text-disabled)",success:"var(--lucent-success-default)",warning:"var(--lucent-warning-default)",danger:"var(--lucent-danger-default)",info:"var(--lucent-info-default)"};function ko({status:t}){return t==="success"?e.jsx("svg",{width:"10",height:"10",viewBox:"0 0 10 10",fill:"none","aria-hidden":!0,children:e.jsx("path",{d:"M2.5 5.2l1.8 1.8L7.5 3.5",stroke:"#fff",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})}):t==="danger"?e.jsx("svg",{width:"10",height:"10",viewBox:"0 0 10 10",fill:"none","aria-hidden":!0,children:e.jsx("path",{d:"M3 3l4 4M7 3L3 7",stroke:"#fff",strokeWidth:"1.5",strokeLinecap:"round"})}):t==="warning"?e.jsx("svg",{width:"10",height:"10",viewBox:"0 0 10 10",fill:"none","aria-hidden":!0,children:e.jsx("path",{d:"M5 2.5v3M5 7.5v.01",stroke:"#fff",strokeWidth:"1.5",strokeLinecap:"round"})}):t==="info"?e.jsxs("svg",{width:"10",height:"10",viewBox:"0 0 10 10",fill:"none","aria-hidden":!0,children:[e.jsx("circle",{cx:"5",cy:"3",r:"0.75",fill:"#fff"}),e.jsx("path",{d:"M5 5v2.5",stroke:"#fff",strokeWidth:"1.5",strokeLinecap:"round"})]}):e.jsx("svg",{width:"6",height:"6",viewBox:"0 0 6 6","aria-hidden":!0,children:e.jsx("circle",{cx:"3",cy:"3",r:"3",fill:"#fff"})})}function So({items:t,style:r}){return e.jsx("ol",{style:{listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",...r},children:t.map((n,i)=>{const a=n.status??"default",o=wo[a],l=i===t.length-1;return e.jsxs("li",{style:{display:"flex",gap:"var(--lucent-space-3)",position:"relative"},children:[e.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",flexShrink:0,width:20},children:[e.jsx("div",{style:{width:20,height:20,borderRadius:"var(--lucent-radius-full)",background:o,display:"flex",alignItems:"center",justifyContent:"center",flexShrink:0,zIndex:1},children:n.icon??e.jsx(ko,{status:a})}),!l&&e.jsx("div",{style:{flex:1,width:1.5,background:"var(--lucent-border-subtle)",minHeight:"var(--lucent-space-3)"}})]}),e.jsxs("div",{style:{flex:1,paddingBottom:l?0:"var(--lucent-space-5)",paddingTop:1,minWidth:0},children:[e.jsxs("div",{style:{display:"flex",alignItems:"baseline",gap:"var(--lucent-space-2)",flexWrap:"wrap"},children:[e.jsx(E.Text,{weight:"medium",size:"sm",children:n.title}),n.date&&e.jsx(E.Text,{size:"xs",color:"secondary",children:n.date})]}),n.description&&e.jsx("div",{style:{marginTop:"var(--lucent-space-1)"},children:e.jsx(E.Text,{size:"sm",color:"secondary",children:n.description})}),n.content&&e.jsx("div",{style:{marginTop:"var(--lucent-space-3)"},children:n.content})]})]},n.id)})})}const To={id:"timeline",name:"Timeline",tier:"molecule",domain:"neutral",specVersion:"1.0",description:"A vertical activity feed with filled status dots, inline timestamps, optional nested content blocks, and custom icons.",designIntent:"Timeline renders as a semantic <ol> with each event as a <li>, preserving document order for assistive technologies. Dots are compact (20px) and filled with the status color, with white iconography for high contrast — this mirrors modern activity-feed patterns. Title and date sit inline (date follows title) to keep the eye on a single reading line, rather than splitting attention across the row. Each item supports an optional content slot for embedded blocks — comment cards, review notes, nested detail panels — placed below the title/description. The connector line is thin (1.5px) and omitted on the last item. Status colors follow the same semantic token set as Alert and Badge so danger/success/warning/info carry consistent meaning across the design system. Custom icons slot in via the icon prop to handle domain-specific event types (e.g. a deploy icon, a payment icon).",props:[{name:"items",type:"array",required:!0,description:"Array of TimelineItem objects. Each has id, title, optional description, optional content (ReactNode for embedded blocks), optional date string, optional status, and optional icon."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer <ol> wrapper."}],usageExamples:[{title:"Basic event log",code:`<Timeline
|
|
425
430
|
items={[
|
|
426
431
|
{ id: '1', title: 'Order placed', date: 'Mar 1, 2026', status: 'success' },
|
|
427
432
|
{ id: '2', title: 'Payment processed', date: 'Mar 1, 2026', status: 'success' },
|
|
@@ -446,7 +451,7 @@ const handleChange = async (updated: UploadFile[]) => {
|
|
|
446
451
|
{ id: '3', title: 'Resubmitted', date: 'Mar 28', status: 'info' },
|
|
447
452
|
{ id: '4', title: 'Approved', date: 'Mar 29', status: 'success' },
|
|
448
453
|
]}
|
|
449
|
-
/>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Event title, description, and date label",required:!0},{componentId:"card",componentName:"Card",role:"Container for nested content blocks in the content slot",required:!1}],accessibility:{role:"list",ariaAttributes:[],keyboardInteractions:["Standard document flow — no interactive elements unless custom icons include them"],notes:"Timeline is a semantic <ol> with <li> items. It is non-interactive by default. If items contain interactive elements (links, buttons), those elements carry their own keyboard behaviour."}},Ht=d.createContext(null);function yo(){const t=d.useContext(Ht);if(!t)throw new Error("useToast must be used inside <ToastProvider>");return t}const xo=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"8",cy:"8",r:"6.5",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M8 5.5V8.5M8 10.5V11",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),wo=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"8",cy:"8",r:"6.5",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M5 8L7 10L11 6",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})]}),ko=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("path",{d:"M8 2L14.5 13H1.5L8 2Z",stroke:"currentColor",strokeWidth:"1.5",strokeLinejoin:"round"}),e.jsx("path",{d:"M8 6V9M8 11V11.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),So=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"8",cy:"8",r:"6.5",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M5.5 5.5L10.5 10.5M10.5 5.5L5.5 10.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),To=()=>e.jsx("svg",{width:"14",height:"14",viewBox:"0 0 14 14",fill:"none","aria-hidden":"true",children:e.jsx("path",{d:"M3 3L11 11M11 3L3 11",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})}),Co={info:e.jsx(xo,{}),success:e.jsx(wo,{}),warning:e.jsx(ko,{}),danger:e.jsx(So,{})},Io={default:{border:"var(--lucent-border-default)",iconColor:"var(--lucent-text-secondary)"},info:{border:"var(--lucent-info-default)",iconColor:"var(--lucent-info-text)"},success:{border:"var(--lucent-success-default)",iconColor:"var(--lucent-success-text)"},warning:{border:"var(--lucent-warning-default)",iconColor:"var(--lucent-warning-text)"},danger:{border:"var(--lucent-danger-default)",iconColor:"var(--lucent-danger-text)"}};let jo=0;function Mo(){return`lucent-toast-${++jo}`}function Gt(t){return t.startsWith("top")}const Eo=200,zo=8,Ao=.04,qo=.2,Do=3,Bo=356;function kt({entry:t,onDismiss:a,hideContent:n,fixedHeight:i}){const{id:r,title:o,description:s,variant:l,action:c,icon:u}=t,h=Io[l],f=u??(l!=="default"?Co[l]:null),S=(c==null?void 0:c.style)??"button";return e.jsx("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",padding:"var(--lucent-space-3) var(--lucent-space-4)",background:"var(--lucent-surface-raised)",border:`1px solid ${h.border}`,borderRadius:"var(--lucent-radius-lg)",boxShadow:n?"none":"var(--lucent-shadow-lg)",boxSizing:"border-box",width:Bo,maxWidth:"calc(100vw - var(--lucent-space-8))",fontFamily:"var(--lucent-font-family-base)",transition:"height var(--lucent-duration-base) var(--lucent-easing-emphasized), box-shadow var(--lucent-duration-base) var(--lucent-easing-default)",...i!==void 0&&{height:i,overflow:"hidden"}},children:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",width:"100%",opacity:n?0:1,transition:"opacity var(--lucent-duration-base) var(--lucent-easing-default)"},children:[f&&e.jsx("span",{style:{flexShrink:0,color:h.iconColor,display:"flex",alignItems:"center"},children:f}),e.jsxs("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:"var(--lucent-space-1)"},children:[e.jsx(E.Text,{as:"span",size:"sm",weight:"semibold",color:"primary",lineHeight:"tight",children:o}),s&&e.jsx(E.Text,{as:"span",size:"sm",color:"secondary",lineHeight:"base",style:{whiteSpace:"pre-line"},children:s})]}),c&&S==="button"&&e.jsx(Po,{label:c.label,onClick:()=>{c.onClick(),a(r)}}),c&&S==="link"&&e.jsx(Ro,{label:c.label,iconColor:h.iconColor,onClick:()=>{c.onClick(),a(r)}}),e.jsx("button",{type:"button","aria-label":"Dismiss",onClick:()=>a(r),style:{flexShrink:0,display:"flex",alignItems:"center",background:"none",border:"none",cursor:"pointer",padding:2,borderRadius:"var(--lucent-radius-sm)",color:"var(--lucent-text-secondary)",opacity:n?0:.6},onMouseEnter:w=>{n||(w.currentTarget.style.opacity="1")},onMouseLeave:w=>{n||(w.currentTarget.style.opacity="0.6")},children:e.jsx(To,{})})]})})}function Po({label:t,onClick:a}){return e.jsx("button",{type:"button",onClick:a,style:{flexShrink:0,padding:"var(--lucent-space-1) var(--lucent-space-3)",background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-strong)",borderRadius:"var(--lucent-radius-md)",cursor:"pointer",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:"var(--lucent-font-weight-medium)",color:"var(--lucent-text-primary)",lineHeight:"var(--lucent-line-height-tight)",whiteSpace:"nowrap",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)"},onMouseEnter:n=>{n.currentTarget.style.background="var(--lucent-surface-secondary)"},onMouseLeave:n=>{n.currentTarget.style.background="var(--lucent-surface)"},children:t})}function Ro({label:t,iconColor:a,onClick:n}){return e.jsx("button",{type:"button",onClick:n,style:{flexShrink:0,padding:0,background:"none",border:"none",cursor:"pointer",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-sm)",fontWeight:"var(--lucent-font-weight-semibold)",color:a==="var(--lucent-text-secondary)"?"var(--lucent-accent-default)":a,textDecoration:"underline",textUnderlineOffset:2,whiteSpace:"nowrap"},children:t})}const St="var(--lucent-space-6)",Lo=40,Fo=120;function $o(t){const a={position:"fixed",zIndex:9999,pointerEvents:"none",boxSizing:"border-box"};return Gt(t)?a.top=Lo:a.top=`calc(100vh - ${Fo}px)`,t.endsWith("left")?a.left=St:t.endsWith("right")?a.right=St:(a.left="50%",a.transform="translateX(-50%)"),a}function No({toasts:t,position:a,onDismiss:n,portalContainer:i}){const[r,o]=d.useState(!1),[s,l]=d.useState(void 0),c=d.useCallback(p=>{p&&l(p.offsetHeight)},[]),u=Gt(a),h=t.filter(p=>p.phase!=="exiting"),f=t.filter(p=>p.phase==="exiting");d.useEffect(()=>{h.length<=1&&o(!1)},[h.length]);const S=d.useRef(null),w=d.useCallback(()=>{S.current&&(clearTimeout(S.current),S.current=null),h.length>1&&o(!0)},[h.length]),g=d.useCallback(()=>{S.current=setTimeout(()=>{o(!1),S.current=null},150)},[]),m=e.jsx("div",{style:$o(a),children:e.jsx("div",{onMouseEnter:w,onMouseLeave:g,style:{margin:"calc(-1 * var(--lucent-space-2))",padding:"var(--lucent-space-2)"},children:e.jsxs("div",{style:{position:"relative",display:"flex",flexDirection:u?"column-reverse":"column",gap:r?"var(--lucent-space-2)":0,transform:r&&!u?"translateY(-100%)":void 0,marginTop:r&&!u?s??0:0,transition:"transform var(--lucent-duration-base) var(--lucent-easing-emphasized), margin var(--lucent-duration-base) var(--lucent-easing-emphasized), gap var(--lucent-duration-base) var(--lucent-easing-emphasized)"},children:[h.map((p,b)=>{const y=h.length-1-b,x=!r&&y>Do,T=y*zo,k=u?T:-T,v=r?1:1-y*Ao,A=r?1:Math.max(0,1-y*qo),C=p.phase==="entering",j=y===0,q=!r&&!j;return e.jsx("div",{ref:j?c:void 0,role:"status","aria-live":"polite","aria-hidden":x,style:{position:j||r?"relative":"absolute",...!r&&!j&&(u?{bottom:0}:{top:0}),zIndex:100-y,transform:C?`translateY(${u?"-20px":"20px"}) scale(0.96)`:r?void 0:`translateY(${k}px) scaleX(${v})`,opacity:C||p.phase==="exiting"?0:A,transformOrigin:u?"bottom center":"top center",transition:C?"none":"transform var(--lucent-duration-base) var(--lucent-easing-emphasized), opacity var(--lucent-duration-base) var(--lucent-easing-default)",pointerEvents:x?"none":"auto",visibility:x?"hidden":"visible"},children:e.jsx(kt,{entry:p,onDismiss:n,hideContent:q,...q&&s!==void 0&&{fixedHeight:s}})},p.id)}),f.map(p=>e.jsx("div",{role:"status","aria-live":"polite",style:{position:"absolute",...u?{bottom:0}:{top:0},zIndex:200,transform:`translateY(${u?"20px":"-20px"}) scale(0.96)`,opacity:0,transformOrigin:u?"bottom center":"top center",transition:"transform var(--lucent-duration-base) var(--lucent-easing-default), opacity var(--lucent-duration-base) var(--lucent-easing-default)",pointerEvents:"none"},children:e.jsx(kt,{entry:p,onDismiss:n})},p.id))]})})});return oe.createPortal(m,i??document.body)}function Wo({children:t,position:a="bottom-right",duration:n=5e3,max:i=5,portalContainer:r}){const[o,s]=d.useState([]),l=d.useCallback(h=>{s(f=>{const S=f.find(w=>w.id===h);return!S||S.phase==="exiting"?f:f.map(w=>w.id===h?{...w,phase:"exiting"}:w)}),setTimeout(()=>{s(f=>f.filter(S=>S.id!==h))},Eo)},[]),c=d.useCallback(h=>{const f=Mo(),S={id:f,title:h.title,variant:h.variant??"default",duration:h.duration??n,...h.description!==void 0&&{description:h.description},...h.action!==void 0&&{action:h.action},...h.icon!==void 0&&{icon:h.icon},phase:"entering"};return s(w=>{const g=[...w,S],m=g.filter(p=>p.phase!=="exiting");if(m.length>i){const p=m.slice(0,m.length-i);for(const b of p)setTimeout(()=>l(b.id),0)}return g}),requestAnimationFrame(()=>{requestAnimationFrame(()=>{s(w=>w.map(g=>g.id===f&&g.phase==="entering"?{...g,phase:"visible"}:g))})}),f},[n,i,l]);d.useEffect(()=>{const h=[];for(const f of o)if(f.phase==="visible"&&f.duration!==1/0){const S=setTimeout(()=>l(f.id),f.duration);h.push(S)}return()=>h.forEach(clearTimeout)},[o,l]);const u={toast:c,dismiss:l};return e.jsxs(Ht.Provider,{value:u,children:[t,o.length>0&&e.jsx(No,{toasts:o,position:a,onDismiss:l,...r!==void 0&&{portalContainer:r}})]})}const Oo={id:"toast",name:"Toast",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"Ephemeral notification system with auto-dismiss, cascading card-stack, hover-to-expand, action buttons/links, and animated enter/exit transitions. Comprises ToastProvider (context + portal), useToast hook, and toast card rendering.",designIntent:`Toast provides ephemeral feedback for actions that don't require the user to navigate away or acknowledge inline. It fills the gap between Alert (persistent, inline) and Modal (blocking).
|
|
454
|
+
/>`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Event title, description, and date label",required:!0},{componentId:"card",componentName:"Card",role:"Container for nested content blocks in the content slot",required:!1}],accessibility:{role:"list",ariaAttributes:[],keyboardInteractions:["Standard document flow — no interactive elements unless custom icons include them"],notes:"Timeline is a semantic <ol> with <li> items. It is non-interactive by default. If items contain interactive elements (links, buttons), those elements carry their own keyboard behaviour."}},Kt=c.createContext(null);function Co(){const t=c.useContext(Kt);if(!t)throw new Error("useToast must be used inside <ToastProvider>");return t}const jo=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"8",cy:"8",r:"6.5",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M8 5.5V8.5M8 10.5V11",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),Io=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"8",cy:"8",r:"6.5",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M5 8L7 10L11 6",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})]}),Mo=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("path",{d:"M8 2L14.5 13H1.5L8 2Z",stroke:"currentColor",strokeWidth:"1.5",strokeLinejoin:"round"}),e.jsx("path",{d:"M8 6V9M8 11V11.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),Eo=()=>e.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"8",cy:"8",r:"6.5",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M5.5 5.5L10.5 10.5M10.5 5.5L5.5 10.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),zo=()=>e.jsx("svg",{width:"14",height:"14",viewBox:"0 0 14 14",fill:"none","aria-hidden":"true",children:e.jsx("path",{d:"M3 3L11 11M11 3L3 11",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})}),Ao={info:e.jsx(jo,{}),success:e.jsx(Io,{}),warning:e.jsx(Mo,{}),danger:e.jsx(Eo,{})},qo={default:{border:"var(--lucent-border-default)",iconColor:"var(--lucent-text-secondary)"},info:{border:"var(--lucent-info-default)",iconColor:"var(--lucent-info-text)"},success:{border:"var(--lucent-success-default)",iconColor:"var(--lucent-success-text)"},warning:{border:"var(--lucent-warning-default)",iconColor:"var(--lucent-warning-text)"},danger:{border:"var(--lucent-danger-default)",iconColor:"var(--lucent-danger-text)"}};let Bo=0;function Ro(){return`lucent-toast-${++Bo}`}function Xt(t){return t.startsWith("top")}const Po=200,Do=8,Lo=.04,Fo=.2,$o=3,No=356;function jt({entry:t,onDismiss:r,hideContent:n,fixedHeight:i}){const{id:a,title:o,description:l,variant:s,action:d,icon:p}=t,h=qo[s],f=p??(s!=="default"?Ao[s]:null),w=(d==null?void 0:d.style)??"button";return e.jsx("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",padding:"var(--lucent-space-3) var(--lucent-space-4)",background:"var(--lucent-surface-raised)",border:`1px solid ${h.border}`,borderRadius:"var(--lucent-radius-lg)",boxShadow:n?"none":"var(--lucent-shadow-lg)",boxSizing:"border-box",width:No,maxWidth:"calc(100vw - var(--lucent-space-8))",fontFamily:"var(--lucent-font-family-base)",transition:"height var(--lucent-duration-base) var(--lucent-easing-emphasized), box-shadow var(--lucent-duration-base) var(--lucent-easing-default)",...i!==void 0&&{height:i,overflow:"hidden"}},children:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-3)",width:"100%",opacity:n?0:1,transition:"opacity var(--lucent-duration-base) var(--lucent-easing-default)"},children:[f&&e.jsx("span",{style:{flexShrink:0,color:h.iconColor,display:"flex",alignItems:"center"},children:f}),e.jsxs("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:"var(--lucent-space-1)"},children:[e.jsx(E.Text,{as:"span",size:"sm",weight:"semibold",color:"primary",lineHeight:"tight",children:o}),l&&e.jsx(E.Text,{as:"span",size:"sm",color:"secondary",lineHeight:"base",style:{whiteSpace:"pre-line"},children:l})]}),d&&w==="button"&&e.jsx(Wo,{label:d.label,onClick:()=>{d.onClick(),r(a)}}),d&&w==="link"&&e.jsx(Vo,{label:d.label,iconColor:h.iconColor,onClick:()=>{d.onClick(),r(a)}}),e.jsx("button",{type:"button","aria-label":"Dismiss",onClick:()=>r(a),style:{flexShrink:0,display:"flex",alignItems:"center",background:"none",border:"none",cursor:"pointer",padding:2,borderRadius:"var(--lucent-radius-sm)",color:"var(--lucent-text-secondary)",opacity:n?0:.6},onMouseEnter:b=>{n||(b.currentTarget.style.opacity="1")},onMouseLeave:b=>{n||(b.currentTarget.style.opacity="0.6")},children:e.jsx(zo,{})})]})})}function Wo({label:t,onClick:r}){return e.jsx("button",{type:"button",onClick:r,style:{flexShrink:0,padding:"var(--lucent-space-1) var(--lucent-space-3)",background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-strong)",borderRadius:"var(--lucent-radius-md)",cursor:"pointer",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:"var(--lucent-font-weight-medium)",color:"var(--lucent-text-primary)",lineHeight:"var(--lucent-line-height-tight)",whiteSpace:"nowrap",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)"},onMouseEnter:n=>{n.currentTarget.style.background="var(--lucent-surface-secondary)"},onMouseLeave:n=>{n.currentTarget.style.background="var(--lucent-surface)"},children:t})}function Vo({label:t,iconColor:r,onClick:n}){return e.jsx("button",{type:"button",onClick:n,style:{flexShrink:0,padding:0,background:"none",border:"none",cursor:"pointer",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-sm)",fontWeight:"var(--lucent-font-weight-semibold)",color:r==="var(--lucent-text-secondary)"?"var(--lucent-accent-default)":r,textDecoration:"underline",textUnderlineOffset:2,whiteSpace:"nowrap"},children:t})}const It="var(--lucent-space-6)",Oo=40,Ho=120;function Go(t){const r={position:"fixed",zIndex:9999,pointerEvents:"none",boxSizing:"border-box"};return Xt(t)?r.top=Oo:r.top=`calc(100vh - ${Ho}px)`,t.endsWith("left")?r.left=It:t.endsWith("right")?r.right=It:(r.left="50%",r.transform="translateX(-50%)"),r}function Uo({toasts:t,position:r,onDismiss:n,portalContainer:i}){const[a,o]=c.useState(!1),[l,s]=c.useState(void 0),d=c.useCallback(u=>{u&&s(u.offsetHeight)},[]),p=Xt(r),h=t.filter(u=>u.phase!=="exiting"),f=t.filter(u=>u.phase==="exiting");c.useEffect(()=>{h.length<=1&&o(!1)},[h.length]);const w=c.useRef(null),b=c.useCallback(()=>{w.current&&(clearTimeout(w.current),w.current=null),h.length>1&&o(!0)},[h.length]),g=c.useCallback(()=>{w.current=setTimeout(()=>{o(!1),w.current=null},150)},[]),m=e.jsx("div",{style:Go(r),children:e.jsx("div",{onMouseEnter:b,onMouseLeave:g,style:{margin:"calc(-1 * var(--lucent-space-2))",padding:"var(--lucent-space-2)"},children:e.jsxs("div",{style:{position:"relative",display:"flex",flexDirection:p?"column-reverse":"column",gap:a?"var(--lucent-space-2)":0,transform:a&&!p?"translateY(-100%)":void 0,marginTop:a&&!p?l??0:0,transition:"transform var(--lucent-duration-base) var(--lucent-easing-emphasized), margin var(--lucent-duration-base) var(--lucent-easing-emphasized), gap var(--lucent-duration-base) var(--lucent-easing-emphasized)"},children:[h.map((u,y)=>{const x=h.length-1-y,T=!a&&x>$o,S=x*Do,k=p?S:-S,v=a?1:1-x*Lo,A=a?1:Math.max(0,1-x*Fo),C=u.phase==="entering",M=x===0,q=!a&&!M;return e.jsx("div",{ref:M?d:void 0,role:"status","aria-live":"polite","aria-hidden":T,style:{position:M||a?"relative":"absolute",...!a&&!M&&(p?{bottom:0}:{top:0}),zIndex:100-x,transform:C?`translateY(${p?"-20px":"20px"}) scale(0.96)`:a?void 0:`translateY(${k}px) scaleX(${v})`,opacity:C||u.phase==="exiting"?0:A,transformOrigin:p?"bottom center":"top center",transition:C?"none":"transform var(--lucent-duration-base) var(--lucent-easing-emphasized), opacity var(--lucent-duration-base) var(--lucent-easing-default)",pointerEvents:T?"none":"auto",visibility:T?"hidden":"visible"},children:e.jsx(jt,{entry:u,onDismiss:n,hideContent:q,...q&&l!==void 0&&{fixedHeight:l}})},u.id)}),f.map(u=>e.jsx("div",{role:"status","aria-live":"polite",style:{position:"absolute",...p?{bottom:0}:{top:0},zIndex:200,transform:`translateY(${p?"20px":"-20px"}) scale(0.96)`,opacity:0,transformOrigin:p?"bottom center":"top center",transition:"transform var(--lucent-duration-base) var(--lucent-easing-default), opacity var(--lucent-duration-base) var(--lucent-easing-default)",pointerEvents:"none"},children:e.jsx(jt,{entry:u,onDismiss:n})},u.id))]})})});return Q.createPortal(m,i??document.body)}function _o({children:t,position:r="bottom-right",duration:n=5e3,max:i=5,portalContainer:a}){const[o,l]=c.useState([]),s=c.useCallback(h=>{l(f=>{const w=f.find(b=>b.id===h);return!w||w.phase==="exiting"?f:f.map(b=>b.id===h?{...b,phase:"exiting"}:b)}),setTimeout(()=>{l(f=>f.filter(w=>w.id!==h))},Po)},[]),d=c.useCallback(h=>{const f=Ro(),w={id:f,title:h.title,variant:h.variant??"default",duration:h.duration??n,...h.description!==void 0&&{description:h.description},...h.action!==void 0&&{action:h.action},...h.icon!==void 0&&{icon:h.icon},phase:"entering"};return l(b=>{const g=[...b,w],m=g.filter(u=>u.phase!=="exiting");if(m.length>i){const u=m.slice(0,m.length-i);for(const y of u)setTimeout(()=>s(y.id),0)}return g}),requestAnimationFrame(()=>{requestAnimationFrame(()=>{l(b=>b.map(g=>g.id===f&&g.phase==="entering"?{...g,phase:"visible"}:g))})}),f},[n,i,s]);c.useEffect(()=>{const h=[];for(const f of o)if(f.phase==="visible"&&f.duration!==1/0){const w=setTimeout(()=>s(f.id),f.duration);h.push(w)}return()=>h.forEach(clearTimeout)},[o,s]);const p={toast:d,dismiss:s};return e.jsxs(Kt.Provider,{value:p,children:[t,o.length>0&&e.jsx(Uo,{toasts:o,position:r,onDismiss:s,...a!==void 0&&{portalContainer:a}})]})}const Yo={id:"toast",name:"Toast",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"Ephemeral notification system with auto-dismiss, cascading card-stack, hover-to-expand, action buttons/links, and animated enter/exit transitions. Comprises ToastProvider (context + portal), useToast hook, and toast card rendering.",designIntent:`Toast provides ephemeral feedback for actions that don't require the user to navigate away or acknowledge inline. It fills the gap between Alert (persistent, inline) and Modal (blocking).
|
|
450
455
|
|
|
451
456
|
**Architecture**: ToastProvider wraps the app and manages a queue of toast entries. It renders toasts into a React portal (document.body by default, configurable via portalContainer) so they float above all page content regardless of stacking contexts. The useToast() hook exposes an imperative \`toast()\` function that returns a dismissible id, and a \`dismiss()\` function.
|
|
452
457
|
|
|
@@ -503,7 +508,7 @@ dismiss(id);`},{title:"Custom icon",description:"Override the built-in variant i
|
|
|
503
508
|
title: 'Synced',
|
|
504
509
|
variant: 'success',
|
|
505
510
|
icon: <CloudSyncIcon />,
|
|
506
|
-
});`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Renders title and description text with correct typography",required:!0},{componentId:"icon",componentName:"Icon (built-in SVGs)",role:"Status variant icons (info, success, warning, danger)",required:!1}],accessibility:{role:"status",ariaAttributes:["aria-live","aria-hidden","aria-label"],keyboardInteractions:["Tab: focuses the dismiss button and action button within the toast","Enter/Space: activates the focused dismiss or action button","Escape: no default behavior (consider adding dismiss-on-escape if needed)"],notes:'Each toast uses role="status" with aria-live="polite" so screen readers announce the content without interrupting the current task. This is appropriate for non-urgent status messages like "Changes saved" or "Profile updated". For truly urgent errors that must interrupt the user, consider wrapping the ToastProvider output in a role="alert" region or using the Alert component instead. The dismiss button carries aria-label="Dismiss" for screen reader clarity. Stacked toasts behind the front toast are marked aria-hidden="true" to prevent screen readers from announcing duplicate or hidden content. When the stack expands on hover, aria-hidden is removed so all toasts become accessible.'}}
|
|
511
|
+
});`}],compositionGraph:[{componentId:"text",componentName:"Text",role:"Renders title and description text with correct typography",required:!0},{componentId:"icon",componentName:"Icon (built-in SVGs)",role:"Status variant icons (info, success, warning, danger)",required:!1}],accessibility:{role:"status",ariaAttributes:["aria-live","aria-hidden","aria-label"],keyboardInteractions:["Tab: focuses the dismiss button and action button within the toast","Enter/Space: activates the focused dismiss or action button","Escape: no default behavior (consider adding dismiss-on-escape if needed)"],notes:'Each toast uses role="status" with aria-live="polite" so screen readers announce the content without interrupting the current task. This is appropriate for non-urgent status messages like "Changes saved" or "Profile updated". For truly urgent errors that must interrupt the user, consider wrapping the ToastProvider output in a role="alert" region or using the Alert component instead. The dismiss button carries aria-label="Dismiss" for screen reader clarity. Stacked toasts behind the front toast are marked aria-hidden="true" to prevent screen readers from announcing duplicate or hidden content. When the stack expands on hover, aria-hidden is removed so all toasts become accessible.'}},We=120,Ko=`
|
|
507
512
|
@keyframes lucent-navmenu-open {
|
|
508
513
|
from { opacity: 0; transform: translateY(-4px); }
|
|
509
514
|
to { opacity: 1; transform: translateY(0); }
|
|
@@ -520,7 +525,7 @@ dismiss(id);`},{title:"Custom icon",description:"Override the built-in variant i
|
|
|
520
525
|
background: color-mix(in srgb, var(--lucent-text-primary) 5%, transparent) !important;
|
|
521
526
|
color: var(--lucent-text-primary) !important;
|
|
522
527
|
}
|
|
523
|
-
`,Ce="cubic-bezier(0.34, 1.56, 0.64, 1)",qe={sm:{fontSize:"var(--lucent-font-size-sm)",paddingY:"5px",paddingX:"var(--lucent-space-3)",gap:"var(--lucent-space-2)",itemGap:"0px",iconWidth:"var(--lucent-space-5)",nestedFontSize:"var(--lucent-font-size-xs)"},md:{fontSize:"var(--lucent-font-size-md)",paddingY:"6px",paddingX:"var(--lucent-space-3)",gap:"var(--lucent-space-2)",itemGap:"2px",iconWidth:"var(--lucent-space-5)",nestedFontSize:"var(--lucent-font-size-sm)"},lg:{fontSize:"var(--lucent-font-size-lg)",paddingY:"var(--lucent-space-2)",paddingX:"var(--lucent-space-4)",gap:"var(--lucent-space-3)",itemGap:"var(--lucent-space-1)",iconWidth:"var(--lucent-space-6)",nestedFontSize:"var(--lucent-font-size-md)"}},re=d.createContext({orientation:"vertical",depth:0,inverse:!1,size:"md",hasIcons:!1,parentHasIcon:!1,requestMeasure:()=>{}});function ye({children:t,orientation:a="vertical",inverse:n=!1,size:i="md",hasIcons:r=!1,style:o,"aria-label":s="Navigation"}){const l=d.useRef(!1);if(!l.current&&typeof document<"u"){const m=document.createElement("style");m.textContent=Vo,document.head.appendChild(m),l.current=!0}const c=qe[i],u=d.useRef(null),h=d.useRef(null),f=d.useRef(!1),S=d.useRef(0),w=d.useCallback(()=>{const m=u.current,p=h.current;if(!m||!p)return;const b=m.querySelector('[data-active="true"]'),y=m.querySelector('[data-active-parent="true"]');let x=null,T=!1;if(b&&!b.closest('[aria-hidden="true"]')?x=b:y&&!y.closest('[aria-hidden="true"]')&&(x=y,T=!0),!x){p.style.opacity="0";return}const k=m.getBoundingClientRect(),v=x.getBoundingClientRect();f.current?p.style.transition=[`top 150ms ${Ce}`,`left 150ms ${Ce}`,`width 150ms ${Ce}`,`height 150ms ${Ce}`,"opacity 100ms ease","background 150ms ease","box-shadow 150ms ease"].join(", "):(p.style.transition="none",f.current=!0),p.style.top=`${v.top-k.top}px`,p.style.left=`${v.left-k.left}px`,p.style.width=`${v.width}px`,p.style.height=`${v.height}px`,p.style.opacity="1",T?(p.style.background=n?"var(--lucent-surface-secondary)":"color-mix(in srgb, var(--lucent-accent-default) 12%, transparent)",p.style.boxShadow="none"):(p.style.background=n?"var(--lucent-surface)":"var(--lucent-accent-default)",p.style.boxShadow=n?"inset -3px 0 0 var(--lucent-accent-default), var(--lucent-shadow-sm)":"none")},[n]),g=d.useCallback(()=>{cancelAnimationFrame(S.current),S.current=requestAnimationFrame(w)},[w]);return d.useEffect(()=>{const m=u.current;if(!m)return;const p=new MutationObserver(g);return p.observe(m,{attributes:!0,attributeFilter:["data-active","data-active-parent","aria-hidden"],subtree:!0}),()=>p.disconnect()},[g]),d.useEffect(()=>{const m=u.current;if(!m)return;const p=new ResizeObserver(g);return p.observe(m),()=>p.disconnect()},[g]),d.useEffect(()=>{document.fonts.ready.then(g)},[g]),d.useEffect(()=>{g()},[t,n,i,g]),e.jsx(re.Provider,{value:{orientation:a,depth:0,inverse:n,size:i,hasIcons:r,parentHasIcon:!1,requestMeasure:g},children:e.jsxs("nav",{ref:u,"aria-label":s,style:{display:"flex",flexDirection:a==="vertical"?"column":"row",gap:c.itemGap,fontFamily:"var(--lucent-font-family-base)",fontSize:c.fontSize,position:"relative",...o},children:[e.jsx("div",{ref:h,"aria-hidden":!0,style:{position:"absolute",borderRadius:"var(--lucent-radius-md)",pointerEvents:"none",zIndex:0,opacity:0,top:0,left:0,width:0,height:0}}),t]})})}function Ut({children:t,href:a,isActive:n=!1,icon:i,badge:r,disabled:o=!1,onClick:s,as:l,style:c}){const{orientation:u,depth:h,inverse:f,size:S,parentHasIcon:w,requestMeasure:g}=d.useContext(re),m=qe[S];d.useEffect(()=>{g()},[n,g]);let p=null;const b=[],y=Array.isArray(t)?t:[t];for(const T of y)T!=null&&typeof T=="object"&&"type"in T&&T.type===ot?p=T:b.push(T);if(p)return e.jsx(Ho,{subMenu:p,orientation:u,depth:h,inverse:f,size:S,isActive:n,disabled:o,...i!==void 0&&{icon:i},...r!==void 0&&{badge:r},...s!==void 0&&{onClick:s},...c!==void 0&&{style:c},children:b});const x=l??"a";return e.jsxs(x,{"data-lucent-navitem":"","data-active":n||void 0,href:o?void 0:a,onClick:o?void 0:s,"aria-current":n?"page":void 0,"aria-disabled":o||void 0,style:{display:"flex",alignItems:"center",gap:m.gap,padding:u==="vertical"?`${m.paddingY} ${m.paddingX} ${m.paddingY} ${i!=null||w?"var(--lucent-space-2)":"var(--lucent-space-4)"}`:`${m.paddingY} ${m.paddingX}`,borderRadius:"var(--lucent-radius-md)",background:"transparent",color:o?"var(--lucent-text-disabled)":n?f?"var(--lucent-text-primary)":"var(--lucent-accent-fg)":"var(--lucent-text-secondary)",border:0,borderBottom:n&&u==="horizontal"?"2px solid var(--lucent-accent-default)":u==="horizontal"?"2px solid transparent":0,boxShadow:"none",fontFamily:"var(--lucent-font-family-base)",fontSize:m.fontSize,fontWeight:n?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",textDecoration:"none",cursor:o?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), color var(--lucent-duration-fast) var(--lucent-easing-default)",userSelect:"none",boxSizing:"border-box",position:"relative",zIndex:1,whiteSpace:u==="horizontal"?"nowrap":void 0,width:u==="vertical"?"100%":void 0,textAlign:"left",outline:"none",...c},children:[i!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0,width:m.iconWidth,minWidth:14,minHeight:14,alignItems:"center",justifyContent:"center",color:"inherit"},children:i}),e.jsx("span",{style:{flex:1},children:b}),r!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0,marginLeft:"auto"},children:r})]})}function Ho({children:t,icon:a,badge:n,isActive:i=!1,disabled:r=!1,onClick:o,style:s,subMenu:l,orientation:c,depth:u,inverse:h,size:f}){const{hasIcons:S,requestMeasure:w}=d.useContext(re),g=qe[f],[m,p]=d.useState(!1),b=d.useRef(null),[y,x]=d.useState(0),T=d.useRef(null),k=d.useRef(null),v=d.useRef(),A=Go(l),C=i&&!A,j=!C&&!m&&A,q=i||A;d.useEffect(()=>{w()},[m,i,A,w]),d.useEffect(()=>{if(c!=="vertical")return;const M=b.current;if(M)if(m){const $=M.scrollHeight;x($);const W=setTimeout(()=>{x(void 0)},130);return()=>clearTimeout(W)}else x(M.scrollHeight),M.getBoundingClientRect(),x(0)},[m,c]),d.useEffect(()=>{if(c!=="horizontal"||!m)return;const M=$=>{T.current&&!T.current.contains($.target)&&B()};return document.addEventListener("mousedown",M),()=>document.removeEventListener("mousedown",M)},[c,m]),d.useLayoutEffect(()=>{if(c!=="horizontal"||!m)return;const M=k.current;if(!M)return;const $=M.getBoundingClientRect();$.right>window.innerWidth&&(M.style.left="auto",M.style.right="0"),$.bottom>window.innerHeight&&(M.style.top="auto",M.style.bottom="100%",M.style.marginTop="0",M.style.marginBottom="var(--lucent-space-1)")},[c,m]);const[D,I]=d.useState("idle"),[F,R]=d.useState(!1),O=()=>{r||(R(!0),I("entering"),p(!0))},B=()=>{I("exiting"),setTimeout(()=>{p(!1),R(!1),I("idle")},$e)},N=()=>{r||(c==="horizontal"?m?B():O():p(!m))},G=M=>{r||(c==="vertical"?(M.key==="ArrowRight"&&!m&&(M.preventDefault(),p(!0)),M.key==="ArrowLeft"&&m&&(M.preventDefault(),p(!1))):(M.key==="ArrowDown"&&!m&&(M.preventDefault(),O()),M.key==="ArrowUp"&&m&&(M.preventDefault(),B())),(M.key==="Enter"||M.key===" ")&&(M.preventDefault(),N()),M.key==="Escape"&&m&&(M.preventDefault(),c==="horizontal"?B():p(!1)))},P=()=>{c==="horizontal"&&!r&&(clearTimeout(v.current),O())},z=()=>{c==="horizontal"&&(v.current=setTimeout(()=>B(),150))};return c==="horizontal"?e.jsxs("div",{ref:T,style:{position:"relative"},onMouseEnter:P,onMouseLeave:z,children:[e.jsxs("button",{"data-lucent-navitem":"","data-hint":q||void 0,onClick:M=>{N(),o==null||o(M)},onKeyDown:G,"aria-expanded":m,"aria-disabled":r||void 0,style:{display:"flex",alignItems:"center",gap:g.gap,padding:`${g.paddingY} ${g.paddingX}`,borderRadius:"var(--lucent-radius-md)",background:"transparent",color:r?"var(--lucent-text-disabled)":q?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",border:0,borderBottom:q?"2px solid var(--lucent-accent-default)":"2px solid transparent",fontFamily:"var(--lucent-font-family-base)",fontSize:g.fontSize,fontWeight:q?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",cursor:r?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), color var(--lucent-duration-fast) var(--lucent-easing-default)",userSelect:"none",whiteSpace:"nowrap",outline:"none",boxSizing:"border-box",...s},children:[a!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0,width:g.iconWidth,minWidth:14,minHeight:14,alignItems:"center",justifyContent:"center",color:"inherit"},children:a}),e.jsx("span",{children:t}),n!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0},children:n})]}),F&&e.jsx(re.Provider,{value:{orientation:"vertical",depth:0,inverse:h,size:f,hasIcons:S,parentHasIcon:!1,requestMeasure:w},children:e.jsx("div",{ref:k,style:{position:"absolute",top:"100%",left:0,marginTop:"var(--lucent-space-1)",minWidth:180,background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-default)",borderRadius:"var(--lucent-radius-lg)",boxShadow:"var(--lucent-shadow-lg)",padding:"var(--lucent-space-1)",zIndex:100,animation:D==="exiting"?`lucent-navmenu-dropdown-out ${$e}ms var(--lucent-easing-default) forwards`:`lucent-navmenu-dropdown-in ${$e}ms var(--lucent-easing-default) forwards`},children:l})})]}):e.jsxs("div",{style:{position:"relative",zIndex:1},children:[e.jsxs("button",{"data-lucent-navitem":"","data-active":C||void 0,"data-active-parent":j||void 0,"data-hint":!C&&!j&&q||void 0,onClick:M=>{N(),o==null||o(M)},onKeyDown:G,"aria-expanded":m,"aria-disabled":r||void 0,style:{display:"flex",alignItems:"center",gap:g.gap,width:"100%",padding:`${g.paddingY} ${g.paddingX} ${g.paddingY} ${a!=null?"var(--lucent-space-2)":"var(--lucent-space-4)"}`,borderRadius:"var(--lucent-radius-md)",background:"transparent",color:r?"var(--lucent-text-disabled)":C?h?"var(--lucent-text-primary)":"var(--lucent-accent-fg)":j||q?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",border:0,boxShadow:"none",fontFamily:"var(--lucent-font-family-base)",fontSize:g.fontSize,fontWeight:q?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",textAlign:"left",textDecoration:"none",cursor:r?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), color var(--lucent-duration-fast) var(--lucent-easing-default)",userSelect:"none",boxSizing:"border-box",outline:"none",...s},children:[a!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0,width:g.iconWidth,minWidth:14,minHeight:14,alignItems:"center",justifyContent:"center",color:"inherit"},children:a}),e.jsx("span",{style:{flex:1},children:t}),n!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0},children:n})]}),e.jsx("div",{ref:b,"aria-hidden":!m,style:{overflow:"hidden",height:y!==void 0?y:"auto",transition:"height 120ms var(--lucent-easing-default)"},children:e.jsx(re.Provider,{value:{orientation:c,depth:u+1,inverse:h,size:f,hasIcons:S,parentHasIcon:a!=null,requestMeasure:w},children:e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:g.itemGap,padding:"var(--lucent-space-1) 0",marginLeft:a!=null?`calc(${g.iconWidth} + ${g.gap})`:"var(--lucent-space-3)",animation:m?"lucent-navmenu-open 200ms var(--lucent-easing-default) forwards":void 0},children:l})})})]})}function Go(t){return et(t)}function et(t){if(t==null)return!1;if(Array.isArray(t))return t.some(et);if(typeof t=="object"&&"props"in t){const a=t;if(a.props.isActive)return!0;if(a.props.children)return et(a.props.children)}return!1}function ot({children:t}){return e.jsx(e.Fragment,{children:t})}function _t({children:t,label:a,defaultOpen:n=!0,open:i,onOpenChange:r,collapsible:o,style:s}){const{orientation:l,inverse:c,size:u,hasIcons:h}=d.useContext(re),f=qe[u],S=o??a!=null,w=h?"var(--lucent-space-2)":"var(--lucent-space-4)";if(l==="horizontal")return e.jsx(e.Fragment,{children:t});const g=i!==void 0,[m,p]=d.useState(n),b=g?i:m,y=d.useRef(null),[x,T]=d.useState(b?void 0:0);d.useEffect(()=>{if(!S)return;const v=y.current;if(v)if(b){const A=v.scrollHeight;T(A);const C=setTimeout(()=>{T(void 0)},130);return()=>clearTimeout(C)}else T(v.scrollHeight),v.getBoundingClientRect(),T(0)},[b,S]);const k=d.useCallback(()=>{const v=!b;g||p(v),r==null||r(v)},[b,g,r]);return a?e.jsxs("div",{style:{display:"flex",flexDirection:"column",marginTop:"var(--lucent-space-3)",...s},children:[S?e.jsxs("button",{onClick:k,"aria-expanded":b,style:{display:"flex",alignItems:"center",justifyContent:"space-between",width:"100%",padding:`var(--lucent-space-1) var(--lucent-space-3) var(--lucent-space-1) ${w}`,background:"none",border:0,cursor:"pointer",outline:"none",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:"var(--lucent-font-weight-semibold)",color:"var(--lucent-text-secondary)",letterSpacing:"0.05em",textTransform:"uppercase",textAlign:"left",userSelect:"none",marginBottom:b?"var(--lucent-space-1)":"0",transition:"margin-bottom 200ms var(--lucent-easing-default)"},children:[e.jsx("span",{children:a}),e.jsx(Uo,{open:b,size:12})]}):e.jsx("div",{style:{padding:`var(--lucent-space-1) var(--lucent-space-3) var(--lucent-space-1) ${w}`,fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:"var(--lucent-font-weight-semibold)",color:"var(--lucent-text-secondary)",letterSpacing:"0.05em",textTransform:"uppercase",userSelect:"none",marginBottom:"var(--lucent-space-1)"},children:a}),S?e.jsx("div",{ref:y,"aria-hidden":!b,style:{overflow:"hidden",height:x!==void 0?x:"auto",transition:"height 120ms var(--lucent-easing-default)"},children:e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:f.itemGap},children:t})}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:f.itemGap},children:t})]}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:f.itemGap,...s},children:t})}function Yt({style:t}){const{orientation:a}=d.useContext(re);return e.jsx("div",{role:"separator",style:{...a==="vertical"?{height:1,margin:"var(--lucent-space-2) var(--lucent-space-4)"}:{width:1,alignSelf:"stretch",margin:"var(--lucent-space-1) var(--lucent-space-1)"},background:"var(--lucent-border-default)",flexShrink:0,...t}})}function Uo({open:t,size:a=14}){return e.jsx("svg",{width:a,height:a,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 200ms var(--lucent-easing-default)"},children:e.jsx("polyline",{points:"6 9 12 15 18 9"})})}ye.Item=Ut;ye.Group=_t;ye.Sub=ot;ye.Separator=Yt;const Ne=120,_o=`
|
|
528
|
+
`,je="cubic-bezier(0.34, 1.56, 0.64, 1)",Be={sm:{fontSize:"var(--lucent-font-size-sm)",paddingY:"5px",paddingX:"var(--lucent-space-3)",gap:"var(--lucent-space-2)",itemGap:"0px",iconWidth:"var(--lucent-space-5)",nestedFontSize:"var(--lucent-font-size-xs)"},md:{fontSize:"var(--lucent-font-size-md)",paddingY:"6px",paddingX:"var(--lucent-space-3)",gap:"var(--lucent-space-2)",itemGap:"2px",iconWidth:"var(--lucent-space-5)",nestedFontSize:"var(--lucent-font-size-sm)"},lg:{fontSize:"var(--lucent-font-size-lg)",paddingY:"var(--lucent-space-2)",paddingX:"var(--lucent-space-4)",gap:"var(--lucent-space-3)",itemGap:"var(--lucent-space-1)",iconWidth:"var(--lucent-space-6)",nestedFontSize:"var(--lucent-font-size-md)"}},ie=c.createContext({orientation:"vertical",depth:0,inverse:!1,size:"md",hasIcons:!1,parentHasIcon:!1,requestMeasure:()=>{}});function xe({children:t,orientation:r="vertical",inverse:n=!1,size:i="md",hasIcons:a=!1,style:o,"aria-label":l="Navigation"}){const s=c.useRef(!1);if(!s.current&&typeof document<"u"){const m=document.createElement("style");m.textContent=Ko,document.head.appendChild(m),s.current=!0}const d=Be[i],p=c.useRef(null),h=c.useRef(null),f=c.useRef(!1),w=c.useRef(0),b=c.useCallback(()=>{const m=p.current,u=h.current;if(!m||!u)return;const y=m.querySelector('[data-active="true"]'),x=m.querySelector('[data-active-parent="true"]');let T=null,S=!1;if(y&&!y.closest('[aria-hidden="true"]')?T=y:x&&!x.closest('[aria-hidden="true"]')&&(T=x,S=!0),!T){u.style.opacity="0";return}const k=m.getBoundingClientRect(),v=T.getBoundingClientRect();f.current?u.style.transition=[`top 150ms ${je}`,`left 150ms ${je}`,`width 150ms ${je}`,`height 150ms ${je}`,"opacity 100ms ease","background 150ms ease","box-shadow 150ms ease"].join(", "):(u.style.transition="none",f.current=!0),u.style.top=`${v.top-k.top}px`,u.style.left=`${v.left-k.left}px`,u.style.width=`${v.width}px`,u.style.height=`${v.height}px`,u.style.opacity="1",S?(u.style.background=n?"var(--lucent-surface-secondary)":"color-mix(in srgb, var(--lucent-accent-default) 12%, transparent)",u.style.boxShadow="none"):(u.style.background=n?"var(--lucent-surface)":"var(--lucent-accent-default)",u.style.boxShadow=n?"inset -3px 0 0 var(--lucent-accent-default), var(--lucent-shadow-sm)":"none")},[n]),g=c.useCallback(()=>{cancelAnimationFrame(w.current),w.current=requestAnimationFrame(b)},[b]);return c.useEffect(()=>{const m=p.current;if(!m)return;const u=new MutationObserver(g);return u.observe(m,{attributes:!0,attributeFilter:["data-active","data-active-parent","aria-hidden"],subtree:!0}),()=>u.disconnect()},[g]),c.useEffect(()=>{const m=p.current;if(!m)return;const u=new ResizeObserver(g);return u.observe(m),()=>u.disconnect()},[g]),c.useEffect(()=>{document.fonts.ready.then(g)},[g]),c.useEffect(()=>{g()},[t,n,i,g]),e.jsx(ie.Provider,{value:{orientation:r,depth:0,inverse:n,size:i,hasIcons:a,parentHasIcon:!1,requestMeasure:g},children:e.jsxs("nav",{ref:p,"aria-label":l,style:{display:"flex",flexDirection:r==="vertical"?"column":"row",gap:d.itemGap,fontFamily:"var(--lucent-font-family-base)",fontSize:d.fontSize,position:"relative",...o},children:[e.jsx("div",{ref:h,"aria-hidden":!0,style:{position:"absolute",borderRadius:"var(--lucent-radius-md)",pointerEvents:"none",zIndex:0,opacity:0,top:0,left:0,width:0,height:0}}),t]})})}function Jt({children:t,href:r,isActive:n=!1,icon:i,badge:a,disabled:o=!1,onClick:l,as:s,style:d}){const{orientation:p,depth:h,inverse:f,size:w,parentHasIcon:b,requestMeasure:g}=c.useContext(ie),m=Be[w];c.useEffect(()=>{g()},[n,g]);let u=null;const y=[],x=Array.isArray(t)?t:[t];for(const S of x)S!=null&&typeof S=="object"&&"type"in S&&S.type===st?u=S:y.push(S);if(u)return e.jsx(Xo,{subMenu:u,orientation:p,depth:h,inverse:f,size:w,isActive:n,disabled:o,...i!==void 0&&{icon:i},...a!==void 0&&{badge:a},...l!==void 0&&{onClick:l},...d!==void 0&&{style:d},children:y});const T=s??"a";return e.jsxs(T,{"data-lucent-navitem":"","data-active":n||void 0,href:o?void 0:r,onClick:o?void 0:l,"aria-current":n?"page":void 0,"aria-disabled":o||void 0,style:{display:"flex",alignItems:"center",gap:m.gap,padding:p==="vertical"?`${m.paddingY} ${m.paddingX} ${m.paddingY} ${i!=null||b?"var(--lucent-space-2)":"var(--lucent-space-4)"}`:`${m.paddingY} ${m.paddingX}`,borderRadius:"var(--lucent-radius-md)",background:"transparent",color:o?"var(--lucent-text-disabled)":n?f?"var(--lucent-text-primary)":"var(--lucent-accent-fg)":"var(--lucent-text-secondary)",border:0,borderBottom:n&&p==="horizontal"?"2px solid var(--lucent-accent-default)":p==="horizontal"?"2px solid transparent":0,boxShadow:"none",fontFamily:"var(--lucent-font-family-base)",fontSize:m.fontSize,fontWeight:n?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",textDecoration:"none",cursor:o?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), color var(--lucent-duration-fast) var(--lucent-easing-default)",userSelect:"none",boxSizing:"border-box",position:"relative",zIndex:1,whiteSpace:p==="horizontal"?"nowrap":void 0,width:p==="vertical"?"100%":void 0,textAlign:"left",outline:"none",...d},children:[i!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0,width:m.iconWidth,minWidth:14,minHeight:14,alignItems:"center",justifyContent:"center",color:"inherit"},children:i}),e.jsx("span",{style:{flex:1},children:y}),a!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0,marginLeft:"auto"},children:a})]})}function Xo({children:t,icon:r,badge:n,isActive:i=!1,disabled:a=!1,onClick:o,style:l,subMenu:s,orientation:d,depth:p,inverse:h,size:f}){const{hasIcons:w,requestMeasure:b}=c.useContext(ie),g=Be[f],[m,u]=c.useState(!1),y=c.useRef(null),[x,T]=c.useState(0),S=c.useRef(null),k=c.useRef(null),v=c.useRef(),A=Jo(s),C=i&&!A,M=!C&&!m&&A,q=i||A;c.useEffect(()=>{b()},[m,i,A,b]),c.useEffect(()=>{if(d!=="vertical")return;const j=y.current;if(j)if(m){const $=j.scrollHeight;T($);const V=setTimeout(()=>{T(void 0)},130);return()=>clearTimeout(V)}else T(j.scrollHeight),j.getBoundingClientRect(),T(0)},[m,d]),c.useEffect(()=>{if(d!=="horizontal"||!m)return;const j=$=>{S.current&&!S.current.contains($.target)&&R()};return document.addEventListener("mousedown",j),()=>document.removeEventListener("mousedown",j)},[d,m]),c.useLayoutEffect(()=>{if(d!=="horizontal"||!m)return;const j=k.current;if(!j)return;const $=j.getBoundingClientRect();$.right>window.innerWidth&&(j.style.left="auto",j.style.right="0"),$.bottom>window.innerHeight&&(j.style.top="auto",j.style.bottom="100%",j.style.marginTop="0",j.style.marginBottom="var(--lucent-space-1)")},[d,m]);const[B,I]=c.useState("idle"),[L,D]=c.useState(!1),N=()=>{a||(D(!0),I("entering"),u(!0))},R=()=>{I("exiting"),setTimeout(()=>{u(!1),D(!1),I("idle")},We)},W=()=>{a||(d==="horizontal"?m?R():N():u(!m))},G=j=>{a||(d==="vertical"?(j.key==="ArrowRight"&&!m&&(j.preventDefault(),u(!0)),j.key==="ArrowLeft"&&m&&(j.preventDefault(),u(!1))):(j.key==="ArrowDown"&&!m&&(j.preventDefault(),N()),j.key==="ArrowUp"&&m&&(j.preventDefault(),R())),(j.key==="Enter"||j.key===" ")&&(j.preventDefault(),W()),j.key==="Escape"&&m&&(j.preventDefault(),d==="horizontal"?R():u(!1)))},P=()=>{d==="horizontal"&&!a&&(clearTimeout(v.current),N())},z=()=>{d==="horizontal"&&(v.current=setTimeout(()=>R(),150))};return d==="horizontal"?e.jsxs("div",{ref:S,style:{position:"relative"},onMouseEnter:P,onMouseLeave:z,children:[e.jsxs("button",{"data-lucent-navitem":"","data-hint":q||void 0,onClick:j=>{W(),o==null||o(j)},onKeyDown:G,"aria-expanded":m,"aria-disabled":a||void 0,style:{display:"flex",alignItems:"center",gap:g.gap,padding:`${g.paddingY} ${g.paddingX}`,borderRadius:"var(--lucent-radius-md)",background:"transparent",color:a?"var(--lucent-text-disabled)":q?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",border:0,borderBottom:q?"2px solid var(--lucent-accent-default)":"2px solid transparent",fontFamily:"var(--lucent-font-family-base)",fontSize:g.fontSize,fontWeight:q?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",cursor:a?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), color var(--lucent-duration-fast) var(--lucent-easing-default)",userSelect:"none",whiteSpace:"nowrap",outline:"none",boxSizing:"border-box",...l},children:[r!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0,width:g.iconWidth,minWidth:14,minHeight:14,alignItems:"center",justifyContent:"center",color:"inherit"},children:r}),e.jsx("span",{children:t}),n!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0},children:n})]}),L&&e.jsx(ie.Provider,{value:{orientation:"vertical",depth:0,inverse:h,size:f,hasIcons:w,parentHasIcon:!1,requestMeasure:b},children:e.jsx("div",{ref:k,style:{position:"absolute",top:"100%",left:0,marginTop:"var(--lucent-space-1)",minWidth:180,background:"var(--lucent-surface)",border:"1px solid var(--lucent-border-default)",borderRadius:"var(--lucent-radius-lg)",boxShadow:"var(--lucent-shadow-lg)",padding:"var(--lucent-space-1)",zIndex:100,animation:B==="exiting"?`lucent-navmenu-dropdown-out ${We}ms var(--lucent-easing-default) forwards`:`lucent-navmenu-dropdown-in ${We}ms var(--lucent-easing-default) forwards`},children:s})})]}):e.jsxs("div",{style:{position:"relative",zIndex:1},children:[e.jsxs("button",{"data-lucent-navitem":"","data-active":C||void 0,"data-active-parent":M||void 0,"data-hint":!C&&!M&&q||void 0,onClick:j=>{W(),o==null||o(j)},onKeyDown:G,"aria-expanded":m,"aria-disabled":a||void 0,style:{display:"flex",alignItems:"center",gap:g.gap,width:"100%",padding:`${g.paddingY} ${g.paddingX} ${g.paddingY} ${r!=null?"var(--lucent-space-2)":"var(--lucent-space-4)"}`,borderRadius:"var(--lucent-radius-md)",background:"transparent",color:a?"var(--lucent-text-disabled)":C?h?"var(--lucent-text-primary)":"var(--lucent-accent-fg)":M||q?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",border:0,boxShadow:"none",fontFamily:"var(--lucent-font-family-base)",fontSize:g.fontSize,fontWeight:q?"var(--lucent-font-weight-medium)":"var(--lucent-font-weight-regular)",textAlign:"left",textDecoration:"none",cursor:a?"not-allowed":"pointer",transition:"background var(--lucent-duration-fast) var(--lucent-easing-default), color var(--lucent-duration-fast) var(--lucent-easing-default)",userSelect:"none",boxSizing:"border-box",outline:"none",...l},children:[r!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0,width:g.iconWidth,minWidth:14,minHeight:14,alignItems:"center",justifyContent:"center",color:"inherit"},children:r}),e.jsx("span",{style:{flex:1},children:t}),n!=null&&e.jsx("span",{style:{display:"flex",flexShrink:0},children:n})]}),e.jsx("div",{ref:y,"aria-hidden":!m,style:{overflow:"hidden",height:x!==void 0?x:"auto",transition:"height 120ms var(--lucent-easing-default)"},children:e.jsx(ie.Provider,{value:{orientation:d,depth:p+1,inverse:h,size:f,hasIcons:w,parentHasIcon:r!=null,requestMeasure:b},children:e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:g.itemGap,padding:"var(--lucent-space-1) 0",marginLeft:r!=null?`calc(${g.iconWidth} + ${g.gap})`:"var(--lucent-space-3)",animation:m?"lucent-navmenu-open 200ms var(--lucent-easing-default) forwards":void 0},children:s})})})]})}function Jo(t){return nt(t)}function nt(t){if(t==null)return!1;if(Array.isArray(t))return t.some(nt);if(typeof t=="object"&&"props"in t){const r=t;if(r.props.isActive)return!0;if(r.props.children)return nt(r.props.children)}return!1}function st({children:t}){return e.jsx(e.Fragment,{children:t})}function Zt({children:t,label:r,defaultOpen:n=!0,open:i,onOpenChange:a,collapsible:o,style:l}){const{orientation:s,inverse:d,size:p,hasIcons:h}=c.useContext(ie),f=Be[p],w=o??r!=null,b=h?"var(--lucent-space-2)":"var(--lucent-space-4)";if(s==="horizontal")return e.jsx(e.Fragment,{children:t});const g=i!==void 0,[m,u]=c.useState(n),y=g?i:m,x=c.useRef(null),[T,S]=c.useState(y?void 0:0);c.useEffect(()=>{if(!w)return;const v=x.current;if(v)if(y){const A=v.scrollHeight;S(A);const C=setTimeout(()=>{S(void 0)},130);return()=>clearTimeout(C)}else S(v.scrollHeight),v.getBoundingClientRect(),S(0)},[y,w]);const k=c.useCallback(()=>{const v=!y;g||u(v),a==null||a(v)},[y,g,a]);return r?e.jsxs("div",{style:{display:"flex",flexDirection:"column",marginTop:"var(--lucent-space-3)",...l},children:[w?e.jsxs("button",{onClick:k,"aria-expanded":y,style:{display:"flex",alignItems:"center",justifyContent:"space-between",width:"100%",padding:`var(--lucent-space-1) var(--lucent-space-3) var(--lucent-space-1) ${b}`,background:"none",border:0,cursor:"pointer",outline:"none",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:"var(--lucent-font-weight-semibold)",color:"var(--lucent-text-secondary)",letterSpacing:"0.05em",textTransform:"uppercase",textAlign:"left",userSelect:"none",marginBottom:y?"var(--lucent-space-1)":"0",transition:"margin-bottom 200ms var(--lucent-easing-default)"},children:[e.jsx("span",{children:r}),e.jsx(Zo,{open:y,size:12})]}):e.jsx("div",{style:{padding:`var(--lucent-space-1) var(--lucent-space-3) var(--lucent-space-1) ${b}`,fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",fontWeight:"var(--lucent-font-weight-semibold)",color:"var(--lucent-text-secondary)",letterSpacing:"0.05em",textTransform:"uppercase",userSelect:"none",marginBottom:"var(--lucent-space-1)"},children:r}),w?e.jsx("div",{ref:x,"aria-hidden":!y,style:{overflow:"hidden",height:T!==void 0?T:"auto",transition:"height 120ms var(--lucent-easing-default)"},children:e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:f.itemGap},children:t})}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:f.itemGap},children:t})]}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:f.itemGap,...l},children:t})}function Qt({style:t}){const{orientation:r}=c.useContext(ie);return e.jsx("div",{role:"separator",style:{...r==="vertical"?{height:1,margin:"var(--lucent-space-2) var(--lucent-space-4)"}:{width:1,alignSelf:"stretch",margin:"var(--lucent-space-1) var(--lucent-space-1)"},background:"var(--lucent-border-default)",flexShrink:0,...t}})}function Zo({open:t,size:r=14}){return e.jsx("svg",{width:r,height:r,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 200ms var(--lucent-easing-default)"},children:e.jsx("polyline",{points:"6 9 12 15 18 9"})})}xe.Item=Jt;xe.Group=Zt;xe.Sub=st;xe.Separator=Qt;const Ve=120,Qo=`
|
|
524
529
|
@keyframes lucent-filter-ms-in {
|
|
525
530
|
from { opacity: 0; transform: scale(0.97) translateY(-2px); }
|
|
526
531
|
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
@@ -529,7 +534,7 @@ dismiss(id);`},{title:"Custom icon",description:"Override the built-in variant i
|
|
|
529
534
|
from { opacity: 1; transform: scale(1) translateY(0); }
|
|
530
535
|
to { opacity: 0; transform: scale(0.97) translateY(-2px); }
|
|
531
536
|
}
|
|
532
|
-
`;let Tt=!1;const Yo={xs:"var(--lucent-space-1)",sm:"var(--lucent-space-2)",md:"var(--lucent-space-2)",lg:"var(--lucent-space-3)"},Ko={xs:"sm",sm:"sm",md:"md",lg:"lg"},We=4;function Xo(t,a){const n=a.offsetHeight,i=a.offsetWidth,r=window.innerWidth,o=window.innerHeight;let s=t.bottom+We,l=t.left,c="top left";return s+n>o&&t.top-n-We>=0&&(s=t.top-n-We,c="bottom left"),l+i>r&&(l=r-i-8),l<0&&(l=8),{top:s,left:l,transformOrigin:c}}function Jo({label:t,options:a,value:n,defaultValue:i=[],onChange:r,variant:o="secondary",size:s="sm",disabled:l=!1,icon:c,style:u}){const h=n!==void 0,[f,S]=d.useState(i),w=h?n:f,[g,m]=d.useState(!1),[p,b]=d.useState(!1),[y,x]=d.useState("idle"),[T,k]=d.useState(null),[v,A]=d.useState(-1),C=d.useRef(null),j=d.useRef(null);if(!Tt&&typeof document<"u"){const B=document.createElement("style");B.textContent=_o,document.head.appendChild(B),Tt=!0}d.useEffect(()=>{if(g)b(!0),x("entering"),A(-1);else if(p){x("exiting");const B=setTimeout(()=>{b(!1),x("idle")},Ne);return()=>clearTimeout(B)}},[g]),d.useLayoutEffect(()=>{if(!p||y!=="entering"||!C.current)return;const B=requestAnimationFrame(()=>{const N=j.current,G=C.current;!N||!G||k(Xo(G.getBoundingClientRect(),N))});return()=>cancelAnimationFrame(B)},[p,y]);const q=d.useCallback(()=>m(!1),[]);d.useEffect(()=>{if(!g)return;const B=G=>{var z,M;const P=G.target;(z=C.current)!=null&&z.contains(P)||(M=j.current)!=null&&M.contains(P)||q()},N=requestAnimationFrame(()=>{document.addEventListener("mousedown",B)});return()=>{cancelAnimationFrame(N),document.removeEventListener("mousedown",B)}},[g,q]),d.useEffect(()=>{if(!g)return;const B=N=>{N.key==="Escape"&&q()};return document.addEventListener("keydown",B),()=>document.removeEventListener("keydown",B)},[g,q]);const D=B=>{const N=w.includes(B)?w.filter(G=>G!==B):[...w,B];h||S(N),r==null||r(N)},I=()=>{h||S([]),r==null||r([]),q()},F=B=>{if(B.key==="ArrowDown")B.preventDefault(),A(N=>(N+1)%a.length);else if(B.key==="ArrowUp")B.preventDefault(),A(N=>(N-1+a.length)%a.length);else if((B.key==="Enter"||B.key===" ")&&v>=0){B.preventDefault();const N=a[v];N&&!N.disabled&&D(N.value)}},R=Yo[s],O=Ko[s];return e.jsxs("span",{ref:C,style:{display:"inline-flex",...u},children:[e.jsxs(E.Button,{variant:o==="outline"&&w.length>0?"secondary":o==="ghost"?"ghost":o,size:s,...t?{chevron:!0}:{},disabled:l,onClick:()=>!l&&m(B=>!B),...c!==void 0&&{leftIcon:c},children:[t||void 0,w.length>0&&e.jsx(ve,{variant:"accent",size:s==="xs"?"sm":s,children:w.length})]}),p&&oe.createPortal(e.jsxs("div",{ref:j,role:"listbox","aria-multiselectable":!0,"aria-label":t,tabIndex:-1,onKeyDown:F,style:{position:"fixed",top:(T==null?void 0:T.top)??-9999,left:(T==null?void 0:T.left)??-9999,zIndex:1e3,minWidth:180,background:"color-mix(in srgb, var(--lucent-surface-overlay) 85%, transparent)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 15%, var(--lucent-border-default))",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 0 24px -4px color-mix(in srgb, var(--lucent-accent-default) 12%, transparent), var(--lucent-shadow-md)",padding:R,maxHeight:"min(320px, calc(100vh - 32px))",overflowY:"auto",animation:y==="exiting"?`lucent-filter-ms-out ${Ne}ms var(--lucent-easing-default) forwards`:`lucent-filter-ms-in ${Ne}ms var(--lucent-easing-decelerate)`,transformOrigin:(T==null?void 0:T.transformOrigin)??"top left",outline:"none",pointerEvents:y==="exiting"?"none":"auto",fontFamily:"var(--lucent-font-family-base)"},children:[a.map((B,N)=>{const G=w.includes(B.value),P=v===N;return e.jsxs("div",{role:"option","aria-selected":G,"aria-disabled":B.disabled,onClick:()=>!B.disabled&&D(B.value),onMouseEnter:()=>A(N),style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-2)",padding:`var(--lucent-space-2) ${R}`,borderRadius:"var(--lucent-radius-md)",cursor:B.disabled?"not-allowed":"pointer",background:P?"var(--lucent-surface-secondary)":"transparent",opacity:B.disabled?.5:1,transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)"},children:[e.jsx(ze,{checked:G,onChange:()=>{},disabled:B.disabled,style:{pointerEvents:"none"}}),B.swatch?e.jsx(ve,{size:s==="xs"?"sm":s,swatch:B.swatch,children:B.label}):e.jsx(E.Text,{size:O,children:B.label})]},B.value)}),e.jsx("div",{role:"separator",style:{height:1,margin:"var(--lucent-space-1) 0",background:"var(--lucent-border-subtle)"}}),e.jsx("div",{onClick:w.length>0?I:void 0,style:{display:"flex",alignItems:"center",padding:`var(--lucent-space-2) ${R}`,borderRadius:"var(--lucent-radius-md)",cursor:w.length>0?"pointer":"default",opacity:w.length>0?1:.5},children:e.jsx(E.Text,{size:O,color:w.length>0?"secondary":"disabled",children:"Clear all"})})]}),document.body)]})}function Zo({label:t,options:a,value:n,defaultValue:i,onChange:r,variant:o="secondary",size:s="sm",disabled:l=!1,icon:c,style:u}){var y;const h=n!==void 0,[f,S]=d.useState(i),w=h?n:f,g=(y=a.find(x=>x.value===w))==null?void 0:y.label,m=w!==void 0,p=x=>{h||S(x),r==null||r(x)},b=()=>{h||S(void 0),r==null||r(void 0)};return e.jsxs(rt,{trigger:e.jsx(E.Button,{variant:o==="outline"&&m?"secondary":o,size:s,chevron:!0,disabled:l,...c!==void 0&&{leftIcon:c},children:g??t}),size:s,...u!==void 0&&{style:u},children:[a.map(x=>e.jsx(je,{selected:w===x.value,...x.disabled!==void 0&&{disabled:x.disabled},onSelect:()=>p(x.value),children:x.label},x.value)),m&&e.jsxs(e.Fragment,{children:[e.jsx(Rt,{}),e.jsx(je,{onSelect:b,children:e.jsx(E.Text,{size:s==="lg"?"md":"sm",color:"secondary",children:"Clear"})})]})]})}const Qo=()=>e.jsxs("svg",{width:16,height:16,viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"6.5",cy:"6.5",r:"4",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M9.5 9.5L13 13",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]});function ei({value:t,defaultValue:a="",onChange:n,placeholder:i="Search…",variant:r="secondary",size:o="sm",width:s=260,disabled:l=!1,style:c}){const u=t!==void 0,[h,f]=d.useState(a),S=u?t:h,[w,g]=d.useState(!!S),m=d.useRef(null);d.useEffect(()=>{w&&requestAnimationFrame(()=>{var x;return(x=m.current)==null?void 0:x.focus()})},[w]);const p=x=>{const T=x.target.value;u||f(T),n==null||n(T)},b=()=>{S||g(!1)},y=()=>{l||g(!0)};return w?e.jsx(E.Input,{ref:m,placeholder:i,size:o,value:S,onChange:p,onBlur:b,disabled:l,style:{width:s,...c}}):e.jsx(E.Button,{variant:r,size:o,disabled:l,onClick:y,"aria-label":"Search",leftIcon:e.jsx(Qo,{}),style:c})}function ti(t,a){if(!t)return a;const n=i=>i.toLocaleDateString();return`${n(t.start)} → ${n(t.end)}`}function ni({label:t="Date range",value:a,defaultValue:n,onChange:i,variant:r="secondary",size:o="sm",min:s,max:l,disabled:c=!1,style:u}){const h=a!==void 0;return e.jsx(Vt,{size:o,...a!==void 0&&{value:a},...n!==void 0&&{defaultValue:n},...i!==void 0&&{onChange:i},...s!==void 0&&{min:s},...l!==void 0&&{max:l},disabled:c,trigger:e.jsx(E.Button,{variant:r==="outline"&&h?"secondary":r,size:o,chevron:!0,disabled:c,children:ti(a,t)}),...u!==void 0&&{style:u}})}const ai={sm:{circle:24,checkIcon:12,connector:2,labelSize:"var(--lucent-font-size-xs)",descSize:"var(--lucent-font-size-xs)",prefixSize:"var(--lucent-font-size-xs)",gap:"var(--lucent-space-2)"},md:{circle:32,checkIcon:14,connector:2,labelSize:"var(--lucent-font-size-sm)",descSize:"var(--lucent-font-size-xs)",prefixSize:"var(--lucent-font-size-xs)",gap:"var(--lucent-space-3)"},lg:{circle:40,checkIcon:18,connector:3,labelSize:"var(--lucent-font-size-md)",descSize:"var(--lucent-font-size-sm)",prefixSize:"var(--lucent-font-size-xs)",gap:"var(--lucent-space-3)"}};function ri(t){return typeof t=="string"?{label:t}:t}function oi({size:t}){return e.jsx("svg",{width:t,height:t,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:3,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,children:e.jsx("polyline",{points:"20 6 9 17 4 12"})})}function tt(t,a){return t<a?"completed":t===a?"current":"pending"}const ii={completed:"Completed",current:"In Progress",pending:"Pending"},si={completed:"success",current:"accent",pending:"neutral"},Ct="lucent-stepper-keyframes";function li(){if(typeof document>"u"||document.getElementById(Ct))return;const t=document.createElement("style");t.id=Ct,t.textContent="@keyframes lucent-stepper-check{0%{transform:scale(0)}60%{transform:scale(1.2)}100%{transform:scale(1)}}",document.head.appendChild(t)}function Kt({index:t,state:a,step:n,cfg:i}){const r=a!=="pending";return e.jsx("div",{"aria-current":a==="current"?"step":void 0,style:{width:i.circle,height:i.circle,borderRadius:"50%",display:"flex",alignItems:"center",justifyContent:"center",fontSize:i.labelSize,fontWeight:"var(--lucent-font-weight-semibold)",fontFamily:"var(--lucent-font-family-base)",background:r?"var(--lucent-accent-default)":"color-mix(in srgb, var(--lucent-text-secondary) 12%, var(--lucent-surface))",color:r?"var(--lucent-accent-fg)":"var(--lucent-text-secondary)",transition:"background var(--lucent-duration-base) var(--lucent-easing-default), color var(--lucent-duration-base) var(--lucent-easing-default)",flexShrink:0},children:n.icon?n.icon:a==="completed"?e.jsx("span",{style:{display:"flex",animation:"lucent-stepper-check 300ms cubic-bezier(0.34, 1.56, 0.64, 1)"},children:e.jsx(oi,{size:i.checkIcon})}):t+1})}function Xt({state:t}){return e.jsx(ve,{variant:si[t],size:"sm",borderless:!0,children:ii[t]})}function ci({step:t,state:a,index:n,cfg:i,numbered:r,showStatus:o}){return e.jsxs(e.Fragment,{children:[r&&e.jsxs("span",{style:{fontSize:i.prefixSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",color:"var(--lucent-text-secondary)",textTransform:"uppercase",letterSpacing:"var(--lucent-letter-spacing-wide)"},children:["Step ",n+1]}),e.jsx("span",{style:{fontSize:i.labelSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:a==="current"?"var(--lucent-font-weight-semibold)":"var(--lucent-font-weight-regular)",color:a!=="pending"?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",transition:"color var(--lucent-duration-base) var(--lucent-easing-default)"},children:t.label}),t.description&&e.jsx("span",{style:{fontSize:i.descSize,fontFamily:"var(--lucent-font-family-base)",color:"var(--lucent-text-secondary)"},children:t.description}),o&&e.jsx(Xt,{state:a})]})}function di({steps:t,current:a,cfg:n,numbered:i,showStatus:r}){return e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:n.gap},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",position:"relative"},children:[e.jsx("div",{style:{position:"absolute",left:n.circle/2,right:n.circle/2,top:n.circle/2-n.connector/2,height:n.connector,borderRadius:n.connector/2,background:"var(--lucent-border-default)",overflow:"hidden"},children:e.jsx("div",{style:{height:"100%",borderRadius:n.connector/2,background:"var(--lucent-accent-default)",width:t.length>1?`${a/(t.length-1)*100}%`:"0%",transition:"width 300ms var(--lucent-easing-default)"}})}),t.map((o,s)=>e.jsx("div",{style:{flex:1,display:"flex",justifyContent:s===0?"flex-start":s===t.length-1?"flex-end":"center",position:"relative",zIndex:1},children:e.jsx(Kt,{index:s,state:tt(s,a),step:o,cfg:n})},o.label))]}),e.jsx("div",{style:{display:"flex"},children:t.map((o,s)=>{const l=tt(s,a),c=s===t.length-1,u=s===0?"flex-start":c?"flex-end":"center",h=s===0?"left":c?"right":"center";return e.jsx("div",{style:{flex:1,display:"flex",flexDirection:"column",alignItems:u,gap:"2px",textAlign:h},children:e.jsx(ci,{step:o,state:l,index:s,cfg:n,numbered:i,showStatus:r})},o.label)})})]})}function ui({steps:t,current:a,cfg:n,numbered:i,showStatus:r}){return e.jsx("div",{style:{display:"flex",flexDirection:"column"},children:t.map((o,s)=>{const l=tt(s,a),c=s===t.length-1;return e.jsxs("div",{style:{display:"flex",gap:n.gap},children:[e.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",flexShrink:0},children:[e.jsx(Kt,{index:s,state:l,step:o,cfg:n}),!c&&e.jsx("div",{style:{width:n.connector,flex:1,minHeight:24,borderRadius:n.connector/2,background:s<a?"var(--lucent-accent-default)":"var(--lucent-border-default)",transition:"background var(--lucent-duration-base) var(--lucent-easing-default)",margin:"4px 0"}})]}),e.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start",gap:"2px",paddingTop:(n.circle-18)/2,paddingBottom:c?0:"var(--lucent-space-4)"},children:[i&&e.jsxs("span",{style:{fontSize:n.prefixSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",color:"var(--lucent-text-secondary)",textTransform:"uppercase",letterSpacing:"var(--lucent-letter-spacing-wide)"},children:["Step ",s+1]}),e.jsx("span",{style:{fontSize:n.labelSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:l==="current"?"var(--lucent-font-weight-semibold)":"var(--lucent-font-weight-regular)",color:l!=="pending"?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",transition:"color var(--lucent-duration-base) var(--lucent-easing-default)"},children:o.label}),o.description&&e.jsx("span",{style:{fontSize:n.descSize,fontFamily:"var(--lucent-font-family-base)",color:"var(--lucent-text-secondary)"},children:o.description}),r&&e.jsx(Xt,{state:l})]})]},o.label)})})}function pi({steps:t,current:a,size:n="md",orientation:i="horizontal",numbered:r=!1,showStatus:o=!1,style:s}){d.useEffect(li,[]);const l=ai[n],c=t.map(ri),u=i==="vertical"?ui:di;return e.jsx("div",{role:"group","aria-label":"Progress steps",style:{fontFamily:"var(--lucent-font-family-base)",...s},children:e.jsx(u,{steps:c,current:a,cfg:l,numbered:r,showStatus:o})})}const hi={id:"stepper",name:"Stepper",tier:"molecule",domain:"neutral",specVersion:"1.0",description:"A step indicator for multi-step flows — onboarding, wizards, checkout. Supports horizontal and vertical orientations with animated transitions.",designIntent:'Use Stepper to visualise progress through a sequence of discrete steps. Steps can be simple strings or objects with label, description, and custom icon. Completed steps show an animated checkmark (spring scale 0→1.2→1), the current step is highlighted with accent, and future steps are subdued. In horizontal mode the connector track runs behind all circles as a continuous bar; the filled portion animates smoothly between steps. First/last labels align left/right; middle labels center under their circles. Enable numbered to show "STEP N" prefixes and showStatus for Completed/In Progress/Pending Chip badges (success/accent/neutral variants). The vertical orientation suits sidebar layouts and forms with longer descriptive steps. Unlike Progress, Stepper is for discrete named stages, not continuous percentages.',props:[{name:"steps",type:"array",required:!0,description:"Step definitions. Each element is either a string (used as label) or an object { label: string, description?: string, icon?: ReactNode }. Custom icons override the default number/checkmark in the circle."},{name:"current",type:"number",required:!0,description:"Zero-based index of the active step. Steps before this index show as completed; steps after show as pending."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls circle diameter (sm=24, md=32, lg=40), checkmark size, connector thickness, and label font size.",enumValues:["sm","md","lg"]},{name:"orientation",type:"enum",required:!1,default:"horizontal",description:"Layout direction. Horizontal shows circles in a row with a connector track behind them; vertical stacks them with a connector column on the left.",enumValues:["horizontal","vertical"]},{name:"numbered",type:"boolean",required:!1,default:"false",description:'Show uppercase "STEP N" prefix above each step label.'},{name:"showStatus",type:"boolean",required:!1,default:"false",description:'Show a Chip badge below each label — "Completed" (success), "In Progress" (accent), or "Pending" (neutral).'},{name:"style",type:"object",required:!1,description:"Inline style overrides for the root container."}],usageExamples:[{title:"Basic horizontal",code:"<Stepper steps={['Profile', 'Preferences', 'Confirm']} current={1} />",description:"Minimal stepper — string labels, no extras."},{title:"Numbered with status badges",code:`<Stepper
|
|
537
|
+
`;let Mt=!1;const ei={xs:"var(--lucent-space-1)",sm:"var(--lucent-space-2)",md:"var(--lucent-space-2)",lg:"var(--lucent-space-3)"},ti={xs:"sm",sm:"sm",md:"md",lg:"lg"},Oe=4;function ni(t,r){const n=r.offsetHeight,i=r.offsetWidth,a=window.innerWidth,o=window.innerHeight;let l=t.bottom+Oe,s=t.left,d="top left";return l+n>o&&t.top-n-Oe>=0&&(l=t.top-n-Oe,d="bottom left"),s+i>a&&(s=a-i-8),s<0&&(s=8),{top:l,left:s,transformOrigin:d}}function ri({label:t,options:r,value:n,defaultValue:i=[],onChange:a,variant:o="secondary",size:l="sm",disabled:s=!1,icon:d,style:p}){const h=n!==void 0,[f,w]=c.useState(i),b=h?n:f,[g,m]=c.useState(!1),[u,y]=c.useState(!1),[x,T]=c.useState("idle"),[S,k]=c.useState(null),[v,A]=c.useState(-1),C=c.useRef(null),M=c.useRef(null);if(!Mt&&typeof document<"u"){const R=document.createElement("style");R.textContent=Qo,document.head.appendChild(R),Mt=!0}c.useEffect(()=>{if(g)y(!0),T("entering"),A(-1);else if(u){T("exiting");const R=setTimeout(()=>{y(!1),T("idle")},Ve);return()=>clearTimeout(R)}},[g]),c.useLayoutEffect(()=>{if(!u||x!=="entering"||!C.current)return;const R=requestAnimationFrame(()=>{const W=M.current,G=C.current;!W||!G||k(ni(G.getBoundingClientRect(),W))});return()=>cancelAnimationFrame(R)},[u,x]);const q=c.useCallback(()=>m(!1),[]);c.useEffect(()=>{if(!g)return;const R=G=>{var z,j;const P=G.target;(z=C.current)!=null&&z.contains(P)||(j=M.current)!=null&&j.contains(P)||q()},W=requestAnimationFrame(()=>{document.addEventListener("mousedown",R)});return()=>{cancelAnimationFrame(W),document.removeEventListener("mousedown",R)}},[g,q]),c.useEffect(()=>{if(!g)return;const R=W=>{W.key==="Escape"&&q()};return document.addEventListener("keydown",R),()=>document.removeEventListener("keydown",R)},[g,q]);const B=R=>{const W=b.includes(R)?b.filter(G=>G!==R):[...b,R];h||w(W),a==null||a(W)},I=()=>{h||w([]),a==null||a([]),q()},L=R=>{if(R.key==="ArrowDown")R.preventDefault(),A(W=>(W+1)%r.length);else if(R.key==="ArrowUp")R.preventDefault(),A(W=>(W-1+r.length)%r.length);else if((R.key==="Enter"||R.key===" ")&&v>=0){R.preventDefault();const W=r[v];W&&!W.disabled&&B(W.value)}},D=ei[l],N=ti[l];return e.jsxs("span",{ref:C,style:{display:"inline-flex",...p},children:[e.jsxs(E.Button,{variant:o==="outline"&&b.length>0?"secondary":o==="ghost"?"ghost":o,size:l,...t?{chevron:!0}:{},disabled:s,onClick:()=>!s&&m(R=>!R),...d!==void 0&&{leftIcon:d},children:[t||void 0,b.length>0&&e.jsx(be,{variant:"accent",size:l==="xs"?"sm":l,children:b.length})]}),u&&Q.createPortal(e.jsxs("div",{ref:M,role:"listbox","aria-multiselectable":!0,"aria-label":t,tabIndex:-1,onKeyDown:L,style:{position:"fixed",top:(S==null?void 0:S.top)??-9999,left:(S==null?void 0:S.left)??-9999,zIndex:1e3,minWidth:180,background:"color-mix(in srgb, var(--lucent-surface-overlay) 85%, transparent)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",border:"1px solid color-mix(in srgb, var(--lucent-accent-default) 15%, var(--lucent-border-default))",borderRadius:"var(--lucent-radius-lg)",boxShadow:"0 0 24px -4px color-mix(in srgb, var(--lucent-accent-default) 12%, transparent), var(--lucent-shadow-md)",padding:D,maxHeight:"min(320px, calc(100vh - 32px))",overflowY:"auto",animation:x==="exiting"?`lucent-filter-ms-out ${Ve}ms var(--lucent-easing-default) forwards`:`lucent-filter-ms-in ${Ve}ms var(--lucent-easing-decelerate)`,transformOrigin:(S==null?void 0:S.transformOrigin)??"top left",outline:"none",pointerEvents:x==="exiting"?"none":"auto",fontFamily:"var(--lucent-font-family-base)"},children:[r.map((R,W)=>{const G=b.includes(R.value),P=v===W;return e.jsxs("div",{role:"option","aria-selected":G,"aria-disabled":R.disabled,onClick:()=>!R.disabled&&B(R.value),onMouseEnter:()=>A(W),style:{display:"flex",alignItems:"center",gap:"var(--lucent-space-2)",padding:`var(--lucent-space-2) ${D}`,borderRadius:"var(--lucent-radius-md)",cursor:R.disabled?"not-allowed":"pointer",background:P?"var(--lucent-surface-secondary)":"transparent",opacity:R.disabled?.5:1,transition:"background var(--lucent-duration-fast) var(--lucent-easing-default)"},children:[e.jsx(Ae,{checked:G,onChange:()=>{},disabled:R.disabled,style:{pointerEvents:"none"}}),R.swatch?e.jsx(be,{size:l==="xs"?"sm":l,swatch:R.swatch,children:R.label}):e.jsx(E.Text,{size:N,children:R.label})]},R.value)}),e.jsx("div",{role:"separator",style:{height:1,margin:"var(--lucent-space-1) 0",background:"var(--lucent-border-subtle)"}}),e.jsx("div",{onClick:b.length>0?I:void 0,style:{display:"flex",alignItems:"center",padding:`var(--lucent-space-2) ${D}`,borderRadius:"var(--lucent-radius-md)",cursor:b.length>0?"pointer":"default",opacity:b.length>0?1:.5},children:e.jsx(E.Text,{size:N,color:b.length>0?"secondary":"disabled",children:"Clear all"})})]}),document.body)]})}function ai({label:t,options:r,value:n,defaultValue:i,onChange:a,variant:o="secondary",size:l="sm",disabled:s=!1,icon:d,style:p}){var x;const h=n!==void 0,[f,w]=c.useState(i),b=h?n:f,g=(x=r.find(T=>T.value===b))==null?void 0:x.label,m=b!==void 0,u=T=>{h||w(T),a==null||a(T)},y=()=>{h||w(void 0),a==null||a(void 0)};return e.jsxs(it,{trigger:e.jsx(E.Button,{variant:o==="outline"&&m?"secondary":o,size:l,chevron:!0,disabled:s,...d!==void 0&&{leftIcon:d},children:g??t}),size:l,...p!==void 0&&{style:p},children:[r.map(T=>e.jsx(Me,{selected:b===T.value,...T.disabled!==void 0&&{disabled:T.disabled},onSelect:()=>u(T.value),children:T.label},T.value)),m&&e.jsxs(e.Fragment,{children:[e.jsx(Nt,{}),e.jsx(Me,{onSelect:y,children:e.jsx(E.Text,{size:l==="lg"?"md":"sm",color:"secondary",children:"Clear"})})]})]})}const oi=()=>e.jsxs("svg",{width:16,height:16,viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e.jsx("circle",{cx:"6.5",cy:"6.5",r:"4",stroke:"currentColor",strokeWidth:"1.5"}),e.jsx("path",{d:"M9.5 9.5L13 13",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]});function ii({value:t,defaultValue:r="",onChange:n,placeholder:i="Search…",variant:a="secondary",size:o="sm",width:l=260,disabled:s=!1,style:d}){const p=t!==void 0,[h,f]=c.useState(r),w=p?t:h,[b,g]=c.useState(!!w),m=c.useRef(null);c.useEffect(()=>{b&&requestAnimationFrame(()=>{var T;return(T=m.current)==null?void 0:T.focus()})},[b]);const u=T=>{const S=T.target.value;p||f(S),n==null||n(S)},y=()=>{w||g(!1)},x=()=>{s||g(!0)};return b?e.jsx(E.Input,{ref:m,placeholder:i,size:o,value:w,onChange:u,onBlur:y,disabled:s,style:{width:l,...d}}):e.jsx(E.Button,{variant:a,size:o,disabled:s,onClick:x,"aria-label":"Search",leftIcon:e.jsx(oi,{}),style:d})}function si(t,r){if(!t)return r;const n=i=>i.toLocaleDateString();return`${n(t.start)} → ${n(t.end)}`}function li({label:t="Date range",value:r,defaultValue:n,onChange:i,variant:a="secondary",size:o="sm",min:l,max:s,disabled:d=!1,style:p}){const h=r!==void 0;return e.jsx(Yt,{size:o,...r!==void 0&&{value:r},...n!==void 0&&{defaultValue:n},...i!==void 0&&{onChange:i},...l!==void 0&&{min:l},...s!==void 0&&{max:s},disabled:d,trigger:e.jsx(E.Button,{variant:a==="outline"&&h?"secondary":a,size:o,chevron:!0,disabled:d,children:si(r,t)}),...p!==void 0&&{style:p}})}const ci={sm:{circle:24,checkIcon:12,connector:2,labelSize:"var(--lucent-font-size-xs)",descSize:"var(--lucent-font-size-xs)",prefixSize:"var(--lucent-font-size-xs)",gap:"var(--lucent-space-2)"},md:{circle:32,checkIcon:14,connector:2,labelSize:"var(--lucent-font-size-sm)",descSize:"var(--lucent-font-size-xs)",prefixSize:"var(--lucent-font-size-xs)",gap:"var(--lucent-space-3)"},lg:{circle:40,checkIcon:18,connector:3,labelSize:"var(--lucent-font-size-md)",descSize:"var(--lucent-font-size-sm)",prefixSize:"var(--lucent-font-size-xs)",gap:"var(--lucent-space-3)"}};function di(t){return typeof t=="string"?{label:t}:t}function ui({size:t}){return e.jsx("svg",{width:t,height:t,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:3,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,children:e.jsx("polyline",{points:"20 6 9 17 4 12"})})}function rt(t,r){return t<r?"completed":t===r?"current":"pending"}const pi={completed:"Completed",current:"In Progress",pending:"Pending"},hi={completed:"success",current:"accent",pending:"neutral"},Et="lucent-stepper-keyframes";function fi(){if(typeof document>"u"||document.getElementById(Et))return;const t=document.createElement("style");t.id=Et,t.textContent="@keyframes lucent-stepper-check{0%{transform:scale(0)}60%{transform:scale(1.2)}100%{transform:scale(1)}}",document.head.appendChild(t)}function en({index:t,state:r,step:n,cfg:i}){const a=r!=="pending";return e.jsx("div",{"aria-current":r==="current"?"step":void 0,style:{width:i.circle,height:i.circle,borderRadius:"50%",display:"flex",alignItems:"center",justifyContent:"center",fontSize:i.labelSize,fontWeight:"var(--lucent-font-weight-semibold)",fontFamily:"var(--lucent-font-family-base)",background:a?"var(--lucent-accent-default)":"color-mix(in srgb, var(--lucent-text-secondary) 12%, var(--lucent-surface))",color:a?"var(--lucent-accent-fg)":"var(--lucent-text-secondary)",transition:"background var(--lucent-duration-base) var(--lucent-easing-default), color var(--lucent-duration-base) var(--lucent-easing-default)",flexShrink:0},children:n.icon?n.icon:r==="completed"?e.jsx("span",{style:{display:"flex",animation:"lucent-stepper-check 300ms cubic-bezier(0.34, 1.56, 0.64, 1)"},children:e.jsx(ui,{size:i.checkIcon})}):t+1})}function tn({state:t}){return e.jsx(be,{variant:hi[t],size:"sm",borderless:!0,children:pi[t]})}function mi({step:t,state:r,index:n,cfg:i,numbered:a,showStatus:o}){return e.jsxs(e.Fragment,{children:[a&&e.jsxs("span",{style:{fontSize:i.prefixSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",color:"var(--lucent-text-secondary)",textTransform:"uppercase",letterSpacing:"var(--lucent-letter-spacing-wide)"},children:["Step ",n+1]}),e.jsx("span",{style:{fontSize:i.labelSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:r==="current"?"var(--lucent-font-weight-semibold)":"var(--lucent-font-weight-regular)",color:r!=="pending"?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",transition:"color var(--lucent-duration-base) var(--lucent-easing-default)"},children:t.label}),t.description&&e.jsx("span",{style:{fontSize:i.descSize,fontFamily:"var(--lucent-font-family-base)",color:"var(--lucent-text-secondary)"},children:t.description}),o&&e.jsx(tn,{state:r})]})}function gi({steps:t,current:r,cfg:n,numbered:i,showStatus:a}){return e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:n.gap},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",position:"relative"},children:[e.jsx("div",{style:{position:"absolute",left:n.circle/2,right:n.circle/2,top:n.circle/2-n.connector/2,height:n.connector,borderRadius:n.connector/2,background:"var(--lucent-border-default)",overflow:"hidden"},children:e.jsx("div",{style:{height:"100%",borderRadius:n.connector/2,background:"var(--lucent-accent-default)",width:t.length>1?`${r/(t.length-1)*100}%`:"0%",transition:"width 300ms var(--lucent-easing-default)"}})}),t.map((o,l)=>e.jsx("div",{style:{flex:1,display:"flex",justifyContent:l===0?"flex-start":l===t.length-1?"flex-end":"center",position:"relative",zIndex:1},children:e.jsx(en,{index:l,state:rt(l,r),step:o,cfg:n})},o.label))]}),e.jsx("div",{style:{display:"flex"},children:t.map((o,l)=>{const s=rt(l,r),d=l===t.length-1,p=l===0?"flex-start":d?"flex-end":"center",h=l===0?"left":d?"right":"center";return e.jsx("div",{style:{flex:1,display:"flex",flexDirection:"column",alignItems:p,gap:"2px",textAlign:h},children:e.jsx(mi,{step:o,state:s,index:l,cfg:n,numbered:i,showStatus:a})},o.label)})})]})}function vi({steps:t,current:r,cfg:n,numbered:i,showStatus:a}){return e.jsx("div",{style:{display:"flex",flexDirection:"column"},children:t.map((o,l)=>{const s=rt(l,r),d=l===t.length-1;return e.jsxs("div",{style:{display:"flex",gap:n.gap},children:[e.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",flexShrink:0},children:[e.jsx(en,{index:l,state:s,step:o,cfg:n}),!d&&e.jsx("div",{style:{width:n.connector,flex:1,minHeight:24,borderRadius:n.connector/2,background:l<r?"var(--lucent-accent-default)":"var(--lucent-border-default)",transition:"background var(--lucent-duration-base) var(--lucent-easing-default)",margin:"4px 0"}})]}),e.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start",gap:"2px",paddingTop:(n.circle-18)/2,paddingBottom:d?0:"var(--lucent-space-4)"},children:[i&&e.jsxs("span",{style:{fontSize:n.prefixSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:"var(--lucent-font-weight-medium)",color:"var(--lucent-text-secondary)",textTransform:"uppercase",letterSpacing:"var(--lucent-letter-spacing-wide)"},children:["Step ",l+1]}),e.jsx("span",{style:{fontSize:n.labelSize,fontFamily:"var(--lucent-font-family-base)",fontWeight:s==="current"?"var(--lucent-font-weight-semibold)":"var(--lucent-font-weight-regular)",color:s!=="pending"?"var(--lucent-text-primary)":"var(--lucent-text-secondary)",transition:"color var(--lucent-duration-base) var(--lucent-easing-default)"},children:o.label}),o.description&&e.jsx("span",{style:{fontSize:n.descSize,fontFamily:"var(--lucent-font-family-base)",color:"var(--lucent-text-secondary)"},children:o.description}),a&&e.jsx(tn,{state:s})]})]},o.label)})})}function bi({steps:t,current:r,size:n="md",orientation:i="horizontal",numbered:a=!1,showStatus:o=!1,style:l}){c.useEffect(fi,[]);const s=ci[n],d=t.map(di),p=i==="vertical"?vi:gi;return e.jsx("div",{role:"group","aria-label":"Progress steps",style:{fontFamily:"var(--lucent-font-family-base)",...l},children:e.jsx(p,{steps:d,current:r,cfg:s,numbered:a,showStatus:o})})}const yi={id:"stepper",name:"Stepper",tier:"molecule",domain:"neutral",specVersion:"1.0",description:"A step indicator for multi-step flows — onboarding, wizards, checkout. Supports horizontal and vertical orientations with animated transitions.",designIntent:'Use Stepper to visualise progress through a sequence of discrete steps. Steps can be simple strings or objects with label, description, and custom icon. Completed steps show an animated checkmark (spring scale 0→1.2→1), the current step is highlighted with accent, and future steps are subdued. In horizontal mode the connector track runs behind all circles as a continuous bar; the filled portion animates smoothly between steps. First/last labels align left/right; middle labels center under their circles. Enable numbered to show "STEP N" prefixes and showStatus for Completed/In Progress/Pending Chip badges (success/accent/neutral variants). The vertical orientation suits sidebar layouts and forms with longer descriptive steps. Unlike Progress, Stepper is for discrete named stages, not continuous percentages.',props:[{name:"steps",type:"array",required:!0,description:"Step definitions. Each element is either a string (used as label) or an object { label: string, description?: string, icon?: ReactNode }. Custom icons override the default number/checkmark in the circle."},{name:"current",type:"number",required:!0,description:"Zero-based index of the active step. Steps before this index show as completed; steps after show as pending."},{name:"size",type:"enum",required:!1,default:"md",description:"Controls circle diameter (sm=24, md=32, lg=40), checkmark size, connector thickness, and label font size.",enumValues:["sm","md","lg"]},{name:"orientation",type:"enum",required:!1,default:"horizontal",description:"Layout direction. Horizontal shows circles in a row with a connector track behind them; vertical stacks them with a connector column on the left.",enumValues:["horizontal","vertical"]},{name:"numbered",type:"boolean",required:!1,default:"false",description:'Show uppercase "STEP N" prefix above each step label.'},{name:"showStatus",type:"boolean",required:!1,default:"false",description:'Show a Chip badge below each label — "Completed" (success), "In Progress" (accent), or "Pending" (neutral).'},{name:"style",type:"object",required:!1,description:"Inline style overrides for the root container."}],usageExamples:[{title:"Basic horizontal",code:"<Stepper steps={['Profile', 'Preferences', 'Confirm']} current={1} />",description:"Minimal stepper — string labels, no extras."},{title:"Numbered with status badges",code:`<Stepper
|
|
533
538
|
steps={['Basic Details', 'Company Details', 'Subscription', 'Payment']}
|
|
534
539
|
current={2}
|
|
535
540
|
numbered
|
|
@@ -565,7 +570,7 @@ dismiss(id);`},{title:"Custom icon",description:"Override the built-in variant i
|
|
|
565
570
|
<Button variant="primary" size="sm" onClick={() => setStep(s => s + 1)}>Continue</Button>
|
|
566
571
|
</Row>
|
|
567
572
|
</Stack>
|
|
568
|
-
</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.'}};function
|
|
573
|
+
</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.'}};function zt(t,r,n){return e.jsx(E.Button,{size:"sm",variant:t.variant??r,...t.onClick!==void 0&&{onClick:t.onClick},...t.leftIcon!==void 0&&{leftIcon:t.leftIcon},...t.rightIcon!==void 0&&{rightIcon:t.rightIcon},...t.disabled!==void 0&&{disabled:t.disabled},...t.loading!==void 0&&{loading:t.loading},...t["aria-label"]!==void 0&&{"aria-label":t["aria-label"]},children:t.label},n)}function xi({title:t,subtitle:r,breadcrumbs:n,action:i,secondaryActions:a,hideDivider:o=!1,style:l}){const s=a!=null&&a.length>0,d=i!=null,p=s||d;return e.jsxs(Xe,{gap:"4",...l!==void 0&&{style:l},children:[n!=null&&n.length>0&&e.jsx(Ht,{items:n}),e.jsxs(Je,{justify:"between",align:"end",wrap:!0,gap:"3",children:[e.jsxs(Xe,{gap:"1",style:{minWidth:0},children:[e.jsx(E.Text,{as:"h1",size:"3xl",weight:"bold",family:"display",children:t}),r!=null&&e.jsx(E.Text,{size:"sm",color:"secondary",children:r})]}),p&&e.jsxs(Je,{gap:"2",wrap:!0,children:[s&&a.map((h,f)=>zt(h,"outline",f)),d&&zt(i,"primary")]})]}),!o&&e.jsx(Lt,{})]})}const wi={id:"page-header",name:"PageHeader",tier:"molecule",domain:"neutral",specVersion:"0.1",description:"A page-level header pairing breadcrumbs, a large display title, optional subtitle, and up to one primary CTA with 0–3 secondary outline actions.",designIntent:'PageHeader is the canonical chrome for a page body. It encapsulates the action-bar pattern — breadcrumbs on top, a 3xl display title with optional subtitle below, action buttons anchored to the title baseline, and a divider separating the header from page content. The component owns three zones: breadcrumbs (nav context), title block, and action slot. \n\n## Multi-action support\nThe `action` prop holds the single primary CTA (rightmost position, `variant="primary"` by default). The `secondaryActions` prop accepts 0–3 additional actions that render to the left of the primary action with `variant="outline"` by default, keeping them visually subordinate. This covers the common detail-page need for Edit / Archive / primary-CTA triples without forcing consumers to rebuild the header from primitives. Beyond 3 secondary actions, reach for SplitButton or an overflow menu instead of widening the row.\n\n## Responsive behaviour\nBoth the outer title/action Row and the inner action Row set `wrap` so the button group flows below the title on narrow viewports instead of overflowing. `align="end"` anchors the buttons to the baseline of the subtitle line when the layout is horizontal.\n\n## Styling\nAll spacing, typography, and colors come from Lucent tokens — no custom values. The title uses the display font family and 3xl size; the subtitle uses the base font at sm/secondary. The bottom divider can be hidden via `hideDivider` when the header sits directly above another bordered surface.',props:[{name:"title",type:"string",required:!0,description:"Page title rendered as an h1 in the display font at 3xl size."},{name:"subtitle",type:"ReactNode",required:!1,description:"Optional subtitle or metadata line rendered below the title at sm/secondary."},{name:"breadcrumbs",type:"array",required:!1,description:"Breadcrumb items rendered above the title via the Breadcrumb molecule. Provides navigation context."},{name:"action",type:"object",required:!1,description:'Single primary CTA rendered rightmost. Shape: { label, onClick?, variant?, leftIcon?, rightIcon?, disabled?, loading? }. Defaults to variant="primary". Pass `null` to explicitly omit when spreading dynamic props.'},{name:"secondaryActions",type:"array",required:!1,description:'0–3 secondary actions rendered to the left of `action`. Each item uses the same PageHeaderAction shape as `action`. Defaults to variant="outline" so they stay visually subordinate. For more than 3 actions, use a SplitButton or overflow menu.'},{name:"hideDivider",type:"boolean",required:!1,default:"false",description:"Hide the bottom divider. Use when the header sits directly above another bordered surface."},{name:"style",type:"object",required:!1,description:"Inline style overrides for the outer Stack wrapper."}],usageExamples:[{title:"Minimal — title only",code:'<PageHeader title="Settings" />'},{title:"With subtitle and single primary action",code:`<PageHeader
|
|
569
574
|
title="Acme Corp"
|
|
570
575
|
subtitle="Last updated 5 minutes ago"
|
|
571
576
|
action={{ label: 'New report', onClick: () => createReport() }}
|
|
@@ -597,7 +602,7 @@ dismiss(id);`},{title:"Custom icon",description:"Override the built-in variant i
|
|
|
597
602
|
{ label: 'Export', onClick: () => exportAll() },
|
|
598
603
|
{ label: 'Filter', onClick: () => openFilter() },
|
|
599
604
|
]}
|
|
600
|
-
/>`}],compositionGraph:[{componentId:"breadcrumb",componentName:"Breadcrumb",role:"Navigation context above the title",required:!1},{componentId:"stack",componentName:"Stack",role:"Vertical layout for title and subtitle",required:!0},{componentId:"row",componentName:"Row",role:"Horizontal layout for title block and action slot",required:!0},{componentId:"text",componentName:"Text",role:"Title and subtitle rendering",required:!0},{componentId:"button",componentName:"Button",role:"Primary and secondary action buttons",required:!1},{componentId:"divider",componentName:"Divider",role:"Separator between header chrome and page content",required:!1}],accessibility:{notes:`The title renders as an <h1>, providing the page heading landmark for assistive tech. Breadcrumbs render inside <nav aria-label="Breadcrumb">. Action buttons inherit Button's focus ring, keyboard activation, and aria-label forwarding — pass aria-label on each action object when the label alone is insufficient (e.g. icon-only buttons).`}},jt={accent:"var(--lucent-accent-default)",success:"var(--lucent-success-default)",warning:"var(--lucent-warning-default)",danger:"var(--lucent-danger-default)",info:"var(--lucent-info-default)"},X=["var(--lucent-accent-default)","var(--lucent-success-default)","var(--lucent-info-default)","var(--lucent-warning-default)","var(--lucent-danger-default)"];function it(t,a="accent"){return t?jt[t]??t:jt[a]}function st(t,a){const[n,i]=t,[r,o]=a,s=i-n;return s===0?()=>(r+o)/2:l=>r+(l-n)/s*(o-r)}function Jt(t,a,n){if(t===a)return[t];const i=(a-t)/Math.max(n-1,1),r=Math.pow(10,Math.floor(Math.log10(i))),o=i/r,s=o<=1.5?r:o<=3?2*r:o<=7?5*r:10*r,l=Math.floor(t/s)*s,c=Math.ceil(a/s)*s,u=[];for(let h=l;h<=c+s*.01;h+=s)u.push(Math.round(h*1e10)/1e10);return u}function De(t){return t.length===0?"":t.map((a,n)=>`${n===0?"M":"L"}${a.x},${a.y}`).join(" ")}function Zt(t,a){if(t.length===0)return"";const n=De(t),i=t[t.length-1],r=t[0];return`${n} L${i.x},${a} L${r.x},${a} Z`}function lt(t){const a=t.length;if(a===0)return"";if(a===1)return`M${t[0].x},${t[0].y}`;if(a===2)return De(t);const n=[];for(let o=0;o<a-1;o++){const s=t[o+1].x-t[o].x;n.push(s===0?0:(t[o+1].y-t[o].y)/s)}const i=new Array(a).fill(0);i[0]=n[0],i[a-1]=n[a-2];for(let o=1;o<a-1;o++)n[o-1]*n[o]<=0?i[o]=0:i[o]=(n[o-1]+n[o])/2;for(let o=0;o<a-1;o++)if(n[o]===0)i[o]=0,i[o+1]=0;else{const s=i[o]/n[o],l=i[o+1]/n[o],c=s*s+l*l;if(c>9){const u=3/Math.sqrt(c);i[o]=u*s*n[o],i[o+1]=u*l*n[o]}}let r=`M${t[0].x},${t[0].y}`;for(let o=0;o<a-1;o++){const s=(t[o+1].x-t[o].x)/3,l=t[o].x+s,c=t[o].y+s*i[o],u=t[o+1].x-s,h=t[o+1].y-s*i[o+1];r+=` C${l},${c} ${u},${h} ${t[o+1].x},${t[o+1].y}`}return r}function Qt(t,a){if(t.length===0)return"";const n=lt(t),i=t[t.length-1],r=t[0];return`${n} L${i.x},${a} L${r.x},${a} Z`}function fe(t,a,n,i){const r=(i-90)*Math.PI/180;return{x:t+n*Math.cos(r),y:a+n*Math.sin(r)}}function gi(t,a,n,i,r,o){const s=fe(t,a,n,r),l=fe(t,a,n,o),c=fe(t,a,i,r),u=fe(t,a,i,o),h=o-r>180?1:0;return[`M${s.x},${s.y}`,`A${n},${n} 0 ${h} 1 ${l.x},${l.y}`,`L${u.x},${u.y}`,`A${i},${i} 0 ${h} 0 ${c.x},${c.y}`,"Z"].join(" ")}const le=100,Mt=8;function vi({data:t,width:a="100%",height:n=32,color:i,filled:r=!1,strokeWidth:o=1.5,smooth:s=!0,formatTooltip:l,ariaLabel:c="Sparkline chart",style:u}){const[h,f]=d.useState(null);if(t.length<2)return null;const S=it(i),w=t.length-1,g=Math.min(...t),m=Math.max(...t),p=st([g,m],[le-Mt,Mt]),b=t.map((C,j)=>({x:j,y:p(C)})),y=s?lt(b):De(b),x=r?s?Qt(b,le):Zt(b,le):null,T=C=>{const j=C.currentTarget.getBoundingClientRect(),q=C.clientX-j.left,D=Math.round(q/j.width*(t.length-1));f(Math.max(0,Math.min(D,t.length-1)))},k=h!==null?b[h]:void 0,v=h!==null?t[h]:void 0,A=h===0||h===t.length-1;return e.jsxs("div",{style:{position:"relative",width:a,height:n,...u},children:[e.jsxs("svg",{viewBox:`0 0 ${w} ${le}`,width:"100%",height:"100%",preserveAspectRatio:"none",role:"img","aria-label":c,style:{display:"block",overflow:"visible"},onMouseMove:T,onMouseLeave:()=>f(null),children:[x&&e.jsx("path",{d:x,fill:`color-mix(in srgb, ${S} 15%, transparent)`,stroke:"none"}),e.jsx("path",{d:y,fill:"none",stroke:S,strokeWidth:o,strokeLinecap:"round",strokeLinejoin:"round",vectorEffect:"non-scaling-stroke"})]}),k&&e.jsx("span",{style:{position:"absolute",left:`${k.x/w*100}%`,top:0,width:1,height:"100%",background:"var(--lucent-text-secondary)",opacity:.3,transform:"translateX(-50%)",transition:"left 150ms ease-out",pointerEvents:"none"}}),k&&e.jsx("span",{style:{position:"absolute",left:`${k.x/w*100}%`,top:`${k.y/le*100}%`,width:8,height:8,borderRadius:"50%",background:S,border:"1.5px solid var(--lucent-surface, #fff)",transform:"translate(-50%, -50%)",transition:"left 150ms ease-out, top 150ms ease-out",pointerEvents:"none"}}),k&&v!==void 0&&!A&&e.jsx("span",{style:{position:"absolute",left:`${k.x/w*100}%`,top:`${k.y/le*100}%`,transition:"left 150ms ease-out, top 150ms ease-out",pointerEvents:"none"},children:e.jsx("span",{style:{position:"absolute",bottom:-2},children:e.jsx(be,{content:l?l(v,h):v,open:!0,placement:"top",children:e.jsx("span",{})})})})]})}const bi={id:"sparkline",name:"SparkLine",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A compact, inline trend line for displaying data direction at a glance.",designIntent:"Use SparkLine inside KPI cards, table cells, or compact dashboard tiles to show a trend without axes or labels — surrounding context provides meaning. Defaults to smooth curves for a polished look; set smooth=false for raw data fidelity.",props:[{name:"data",type:"custom",required:!0,description:"Array of numeric values."},{name:"width",type:"union",required:!1,default:"'100%'",description:"Pixel width or CSS string."},{name:"height",type:"number",required:!1,default:"32",description:"Pixel height."},{name:"color",type:"string",required:!1,default:"accent",description:"Color variant name or CSS color."},{name:"filled",type:"boolean",required:!1,default:"false",description:"Show translucent area fill below the line."},{name:"strokeWidth",type:"number",required:!1,default:"1.5",description:"Line stroke width."},{name:"smooth",type:"boolean",required:!1,default:"true",description:"Use smooth monotone-x curves."}],usageExamples:[{title:"Basic",code:"<SparkLine data={[4, 7, 3, 8, 5, 9, 6]} />"},{title:"Filled",code:'<SparkLine data={[4, 7, 3, 8, 5, 9, 6]} filled color="success" />'}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label"],notes:"Purely decorative — ensure surrounding text provides data context."}},ee=300,ce=200,te=40,Oe=8,Ve=16,yi=24;function xi(t,a,n,i,r){const o=Math.min(r,n/2,i);return[`M${t},${a+i}`,`L${t},${a+o}`,`Q${t},${a} ${t+o},${a}`,`L${t+n-o},${a}`,`Q${t+n},${a} ${t+n},${a+o}`,`L${t+n},${a+i}`,"Z"].join(" ")}function ae(t,a){return t/a*100}const He={position:"absolute",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",color:"var(--lucent-text-secondary)",whiteSpace:"nowrap",pointerEvents:"none"};function wi({data:t,width:a="100%",height:n=200,color:i,gridLines:r=!0,showValues:o=!1,formatValue:s,barGap:l=.3,barRadius:c=2,ariaLabel:u="Bar chart",style:h}){const[f,S]=d.useState(null);if(t.length===0)return null;const w=it(i),g=ee-te-Oe,m=ce-yi,p=Math.max(...t.map(j=>j.value),0),b=Jt(0,p,5),y=b[b.length-1]??p,x=st([0,y],[m,Ve]),T=g/t.length,k=T*(1-l),v=(T-k)/2,A=s??(j=>j>=1e3?`${(j/1e3).toFixed(1)}k`:String(j)),C=j=>{const q=t[j],D=te+j*T+T/2,I=x(q.value);S({index:j,x:ae(D,ee),y:ae(I,ce)})};return e.jsxs("div",{style:{position:"relative",width:a,height:n,...h},children:[e.jsxs("svg",{viewBox:`0 0 ${ee} ${ce}`,width:"100%",height:"100%",preserveAspectRatio:"none",role:"img","aria-label":u,style:{display:"block"},children:[b.map(j=>{const q=x(j);return r?e.jsx("line",{x1:te,y1:q,x2:ee-Oe,y2:q,stroke:"var(--lucent-border-subtle, #e5e7eb)",strokeWidth:.5,strokeDasharray:"3 3"},j):null}),t.map((j,q)=>{const D=te+q*T+v,I=Math.max(0,m-x(j.value)),F=m-I,R=j.color??w,O=(f==null?void 0:f.index)===q,B=f!==null&&!O;return e.jsxs("g",{onMouseEnter:()=>C(q),onMouseLeave:()=>S(null),style:{cursor:"pointer"},children:[e.jsx("rect",{x:te+q*T,y:Ve,width:T,height:m-Ve,fill:"transparent"}),e.jsx("path",{d:xi(D,F,k,I,c),fill:R,opacity:B?.35:1,style:{transition:"opacity 150ms"}})]},q)}),e.jsx("line",{x1:te,y1:m,x2:ee-Oe,y2:m,stroke:"var(--lucent-border-default, #d1d5db)",strokeWidth:.5})]}),b.map(j=>e.jsx("span",{style:{...He,right:`${ae(ee-te+6,ee)}%`,top:`${ae(x(j),ce)}%`,transform:"translateY(-50%)",textAlign:"right"},children:A(j)},j)),t.map((j,q)=>{const D=ae(te+q*T+T/2,ee),I=(f==null?void 0:f.index)===q,F=f!==null&&!I,R=ae(x(j.value),ce);return e.jsxs("span",{style:{display:"contents"},children:[e.jsx("span",{style:{...He,left:`${D}%`,top:`${ae(m+4,ce)}%`,transform:"translateX(-50%)",fontWeight:I?600:400},children:j.label}),o&&e.jsx("span",{style:{...He,left:`${D}%`,top:`${R}%`,transform:"translate(-50%, -100%) translateY(-4px)",opacity:F?.35:1,transition:"opacity 150ms"},children:A(j.value)})]},q)}),f&&e.jsx("span",{style:{position:"absolute",left:`${f.x}%`,top:`${f.y}%`,pointerEvents:"none"},children:e.jsx("span",{style:{position:"absolute",bottom:0},children:e.jsx(be,{content:e.jsxs(e.Fragment,{children:[e.jsx("span",{style:{fontWeight:600},children:t[f.index].label})," ",A(t[f.index].value)]}),open:!0,placement:"top",children:e.jsx("span",{})})})})]})}const ki={id:"bar-chart",name:"BarChart",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A vertical bar chart with automatic axis labels and optional grid lines.",designIntent:"Use BarChart for categorical comparisons — revenue by month, counts by category, etc. Bars auto-scale to fit the data. Set showValues to display exact numbers above each bar. Per-point color overrides are available for highlighting specific bars.",props:[{name:"data",type:"custom",required:!0,description:"Array of { label, value, color? } objects."},{name:"width",type:"union",required:!1,default:"'100%'",description:"Pixel width or CSS string."},{name:"height",type:"number",required:!1,default:"200",description:"Pixel height."},{name:"color",type:"string",required:!1,default:"accent",description:"Bar color variant or CSS color."},{name:"gridLines",type:"boolean",required:!1,default:"true",description:"Show horizontal grid lines."},{name:"showValues",type:"boolean",required:!1,default:"false",description:"Show value labels above bars."},{name:"formatValue",type:"custom",required:!1,description:"Custom value formatter function."},{name:"barGap",type:"number",required:!1,default:"0.3",description:"Gap between bars (0–1 fraction of slot width)."},{name:"barRadius",type:"number",required:!1,default:"2",description:"Corner radius on bar tops."}],usageExamples:[{title:"Basic",code:"<BarChart data={[{ label: 'Jan', value: 40 }, { label: 'Feb', value: 65 }, { label: 'Mar', value: 50 }]} />"},{title:"With values",code:`<BarChart data={[{ label: 'A', value: 120 }, { label: 'B', value: 80 }]} showValues color="success" />`}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label"],notes:"Provide a descriptive ariaLabel summarising the data for screen readers."}},K=400,Z=200,ne=40,Ge=12,Ie=12,Si=24;function Ti({data:t,series:a,width:n="100%",height:i=200,color:r,gridLines:o=!0,showDots:s=!1,dotRadius:l=3,smooth:c=!0,fillOpacity:u=.15,formatValue:h,xLabelCount:f,ariaLabel:S="Area chart",style:w}){const[g,m]=d.useState(null),p=a??(t?[{id:"_default",name:"",data:t,color:it(r)}]:[]),b=p[0];if(!b||b.data.length===0)return null;const y=b.data.map(z=>z.label),x=p.flatMap(z=>z.data.map(M=>M.value)),T=Math.max(...x,0),k=K-ne-Ge,v=Z-Si,A=Jt(0,T,5),C=A[A.length-1]??T,j=st([0,C],[v,Ie]),q=y.length>1?k/(y.length-1):0,D=h??(z=>z>=1e3?`${(z/1e3).toFixed(1)}k`:String(z)),I=f??Math.min(y.length,7),F=y.length<=I?1:Math.ceil(y.length/I),R=c?lt:De,O=c?Qt:Zt,B=p.map(z=>z.data.map((M,$)=>({x:ne+$*q,y:j(M.value)}))),N=q/2,G=z=>{const M=z.currentTarget.getBoundingClientRect(),W=(z.clientX-M.left)/M.width*K-ne;if(W<-N||W>k+N){m(null);return}const L=q>0?Math.round(W/q):0,V=Math.max(0,Math.min(L,y.length-1)),U=ne+V*q,Q=Math.min(...B.map(J=>{var H;return((H=J[V])==null?void 0:H.y)??Ie}));m({index:V,x:U/K*100,y:Q/Z*100})},P=g&&e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,marginBottom:p.length>1?2:0},children:y[g.index]}),p.map((z,M)=>{var L;const $=(L=z.data[g.index])==null?void 0:L.value;if($===void 0)return null;const W=z.color??X[M%X.length];return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[p.length>1&&e.jsx("span",{style:{width:6,height:6,borderRadius:"50%",background:W,flexShrink:0}}),e.jsxs("span",{children:[z.name?`${z.name}: `:"",D($)]})]},z.id)})]});return e.jsxs("div",{style:{position:"relative",width:n,height:i,...w},children:[e.jsxs("svg",{viewBox:`0 0 ${K} ${Z}`,width:"100%",height:"100%",preserveAspectRatio:"none",role:"img","aria-label":S,style:{display:"block"},onMouseMove:G,onMouseLeave:()=>m(null),children:[A.map(z=>{const M=j(z);return o?e.jsx("line",{x1:ne,y1:M,x2:K-Ge,y2:M,stroke:"var(--lucent-border-subtle, #e5e7eb)",strokeWidth:.5,strokeDasharray:"3 3"},z):null}),e.jsx("line",{x1:ne,y1:v,x2:K-Ge,y2:v,stroke:"var(--lucent-border-default, #d1d5db)",strokeWidth:.5}),p.map((z,M)=>{const $=z.color??X[M%X.length],W=B[M],L=R(W),V=O(W,v);return e.jsxs("g",{children:[e.jsx("path",{d:V,fill:$,opacity:u,stroke:"none"}),e.jsx("path",{d:L,fill:"none",stroke:$,strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round"})]},z.id)})]}),s&&p.map((z,M)=>{const $=z.color??X[M%X.length];return B[M].map((L,V)=>e.jsx("span",{style:{position:"absolute",left:`${L.x/K*100}%`,top:`${L.y/Z*100}%`,width:l*2,height:l*2,borderRadius:"50%",background:$,border:"1.5px solid var(--lucent-surface, #fff)",transform:"translate(-50%, -50%)",pointerEvents:"none"}},`${z.id}-${V}`))}),g&&e.jsxs(e.Fragment,{children:[e.jsx("span",{style:{position:"absolute",left:`${g.x}%`,top:`${Ie/Z*100}%`,width:1,height:`${(v-Ie)/Z*100}%`,background:"var(--lucent-border-default)",opacity:.5,transform:"translateX(-50%)",pointerEvents:"none"}}),p.map((z,M)=>{var L;const $=(L=B[M])==null?void 0:L[g.index];if(!$)return null;const W=z.color??X[M%X.length];return e.jsx("span",{style:{position:"absolute",left:`${$.x/K*100}%`,top:`${$.y/Z*100}%`,width:10,height:10,borderRadius:"50%",background:W,border:"2px solid var(--lucent-surface, #fff)",transform:"translate(-50%, -50%)",pointerEvents:"none"}},z.id)})]}),A.map(z=>e.jsx("span",{style:{position:"absolute",right:`${(K-ne+6)/K*100}%`,top:`${j(z)/Z*100}%`,transform:"translateY(-50%)",textAlign:"right",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",color:"var(--lucent-text-secondary)",whiteSpace:"nowrap",pointerEvents:"none"},children:D(z)},z)),y.map((z,M)=>{if(M%F!==0)return null;const $=(g==null?void 0:g.index)===M;return e.jsx("span",{style:{position:"absolute",left:`${(ne+M*q)/K*100}%`,top:`${(v+4)/Z*100}%`,transform:"translateX(-50%)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",color:"var(--lucent-text-secondary)",fontWeight:$?600:400,whiteSpace:"nowrap",pointerEvents:"none"},children:z},M)}),g&&e.jsx("span",{style:{position:"absolute",left:`${g.x}%`,top:`${g.y}%`,pointerEvents:"none"},children:e.jsx("span",{style:{position:"absolute",bottom:0},children:e.jsx(be,{content:P,open:!0,placement:"top",children:e.jsx("span",{})})})})]})}const Ci={id:"area-chart",name:"AreaChart",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A filled line chart for time-series data with optional multi-series support.",designIntent:"Use AreaChart for continuous data over time — revenue trends, user growth, etc. The translucent fill emphasises volume while the line shows direction. Multi-series mode auto-assigns colors from the lucent palette. Pass formatValue to customise y-axis labels (currency, percentages, etc.).",props:[{name:"data",type:"custom",required:!1,description:"Single-series data: { label, value }[]."},{name:"series",type:"custom",required:!1,description:"Multi-series data: { id, name, data, color? }[]."},{name:"width",type:"union",required:!1,default:"'100%'",description:"Pixel width or CSS string."},{name:"height",type:"number",required:!1,default:"200",description:"Pixel height."},{name:"color",type:"string",required:!1,default:"accent",description:"Line/fill color for single-series."},{name:"gridLines",type:"boolean",required:!1,default:"true",description:"Show horizontal grid lines."},{name:"showDots",type:"boolean",required:!1,default:"false",description:"Show dots at data points."},{name:"smooth",type:"boolean",required:!1,default:"true",description:"Use smooth monotone-x curves."},{name:"fillOpacity",type:"number",required:!1,default:"0.15",description:"Area fill opacity (0–1)."},{name:"formatValue",type:"custom",required:!1,description:"Custom value formatter for y-axis."},{name:"xLabelCount",type:"number",required:!1,description:"Max x-axis labels to display."}],usageExamples:[{title:"Basic",code:"<AreaChart data={[{ label: 'Jan', value: 40 }, { label: 'Feb', value: 65 }, { label: 'Mar', value: 50 }]} />"},{title:"Multi-series",code:"<AreaChart series={[{ id: 'a', name: 'Revenue', data: [...] }, { id: 'b', name: 'Costs', data: [...] }]} />"}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label"],notes:"Provide ariaLabel summarising the data trend."}},Ue=100,Et=50,zt=50,_e=45;function Ii({data:t,size:a=160,thickness:n=.35,segmentGap:i=2,centerLabel:r,startAngle:o=0,ariaLabel:s="Donut chart",style:l}){const[c,u]=d.useState(null);if(t.length===0)return null;const h=t.reduce((k,v)=>k+v.value,0);if(h===0)return null;const f=_e*(1-n),w=360-i*t.length;let g=o;const m=t.map((k,v)=>{const A=k.value/h*w,C=g,j=g+A;return g=j+i,{...k,segStart:C,segEnd:j,color:k.color??X[v%X.length]}}),p=f*2*.7,b=(Ue-p)/2,y=c!==null?m[c]:void 0;let x,T=null;if(y){const k=(y.segStart+y.segEnd)/2;x=fe(Et,zt,_e+8,k);const v=(y.value/h*100).toFixed(1);T=e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[e.jsx("span",{style:{width:6,height:6,borderRadius:"50%",background:y.color,flexShrink:0}}),e.jsxs("span",{children:[e.jsx("span",{style:{fontWeight:600},children:y.label})," ",y.value," (",v,"%)"]})]})}return e.jsxs("div",{style:{position:"relative",width:a,height:a,...l},children:[e.jsxs("svg",{viewBox:`0 0 ${Ue} ${Ue}`,width:"100%",height:"100%",role:"img","aria-label":s,style:{display:"block"},children:[m.map((k,v)=>{const A=c===v,C=c!==null&&!A,q=((k.segStart+k.segEnd)/2-90)*Math.PI/180,D=A?3:0,I=Math.cos(q)*D,F=Math.sin(q)*D;return e.jsx("path",{d:gi(Et,zt,_e,f,k.segStart,k.segEnd),fill:k.color,transform:`translate(${I}, ${F})`,opacity:C?.4:1,style:{transition:"transform 150ms, opacity 150ms",cursor:"pointer"},onMouseEnter:()=>u(v),onMouseLeave:()=>u(null)},v)}),r!=null&&e.jsx("foreignObject",{x:b,y:b,width:p,height:p,children:e.jsx("div",{style:{width:"100%",height:"100%",display:"flex",alignItems:"center",justifyContent:"center",textAlign:"center",fontFamily:"var(--lucent-font-family-base)",color:"var(--lucent-text-primary)"},children:r})})]}),x&&e.jsx("span",{style:{position:"absolute",left:`${x.x}%`,top:`${x.y}%`,pointerEvents:"none"},children:e.jsx(be,{content:T,open:!0,placement:"top",children:e.jsx("span",{})})})]})}const ji={id:"donut-chart",name:"DonutChart",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A ring chart for displaying proportions with an optional center label.",designIntent:"Use DonutChart for part-to-whole comparisons — category breakdown, budget allocation, etc. The center label slot is ideal for a total value or summary text. Segment colors auto-cycle through the lucent palette; override per-segment for semantic meaning.",props:[{name:"data",type:"custom",required:!0,description:"Array of { label, value, color? } segments."},{name:"size",type:"number",required:!1,default:"160",description:"Overall pixel size (square)."},{name:"thickness",type:"number",required:!1,default:"0.35",description:"Ring thickness (0–1 fraction of radius)."},{name:"segmentGap",type:"number",required:!1,default:"2",description:"Gap between segments in degrees."},{name:"centerLabel",type:"custom",required:!1,description:"ReactNode displayed in the center of the ring."},{name:"startAngle",type:"number",required:!1,default:"0",description:"Starting angle in degrees (0 = 12 o'clock)."}],usageExamples:[{title:"Basic",code:"<DonutChart data={[{ label: 'A', value: 60 }, { label: 'B', value: 40 }]} />"},{title:"With center label",code:`<DonutChart data={[{ label: 'Used', value: 75 }, { label: 'Free', value: 25 }]} centerLabel={<Text size="lg" weight="bold">75%</Text>} />`}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label"],notes:"Provide ariaLabel describing the proportions for screen readers."}},Mi={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"]}},Ei={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';
|
|
605
|
+
/>`}],compositionGraph:[{componentId:"breadcrumb",componentName:"Breadcrumb",role:"Navigation context above the title",required:!1},{componentId:"stack",componentName:"Stack",role:"Vertical layout for title and subtitle",required:!0},{componentId:"row",componentName:"Row",role:"Horizontal layout for title block and action slot",required:!0},{componentId:"text",componentName:"Text",role:"Title and subtitle rendering",required:!0},{componentId:"button",componentName:"Button",role:"Primary and secondary action buttons",required:!1},{componentId:"divider",componentName:"Divider",role:"Separator between header chrome and page content",required:!1}],accessibility:{notes:`The title renders as an <h1>, providing the page heading landmark for assistive tech. Breadcrumbs render inside <nav aria-label="Breadcrumb">. Action buttons inherit Button's focus ring, keyboard activation, and aria-label forwarding — pass aria-label on each action object when the label alone is insufficient (e.g. icon-only buttons).`}},At={accent:"var(--lucent-accent-default)",success:"var(--lucent-success-default)",warning:"var(--lucent-warning-default)",danger:"var(--lucent-danger-default)",info:"var(--lucent-info-default)"},X=["var(--lucent-accent-default)","var(--lucent-success-default)","var(--lucent-info-default)","var(--lucent-warning-default)","var(--lucent-danger-default)"];function lt(t,r="accent"){return t?At[t]??t:At[r]}function ct(t,r){const[n,i]=t,[a,o]=r,l=i-n;return l===0?()=>(a+o)/2:s=>a+(s-n)/l*(o-a)}function nn(t,r,n){if(t===r)return[t];const i=(r-t)/Math.max(n-1,1),a=Math.pow(10,Math.floor(Math.log10(i))),o=i/a,l=o<=1.5?a:o<=3?2*a:o<=7?5*a:10*a,s=Math.floor(t/l)*l,d=Math.ceil(r/l)*l,p=[];for(let h=s;h<=d+l*.01;h+=l)p.push(Math.round(h*1e10)/1e10);return p}function Re(t){return t.length===0?"":t.map((r,n)=>`${n===0?"M":"L"}${r.x},${r.y}`).join(" ")}function rn(t,r){if(t.length===0)return"";const n=Re(t),i=t[t.length-1],a=t[0];return`${n} L${i.x},${r} L${a.x},${r} Z`}function dt(t){const r=t.length;if(r===0)return"";if(r===1)return`M${t[0].x},${t[0].y}`;if(r===2)return Re(t);const n=[];for(let o=0;o<r-1;o++){const l=t[o+1].x-t[o].x;n.push(l===0?0:(t[o+1].y-t[o].y)/l)}const i=new Array(r).fill(0);i[0]=n[0],i[r-1]=n[r-2];for(let o=1;o<r-1;o++)n[o-1]*n[o]<=0?i[o]=0:i[o]=(n[o-1]+n[o])/2;for(let o=0;o<r-1;o++)if(n[o]===0)i[o]=0,i[o+1]=0;else{const l=i[o]/n[o],s=i[o+1]/n[o],d=l*l+s*s;if(d>9){const p=3/Math.sqrt(d);i[o]=p*l*n[o],i[o+1]=p*s*n[o]}}let a=`M${t[0].x},${t[0].y}`;for(let o=0;o<r-1;o++){const l=(t[o+1].x-t[o].x)/3,s=t[o].x+l,d=t[o].y+l*i[o],p=t[o+1].x-l,h=t[o+1].y-l*i[o+1];a+=` C${s},${d} ${p},${h} ${t[o+1].x},${t[o+1].y}`}return a}function an(t,r){if(t.length===0)return"";const n=dt(t),i=t[t.length-1],a=t[0];return`${n} L${i.x},${r} L${a.x},${r} Z`}function ge(t,r,n,i){const a=(i-90)*Math.PI/180;return{x:t+n*Math.cos(a),y:r+n*Math.sin(a)}}function ki(t,r,n,i,a,o){const l=ge(t,r,n,a),s=ge(t,r,n,o),d=ge(t,r,i,a),p=ge(t,r,i,o),h=o-a>180?1:0;return[`M${l.x},${l.y}`,`A${n},${n} 0 ${h} 1 ${s.x},${s.y}`,`L${p.x},${p.y}`,`A${i},${i} 0 ${h} 0 ${d.x},${d.y}`,"Z"].join(" ")}const ce=100,qt=8;function Si({data:t,width:r="100%",height:n=32,color:i,filled:a=!1,strokeWidth:o=1.5,smooth:l=!0,formatTooltip:s,ariaLabel:d="Sparkline chart",style:p}){const[h,f]=c.useState(null);if(t.length<2)return null;const w=lt(i),b=t.length-1,g=Math.min(...t),m=Math.max(...t),u=ct([g,m],[ce-qt,qt]),y=t.map((C,M)=>({x:M,y:u(C)})),x=l?dt(y):Re(y),T=a?l?an(y,ce):rn(y,ce):null,S=C=>{const M=C.currentTarget.getBoundingClientRect(),q=C.clientX-M.left,B=Math.round(q/M.width*(t.length-1));f(Math.max(0,Math.min(B,t.length-1)))},k=h!==null?y[h]:void 0,v=h!==null?t[h]:void 0,A=h===0||h===t.length-1;return e.jsxs("div",{style:{position:"relative",width:r,height:n,...p},children:[e.jsxs("svg",{viewBox:`0 0 ${b} ${ce}`,width:"100%",height:"100%",preserveAspectRatio:"none",role:"img","aria-label":d,style:{display:"block",overflow:"visible"},onMouseMove:S,onMouseLeave:()=>f(null),children:[T&&e.jsx("path",{d:T,fill:`color-mix(in srgb, ${w} 15%, transparent)`,stroke:"none"}),e.jsx("path",{d:x,fill:"none",stroke:w,strokeWidth:o,strokeLinecap:"round",strokeLinejoin:"round",vectorEffect:"non-scaling-stroke"})]}),k&&e.jsx("span",{style:{position:"absolute",left:`${k.x/b*100}%`,top:0,width:1,height:"100%",background:"var(--lucent-text-secondary)",opacity:.3,transform:"translateX(-50%)",transition:"left 150ms ease-out",pointerEvents:"none"}}),k&&e.jsx("span",{style:{position:"absolute",left:`${k.x/b*100}%`,top:`${k.y/ce*100}%`,width:8,height:8,borderRadius:"50%",background:w,border:"1.5px solid var(--lucent-surface, #fff)",transform:"translate(-50%, -50%)",transition:"left 150ms ease-out, top 150ms ease-out",pointerEvents:"none"}}),k&&v!==void 0&&!A&&e.jsx("span",{style:{position:"absolute",left:`${k.x/b*100}%`,top:`${k.y/ce*100}%`,transition:"left 150ms ease-out, top 150ms ease-out",pointerEvents:"none"},children:e.jsx("span",{style:{position:"absolute",bottom:-2},children:e.jsx(ye,{content:s?s(v,h):v,open:!0,placement:"top",children:e.jsx("span",{})})})})]})}const Ti={id:"sparkline",name:"SparkLine",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A compact, inline trend line for displaying data direction at a glance.",designIntent:"Use SparkLine inside KPI cards, table cells, or compact dashboard tiles to show a trend without axes or labels — surrounding context provides meaning. Defaults to smooth curves for a polished look; set smooth=false for raw data fidelity.",props:[{name:"data",type:"custom",required:!0,description:"Array of numeric values."},{name:"width",type:"union",required:!1,default:"'100%'",description:"Pixel width or CSS string."},{name:"height",type:"number",required:!1,default:"32",description:"Pixel height."},{name:"color",type:"string",required:!1,default:"accent",description:"Color variant name or CSS color."},{name:"filled",type:"boolean",required:!1,default:"false",description:"Show translucent area fill below the line."},{name:"strokeWidth",type:"number",required:!1,default:"1.5",description:"Line stroke width."},{name:"smooth",type:"boolean",required:!1,default:"true",description:"Use smooth monotone-x curves."}],usageExamples:[{title:"Basic",code:"<SparkLine data={[4, 7, 3, 8, 5, 9, 6]} />"},{title:"Filled",code:'<SparkLine data={[4, 7, 3, 8, 5, 9, 6]} filled color="success" />'}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label"],notes:"Purely decorative — ensure surrounding text provides data context."}},te=300,de=200,ne=40,He=8,Ge=16,Ci=24;function ji(t,r,n,i,a){const o=Math.min(a,n/2,i);return[`M${t},${r+i}`,`L${t},${r+o}`,`Q${t},${r} ${t+o},${r}`,`L${t+n-o},${r}`,`Q${t+n},${r} ${t+n},${r+o}`,`L${t+n},${r+i}`,"Z"].join(" ")}function ae(t,r){return t/r*100}const Ue={position:"absolute",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",color:"var(--lucent-text-secondary)",whiteSpace:"nowrap",pointerEvents:"none"};function Ii({data:t,width:r="100%",height:n=200,color:i,gridLines:a=!0,showValues:o=!1,formatValue:l,barGap:s=.3,barRadius:d=2,ariaLabel:p="Bar chart",style:h}){const[f,w]=c.useState(null);if(t.length===0)return null;const b=lt(i),g=te-ne-He,m=de-Ci,u=Math.max(...t.map(M=>M.value),0),y=nn(0,u,5),x=y[y.length-1]??u,T=ct([0,x],[m,Ge]),S=g/t.length,k=S*(1-s),v=(S-k)/2,A=l??(M=>M>=1e3?`${(M/1e3).toFixed(1)}k`:String(M)),C=M=>{const q=t[M],B=ne+M*S+S/2,I=T(q.value);w({index:M,x:ae(B,te),y:ae(I,de)})};return e.jsxs("div",{style:{position:"relative",width:r,height:n,...h},children:[e.jsxs("svg",{viewBox:`0 0 ${te} ${de}`,width:"100%",height:"100%",preserveAspectRatio:"none",role:"img","aria-label":p,style:{display:"block"},children:[y.map(M=>{const q=T(M);return a?e.jsx("line",{x1:ne,y1:q,x2:te-He,y2:q,stroke:"var(--lucent-border-subtle, #e5e7eb)",strokeWidth:.5,strokeDasharray:"3 3"},M):null}),t.map((M,q)=>{const B=ne+q*S+v,I=Math.max(0,m-T(M.value)),L=m-I,D=M.color??b,N=(f==null?void 0:f.index)===q,R=f!==null&&!N;return e.jsxs("g",{onMouseEnter:()=>C(q),onMouseLeave:()=>w(null),style:{cursor:"pointer"},children:[e.jsx("rect",{x:ne+q*S,y:Ge,width:S,height:m-Ge,fill:"transparent"}),e.jsx("path",{d:ji(B,L,k,I,d),fill:D,opacity:R?.35:1,style:{transition:"opacity 150ms"}})]},q)}),e.jsx("line",{x1:ne,y1:m,x2:te-He,y2:m,stroke:"var(--lucent-border-default, #d1d5db)",strokeWidth:.5})]}),y.map(M=>e.jsx("span",{style:{...Ue,right:`${ae(te-ne+6,te)}%`,top:`${ae(T(M),de)}%`,transform:"translateY(-50%)",textAlign:"right"},children:A(M)},M)),t.map((M,q)=>{const B=ae(ne+q*S+S/2,te),I=(f==null?void 0:f.index)===q,L=f!==null&&!I,D=ae(T(M.value),de);return e.jsxs("span",{style:{display:"contents"},children:[e.jsx("span",{style:{...Ue,left:`${B}%`,top:`${ae(m+4,de)}%`,transform:"translateX(-50%)",fontWeight:I?600:400},children:M.label}),o&&e.jsx("span",{style:{...Ue,left:`${B}%`,top:`${D}%`,transform:"translate(-50%, -100%) translateY(-4px)",opacity:L?.35:1,transition:"opacity 150ms"},children:A(M.value)})]},q)}),f&&e.jsx("span",{style:{position:"absolute",left:`${f.x}%`,top:`${f.y}%`,pointerEvents:"none"},children:e.jsx("span",{style:{position:"absolute",bottom:0},children:e.jsx(ye,{content:e.jsxs(e.Fragment,{children:[e.jsx("span",{style:{fontWeight:600},children:t[f.index].label})," ",A(t[f.index].value)]}),open:!0,placement:"top",children:e.jsx("span",{})})})})]})}const Mi={id:"bar-chart",name:"BarChart",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A vertical bar chart with automatic axis labels and optional grid lines.",designIntent:"Use BarChart for categorical comparisons — revenue by month, counts by category, etc. Bars auto-scale to fit the data. Set showValues to display exact numbers above each bar. Per-point color overrides are available for highlighting specific bars.",props:[{name:"data",type:"custom",required:!0,description:"Array of { label, value, color? } objects."},{name:"width",type:"union",required:!1,default:"'100%'",description:"Pixel width or CSS string."},{name:"height",type:"number",required:!1,default:"200",description:"Pixel height."},{name:"color",type:"string",required:!1,default:"accent",description:"Bar color variant or CSS color."},{name:"gridLines",type:"boolean",required:!1,default:"true",description:"Show horizontal grid lines."},{name:"showValues",type:"boolean",required:!1,default:"false",description:"Show value labels above bars."},{name:"formatValue",type:"custom",required:!1,description:"Custom value formatter function."},{name:"barGap",type:"number",required:!1,default:"0.3",description:"Gap between bars (0–1 fraction of slot width)."},{name:"barRadius",type:"number",required:!1,default:"2",description:"Corner radius on bar tops."}],usageExamples:[{title:"Basic",code:"<BarChart data={[{ label: 'Jan', value: 40 }, { label: 'Feb', value: 65 }, { label: 'Mar', value: 50 }]} />"},{title:"With values",code:`<BarChart data={[{ label: 'A', value: 120 }, { label: 'B', value: 80 }]} showValues color="success" />`}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label"],notes:"Provide a descriptive ariaLabel summarising the data for screen readers."}},K=400,Z=200,re=40,_e=12,Ie=12,Ei=24;function zi({data:t,series:r,width:n="100%",height:i=200,color:a,gridLines:o=!0,showDots:l=!1,dotRadius:s=3,smooth:d=!0,fillOpacity:p=.15,formatValue:h,xLabelCount:f,ariaLabel:w="Area chart",style:b}){const[g,m]=c.useState(null),u=r??(t?[{id:"_default",name:"",data:t,color:lt(a)}]:[]),y=u[0];if(!y||y.data.length===0)return null;const x=y.data.map(z=>z.label),T=u.flatMap(z=>z.data.map(j=>j.value)),S=Math.max(...T,0),k=K-re-_e,v=Z-Ei,A=nn(0,S,5),C=A[A.length-1]??S,M=ct([0,C],[v,Ie]),q=x.length>1?k/(x.length-1):0,B=h??(z=>z>=1e3?`${(z/1e3).toFixed(1)}k`:String(z)),I=f??Math.min(x.length,7),L=x.length<=I?1:Math.ceil(x.length/I),D=d?dt:Re,N=d?an:rn,R=u.map(z=>z.data.map((j,$)=>({x:re+$*q,y:M(j.value)}))),W=q/2,G=z=>{const j=z.currentTarget.getBoundingClientRect(),V=(z.clientX-j.left)/j.width*K-re;if(V<-W||V>k+W){m(null);return}const F=q>0?Math.round(V/q):0,O=Math.max(0,Math.min(F,x.length-1)),U=re+O*q,ee=Math.min(...R.map(J=>{var H;return((H=J[O])==null?void 0:H.y)??Ie}));m({index:O,x:U/K*100,y:ee/Z*100})},P=g&&e.jsxs("div",{children:[e.jsx("div",{style:{fontWeight:600,marginBottom:u.length>1?2:0},children:x[g.index]}),u.map((z,j)=>{var F;const $=(F=z.data[g.index])==null?void 0:F.value;if($===void 0)return null;const V=z.color??X[j%X.length];return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[u.length>1&&e.jsx("span",{style:{width:6,height:6,borderRadius:"50%",background:V,flexShrink:0}}),e.jsxs("span",{children:[z.name?`${z.name}: `:"",B($)]})]},z.id)})]});return e.jsxs("div",{style:{position:"relative",width:n,height:i,...b},children:[e.jsxs("svg",{viewBox:`0 0 ${K} ${Z}`,width:"100%",height:"100%",preserveAspectRatio:"none",role:"img","aria-label":w,style:{display:"block"},onMouseMove:G,onMouseLeave:()=>m(null),children:[A.map(z=>{const j=M(z);return o?e.jsx("line",{x1:re,y1:j,x2:K-_e,y2:j,stroke:"var(--lucent-border-subtle, #e5e7eb)",strokeWidth:.5,strokeDasharray:"3 3"},z):null}),e.jsx("line",{x1:re,y1:v,x2:K-_e,y2:v,stroke:"var(--lucent-border-default, #d1d5db)",strokeWidth:.5}),u.map((z,j)=>{const $=z.color??X[j%X.length],V=R[j],F=D(V),O=N(V,v);return e.jsxs("g",{children:[e.jsx("path",{d:O,fill:$,opacity:p,stroke:"none"}),e.jsx("path",{d:F,fill:"none",stroke:$,strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round"})]},z.id)})]}),l&&u.map((z,j)=>{const $=z.color??X[j%X.length];return R[j].map((F,O)=>e.jsx("span",{style:{position:"absolute",left:`${F.x/K*100}%`,top:`${F.y/Z*100}%`,width:s*2,height:s*2,borderRadius:"50%",background:$,border:"1.5px solid var(--lucent-surface, #fff)",transform:"translate(-50%, -50%)",pointerEvents:"none"}},`${z.id}-${O}`))}),g&&e.jsxs(e.Fragment,{children:[e.jsx("span",{style:{position:"absolute",left:`${g.x}%`,top:`${Ie/Z*100}%`,width:1,height:`${(v-Ie)/Z*100}%`,background:"var(--lucent-border-default)",opacity:.5,transform:"translateX(-50%)",pointerEvents:"none"}}),u.map((z,j)=>{var F;const $=(F=R[j])==null?void 0:F[g.index];if(!$)return null;const V=z.color??X[j%X.length];return e.jsx("span",{style:{position:"absolute",left:`${$.x/K*100}%`,top:`${$.y/Z*100}%`,width:10,height:10,borderRadius:"50%",background:V,border:"2px solid var(--lucent-surface, #fff)",transform:"translate(-50%, -50%)",pointerEvents:"none"}},z.id)})]}),A.map(z=>e.jsx("span",{style:{position:"absolute",right:`${(K-re+6)/K*100}%`,top:`${M(z)/Z*100}%`,transform:"translateY(-50%)",textAlign:"right",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",color:"var(--lucent-text-secondary)",whiteSpace:"nowrap",pointerEvents:"none"},children:B(z)},z)),x.map((z,j)=>{if(j%L!==0)return null;const $=(g==null?void 0:g.index)===j;return e.jsx("span",{style:{position:"absolute",left:`${(re+j*q)/K*100}%`,top:`${(v+4)/Z*100}%`,transform:"translateX(-50%)",fontFamily:"var(--lucent-font-family-base)",fontSize:"var(--lucent-font-size-xs)",color:"var(--lucent-text-secondary)",fontWeight:$?600:400,whiteSpace:"nowrap",pointerEvents:"none"},children:z},j)}),g&&e.jsx("span",{style:{position:"absolute",left:`${g.x}%`,top:`${g.y}%`,pointerEvents:"none"},children:e.jsx("span",{style:{position:"absolute",bottom:0},children:e.jsx(ye,{content:P,open:!0,placement:"top",children:e.jsx("span",{})})})})]})}const Ai={id:"area-chart",name:"AreaChart",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A filled line chart for time-series data with optional multi-series support.",designIntent:"Use AreaChart for continuous data over time — revenue trends, user growth, etc. The translucent fill emphasises volume while the line shows direction. Multi-series mode auto-assigns colors from the lucent palette. Pass formatValue to customise y-axis labels (currency, percentages, etc.).",props:[{name:"data",type:"custom",required:!1,description:"Single-series data: { label, value }[]."},{name:"series",type:"custom",required:!1,description:"Multi-series data: { id, name, data, color? }[]."},{name:"width",type:"union",required:!1,default:"'100%'",description:"Pixel width or CSS string."},{name:"height",type:"number",required:!1,default:"200",description:"Pixel height."},{name:"color",type:"string",required:!1,default:"accent",description:"Line/fill color for single-series."},{name:"gridLines",type:"boolean",required:!1,default:"true",description:"Show horizontal grid lines."},{name:"showDots",type:"boolean",required:!1,default:"false",description:"Show dots at data points."},{name:"smooth",type:"boolean",required:!1,default:"true",description:"Use smooth monotone-x curves."},{name:"fillOpacity",type:"number",required:!1,default:"0.15",description:"Area fill opacity (0–1)."},{name:"formatValue",type:"custom",required:!1,description:"Custom value formatter for y-axis."},{name:"xLabelCount",type:"number",required:!1,description:"Max x-axis labels to display."}],usageExamples:[{title:"Basic",code:"<AreaChart data={[{ label: 'Jan', value: 40 }, { label: 'Feb', value: 65 }, { label: 'Mar', value: 50 }]} />"},{title:"Multi-series",code:"<AreaChart series={[{ id: 'a', name: 'Revenue', data: [...] }, { id: 'b', name: 'Costs', data: [...] }]} />"}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label"],notes:"Provide ariaLabel summarising the data trend."}},Ye=100,Bt=50,Rt=50,Ke=45;function qi({data:t,size:r=160,thickness:n=.35,segmentGap:i=2,centerLabel:a,startAngle:o=0,ariaLabel:l="Donut chart",style:s}){const[d,p]=c.useState(null);if(t.length===0)return null;const h=t.reduce((k,v)=>k+v.value,0);if(h===0)return null;const f=Ke*(1-n),b=360-i*t.length;let g=o;const m=t.map((k,v)=>{const A=k.value/h*b,C=g,M=g+A;return g=M+i,{...k,segStart:C,segEnd:M,color:k.color??X[v%X.length]}}),u=f*2*.7,y=(Ye-u)/2,x=d!==null?m[d]:void 0;let T,S=null;if(x){const k=(x.segStart+x.segEnd)/2;T=ge(Bt,Rt,Ke+8,k);const v=(x.value/h*100).toFixed(1);S=e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[e.jsx("span",{style:{width:6,height:6,borderRadius:"50%",background:x.color,flexShrink:0}}),e.jsxs("span",{children:[e.jsx("span",{style:{fontWeight:600},children:x.label})," ",x.value," (",v,"%)"]})]})}return e.jsxs("div",{style:{position:"relative",width:r,height:r,...s},children:[e.jsxs("svg",{viewBox:`0 0 ${Ye} ${Ye}`,width:"100%",height:"100%",role:"img","aria-label":l,style:{display:"block"},children:[m.map((k,v)=>{const A=d===v,C=d!==null&&!A,q=((k.segStart+k.segEnd)/2-90)*Math.PI/180,B=A?3:0,I=Math.cos(q)*B,L=Math.sin(q)*B;return e.jsx("path",{d:ki(Bt,Rt,Ke,f,k.segStart,k.segEnd),fill:k.color,transform:`translate(${I}, ${L})`,opacity:C?.4:1,style:{transition:"transform 150ms, opacity 150ms",cursor:"pointer"},onMouseEnter:()=>p(v),onMouseLeave:()=>p(null)},v)}),a!=null&&e.jsx("foreignObject",{x:y,y,width:u,height:u,children:e.jsx("div",{style:{width:"100%",height:"100%",display:"flex",alignItems:"center",justifyContent:"center",textAlign:"center",fontFamily:"var(--lucent-font-family-base)",color:"var(--lucent-text-primary)"},children:a})})]}),T&&e.jsx("span",{style:{position:"absolute",left:`${T.x}%`,top:`${T.y}%`,pointerEvents:"none"},children:e.jsx(ye,{content:S,open:!0,placement:"top",children:e.jsx("span",{})})})]})}const Bi={id:"donut-chart",name:"DonutChart",tier:"atom",domain:"neutral",specVersion:"0.1",description:"A ring chart for displaying proportions with an optional center label.",designIntent:"Use DonutChart for part-to-whole comparisons — category breakdown, budget allocation, etc. The center label slot is ideal for a total value or summary text. Segment colors auto-cycle through the lucent palette; override per-segment for semantic meaning.",props:[{name:"data",type:"custom",required:!0,description:"Array of { label, value, color? } segments."},{name:"size",type:"number",required:!1,default:"160",description:"Overall pixel size (square)."},{name:"thickness",type:"number",required:!1,default:"0.35",description:"Ring thickness (0–1 fraction of radius)."},{name:"segmentGap",type:"number",required:!1,default:"2",description:"Gap between segments in degrees."},{name:"centerLabel",type:"custom",required:!1,description:"ReactNode displayed in the center of the ring."},{name:"startAngle",type:"number",required:!1,default:"0",description:"Starting angle in degrees (0 = 12 o'clock)."}],usageExamples:[{title:"Basic",code:"<DonutChart data={[{ label: 'A', value: 60 }, { label: 'B', value: 40 }]} />"},{title:"With center label",code:`<DonutChart data={[{ label: 'Used', value: 75 }, { label: 'Free', value: 25 }]} centerLabel={<Text size="lg" weight="bold">75%</Text>} />`}],compositionGraph:[],accessibility:{role:"img",ariaAttributes:["aria-label"],notes:"Provide ariaLabel describing the proportions for screen readers."}},Ri={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"]}},Pi={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';
|
|
601
606
|
|
|
602
607
|
<LucentProvider preset="modern">
|
|
603
608
|
<App />
|
|
@@ -689,6 +694,6 @@ const myTheme = createTheme({
|
|
|
689
694
|
<App />
|
|
690
695
|
</main>
|
|
691
696
|
</div>
|
|
692
|
-
</LucentProvider>`}],compositionGraph:[]},
|
|
697
|
+
</LucentProvider>`}],compositionGraph:[]},Di={accentDefault:"#e9c96b",accentHover:"#ddb84e",accentSubtle:"#fef9ec"};function Li(t,r="light"){const n=r==="light";return{accentDefault:t,accentHover:E.adjustLightness(t,n?.05:-.07),accentSubtle:E.adjustLightness(t,n?.85:-.6),accentBorder:E.adjustLightness(t,n?-.15:.15),accentFg:E.getAccentFg(t)}}function Y(t,r){return{field:t,message:r}}function on(t){const r=[];if(typeof t!="object"||t===null)return{valid:!1,errors:[Y("manifest","Must be a non-null object")]};const n=t,i=["id","name","description","designIntent","specVersion"];for(const o of i)(typeof n[o]!="string"||n[o].trim()==="")&&r.push(Y(o,"Must be a non-empty string"));typeof n.id=="string"&&!/^[a-z][a-z0-9-]*$/.test(n.id)&&r.push(Y("id",'Must be kebab-case (e.g. "button", "form-field")'));const a=["atom","molecule","block","flow","overlay","provider"];return a.includes(n.tier)||r.push(Y("tier",`Must be one of: ${a.join(", ")}`)),(typeof n.domain!="string"||n.domain.trim()==="")&&r.push(Y("domain","Must be a non-empty string")),Array.isArray(n.props)?n.props.forEach((o,l)=>{const s=o,d=`props[${l}]`;(typeof s.name!="string"||s.name==="")&&r.push(Y(`${d}.name`,"Must be a non-empty string")),(typeof s.type!="string"||s.type==="")&&r.push(Y(`${d}.type`,"Must be a non-empty string")),typeof s.required!="boolean"&&r.push(Y(`${d}.required`,"Must be a boolean")),(typeof s.description!="string"||s.description==="")&&r.push(Y(`${d}.description`,"Must be a non-empty string"))}):r.push(Y("props","Must be an array")),Array.isArray(n.usageExamples)?n.usageExamples.length===0?r.push(Y("usageExamples","Must have at least one example")):n.usageExamples.forEach((o,l)=>{const s=o,d=`usageExamples[${l}]`;(typeof s.title!="string"||s.title==="")&&r.push(Y(`${d}.title`,"Must be a non-empty string")),(typeof s.code!="string"||s.code==="")&&r.push(Y(`${d}.code`,"Must be a non-empty string"))}):r.push(Y("usageExamples","Must be an array")),Array.isArray(n.compositionGraph)||r.push(Y("compositionGraph","Must be an array (empty array is fine for atoms)")),typeof n.specVersion=="string"&&!/^\d+\.\d+$/.test(n.specVersion)&&r.push(Y("specVersion",'Must be "MAJOR.MINOR" format, e.g. "0.1"')),{valid:r.length===0,errors:r}}function Fi(t){const r=on(t);if(!r.valid){const n=r.errors.map(i=>` ${i.field}: ${i.message}`).join(`
|
|
693
698
|
`);throw new Error(`Invalid ComponentManifest:
|
|
694
|
-
${n}`)}}function
|
|
699
|
+
${n}`)}}function $i(t){if(typeof t!="object"||t===null)return!1;const r=t;return typeof r.name=="string"&&typeof r.type=="string"&&typeof r.required=="boolean"&&typeof r.description=="string"}const Ni="1.0",Wi="0.1.0";exports.Badge=E.Badge;exports.Button=E.Button;exports.ColorPicker=E.ColorPicker;exports.ColorSwatch=E.ColorSwatch;exports.Input=E.Input;exports.LucentProvider=E.LucentProvider;exports.SegmentedControl=E.SegmentedControl;exports.Select=E.Select;exports.Slider=E.Slider;exports.Tabs=E.Tabs;exports.Text=E.Text;exports.Toggle=E.Toggle;exports.bentoPreset=E.bentoPreset;exports.bloomPreset=E.bloomPreset;exports.brandPalette=E.brandPalette;exports.brutalistPreset=E.brutalistPreset;exports.brutalistShadow=E.brutalistShadow;exports.compactDensity=E.compactDensity;exports.createTheme=E.createTheme;exports.darkTokens=E.darkTokens;exports.defaultDensity=E.defaultDensity;exports.defaultPalette=E.defaultPalette;exports.deriveDarkFromLight=E.deriveDarkFromLight;exports.deriveTokens=E.deriveTokens;exports.elevatedShadow=E.elevatedShadow;exports.emeraldPalette=E.emeraldPalette;exports.enterprisePreset=E.enterprisePreset;exports.flatShadow=E.flatShadow;exports.getAccentFg=E.getAccentFg;exports.getContrastText=E.getContrastText;exports.glowShadow=E.glowShadow;exports.indigoPalette=E.indigoPalette;exports.lightTokens=E.lightTokens;exports.liquidGlassPreset=E.liquidGlassPreset;exports.liquidGlassShadow=E.liquidGlassShadow;exports.makeLibraryCSS=E.makeLibraryCSS;exports.minimalPreset=E.minimalPreset;exports.modernPreset=E.modernPreset;exports.naturalShadow=E.naturalShadow;exports.neumorphicShadow=E.neumorphicShadow;exports.oceanPalette=E.oceanPalette;exports.pillShape=E.pillShape;exports.playfulPreset=E.playfulPreset;exports.resolvePreset=E.resolvePreset;exports.rosePalette=E.rosePalette;exports.roundedShape=E.roundedShape;exports.sharpShape=E.sharpShape;exports.softUIPreset=E.softUIPreset;exports.spaciousDensity=E.spaciousDensity;exports.subtleShadow=E.subtleShadow;exports.terminalPreset=E.terminalPreset;exports.useLucent=E.useLucent;exports.Alert=ga;exports.AlertManifest=va;exports.AreaChart=zi;exports.AreaChartManifest=Ai;exports.Avatar=wn;exports.AvatarManifest=kn;exports.BadgeManifest=hn;exports.BarChart=Ii;exports.BarChartManifest=Mi;exports.Breadcrumb=Ht;exports.ButtonGroup=Ot;exports.ButtonGroupManifest=$r;exports.ButtonManifest=sn;exports.CHIP_MANIFEST=vn;exports.COLOR_PICKER_MANIFEST=dr;exports.COLOR_SWATCH_MANIFEST=ur;exports.COMMAND_PALETTE_MANIFEST=Wa;exports.Card=ia;exports.CardBleed=sa;exports.CardManifest=la;exports.CardPaddingContext=qe;exports.Checkbox=Ae;exports.CheckboxManifest=zn;exports.Chip=be;exports.CodeBlock=tr;exports.CodeBlockManifest=nr;exports.Collapsible=ja;exports.CommandPalette=Na;exports.DATA_TABLE_MANIFEST=La;exports.DATE_PICKER_MANIFEST=co;exports.DATE_RANGE_PICKER_MANIFEST=go;exports.DataTable=Ba;exports.DatePicker=lo;exports.DateRangePicker=Yt;exports.Divider=Lt;exports.DividerManifest=jn;exports.DonutChart=qi;exports.DonutChartManifest=Bi;exports.EmptyState=ba;exports.EmptyStateManifest=ya;exports.FILE_UPLOAD_MANIFEST=xo;exports.FileUpload=yo;exports.FilterDateRange=li;exports.FilterMultiSelect=ri;exports.FilterSearch=ii;exports.FilterSelect=ai;exports.FormField=Nr;exports.FormFieldManifest=Wr;exports.Icon=_n;exports.IconManifest=Yn;exports.InputManifest=ln;exports.LUCENT_UI_VERSION=Wi;exports.LucentProviderManifest=Pi;exports.MANIFEST_SPEC_VERSION=Ni;exports.MULTI_SELECT_MANIFEST=_a;exports.Menu=it;exports.MenuGroup=Er;exports.MenuItem=Me;exports.MenuManifest=zr;exports.MenuSeparator=Nt;exports.MultiSelect=Ua;exports.NavLink=Xn;exports.NavMenu=xe;exports.NavMenuGroup=Zt;exports.NavMenuItem=Jt;exports.NavMenuSeparator=Qt;exports.NavMenuSub=st;exports.PageHeader=xi;exports.PageHeaderManifest=wi;exports.PageLayout=Aa;exports.Progress=kr;exports.ProgressManifest=Sr;exports.Radio=Pn;exports.RadioGroup=$t;exports.RadioGroupUncontrolled=Dn;exports.RadioManifest=Ln;exports.Row=Je;exports.RowManifest=br;exports.SEGMENTED_CONTROL_MANIFEST=pr;exports.SearchInput=_r;exports.SearchInputManifest=Yr;exports.SelectManifest=$n;exports.Skeleton=ka;exports.SkeletonManifest=Sa;exports.SliderManifest=Jn;exports.SparkLine=Si;exports.SparkLineManifest=Ti;exports.Spinner=Dt;exports.SpinnerManifest=Cn;exports.SplitButton=Vt;exports.SplitButtonManifest=Fr;exports.Stack=Xe;exports.StackManifest=mr;exports.Stepper=bi;exports.StepperManifest=yi;exports.TIMELINE_MANIFEST=To;exports.Table=pe;exports.TableManifest=cr;exports.Tag=Vn;exports.TagManifest=On;exports.TextManifest=Kn;exports.Textarea=Pt;exports.TextareaManifest=pn;exports.ThemeAnchorsSpec=Ri;exports.Timeline=So;exports.ToastManifest=Yo;exports.ToastProvider=_o;exports.ToggleManifest=Fn;exports.Tooltip=ye;exports.TooltipManifest=Gn;exports.accentTokens=Li;exports.assertManifest=Fi;exports.brandTokens=Di;exports.isValidPropDescriptor=$i;exports.useMediaQuery=Ut;exports.useToast=Co;exports.validateManifest=on;
|