@santi020k/lumen-mcp 1.2.0 → 1.3.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.
Files changed (2) hide show
  1. package/data/lumen-data.json +435 -155
  2. package/package.json +1 -1
@@ -44,7 +44,7 @@
44
44
  "Dialog": "fc28ce350e649ebac705ed2e4813082b5782386a4f8fce76556f21414a164839",
45
45
  "Direction": "47d34c6c9fa03fb4f4e80e5e9be17c9fe283ca1c059c73b482ab6195dfc68752",
46
46
  "Drawer": "d1710054d3df8035d344006497d1783d6185023ca1f360afc857dcade44bd494",
47
- "DropdownMenu": "a6627d45ce1fc345df154a18e6421a9cc0d75aacdf827d9c24dfe7c5328b8157",
47
+ "DropdownMenu": "d667939d52d8351069627fa421a2572d0cacc7d86edead58041a3ef5e84a5cbd",
48
48
  "Empty": "0c1e046f7c001e713fdcaecbf3d9f5c8218fd7e9603e626365bde0fbaeea0fc6",
49
49
  "ErrorSummary": "63137ef56d5026581f2ecdfeca56028c336664ab080a1a1fb58359d583a36efe",
50
50
  "Eyebrow": "236fb44e3a969d3b503aa0090b46f2441246165d8c58eb091d8f30ce67740cc7",
@@ -115,7 +115,7 @@
115
115
  "TreeGrid": "086263fadebfd36f1fa1fc84f0e7b1ab6272f84f5fd187b4c241687e5f75a71b",
116
116
  "Typography": "730c2096e1a1c3dd99f8ef2583aeddb4da41d0eb7019ddb82868d890da366c9c",
117
117
  "VirtualList": "5f818f334a0ea75082e60e5dec33356f379c8287f7f593dd221705a95b2e4a14",
118
- "LanguageToggle": "38a591135646843ed5df04592e4c765f1895cdf1bc8a261b1d3861a32dd8bf9d",
118
+ "LanguageToggle": "275da6c082eb6a28b45ddb14967b11bb705a1838466a953512d1695ea17d53b8",
119
119
  "Particles": "760681a833d03179255c264266bf722480b041e5983a907c49ff41eb51efc723",
120
120
  "AnimatedNumber": "b98a6af875149ff889a35d0669ad8a9049b032bb6fd39a3e7de0ccc8b12324bd",
121
121
  "RevealGroup": "7a8325fa6618717e4a90df7f5e0a99699774a56876d8d047e4b0b7272091e0bf",
@@ -156,44 +156,47 @@
156
156
  "VisuallyHidden": "17808de2f795be62dbeb1f8c649fedd7dda5acbd8af3bbc1d1bbec20e788e3b3"
157
157
  },
158
158
  "nativeComponents": {
159
- "Theme": "e3bf80210f192c1d6acef07eb8c5ab3f5e0f224a37fc3a2b3a38c40319f19971",
160
- "Text": "91b938e39d42c90174a32429d2cd86862729b0c20a97d9df1b68c6976d842e81",
161
- "Surface": "d99c1a6c01ef92c52c1d7025894284d71c2c18588ea89ea50d8c00e7dc1cc3bc",
162
- "Icon": "598767ab0ea1fd2a1c7a21c8c20a5e03f6b62efdcc4f146269a34b2043ce734e",
163
- "Icon button": "f61aa77238a290e382c06ea11a95a72c3c18870f5a8dd84906e69070a8b252bd",
164
- "Button": "69e64b30b8e501476fca79da5f1a36da0b51a6908e039d544157c4eb5ae1d145",
165
- "Text field": "c8b731acf6cd684f94aaded284d6763166d336f3bed492c1dc9cb4407acdbb61",
166
- "Badge": "a636cc00f6aad99a3578ee08ae1599067497f25c0d9ef46db282ac78e1c83347",
167
- "Divider": "f55f4d6dfea6512ac5fb6ed59892652533d30aa141610415b907861b264aca3c",
168
- "Spinner": "4ef03b525c95e5757af62ecb19dba577c2dbe33fd1ab3a84a6916cb63b16ad03",
169
- "Card": "5aa90060aea1b9b3ac7f4e2e10db2c2bafc23087cc96fc651d4c0f3daef4134a",
170
- "Alert": "d4d51444d7af9bc623fcafcd257e9bfaa8b36506530f285effa87ac9ba03bb9e",
171
- "Progress": "20d3643ee0c7202a395852481958f00758d68eb1b4fb53dfc0ef52d49b895e16",
172
- "Skeleton": "a04f315e615a08c23162a99b895b137883b2229518e68df5c91f305459103681",
173
- "Disclosure": "290b319b4e38ec00a68da099c626ae57ddc80aa55304bf043cf0968f173e7eb1",
174
- "Avatar": "04f944f6238903ce51c1902009e682fedb91f1f2775f2c0c39cad518dfde79ef",
175
- "Textarea": "2b5c7bdc3d74329263e1e76a759c44bce991f490752a62fae5304e257880c239",
176
- "Field group": "6ef6b94e628bfbba3f9b1ba34cc0d737c8b0a31ec19db8f36a7aeec0d3d6d841",
177
- "Chip": "4e318da173a4e6bf352e6737c4702d5b884cf21d19a7ced885e10b462a24ae5c",
178
- "Button group": "8d095e5291fa5d6af81797eae9c9408f9b84963b1f5c889b62e8af3294936c4c",
179
- "Toast": "e1e9cb27137083761bc4353e05f7120b892b57887fe6bb17beebe276bb0724b7",
180
- "Toggle": "61c0b528b2133d58bc3215d8dc2457a6ab9582cc7cabeef18370e08f325feb68",
181
- "Settings row": "2911ff11944688b2b283c13a0eaa9520e74d4080949856094d5028d908f97f43",
182
- "Picker": "9b1ab173e791d5aeb3588aff6c1b04f38cc683ee9c4159afd7a75e6f11b2c232",
183
- "Slider": "6d850620f0bab02a3ef537ae4fa08c3eed2231c290717cfc49fb069a3d9f64c1",
184
- "Search field": "88f988a2ffbbf7dd8aa755115e86b8ee80ddfa484aea8c4b703b8a85af81ee2a",
185
- "Checkbox": "161d83784d8564d6db3922c27fe33d65296e83130c24c55a0c3dc9e1e0c61131",
186
- "Radio group": "cbaf6a3bd63af10523200d027be00c090f269ed0ede9f4030736515ffd132ee8",
187
- "Segmented control": "b4a3874c8a09792b2f8f82e75f466b9de1bbfd70ce39a932d42dc5a30adb2985",
188
- "Empty state": "a3990319f98c68fe5fa7b2f716c67339fb5d30a9efcaf59f858c4c246fa9446e",
189
- "List row": "670b8b3cb7dfa3b8fe5fbbdc06e4cf0239558e0ffb4bcb8e9f7d40b81487ed83",
190
- "Banner": "5412202fe26ead36f1efad5085180e52ccdb4d0ec3af2b5b674b065331cb9d2f",
191
- "Stat": "00eda0e225039a3ebbfeda176c1e36aac4b460a81981d592d06383bf5843e9ce",
192
- "Gauge": "6a82fdae48902c60fb9971601da1f9e356ddf706c68238a4594c900dbdb2986d",
193
- "Section header": "be157330da9fcf933bf8fd0e5ffe0edfa510b2c4d25bd58410a8b5d25dd18f7f",
194
- "Status bar": "ae0cb0d9e344fa22d40ba21d73dbe27ffd6bca1ff4cb44780f43d5a323ea79fb",
195
- "Shortcut recorder": "b699c615e12ba9b38ef397ce20141ade4cd942e88a6624e76278682d4dd105b2",
196
- "Symbol picker": "e478f52cbe9b893d15731503fc42701716eee55d3231ccab00330e756c5f3e7c"
159
+ "Theme": "ffb8e66eaa1fa80ecc138bb047ec2ee0d2aaddb14e75103f9acd330446891609",
160
+ "Text": "da836769d7734d25de5c5819b3d8eb7cdb0a7347baa4a757ecfcbde2388391ba",
161
+ "Surface": "96dd2928844a043dd042f3d3bed947be32d2647a369889cdf1e2ba30183f7f33",
162
+ "Icon": "efa0d01b9a7313a5f5b39e9a89891fe3839ae62e42dc38b8cff5f36611488cfb",
163
+ "Icon button": "4ef0f3d4823ee71a3ebb66948cedb2af41f5bf79aa5c47feb66ba29ba01e26a0",
164
+ "Button": "57380934403cb9b81a4642949ca603126a93d283a88dc8830b0a5a4722e397e0",
165
+ "Text field": "b56b239d29ba5793155f283b478813aa119de3554adda6d52955aac73c577e6b",
166
+ "Badge": "e2ff8ccfad55087cdda7c19bc148efdca01142c25658690cc9ee542e011ab7f8",
167
+ "Divider": "2be6683f7ff4b9e968611caaf702e2c5250c5ba70325c0c02ddd234881cf7a9e",
168
+ "Spinner": "e68abcee7d2c9376928288910ea04182e8523ecac0af93c23ab19295fda0bb88",
169
+ "Card": "61aaa3cbb98abd950860e49fcd50f8f4128e73e9c85819dcd8621e5db29dd328",
170
+ "Alert": "f27a83a0ec1ec574ef997b78c9790d9559df29b5171c46a790175b5c8ac32859",
171
+ "Progress": "ed0663159b887c4b71392606ce82d54764f820d8153d30451fddc8c9b6cccbfd",
172
+ "Skeleton": "06233b29a2d20ed367019548a06426ed44c3025e4b816cd9711cb97da8770009",
173
+ "Disclosure": "be4def6a676413bc1f1ff0c35a4a8b12869e7488476811eff5d72937e4bd4f25",
174
+ "Avatar": "ad5292bdb58a1d79e2147ce6d1db9633a9b234780a53d5b1268fb715c653b026",
175
+ "Textarea": "221a265a3ed0eca986f1b6e377f92217ab343359a64081a021d0cf079eaa2c6e",
176
+ "Field group": "1d736820d5eabd202090e35555be772a06c26a1c47745417d5a23faa07c1e3fa",
177
+ "Chip": "fcbce7af9bb38a1bce7f5c337a0cdb8eb8721aa0a0f36c510ff764acdef986f5",
178
+ "Button group": "82c35614035d04e4dc0815ee22be89b89856516ed7fe557ac430c9b2f37aa18f",
179
+ "Toast": "654dcf711be6af245dcb4ea8ba8d100d4c5bcea0e281ab2e3009f0dd765f8caf",
180
+ "Toggle": "b2e7e0723a55a6118cf20f978a4f880d814440107827e509b11e8c3c92b00f4b",
181
+ "Settings row": "8bc392c37d446ba1ed276556ac57910335d8dab7bc013e56fc9383819a48baa5",
182
+ "Picker": "ed96336bb1fec3088a1aafebfcb7f0091352de1d47c666c0c54b67ebb3a472c0",
183
+ "Slider": "95d3f6367e952633325dc1214a240424b0299fa4ce1457eadcbc995489ed0828",
184
+ "Search field": "d65de25840be13dc68e30eb29fc7aaa4ebdc0f0d249a2746e980135ac189fd95",
185
+ "Checkbox": "92f5ee04fd72835caa5c1f5bc3ebc20bb410d2432d0315086d8e2bf78e15aed8",
186
+ "Radio group": "3141c60b1da4215efe0387288065882fca8a5001d781d310e283c58242f849c5",
187
+ "Segmented control": "dab80875fe58104220daa744cbbc94cc064b6a4fafcc3f19bf19cec3e3a38439",
188
+ "Empty state": "79b9eb98c5a2df2ea40c9abc6dcd85c90952579062bd2ff37ae9ec0e973e975f",
189
+ "List row": "86c669b2676865cecc20a5d8b22a305d06647bfbf534fbe8faf802e2b29053c2",
190
+ "Banner": "cda55c474bb568f7fd5219e4dc88cce4720c7ffb05f423aa7d1305d65cdc419a",
191
+ "Stat": "bb791e0391c78ea66b83502bbb1af85e454df5b29334bc334752765c95290378",
192
+ "Gauge": "d8bb93ae076c2fe70e2dbc149cf6d99bdb2199c176dff0f6ccd0016eb1b62b49",
193
+ "Section header": "dfdadc03ca5d89b0e19d197edce1e2d8e83e2d39381aa7dcbc328b7d53d52b7c",
194
+ "Status bar": "a1b36ae346299e01bcac31b852aa1cf84d46aef3fda333b321b2610e7792a289",
195
+ "Refresh control": "37a3e0a9d085780b9d3dead61fee2849108ae4f33e6d0814ddf680c4f5829b7c",
196
+ "Date field": "2397d6907ac230497077d3b388b330fc43680f6e7cadd2faef3480a71d719ec3",
197
+ "Floating action button": "614a972743528ef2df98f9a64281eef4cbed1725c42b8c86da534645be15e8f6",
198
+ "Shortcut recorder": "a5a2fa237d2efe604431bc35d46f063328fdc0fd8c0485d53d1692b1f0a76926",
199
+ "Symbol picker": "57b3383a74549aa62dd97dfa627464818e4b65d5b8c130ecb3697059668fbb27"
197
200
  },
198
201
  "recipes": {
199
202
  "all-components": "517dfb03b8ca5483f5412a00ca2a151763d8d4429a44e398d4d74733793f0fd7",
@@ -9534,8 +9537,8 @@
9534
9537
  ],
9535
9538
  "setup": "Use useDropdownMenu from @santi020k/lumen-react; do not mount Astro UIPrimitives in React."
9536
9539
  },
9537
- "example": "import { Button, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent } from '@santi020k/lumen-react'\nimport { useDropdownMenu } from '@santi020k/lumen-react'\n\nexport function ActionsMenu() {\n const menu = useDropdownMenu()\n\n return (\n <DropdownMenu {...menu.rootProps}>\n <DropdownMenuTrigger {...menu.triggerProps}>Actions</DropdownMenuTrigger>\n <DropdownMenuContent {...menu.panelProps}>\n <button>Edit</button>\n <button>Delete</button>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}",
9538
- "importStatement": "import { Button, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent } from '@santi020k/lumen-react'\nimport { useDropdownMenu } from '@santi020k/lumen-react'",
9540
+ "example": "import {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger\n} from '@santi020k/lumen-react'\n\nexport function ActionsMenu() {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger>Actions</DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuItem>Edit</DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem status=\"Admin\">Delete</DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}",
9541
+ "importStatement": "import {",
9539
9542
  "language": "tsx",
9540
9543
  "packageName": "@santi020k/lumen-react",
9541
9544
  "props": [],
@@ -24322,11 +24325,41 @@
24322
24325
  },
24323
24326
  {
24324
24327
  "apiReference": [
24328
+ {
24329
+ "attribute": "locales",
24330
+ "defaultValue": "English and Spanish",
24331
+ "description": "Defines the ordered locale values and human-readable labels to cycle through.",
24332
+ "values": "LumenLocaleOption[]"
24333
+ },
24334
+ {
24335
+ "attribute": "defaultValue",
24336
+ "defaultValue": "first locale",
24337
+ "description": "Sets the initial locale for uncontrolled usage.",
24338
+ "values": "string"
24339
+ },
24340
+ {
24341
+ "attribute": "value",
24342
+ "defaultValue": "-",
24343
+ "description": "Enables controlled usage; ui:language-change requests the next locale without mutating document language.",
24344
+ "values": "string"
24345
+ },
24346
+ {
24347
+ "attribute": "storageKey",
24348
+ "defaultValue": "-",
24349
+ "description": "Persists uncontrolled locale selection when browser storage is available.",
24350
+ "values": "string"
24351
+ },
24352
+ {
24353
+ "attribute": "labelTemplate",
24354
+ "defaultValue": "Change language from {current} to {next}",
24355
+ "description": "Localizes the accessible label while retaining current and next locale names.",
24356
+ "values": "string"
24357
+ },
24325
24358
  {
24326
24359
  "attribute": "children",
24327
- "defaultValue": "required",
24328
- "description": "Shows the current locale or language abbreviation.",
24329
- "values": "locale label"
24360
+ "defaultValue": "next locale label",
24361
+ "description": "Overrides the generated visible next-locale label.",
24362
+ "values": "locale label or custom content"
24330
24363
  },
24331
24364
  {
24332
24365
  "attribute": "type",
@@ -24347,13 +24380,13 @@
24347
24380
  "values": "HTML attributes"
24348
24381
  }
24349
24382
  ],
24350
- "astroSource": "---\nimport type { HTMLAttributes } from 'astro/types'\n\nimport { resolveLumenAstroProps } from '@santi020k/lumen-core'\n\ninterface Props extends HTMLAttributes<'button'> {\n class?: string | null\n className?: string\n}\n\nconst {\n class: classProp = '',\n className = '',\n type = 'button',\n ...rest\n} = Astro.props\n\nconst { classList, passthrough } = resolveLumenAstroProps(rest, [\n 'ui-language-toggle'\n], classProp, className)\n---\n\n<button\n type={type}\n class:list={classList}\n data-ui-language-toggle\n {...passthrough}\n>\n <slot />\n</button>\n",
24383
+ "astroSource": "---\nimport type { HTMLAttributes } from 'astro/types'\n\nimport {\n formatLumenLanguageLabel,\n getLumenLocalePair,\n type LumenLocaleOption,\n normalizeLumenLocales,\n resolveLumenAstroProps\n} from '@santi020k/lumen-core'\n\ninterface Props extends HTMLAttributes<'button'> {\n class?: string | null\n className?: string\n defaultValue?: string\n labelTemplate?: string\n locales?: readonly LumenLocaleOption[]\n storageKey?: string\n value?: string\n}\n\nconst {\n class: classProp = '',\n className = '',\n defaultValue,\n labelTemplate = 'Change language from {current} to {next}',\n locales: localeOptions,\n storageKey,\n type = 'button',\n value,\n ...rest\n} = Astro.props\n\nconst locales = normalizeLumenLocales(localeOptions)\nconst initialValue = value ?? defaultValue ?? locales[0]?.value\nconst { current, next } = getLumenLocalePair(locales, initialValue)\nconst accessibleLabel = formatLumenLanguageLabel(labelTemplate, current, next)\n\nconst { classList, passthrough } = resolveLumenAstroProps(rest, [\n 'ui-language-toggle'\n], classProp, className)\n---\n\n<button\n type={type}\n class:list={classList}\n data-ui-language-toggle\n data-ui-language-controlled={value === undefined ? undefined : ''}\n data-ui-language-initial-value={initialValue}\n data-ui-language-label-template={labelTemplate}\n data-ui-language-locales={JSON.stringify(locales)}\n data-ui-language-value={current.value}\n data-ui-language-next-value={next.value}\n data-ui-language-storage-key={storageKey}\n aria-label={accessibleLabel}\n {...passthrough}\n>\n {\n Astro.slots.has('default') ?\n <slot /> :\n <span data-ui-language-label lang={next.value}>{next.label}</span>\n }\n</button>\n",
24351
24384
  "category": "Actions",
24352
24385
  "collections": [],
24353
24386
  "dependencies": [],
24354
- "description": "Presents the current locale as a compact control for changing language.",
24387
+ "description": "Coordinates controlled or persistent locale selection and document language as a compact accessible control.",
24355
24388
  "behavior": {
24356
- "astro": "none",
24389
+ "astro": "ui-primitives",
24357
24390
  "elements": "registered-element",
24358
24391
  "react": "component"
24359
24392
  },
@@ -24364,7 +24397,11 @@
24364
24397
  "frameworkDetails": {
24365
24398
  "astro": {
24366
24399
  "available": true,
24367
- "example": "<LanguageToggle aria-label=\"Change language\">EN</LanguageToggle>",
24400
+ "behavior": {
24401
+ "mode": "runtime",
24402
+ "setup": "Mount UIPrimitives once in the application's root layout; do not mount it beside each component."
24403
+ },
24404
+ "example": "<LanguageToggle defaultValue=\"en\" storageKey=\"site-language\" locales={[{ label: 'English', value: 'en' }, { label: 'Español', value: 'es' }]} />",
24368
24405
  "importStatement": "import { LanguageToggle } from '@santi020k/lumen-astro'",
24369
24406
  "language": "astro",
24370
24407
  "packageName": "@santi020k/lumen-astro",
@@ -24378,14 +24415,45 @@
24378
24415
  "name": "className",
24379
24416
  "optional": true,
24380
24417
  "type": "string"
24418
+ },
24419
+ {
24420
+ "name": "defaultValue",
24421
+ "optional": true,
24422
+ "type": "string"
24423
+ },
24424
+ {
24425
+ "name": "labelTemplate",
24426
+ "optional": true,
24427
+ "type": "string"
24428
+ },
24429
+ {
24430
+ "name": "locales",
24431
+ "optional": true,
24432
+ "type": "readonly LumenLocaleOption[]"
24433
+ },
24434
+ {
24435
+ "name": "storageKey",
24436
+ "optional": true,
24437
+ "type": "string"
24438
+ },
24439
+ {
24440
+ "name": "value",
24441
+ "optional": true,
24442
+ "type": "string"
24381
24443
  }
24382
24444
  ],
24383
24445
  "propsExtends": "HTMLAttributes<'button'>",
24384
- "source": "---\nimport type { HTMLAttributes } from 'astro/types'\n\nimport { resolveLumenAstroProps } from '@santi020k/lumen-core'\n\ninterface Props extends HTMLAttributes<'button'> {\n class?: string | null\n className?: string\n}\n\nconst {\n class: classProp = '',\n className = '',\n type = 'button',\n ...rest\n} = Astro.props\n\nconst { classList, passthrough } = resolveLumenAstroProps(rest, [\n 'ui-language-toggle'\n], classProp, className)\n---\n\n<button\n type={type}\n class:list={classList}\n data-ui-language-toggle\n {...passthrough}\n>\n <slot />\n</button>\n",
24446
+ "source": "---\nimport type { HTMLAttributes } from 'astro/types'\n\nimport {\n formatLumenLanguageLabel,\n getLumenLocalePair,\n type LumenLocaleOption,\n normalizeLumenLocales,\n resolveLumenAstroProps\n} from '@santi020k/lumen-core'\n\ninterface Props extends HTMLAttributes<'button'> {\n class?: string | null\n className?: string\n defaultValue?: string\n labelTemplate?: string\n locales?: readonly LumenLocaleOption[]\n storageKey?: string\n value?: string\n}\n\nconst {\n class: classProp = '',\n className = '',\n defaultValue,\n labelTemplate = 'Change language from {current} to {next}',\n locales: localeOptions,\n storageKey,\n type = 'button',\n value,\n ...rest\n} = Astro.props\n\nconst locales = normalizeLumenLocales(localeOptions)\nconst initialValue = value ?? defaultValue ?? locales[0]?.value\nconst { current, next } = getLumenLocalePair(locales, initialValue)\nconst accessibleLabel = formatLumenLanguageLabel(labelTemplate, current, next)\n\nconst { classList, passthrough } = resolveLumenAstroProps(rest, [\n 'ui-language-toggle'\n], classProp, className)\n---\n\n<button\n type={type}\n class:list={classList}\n data-ui-language-toggle\n data-ui-language-controlled={value === undefined ? undefined : ''}\n data-ui-language-initial-value={initialValue}\n data-ui-language-label-template={labelTemplate}\n data-ui-language-locales={JSON.stringify(locales)}\n data-ui-language-value={current.value}\n data-ui-language-next-value={next.value}\n data-ui-language-storage-key={storageKey}\n aria-label={accessibleLabel}\n {...passthrough}\n>\n {\n Astro.slots.has('default') ?\n <slot /> :\n <span data-ui-language-label lang={next.value}>{next.label}</span>\n }\n</button>\n",
24385
24447
  "styleImport": "import '@santi020k/lumen-astro/styles.css'"
24386
24448
  },
24387
24449
  "elements": {
24388
24450
  "attributes": [
24451
+ "data-ui-language-toggle",
24452
+ "locales",
24453
+ "defaultValue",
24454
+ "value",
24455
+ "storageKey",
24456
+ "labelTemplate",
24389
24457
  "children",
24390
24458
  "type",
24391
24459
  "class, className",
@@ -24394,28 +24462,38 @@
24394
24462
  "available": true,
24395
24463
  "behavior": {
24396
24464
  "mode": "registration",
24397
- "setup": "Call defineLumenElements() once before rendering Lumen custom elements."
24465
+ "setup": "Call defineLumenElements() once before rendering Lumen custom elements. Registration includes this component’s interactive behavior."
24398
24466
  },
24399
- "example": "<lumen-language-toggle aria-label=\"Change language\">EN</lumen-language-toggle>",
24467
+ "example": "<lumen-language-toggle></lumen-language-toggle>",
24400
24468
  "importStatement": "import { defineLumenElements } from '@santi020k/lumen-elements/define'",
24401
24469
  "language": "html",
24402
24470
  "packageName": "@santi020k/lumen-elements",
24403
24471
  "props": [],
24404
24472
  "propsExtends": "HTMLElement attributes",
24405
24473
  "registration": "defineLumenElements()",
24406
- "source": "LanguageToggle: {\n baseClassName: 'ui-language-toggle',\n tagName: 'lumen-language-toggle'\n }",
24474
+ "source": "LanguageToggle: {\n baseClassName: 'ui-language-toggle',\n defaults: { 'data-ui-language-toggle': '' },\n tagName: 'lumen-language-toggle'\n }",
24407
24475
  "styleImport": "import '@santi020k/lumen-elements/styles.css'",
24408
24476
  "tagName": "lumen-language-toggle"
24409
24477
  },
24410
24478
  "react": {
24411
24479
  "available": true,
24412
- "example": "<LanguageToggle aria-label=\"Change language\">EN</LanguageToggle>",
24480
+ "behavior": {
24481
+ "mode": "built-in",
24482
+ "setup": "Interactive behavior is implemented by the React component; do not mount Astro UIPrimitives."
24483
+ },
24484
+ "example": "<LanguageToggle defaultValue=\"en\" storageKey=\"site-language\" locales={[{ label: 'English', value: 'en' }, { label: 'Español', value: 'es' }]} />",
24413
24485
  "importStatement": "import { LanguageToggle } from '@santi020k/lumen-react'",
24414
24486
  "language": "tsx",
24415
24487
  "packageName": "@santi020k/lumen-react",
24416
- "props": [],
24417
- "propsExtends": "ComponentPropsWithoutRef<'button'>",
24418
- "source": "export type LanguageToggleProps = ComponentPropsWithoutRef<'button'>\n\nexport const LanguageToggle = ({\n className,\n type = 'button',\n ...props\n}: LanguageToggleProps) => (\n <button\n className={composeClassName('ui-language-toggle', className)}\n type={type}\n {...props}\n />\n)",
24488
+ "props": [
24489
+ {
24490
+ "name": "labelTemplate",
24491
+ "optional": true,
24492
+ "type": "string | undefined"
24493
+ }
24494
+ ],
24495
+ "propsExtends": "Omit<ComponentPropsWithoutRef<'button'>, 'defaultValue' | 'value'>, LanguageToggleOptions",
24496
+ "source": "export interface LanguageToggleProps\n extends Omit<ComponentPropsWithoutRef<'button'>, 'defaultValue' | 'value'>,\n LanguageToggleOptions {\n labelTemplate?: string | undefined\n}\n\nexport const LanguageToggle = ({\n children,\n className,\n defaultValue,\n labelTemplate = 'Change language from {current} to {next}',\n locales,\n onClick,\n onValueChange,\n storageKey,\n type = 'button',\n value,\n ...props\n}: LanguageToggleProps) => {\n const language = useLanguageToggle({\n defaultValue,\n locales,\n onValueChange,\n storageKey,\n value\n })\n\n const accessibleLabel = formatLumenLanguageLabel(\n labelTemplate,\n language.currentLocale,\n language.nextLocale\n )\n\n return (\n <button\n aria-label={accessibleLabel}\n className={composeClassName('ui-language-toggle', className)}\n data-ui-language-next-value={language.nextLocale.value}\n data-ui-language-toggle\n data-ui-language-value={language.value}\n onClick={event => {\n onClick?.(event)\n\n if (event.defaultPrevented) return\n\n language.selectNext()\n\n event.currentTarget.dispatchEvent(new CustomEvent('ui:language-change', {\n bubbles: true,\n detail: {\n previousValue: language.currentLocale.value,\n value: language.nextLocale.value\n }\n }))\n }}\n type={type}\n {...props}\n >\n {children ?? (\n <span lang={language.nextLocale.value}>\n {language.nextLocale.label}\n </span>\n )}\n </button>\n )\n}",
24419
24497
  "styleImport": "import '@santi020k/lumen-react/styles.css'"
24420
24498
  }
24421
24499
  },
@@ -24428,17 +24506,19 @@
24428
24506
  "kebab": "language-toggle",
24429
24507
  "keyboardInteractions": [],
24430
24508
  "keywords": [
24509
+ "accessible",
24431
24510
  "actions",
24432
- "changing",
24511
+ "and",
24433
24512
  "compact",
24434
24513
  "control",
24435
- "current",
24436
- "for",
24514
+ "controlled",
24515
+ "coordinates",
24516
+ "document",
24437
24517
  "language",
24438
24518
  "languagetoggle",
24439
24519
  "locale",
24440
- "presents",
24441
- "the"
24520
+ "persistent",
24521
+ "selection"
24442
24522
  ],
24443
24523
  "name": "LanguageToggle",
24444
24524
  "props": [
@@ -24451,6 +24531,31 @@
24451
24531
  "name": "className",
24452
24532
  "optional": true,
24453
24533
  "type": "string"
24534
+ },
24535
+ {
24536
+ "name": "defaultValue",
24537
+ "optional": true,
24538
+ "type": "string"
24539
+ },
24540
+ {
24541
+ "name": "labelTemplate",
24542
+ "optional": true,
24543
+ "type": "string"
24544
+ },
24545
+ {
24546
+ "name": "locales",
24547
+ "optional": true,
24548
+ "type": "readonly LumenLocaleOption[]"
24549
+ },
24550
+ {
24551
+ "name": "storageKey",
24552
+ "optional": true,
24553
+ "type": "string"
24554
+ },
24555
+ {
24556
+ "name": "value",
24557
+ "optional": true,
24558
+ "type": "string"
24454
24559
  }
24455
24560
  ],
24456
24561
  "propsExtends": "HTMLAttributes<'button'>",
@@ -32189,13 +32294,13 @@
32189
32294
  }
32190
32295
  ],
32191
32296
  "docs": {
32192
- "aiUsage": "# AI Usage Guide\n\nUse this guide when an AI agent is building an app with Lumen or updating examples that teach\nanother agent how to use the library.\n\n## Install the Lumen Agent Skill\n\nLumen ships a portable Agent Skill for AI coding tools that support the open `SKILL.md` format:\n\n```bash\nnpx skills add santi020k/lumen --skill lumen-ui\n```\n\nThe source lives in [`skills/lumen-ui`](../skills/lumen-ui). The skill provides the durable\nframework-selection, component-composition, theming, accessibility, and verification workflow.\nUse it by naming `$lumen-ui` in a prompt, or let a compatible agent trigger it from Lumen-specific\nrequests.\n\nThe skill and MCP server complement each other: the skill explains how to work, while\n`@santi020k/lumen-mcp` supplies structured catalog search, framework-specific contracts, recipes,\nsource, tokens, resources, and rules.\n\n## Pick the Right Package\n\nUse the framework requested by the user. Every adapter shares the same Lumen foundation.\n\n| App Target | Install | Import Components From | Load Styles From |\n| --- | --- | --- | --- |\n| Astro | `@santi020k/lumen-astro` | `@santi020k/lumen-astro` | `@santi020k/lumen-astro/styles.css` |\n| React | `@santi020k/lumen-react` | `@santi020k/lumen-react` | `@santi020k/lumen-react/styles.css` |\n| React Hook Form composites | `@santi020k/lumen-react-hook-form` | `@santi020k/lumen-react-hook-form` | Uses React styles |\n| Web Components | `@santi020k/lumen-elements` | `@santi020k/lumen-elements/define` | `@santi020k/lumen-elements/styles.css` |\n| React Native / Expo | `@santi020k/lumen-react-native` | `@santi020k/lumen-react-native` | Not applicable |\n| Apple / SwiftUI | Swift Package `https://github.com/santi020k/lumen` | `LumenUI` | Not applicable |\n| Android / Compose | Local `packages/compose` module | `com.santi020k.lumen` | Not applicable |\n| Package metadata | `@santi020k/lumen-core` | `@santi020k/lumen-core` | Not applicable |\n| Optional brand icons | `@santi020k/lumen-icons-brand` | Register once, then use the framework `Icon` | Uses framework styles |\n\nEach framework package includes the shared foundation. Install `@santi020k/lumen` separately only\nwhen you need its framework-neutral CLI or registry metadata.\n\nFor React Native, mount one `LumenProvider` near the app root. For SwiftUI, attach the `LumenUI`\nSwift Package product to the application target and apply `.lumenTheme(...)` near the root. For\nCompose, include the local `lumen-compose` Gradle module and wrap content in `LumenTheme`. Native\nadapters do not load CSS or the Astro runtime.\n\nBrand marks are intentionally excluded from the default Lucide catalog. When a product needs them,\ninstall and register the optional pack once before rendering a namespaced icon:\n\n```ts\nimport { registerLumenBrandIcons } from '@santi020k/lumen-icons-brand'\n\nregisterLumenBrandIcons()\n```\n\nThen use any canonical Font Awesome Free brand name, such as `brand:github`, `brand:discord`,\n`brand:figma`, `brand:linkedin`, `brand:whatsapp`, or `brand:youtube`, through the existing Astro,\nReact, or Web Component `Icon` API. The package also provides `brand:x` as an alias for\n`brand:x-twitter`.\n\nBrand marks keep their recognizable filled geometry instead of being redrawn as Lucide outlines.\nThey still inherit the same Lumen size, alignment, `currentColor`, and accessible-label behavior as\nthe default icon catalog.\n\n## MCP-Assisted Workflow\n\nConnect `@santi020k/lumen-mcp` when the agent supports Model Context Protocol. The server returns\nvalidated structured content as well as readable text, so agents can select components without\nscraping documentation.\n\nThe MCP per-component catalog covers Astro, React, Web Components, React Native, SwiftUI, and\nCompose. Use `lumen_list_native_components` to discover platform availability and\n`lumen_get_native_component` for installation, setup, API, examples, accessibility guidance, and\noptional reference source. The skill still verifies installed packages when local versions differ.\n\n```bash\ncodex mcp add lumen -- npx -y @santi020k/lumen-mcp\n```\n\nFor Codex project configuration:\n\n```toml\n[mcp_servers.lumen]\nenabled = true\ncommand = \"npx\"\nargs = [\"-y\", \"@santi020k/lumen-mcp\"]\n```\n\nUse this sequence:\n\n1. Read `lumen://meta` and `lumen://diagnostics` to identify and verify the snapshot.\n2. Read `lumen://rules`.\n3. Search for the use case with `lumen_search`, passing the target `framework` when known.\n4. Inspect the selected framework with `lumen_get_component` and `detail: \"usage\"`.\n5. Follow its framework behavior setup and hook/controller contract.\n6. Read related multi-component guidance with `lumen_get_recipe`.\n7. Request `detail: \"source\"` only when the usage contract is insufficient.\n8. Read `lumen://tokens` before introducing custom styling.\n9. Retain `lumen://catalog-manifest` and use `lumen_diff_catalog` after upgrades to identify contracts that need refreshing.\n\nExample framework-aware component request:\n\n```json\n{\n \"name\": \"lumen_get_component\",\n \"arguments\": {\n \"name\": \"DateRangePicker\",\n \"framework\": \"react\",\n \"detail\": \"usage\"\n }\n}\n```\n\nStable resources are available at `lumen://rules`, `lumen://tokens`, `lumen://components`,\n`lumen://meta`, `lumen://catalog-manifest`, `lumen://diagnostics`,\n`lumen://components/{name}`, and `lumen://recipes/{name}`. See\n[`packages/mcp/README.md`](../packages/mcp/README.md) for Claude Desktop, Cursor, generic stdio,\nlocal repository, and programmatic examples.\n\n## Minimal Setup\n\nImport the stylesheet once in the app's global CSS, root layout, or app entry.\n\nThis section applies to web targets. Native adapters use their provider or theme APIs instead of a\nstylesheet.\n\n```css\n@import \"@santi020k/lumen-react/styles.css\";\n```\n\nIf Tailwind is present, keep both imports in the same shared CSS entry.\n\n```css\n@import \"@santi020k/lumen-react/layers.css\";\n@import \"tailwindcss\";\n@import \"@santi020k/lumen-react/styles.css\";\n```\n\nKeep the layer prelude first. It places Tailwind base styles below Lumen components and Tailwind\nutilities above them, so consumer spacing, display, radius, width, and responsive visibility\nclasses override Lumen defaults predictably.\n\nAstro apps should mount `UIPrimitives` once in the root layout when using interactive primitives\nsuch as dialogs, menus, popovers, tabs, carousels, command lists, toggles, validated forms, or\ntoasts. Do not place `UIPrimitives` in each component example or next to each primitive instance;\none root include enhances all matching `data-ui-*` markup on the page.\n\n```astro\n---\nimport { UIPrimitives } from '@santi020k/lumen-astro'\n---\n\n<UIPrimitives />\n```\n\n## Astro\n\nUse direct named imports from `@santi020k/lumen-astro`.\n\n```astro\n---\nimport { Button, Card, Input, Label } from '@santi020k/lumen-astro'\n---\n\n<Card>\n <Label for=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"you@example.com\" />\n <Button type=\"submit\">Subscribe</Button>\n</Card>\n```\n\n`Stat` defaults to a `div`. Choose `as=\"article\"` for a self-contained metric with supporting\ncontent, or `as=\"section\"` for a labeled region. Do not use a semantic root solely for styling.\nIts visual variants are `default`, `accent`, and `glass`; reserve `accent` for featured metrics and\nkeep glass selective.\n\nFor long reading surfaces, use `Anchor` for local section navigation and pass each item’s optional\nheading `depth`, `index`, and `description` when hierarchy matters. Use `activationOffset` for fixed\napplication headers. Use `ScrollProgress` for document position; keep `Progress` for\napplication-controlled task completion. In Astro, dispatch `ui:progress-change` with\n`{ value, max? }` from the `Progress` root when browser state changes after hydration. `Pill`\naccepts `neutral`, `brand`, and `outline`\nvariants, plus native link and count composition.\n\n## React\n\nReact components share the same visual classes and data attributes as Astro components. For\nbehavior-heavy primitives, use the React behavior hooks from `@santi020k/lumen-react` (`useDialog`,\n`usePopover`, `useDropdownMenu`, `useTabs`, `useSelect`, `useToast`, and `useTooltip`), which track\nthe Astro runtime's ARIA, keyboard, Escape, dismissal, and toast controller semantics.\n\nThe React package entry is a client boundary. In Next.js, it can be imported by a Server Component\nwithout evaluating Lumen's contexts and hooks in the React server runtime; the rendered Lumen\nsubtree becomes a Client Component boundary. Vite and other client-rendered React applications use\nthe same entry without additional setup.\n`Button`, `Link`, `ButtonLink`, and `Input` accept React 19 DOM refs directly as props.\n`Button asChild` composes the button contract onto one child element. Across adapters, native\nnumeric `size` remains available on `Input` and `NativeSelect`; use `visualSize` in Astro and React,\nor `visual-size` in Elements, for presentation. The pre-1.0 Astro and Elements `size=\"sm|lg\"`\nvisual alias is deprecated but remains available for incremental migration.\n`Stat as` and `variant` follow the same semantic-root and visual guidance as the Astro component.\n\n```tsx\nimport '@santi020k/lumen-react/styles.css'\nimport { Button, Card, Input, Label } from '@santi020k/lumen-react'\n\nexport function SubscribeForm() {\n return (\n <Card>\n <Label htmlFor=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"you@example.com\" />\n <Button type=\"submit\">Subscribe</Button>\n </Card>\n )\n}\n```\n\nFor React Hook Form, spread `register()` directly onto native-backed controls. Install\n`@santi020k/lumen-react-hook-form` only for controlled composites such as `Select`, `DatePicker`,\n`InputOTP`, and `ListBox`. Do not combine React Hook Form and `useFormValidation` unless the\napplication intentionally coordinates two validation sources. For schema validation, use\n`zodResolver` or `yupResolver` from `@hookform/resolvers`; keep Zod or Yup in the application and\nrender the resulting React Hook Form errors through Lumen's `FieldError` contract.\n\n## Web Components\n\nRegister custom elements once, then use `lumen-*` tags in HTML. The elements adapter includes\ninteractive behavior parity for DataTable, Dialog, Popover, DropdownMenu, ContextMenu, Tabs,\nSelect, ThemeBuilder, Toast, Tooltip, forms, RichTextEditor, Schedule, and VirtualList: selection,\nvalidation, rich text command, context menu, schedule drag/drop, theme, and range events, ARIA\nstate, roving/listbox keyboard paths, Escape/outside dismissal, focus return/trapping, native\nselect form participation, and the same toast controller events as the Astro runtime.\n\n```html\n<script type=\"module\">\n import { defineLumenElements } from '@santi020k/lumen-elements/define'\n\n defineLumenElements()\n</script>\n\n<lumen-card>\n <label for=\"email\">Email</label>\n <lumen-input id=\"email\" type=\"email\" placeholder=\"you@example.com\"></lumen-input>\n <lumen-button type=\"submit\">Subscribe</lumen-button>\n</lumen-card>\n```\n\nPut input elements inside a native `<form data-ui-form>`. Scalar Lumen controls participate in\n`FormData`, validity, disabled state, focus, and reset through form-associated custom-element\nbehavior with a native-control fallback. Do not generate `<lumen-form>` as the submission\ncontainer.\n\n## Component Selection\n\nThe shared catalog includes:\n\n`Accordion`, `Alert`, `AlertDialog`, `Agenda`, `AspectRatio`, `Attachment`, `Autocomplete`,\n`Avatar`, `Badge`, `BarChart`, `Breadcrumb`, `Bubble`, `Button`, `ButtonGroup`, `Calendar`, `Card`,\n`Carousel`, `Chart`, `Checkbox`, `CheckboxGroup`, `Collapsible`, `Code`, `Combobox`, `Command`,\n`Container`, `ColorPicker`,\n`ContextMenu`, `DataTable`, `DatePicker`, `DateRangePicker`, `Dialog`, `Direction`, `Drawer`,\n`DropdownMenu`, `Empty`, `ErrorSummary`, `Field`, `FieldError`, `Form`, `Grid`, `HoverCard`, `Icon`,\n`Input`, `InputGroup`, `InputOTP`, `Item`, `KanbanBoard`, `KanbanColumn`, `Kbd`, `Label`, `LineChart`, `ListBox`, `Marker`, `Menubar`,\n`Message`, `MessageScroller`, `NativeSelect`, `ContextNavigation`, `NavigationMenu`, `NumberField`, `Pagination`,\n`PasswordField`, `PieChart`, `Popover`, `Progress`, `CopyButton`, `RadioGroup`, `Resizable`, `RichTextEditor`,\n`ScrollArea`, `Schedule`, `SearchField`, `Select`, `Separator`, `Sheet`, `Sidebar`, `Skeleton`,\n`Slider`, `Sparkline`, `Spinner`, `Stack`, `Switch`, `Table`, `Tabs`, `TagGroup`, `Textarea`, `ThemeBuilder`,\n`TimeField`, `Toast`, `Toggle`, `ToggleGroup`, `Tooltip`, `Tree`, `TreeGrid`, `Typography`, and\n`VirtualList`, and `VisuallyHidden`.\n\n## Product Recipes\n\n- Start full product surfaces from the bundled `analytics-dashboard`, `saas-admin`,\n `commerce-dashboard`, `project-workspace`, or `auth-onboarding` recipe. Install with\n `lumen add <recipe>` for Astro, or add `--target react` / `--target elements`. Keep the generated\n shared template stylesheet, then replace sample data and routes while preserving public Lumen\n components, semantic tokens, accessible names, focus paths, and responsive navigation.\n- Use `Sparkline` for a compact trend beside a labelled metric, `BarChart` for categorical\n comparison, `LineChart` for ordered or time-series data, and `PieChart` for a small\n part-to-whole breakdown. Pass already-aggregated `LumenChartSeries` data; keep statistics,\n resampling, model evaluation, and domain calculations in application code. Keep `showTable`\n enabled unless the same values are already available in a nearby semantic table. Prefer a bar\n chart when precise slice comparison matters or the pie would exceed roughly seven categories.\n Use `Chart` as the custom SVG/canvas escape hatch.\n Keep stable identity in `datum.x` and put display text in `datum.xLabel`. Use `formatCategory`\n and `formatValue` for localized axes, SVG titles, and data tables. Choose\n `presentation=\"bare\"` inside an existing Card, `categoryWidth` for long horizontal labels,\n `markers=\"auto\"` or a numeric marker step for dense lines, and `emptyLabel` for actionable\n source-specific guidance.\n- Use `Icon name=\"search\"` or any Lucide icon name for standard interface pictograms. Kebab-case\n names and Lucide aliases work. Pair decorative icons with `decorative`, and pass `label` only\n when the icon carries meaning on its own. Install and register\n `@santi020k/lumen-icons-brand` for supported brand marks, then use names such as\n `brand:github`. Render project-specific or unsupported brand SVGs through the default `Icon`\n slot. Astro reports unknown named icons during development instead of failing silently.\n- Use `Schedule` with `Agenda`, `Calendar`, and `DatePicker` for booking, CRM, content calendar, and\n event planning screens. Mark slots with `data-ui-schedule-slot` and events with\n `data-ui-schedule-event`; the runtime emits `ui:schedule-change` when a draggable event is moved.\n `Calendar` renders a hidden input plus selectable date grid; Astro `UIPrimitives` and registered\n Web Components wire month navigation and roving keyboard focus directly, while React apps can use\n the `Calendar` component or `useCalendar` for the same date selection contract.\n Astro `UIPrimitives` and registered Web Components bind schedule drag/drop directly; React apps\n can use `useSchedule` for `rootProps`, `getEventProps`, `getSlotProps`, and `ui:schedule-change`\n parity. Use `createScheduleSlots`, `moveScheduleEvent`, and `expandRecurringScheduleEvent` from\n `@santi020k/lumen-core` for reusable schedule state; use `getScheduleConflicts` and\n `canPlaceScheduleEvent` before committing drag/drop changes. Persist local schedule edits with\n `createScheduleStorageKey`, `saveScheduleEvents`, and `loadScheduleEvents` against any\n `localStorage`-compatible adapter. Use `resizeScheduleEvent` and `resizeScheduleEvents` for\n snap-to-grid resize handles.\n- Use `KanbanBoard` and `KanbanColumn` for status-based workspaces while continuing to use `Card` for\n items. Mark items with `data-ui-kanban-item` and put `data-ui-kanban-handle` on a dedicated button;\n do not make the whole card draggable. Astro and Elements emit the cancellable, controlled\n `ui:kanban-move-request` event, and React provides `useKanban`. The event reports `itemId`,\n `fromColumn`, `toColumn`, optional `beforeId`, and keyboard or pointer input, but never moves DOM or\n persists application data. The host owns validation, `aria-busy`, optimistic state, rollback,\n status names, and APIs. Use compact `Empty` states inside columns, preserve every column with\n `Skeleton` blocks while loading, and paginate large columns. See [Kanban composition and movement](kanban.md).\n- Use `DataTable`, `Tree`, `TreeGrid`, `VirtualList`, `Pagination`, and `Command` for dense data\n collection workflows. Astro and Elements wire selectable/sortable `DataTable` behavior and\n `VirtualList` range events; React emits the same data attributes for app-level adapters. `Tree`\n and `TreeGrid` use roving keyboard focus, and `VirtualList` emits `ui:virtual-list-range` as the\n rendered range changes. Use `createDataViewState`,\n `serializeDataViewState`, `parseDataViewState`, `applyDataViewState`, and `getVirtualRange` for\n saved views, filtering, sorting, pagination, and virtualization math. Use\n `createDataViewStorageKey`, `saveDataViewState`, and `loadDataViewState` when saved views should\n survive navigation or reloads. Use `pinDataViewColumn`, `unpinDataViewColumn`,\n `toggleDataViewSelection`, `createDataViewSearchParams`, and `createDataViewServerRequest` when\n connecting dense views to router state or server-backed data.\n- Use `Resizable` for split-pane editors, dashboards, inspectors, and side-by-side review layouts.\n Astro `UIPrimitives` and registered Web Components generate separator handles and pane sizing\n directly; React apps can use the `Resizable` component or `useResizable` for pointer, keyboard,\n min/max, and double-click reset parity.\n- Use `ThemeBuilder`, `ColorPicker`, `Card`, `Button`, and `Input` to build token preview and export\n tools. Add `data-ui-theme-brand-hue`, `data-ui-theme-accent-hue`, `data-ui-theme-scheme`,\n `data-ui-theme-mode`, manual color inputs such as `data-ui-theme-primary-hex`, and\n `data-ui-theme-target` for scoped live previews. Add `data-ui-theme-export-format`,\n `data-ui-theme-export`, and `data-ui-theme-output` to copy CSS, Figma variable JSON, or design\n token JSON. Astro `UIPrimitives` and registered Web Components wire those controls directly;\n React apps can use `useThemeBuilder` for the same tokens, exports, and control props. Use\n `createThemePalette`, `createThemeBuilderTokens`, `exportThemeBuilderValue`, `exportThemeCss`,\n `parseThemeCss`, and `scoreThemeContrast`\n for theme import/export and accessibility checks. Use `suggestReadableInk`, `mergeThemeTokens`,\n and `tuneThemeContrast` when generated palettes need readable foreground repair.\n- Use `RichTextEditor` with `ButtonGroup`, `ToggleGroup`, and `Textarea` for editor compositions.\n Controls with `data-ui-editor-command` dispatch browser editing commands and emit\n `ui:editor-command`; use `data-ui-editor-value` for headings, links, and other value-bearing\n commands. Editable surfaces support common formatting shortcuts, synchronize toggle\n `aria-pressed` state, and emit `ui:editor-change` with HTML and plain text. Astro `UIPrimitives`\n and registered Web Components bind those controls directly; React apps can use\n `useRichTextEditor` for `rootProps`, `getCommandProps`, `getEditableProps`, and `executeCommand`.\n For Tiptap, ProseMirror, Lexical, or Markdown engines, keep the external editor\n package in the app and bridge toolbar buttons through the emitted command event instead of adding\n the editor engine to Lumen.\n- Use `Autocomplete`, `SearchField`, `NumberField`, `TimeField`, `DateRangePicker`, `ColorPicker`,\n and `TagGroup` when forms need more than plain text inputs. Add `data-ui-form` to forms that\n should reflect native Constraint Validation API state into `Field` error slots on blur and submit.\n For `InputOTP`, Astro `UIPrimitives` and registered Web Components render/sync a real native input\n with visual OTP segments; React apps can use the `InputOTP` component or `useInputOTP` for the\n same sanitization, paste handling, and caret navigation.\n For `DateRangePicker`, Astro `UIPrimitives` and registered Web Components sync the paired native\n date input `min`/`max` constraints directly; React apps can use `useDateRangePicker` for the same\n start/end syncing props.\n Use native attributes such as `required`, `pattern`, `min`, `max`, and `type`; add\n `data-error-required`, `data-error-pattern`, `data-error-min`, `data-error-max`, or\n `data-error-custom` when a control needs custom copy. Listen for `ui:validate` to call\n `control.setCustomValidity(message)` before the runtime reads validity, and listen for\n `ui:invalid` or `ui:valid` on the form for submission state. Astro `UIPrimitives` and registered\n Web Components bind `data-ui-form` directly; React apps can use `useFormValidation` with `Field`\n to get the same control props, CustomEvents, and error slot updates.\n- Use `Toast` for transient feedback. Static `<Toast>` markup works without\n JavaScript. With Astro `UIPrimitives` or registered Web Components, create toasts with\n `window.LumenToast.create(detail)`, `LumenToast.create(detail)` from `@santi020k/lumen-elements`,\n or `document.dispatchEvent(new CustomEvent('ui:toast', { detail }))`; update them with\n `.update(id, detail)` or `ui:toast-update`; and dismiss them with `.dismiss(id?)` or\n `ui:toast-dismiss`. `detail` supports `title`, `description`, `variant`, `duration`, `placement`,\n `max`, and `action: { label, value }`. Destructive toasts use `role=\"alert\"`; other variants use\n `role=\"status\"`. Runtime toasts pause their timeout on hover or focus, and Escape dismisses the\n focused toast.\n\n## Runtime CustomEvents\n\nWhen `UIPrimitives` is mounted, or when Web Components are registered for matching behavior, Lumen\nuses these CustomEvents:\n\n| Event | Target | Detail | Fires When |\n| --- | --- | --- | --- |\n| `ui:data-table-selection-change` | `DataTable` root `[data-ui-datatable]` | `{ values: string[] }` | Selectable row checkboxes, the select-all checkbox, or form reset changes selected row values. |\n| `ui:schedule-change` | `Schedule` root `[data-ui-schedule]` | `{ eventId?: string, slot?: string }` | A draggable schedule event is dropped on a `[data-ui-schedule-slot]`. |\n| `ui:virtual-list-range` | `VirtualList` root `[data-ui-virtual-list]` | `{ startIndex: number, endIndex: number }` | The virtual list calculates its visible range on init or scroll. |\n| `ui:kanban-move-request` | `KanbanBoard` root `[data-ui-kanban]` | `{ itemId: string, fromColumn: string, toColumn: string, beforeId?: string, input: 'keyboard' \\| 'pointer' }` | A handle requests a controlled move; the event is cancellable and never moves data or DOM. |\n| `ui:tag-remove` | `TagGroup` root `.ui-tag-group` or `[data-ui-tag-group]` | `{ value?: string }` | A `[data-ui-tag-remove]` control removes its closest tag or list item. |\n| `ui:editor-command` | `RichTextEditor` root `[data-ui-rich-text-editor]` | `{ command: string, executed: boolean, value?: string }` | A toolbar control or keyboard shortcut runs an editor command. |\n| `ui:editor-change` | `RichTextEditor` root `[data-ui-rich-text-editor]` | `{ html: string, text: string }` | Editable content changes or an editor command runs. |\n| `ui:theme-change` | `ThemeBuilder` root `[data-ui-theme-builder]` | `{ hue: number, accentHue: number, mode: 'generated' \\| 'manual', scheme: 'dark' \\| 'light', tokens: Record<string, string> }` | Hue, manual color, mode, or scheme controls update generated tokens. |\n| `ui:theme-export` | `ThemeBuilder` root `[data-ui-theme-builder]` | `{ css?: string, format: 'css' \\| 'figma' \\| 'tokens', tokens: Record<string, string> \\| null, value: string }` | A `[data-ui-theme-export]` control writes the selected export to the output and clipboard when available. |\n| `ui:validate` | Form `[data-ui-form]` | `{ control, form, value }` | Before validation state is read, so custom code can call `control.setCustomValidity(message)`. |\n| `ui:invalid` | Form `[data-ui-form]` | `{ control?, controls?, form }` | Blur or submit validation finds invalid controls. Failed submits focus the first invalid control. |\n| `ui:valid` | Form `[data-ui-form]` | `{ control?, controls?, form }` | Blur or submit validation finds the checked control or form valid. |\n| `ui:toast` | `document` | `{ id?, title?, description?, variant?, duration?, placement?, action? }` | Creates a runtime toast in the matching notification viewport. |\n| `ui:toast-update` | `document` | `{ id, title?, description?, variant?, duration? }` | Updates a runtime toast. |\n| `ui:toast-dismiss` | `document` | `{ id? }` | Dismisses one runtime toast, or all runtime toasts when `id` is omitted. |\n| `ui:toast-action` | Toast `[data-ui-toast]` | `{ id, value? }` | A runtime toast action button is pressed, unless the action specifies a custom event name. |\n\n## Registry Helper\n\n`@santi020k/lumen` exports typed component, recipe, and documentation metadata from\n`@santi020k/lumen/registry`. The package also ships a small `lumen` binary. Individual components\nare addressable by name, including kebab-case aliases such as `data-table`:\n\n```bash\nlumen list\nlumen show Button\nlumen show data-table\nlumen add Button\nlumen add Button --target react\nlumen add Button --target elements\nlumen add data-table\nlumen show scheduler\nlumen add scheduler\nlumen add scheduler --target react\nlumen add scheduler --target elements\nlumen add scheduler --dry-run\nlumen add scheduler --fail-on-conflict\nlumen add scheduler --force\nlumen add scheduler --merge\nlumen list --registry ./registry/lumen.registry.json\nlumen add custom-recipe --registry ./registry/custom.registry.json\nlumen add private-recipe --registry https://example.com/lumen.registry.json --registry-token \"$TOKEN\"\nlumen install\nlumen doctor --json\nlumen init --framework astro --tailwind\n```\n\n`lumen add <component>` defaults to an Astro wrapper. Use `--target react` for a local React wrapper\nor `--target elements` for a custom-elements starter. Bundled recipes also accept `--target astro`,\n`--target react`, and `--target elements`; external registry inline files keep their authored paths\nand sources.\n\nBefore inventing a wrapper or custom class pattern, check the component source in\n`packages/astro/components`. Astro is the reference implementation for props, class names, data\nattributes, and accessible markup.\n\n## Styling Rules for Generated Code\n\n- Do not require consumers to configure Tailwind for Lumen components.\n- Prefer component props and composition before adding custom CSS.\n- Use the shared token names for custom surfaces: `canvas`, `surface`, `surface-muted`,\n `surface-strong`, `line`, `ink`, `ink-soft`, `ink-muted`, `brand`, `brand-solid`, `brand-soft`,\n `accent`, `success`, `warning`, and `danger`.\n- Use `glass` as a boolean prop or attribute for glass surfaces when supported.\n- Preserve semantic labels, keyboard paths, focus states, and native form attributes.\n\n## Figma Handoff\n\nUse `exportThemeFigmaVariables` from `@santi020k/lumen-core/figma` when a design workflow needs\nFigma variable-friendly color values, and `exportThemeDesignTokens` when exporting standard design\ntoken JSON for token importers. See [docs/figma.md](figma.md) for the recommended Figma workflow.\n\n## When Editing This Repo\n\n- Shared contracts belong in `packages/core`.\n- Astro implementation and standalone CSS belong in `packages/astro`.\n- React-specific primitives belong in `packages/react`.\n- Web Component-specific primitives belong in `packages/elements`.\n- Update the relevant package README when public usage changes.\n- Add tests beside package code as `*.test.ts` when behavior, exported metadata, or component\n contracts change.\n",
32297
+ "aiUsage": "# AI Usage Guide\n\nUse this guide when an AI agent is building an app with Lumen or updating examples that teach\nanother agent how to use the library.\n\n## Install the Lumen Agent Skill\n\nLumen ships a portable Agent Skill for AI coding tools that support the open `SKILL.md` format:\n\n```bash\nnpx skills add santi020k/lumen --skill lumen-ui\n```\n\nThe source lives in [`skills/lumen-ui`](../skills/lumen-ui). The skill provides the durable\nframework-selection, component-composition, theming, accessibility, and verification workflow.\nUse it by naming `$lumen-ui` in a prompt, or let a compatible agent trigger it from Lumen-specific\nrequests.\n\nThe skill and MCP server complement each other: the skill explains how to work, while\n`@santi020k/lumen-mcp` supplies structured catalog search, framework-specific contracts, recipes,\nsource, tokens, resources, and rules.\n\n## Pick the Right Package\n\nUse the framework requested by the user. Every adapter shares the same Lumen foundation.\n\n| App Target | Install | Import Components From | Load Styles From |\n| -------------------------- | ---------------------------------------------------------------------- | -------------------------------------------- | -------------------------------------- |\n| Astro | `@santi020k/lumen-astro` | `@santi020k/lumen-astro` | `@santi020k/lumen-astro/styles.css` |\n| React | `@santi020k/lumen-react` | `@santi020k/lumen-react` | `@santi020k/lumen-react/styles.css` |\n| React Hook Form composites | `@santi020k/lumen-react-hook-form` | `@santi020k/lumen-react-hook-form` | Uses React styles |\n| Web Components | `@santi020k/lumen-elements` | `@santi020k/lumen-elements/define` | `@santi020k/lumen-elements/styles.css` |\n| React Native / Expo | `@santi020k/lumen-react-native` | `@santi020k/lumen-react-native` | Not applicable |\n| Apple / SwiftUI | Swift Package `https://github.com/santi020k/lumen`, pinned to `1.2.0` | `LumenUI` | Not applicable |\n| Android / Compose | Maven Central `com.santi020k:lumen-compose:0.2.0` | `com.santi020k.lumen` | Not applicable |\n| Package metadata | `@santi020k/lumen-core` | `@santi020k/lumen-core` | Not applicable |\n| Optional brand icons | `@santi020k/lumen-icons-brand` | Register once, then use the framework `Icon` | Uses framework styles |\n\nEach framework package includes the shared foundation. Install `@santi020k/lumen` separately only\nwhen you need its framework-neutral CLI or registry metadata.\n\nFor React Native, mount one `LumenProvider` near the app root. For SwiftUI, attach the `LumenUI`\nSwift Package product to the application target, use an exact or compatible release-version rule,\nand apply `.lumenTheme(...)` near the root. For Compose, install\n`com.santi020k:lumen-compose:0.2.0` from Maven Central and wrap content in `LumenTheme`. Native\nadapters do not load CSS or the Astro runtime.\n\nBrand marks are intentionally excluded from the default Lucide catalog. When a product needs them,\ninstall and register the optional pack once before rendering a namespaced icon:\n\n```ts\nimport { registerLumenBrandIcons } from \"@santi020k/lumen-icons-brand\";\n\nregisterLumenBrandIcons();\n```\n\nThen use any canonical Font Awesome Free brand name, such as `brand:github`, `brand:discord`,\n`brand:figma`, `brand:linkedin`, `brand:whatsapp`, or `brand:youtube`, through the existing Astro,\nReact, or Web Component `Icon` API. The package also provides `brand:x` as an alias for\n`brand:x-twitter`.\n\nBrand marks keep their recognizable filled geometry instead of being redrawn as Lucide outlines.\nThey still inherit the same Lumen size, alignment, `currentColor`, and accessible-label behavior as\nthe default icon catalog.\n\n## MCP-Assisted Workflow\n\nConnect `@santi020k/lumen-mcp` when the agent supports Model Context Protocol. The server returns\nvalidated structured content as well as readable text, so agents can select components without\nscraping documentation.\n\nThe MCP per-component catalog covers Astro, React, Web Components, React Native, SwiftUI, and\nCompose. Use `lumen_list_native_components` to discover platform availability and\n`lumen_get_native_component` for installation, setup, API, examples, accessibility guidance, and\noptional reference source. The skill still verifies installed packages when local versions differ.\n\n```bash\ncodex mcp add lumen -- npx -y @santi020k/lumen-mcp\n```\n\nFor Codex project configuration:\n\n```toml\n[mcp_servers.lumen]\nenabled = true\ncommand = \"npx\"\nargs = [\"-y\", \"@santi020k/lumen-mcp\"]\n```\n\nUse this sequence:\n\n1. Read `lumen://meta` and `lumen://diagnostics` to identify and verify the snapshot.\n2. Read `lumen://rules`.\n3. Search for the use case with `lumen_search`, passing the target `framework` when known.\n4. Inspect the selected framework with `lumen_get_component` and `detail: \"usage\"`.\n5. Follow its framework behavior setup and hook/controller contract.\n6. Read related multi-component guidance with `lumen_get_recipe`.\n7. Request `detail: \"source\"` only when the usage contract is insufficient.\n8. Read `lumen://tokens` before introducing custom styling.\n9. Retain `lumen://catalog-manifest` and use `lumen_diff_catalog` after upgrades to identify contracts that need refreshing.\n\nExample framework-aware component request:\n\n```json\n{\n \"name\": \"lumen_get_component\",\n \"arguments\": {\n \"name\": \"DateRangePicker\",\n \"framework\": \"react\",\n \"detail\": \"usage\"\n }\n}\n```\n\nStable resources are available at `lumen://rules`, `lumen://tokens`, `lumen://components`,\n`lumen://meta`, `lumen://catalog-manifest`, `lumen://diagnostics`,\n`lumen://components/{name}`, and `lumen://recipes/{name}`. See\n[`packages/mcp/README.md`](../packages/mcp/README.md) for Claude Desktop, Cursor, generic stdio,\nlocal repository, and programmatic examples.\n\n## Minimal Setup\n\nImport the stylesheet once in the app's global CSS, root layout, or app entry.\n\nThis section applies to web targets. Native adapters use their provider or theme APIs instead of a\nstylesheet.\n\n```css\n@import \"@santi020k/lumen-react/styles.css\";\n```\n\nIf Tailwind is present, keep both imports in the same shared CSS entry.\n\n```css\n@import \"@santi020k/lumen-react/layers.css\";\n@import \"tailwindcss\";\n@import \"@santi020k/lumen-react/styles.css\";\n```\n\nKeep the layer prelude first. It places Tailwind base styles below Lumen components and Tailwind\nutilities above them, so consumer spacing, display, radius, width, and responsive visibility\nclasses override Lumen defaults predictably.\n\nAstro apps should mount `UIPrimitives` once in the root layout when using interactive primitives\nsuch as dialogs, menus, popovers, tabs, carousels, command lists, toggles, validated forms, or\ntoasts. Do not place `UIPrimitives` in each component example or next to each primitive instance;\none root include enhances all matching `data-ui-*` markup on the page.\n\n```astro\n---\nimport { UIPrimitives } from '@santi020k/lumen-astro'\n---\n\n<UIPrimitives />\n```\n\n## Astro\n\nUse direct named imports from `@santi020k/lumen-astro`.\n\n```astro\n---\nimport { Button, Card, Input, Label } from '@santi020k/lumen-astro'\n---\n\n<Card>\n <Label for=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"you@example.com\" />\n <Button type=\"submit\">Subscribe</Button>\n</Card>\n```\n\n`Stat` defaults to a `div`. Choose `as=\"article\"` for a self-contained metric with supporting\ncontent, or `as=\"section\"` for a labeled region. Do not use a semantic root solely for styling.\nIts visual variants are `default`, `accent`, and `glass`; reserve `accent` for featured metrics and\nkeep glass selective.\n\nFor long reading surfaces, use `Anchor` for local section navigation and pass each item’s optional\nheading `depth`, `index`, and `description` when hierarchy matters. Use `activationOffset` for fixed\napplication headers. Use `ScrollProgress` for document position; keep `Progress` for\napplication-controlled task completion. In Astro, dispatch `ui:progress-change` with\n`{ value, max? }` from the `Progress` root when browser state changes after hydration. `Pill`\naccepts `neutral`, `brand`, and `outline`\nvariants, plus native link and count composition.\n\n## React\n\nReact components share the same visual classes and data attributes as Astro components. For\nbehavior-heavy primitives, use the React behavior hooks from `@santi020k/lumen-react` (`useDialog`,\n`usePopover`, `useDropdownMenu`, `useTabs`, `useSelect`, `useToast`, and `useTooltip`), which track\nthe Astro runtime's ARIA, keyboard, Escape, dismissal, and toast controller semantics.\n\nThe React package entry is a client boundary. In Next.js, it can be imported by a Server Component\nwithout evaluating Lumen's contexts and hooks in the React server runtime; the rendered Lumen\nsubtree becomes a Client Component boundary. Vite and other client-rendered React applications use\nthe same entry without additional setup.\n`Button`, `Link`, `ButtonLink`, and `Input` accept React 19 DOM refs directly as props.\n`Button asChild` composes the button contract onto one child element. Across adapters, native\nnumeric `size` remains available on `Input` and `NativeSelect`; use `visualSize` in Astro and React,\nor `visual-size` in Elements, for presentation. The pre-1.0 Astro and Elements `size=\"sm|lg\"`\nvisual alias is deprecated but remains available for incremental migration.\n`Stat as` and `variant` follow the same semantic-root and visual guidance as the Astro component.\n\n```tsx\nimport \"@santi020k/lumen-react/styles.css\";\nimport { Button, Card, Input, Label } from \"@santi020k/lumen-react\";\n\nexport function SubscribeForm() {\n return (\n <Card>\n <Label htmlFor=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"you@example.com\" />\n <Button type=\"submit\">Subscribe</Button>\n </Card>\n );\n}\n```\n\nFor React Hook Form, spread `register()` directly onto native-backed controls. Install\n`@santi020k/lumen-react-hook-form` only for controlled composites such as `Select`, `DatePicker`,\n`InputOTP`, and `ListBox`. Do not combine React Hook Form and `useFormValidation` unless the\napplication intentionally coordinates two validation sources. For schema validation, use\n`zodResolver` or `yupResolver` from `@hookform/resolvers`; keep Zod or Yup in the application and\nrender the resulting React Hook Form errors through Lumen's `FieldError` contract.\n\n## Web Components\n\nRegister custom elements once, then use `lumen-*` tags in HTML. The elements adapter includes\ninteractive behavior parity for DataTable, Dialog, Popover, DropdownMenu, ContextMenu, Tabs,\nSelect, ThemeBuilder, Toast, Tooltip, forms, RichTextEditor, Schedule, and VirtualList: selection,\nvalidation, rich text command, context menu, schedule drag/drop, theme, and range events, ARIA\nstate, roving/listbox keyboard paths, Escape/outside dismissal, focus return/trapping, native\nselect form participation, and the same toast controller events as the Astro runtime.\n\n```html\n<script type=\"module\">\n import { defineLumenElements } from \"@santi020k/lumen-elements/define\";\n\n defineLumenElements();\n</script>\n\n<lumen-card>\n <label for=\"email\">Email</label>\n <lumen-input\n id=\"email\"\n type=\"email\"\n placeholder=\"you@example.com\"\n ></lumen-input>\n <lumen-button type=\"submit\">Subscribe</lumen-button>\n</lumen-card>\n```\n\nPut input elements inside a native `<form data-ui-form>`. Scalar Lumen controls participate in\n`FormData`, validity, disabled state, focus, and reset through form-associated custom-element\nbehavior with a native-control fallback. Do not generate `<lumen-form>` as the submission\ncontainer.\n\n## Component Selection\n\nThe shared catalog includes:\n\n`Accordion`, `Alert`, `AlertDialog`, `Agenda`, `AspectRatio`, `Attachment`, `Autocomplete`,\n`Avatar`, `Badge`, `BarChart`, `Breadcrumb`, `Bubble`, `Button`, `ButtonGroup`, `Calendar`, `Card`,\n`Carousel`, `Chart`, `Checkbox`, `CheckboxGroup`, `Collapsible`, `Code`, `Combobox`, `Command`,\n`Container`, `ColorPicker`,\n`ContextMenu`, `DataTable`, `DatePicker`, `DateRangePicker`, `Dialog`, `Direction`, `Drawer`,\n`DropdownMenu`, `Empty`, `ErrorSummary`, `Field`, `FieldError`, `Form`, `Grid`, `HoverCard`, `Icon`,\n`Input`, `InputGroup`, `InputOTP`, `Item`, `KanbanBoard`, `KanbanColumn`, `Kbd`, `Label`, `LineChart`, `ListBox`, `Marker`, `Menubar`,\n`Message`, `MessageScroller`, `NativeSelect`, `ContextNavigation`, `NavigationMenu`, `NumberField`, `Pagination`,\n`PasswordField`, `PieChart`, `Popover`, `Progress`, `CopyButton`, `RadioGroup`, `Resizable`, `RichTextEditor`,\n`ScrollArea`, `Schedule`, `SearchField`, `Select`, `Separator`, `Sheet`, `Sidebar`, `Skeleton`,\n`Slider`, `Sparkline`, `Spinner`, `Stack`, `Switch`, `Table`, `Tabs`, `TagGroup`, `Textarea`, `ThemeBuilder`,\n`TimeField`, `Toast`, `Toggle`, `ToggleGroup`, `Tooltip`, `Tree`, `TreeGrid`, `Typography`, and\n`VirtualList`, and `VisuallyHidden`.\n\n## Product Recipes\n\n- Start full product surfaces from the bundled `analytics-dashboard`, `saas-admin`,\n `commerce-dashboard`, `project-workspace`, or `auth-onboarding` recipe. Install with\n `lumen add <recipe>` for Astro, or add `--target react` / `--target elements`. Keep the generated\n shared template stylesheet, then replace sample data and routes while preserving public Lumen\n components, semantic tokens, accessible names, focus paths, and responsive navigation.\n- Use `Sparkline` for a compact trend beside a labelled metric, `BarChart` for categorical\n comparison, `LineChart` for ordered or time-series data, and `PieChart` for a small\n part-to-whole breakdown. Pass already-aggregated `LumenChartSeries` data; keep statistics,\n resampling, model evaluation, and domain calculations in application code. Keep `showTable`\n enabled unless the same values are already available in a nearby semantic table. Prefer a bar\n chart when precise slice comparison matters or the pie would exceed roughly seven categories.\n Use `Chart` as the custom SVG/canvas escape hatch.\n Keep stable identity in `datum.x` and put display text in `datum.xLabel`. Use `formatCategory`\n and `formatValue` for localized axes, SVG titles, and data tables. Choose\n `presentation=\"bare\"` inside an existing Card, `categoryWidth` for long horizontal labels,\n `markers=\"auto\"` or a numeric marker step for dense lines, and `emptyLabel` for actionable\n source-specific guidance.\n- Use `Icon name=\"search\"` or any Lucide icon name for standard interface pictograms. Kebab-case\n names and Lucide aliases work. Pair decorative icons with `decorative`, and pass `label` only\n when the icon carries meaning on its own. Install and register\n `@santi020k/lumen-icons-brand` for supported brand marks, then use names such as\n `brand:github`. Render project-specific or unsupported brand SVGs through the default `Icon`\n slot. Astro reports unknown named icons during development instead of failing silently.\n- Use `Schedule` with `Agenda`, `Calendar`, and `DatePicker` for booking, CRM, content calendar, and\n event planning screens. Mark slots with `data-ui-schedule-slot` and events with\n `data-ui-schedule-event`; the runtime emits `ui:schedule-change` when a draggable event is moved.\n `Calendar` renders a hidden input plus selectable date grid; Astro `UIPrimitives` and registered\n Web Components wire month navigation and roving keyboard focus directly, while React apps can use\n the `Calendar` component or `useCalendar` for the same date selection contract.\n Astro `UIPrimitives` and registered Web Components bind schedule drag/drop directly; React apps\n can use `useSchedule` for `rootProps`, `getEventProps`, `getSlotProps`, and `ui:schedule-change`\n parity. Use `createScheduleSlots`, `moveScheduleEvent`, and `expandRecurringScheduleEvent` from\n `@santi020k/lumen-core` for reusable schedule state; use `getScheduleConflicts` and\n `canPlaceScheduleEvent` before committing drag/drop changes. Persist local schedule edits with\n `createScheduleStorageKey`, `saveScheduleEvents`, and `loadScheduleEvents` against any\n `localStorage`-compatible adapter. Use `resizeScheduleEvent` and `resizeScheduleEvents` for\n snap-to-grid resize handles.\n- Use `KanbanBoard` and `KanbanColumn` for status-based workspaces while continuing to use `Card` for\n items. Mark items with `data-ui-kanban-item` and put `data-ui-kanban-handle` on a dedicated button;\n do not make the whole card draggable. Astro and Elements emit the cancellable, controlled\n `ui:kanban-move-request` event, and React provides `useKanban`. The event reports `itemId`,\n `fromColumn`, `toColumn`, optional `beforeId`, and keyboard or pointer input, but never moves DOM or\n persists application data. The host owns validation, `aria-busy`, optimistic state, rollback,\n status names, and APIs. Use compact `Empty` states inside columns, preserve every column with\n `Skeleton` blocks while loading, and paginate large columns. See [Kanban composition and movement](kanban.md).\n- Use `DataTable`, `Tree`, `TreeGrid`, `VirtualList`, `Pagination`, and `Command` for dense data\n collection workflows. Astro and Elements wire selectable/sortable `DataTable` behavior and\n `VirtualList` range events; React emits the same data attributes for app-level adapters. `Tree`\n and `TreeGrid` use roving keyboard focus, and `VirtualList` emits `ui:virtual-list-range` as the\n rendered range changes. Use `createDataViewState`,\n `serializeDataViewState`, `parseDataViewState`, `applyDataViewState`, and `getVirtualRange` for\n saved views, filtering, sorting, pagination, and virtualization math. Use\n `createDataViewStorageKey`, `saveDataViewState`, and `loadDataViewState` when saved views should\n survive navigation or reloads. Use `pinDataViewColumn`, `unpinDataViewColumn`,\n `toggleDataViewSelection`, `createDataViewSearchParams`, and `createDataViewServerRequest` when\n connecting dense views to router state or server-backed data.\n- Use `Resizable` for split-pane editors, dashboards, inspectors, and side-by-side review layouts.\n Astro `UIPrimitives` and registered Web Components generate separator handles and pane sizing\n directly; React apps can use the `Resizable` component or `useResizable` for pointer, keyboard,\n min/max, and double-click reset parity.\n- Use `ThemeBuilder`, `ColorPicker`, `Card`, `Button`, and `Input` to build token preview and export\n tools. Add `data-ui-theme-brand-hue`, `data-ui-theme-accent-hue`, `data-ui-theme-scheme`,\n `data-ui-theme-mode`, manual color inputs such as `data-ui-theme-primary-hex`, and\n `data-ui-theme-target` for scoped live previews. Add `data-ui-theme-export-format`,\n `data-ui-theme-export`, and `data-ui-theme-output` to copy CSS, Figma variable JSON, or design\n token JSON. Astro `UIPrimitives` and registered Web Components wire those controls directly;\n React apps can use `useThemeBuilder` for the same tokens, exports, and control props. Use\n `createThemePalette`, `createThemeBuilderTokens`, `exportThemeBuilderValue`, `exportThemeCss`,\n `parseThemeCss`, and `scoreThemeContrast`\n for theme import/export and accessibility checks. Use `suggestReadableInk`, `mergeThemeTokens`,\n and `tuneThemeContrast` when generated palettes need readable foreground repair.\n- Use `RichTextEditor` with `ButtonGroup`, `ToggleGroup`, and `Textarea` for editor compositions.\n Controls with `data-ui-editor-command` dispatch browser editing commands and emit\n `ui:editor-command`; use `data-ui-editor-value` for headings, links, and other value-bearing\n commands. Editable surfaces support common formatting shortcuts, synchronize toggle\n `aria-pressed` state, and emit `ui:editor-change` with HTML and plain text. Astro `UIPrimitives`\n and registered Web Components bind those controls directly; React apps can use\n `useRichTextEditor` for `rootProps`, `getCommandProps`, `getEditableProps`, and `executeCommand`.\n For Tiptap, ProseMirror, Lexical, or Markdown engines, keep the external editor\n package in the app and bridge toolbar buttons through the emitted command event instead of adding\n the editor engine to Lumen.\n- Use `Autocomplete`, `SearchField`, `NumberField`, `TimeField`, `DateRangePicker`, `ColorPicker`,\n and `TagGroup` when forms need more than plain text inputs. Add `data-ui-form` to forms that\n should reflect native Constraint Validation API state into `Field` error slots on blur and submit.\n For `InputOTP`, Astro `UIPrimitives` and registered Web Components render/sync a real native input\n with visual OTP segments; React apps can use the `InputOTP` component or `useInputOTP` for the\n same sanitization, paste handling, and caret navigation.\n For `DateRangePicker`, Astro `UIPrimitives` and registered Web Components sync the paired native\n date input `min`/`max` constraints directly; React apps can use `useDateRangePicker` for the same\n start/end syncing props.\n Use native attributes such as `required`, `pattern`, `min`, `max`, and `type`; add\n `data-error-required`, `data-error-pattern`, `data-error-min`, `data-error-max`, or\n `data-error-custom` when a control needs custom copy. Listen for `ui:validate` to call\n `control.setCustomValidity(message)` before the runtime reads validity, and listen for\n `ui:invalid` or `ui:valid` on the form for submission state. Astro `UIPrimitives` and registered\n Web Components bind `data-ui-form` directly; React apps can use `useFormValidation` with `Field`\n to get the same control props, CustomEvents, and error slot updates.\n- Use `Toast` for transient feedback. Static `<Toast>` markup works without\n JavaScript. With Astro `UIPrimitives` or registered Web Components, create toasts with\n `window.LumenToast.create(detail)`, `LumenToast.create(detail)` from `@santi020k/lumen-elements`,\n or `document.dispatchEvent(new CustomEvent('ui:toast', { detail }))`; update them with\n `.update(id, detail)` or `ui:toast-update`; and dismiss them with `.dismiss(id?)` or\n `ui:toast-dismiss`. `detail` supports `title`, `description`, `variant`, `duration`, `placement`,\n `max`, and `action: { label, value }`. Destructive toasts use `role=\"alert\"`; other variants use\n `role=\"status\"`. Runtime toasts pause their timeout on hover or focus, and Escape dismisses the\n focused toast.\n\n## Runtime CustomEvents\n\nWhen `UIPrimitives` is mounted, or when Web Components are registered for matching behavior, Lumen\nuses these CustomEvents:\n\n| Event | Target | Detail | Fires When |\n| -------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |\n| `ui:data-table-selection-change` | `DataTable` root `[data-ui-datatable]` | `{ values: string[] }` | Selectable row checkboxes, the select-all checkbox, or form reset changes selected row values. |\n| `ui:schedule-change` | `Schedule` root `[data-ui-schedule]` | `{ eventId?: string, slot?: string }` | A draggable schedule event is dropped on a `[data-ui-schedule-slot]`. |\n| `ui:virtual-list-range` | `VirtualList` root `[data-ui-virtual-list]` | `{ startIndex: number, endIndex: number }` | The virtual list calculates its visible range on init or scroll. |\n| `ui:kanban-move-request` | `KanbanBoard` root `[data-ui-kanban]` | `{ itemId: string, fromColumn: string, toColumn: string, beforeId?: string, input: 'keyboard' \\| 'pointer' }` | A handle requests a controlled move; the event is cancellable and never moves data or DOM. |\n| `ui:tag-remove` | `TagGroup` root `.ui-tag-group` or `[data-ui-tag-group]` | `{ value?: string }` | A `[data-ui-tag-remove]` control removes its closest tag or list item. |\n| `ui:editor-command` | `RichTextEditor` root `[data-ui-rich-text-editor]` | `{ command: string, executed: boolean, value?: string }` | A toolbar control or keyboard shortcut runs an editor command. |\n| `ui:editor-change` | `RichTextEditor` root `[data-ui-rich-text-editor]` | `{ html: string, text: string }` | Editable content changes or an editor command runs. |\n| `ui:theme-change` | `ThemeBuilder` root `[data-ui-theme-builder]` | `{ hue: number, accentHue: number, mode: 'generated' \\| 'manual', scheme: 'dark' \\| 'light', tokens: Record<string, string> }` | Hue, manual color, mode, or scheme controls update generated tokens. |\n| `ui:theme-export` | `ThemeBuilder` root `[data-ui-theme-builder]` | `{ css?: string, format: 'css' \\| 'figma' \\| 'tokens', tokens: Record<string, string> \\| null, value: string }` | A `[data-ui-theme-export]` control writes the selected export to the output and clipboard when available. |\n| `ui:language-change` | `LanguageToggle` control | `{ previousValue: string, value: string }` | Requests or applies the next configured locale. |\n| `ui:validate` | Form `[data-ui-form]` | `{ control, form, value }` | Before validation state is read, so custom code can call `control.setCustomValidity(message)`. |\n| `ui:invalid` | Form `[data-ui-form]` | `{ control?, controls?, form }` | Blur or submit validation finds invalid controls. Failed submits focus the first invalid control. |\n| `ui:valid` | Form `[data-ui-form]` | `{ control?, controls?, form }` | Blur or submit validation finds the checked control or form valid. |\n| `ui:toast` | `document` | `{ id?, title?, description?, variant?, duration?, placement?, action? }` | Creates a runtime toast in the matching notification viewport. |\n| `ui:toast-update` | `document` | `{ id, title?, description?, variant?, duration? }` | Updates a runtime toast. |\n| `ui:toast-dismiss` | `document` | `{ id? }` | Dismisses one runtime toast, or all runtime toasts when `id` is omitted. |\n| `ui:toast-action` | Toast `[data-ui-toast]` | `{ id, value? }` | A runtime toast action button is pressed, unless the action specifies a custom event name. |\n\n## Registry Helper\n\n`@santi020k/lumen` exports typed component, recipe, and documentation metadata from\n`@santi020k/lumen/registry`. The package also ships a small `lumen` binary. Individual components\nare addressable by name, including kebab-case aliases such as `data-table`:\n\n```bash\nlumen list\nlumen show Button\nlumen show data-table\nlumen add Button\nlumen add Button --target react\nlumen add Button --target elements\nlumen add data-table\nlumen show scheduler\nlumen add scheduler\nlumen add scheduler --target react\nlumen add scheduler --target elements\nlumen add scheduler --dry-run\nlumen add scheduler --fail-on-conflict\nlumen add scheduler --force\nlumen add scheduler --merge\nlumen list --registry ./registry/lumen.registry.json\nlumen add custom-recipe --registry ./registry/custom.registry.json\nlumen add private-recipe --registry https://example.com/lumen.registry.json --registry-token \"$TOKEN\"\nlumen install\nlumen doctor --json\nlumen init --framework astro --tailwind\n```\n\n`lumen add <component>` defaults to an Astro wrapper. Use `--target react` for a local React wrapper\nor `--target elements` for a custom-elements starter. Bundled recipes also accept `--target astro`,\n`--target react`, and `--target elements`; external registry inline files keep their authored paths\nand sources.\n\nBefore inventing a wrapper or custom class pattern, check the component source in\n`packages/astro/components`. Astro is the reference implementation for props, class names, data\nattributes, and accessible markup.\n\n## Styling Rules for Generated Code\n\n- Do not require consumers to configure Tailwind for Lumen components.\n- Prefer component props and composition before adding custom CSS.\n- Use the shared token names for custom surfaces: `canvas`, `surface`, `surface-muted`,\n `surface-strong`, `line`, `ink`, `ink-soft`, `ink-muted`, `brand`, `brand-solid`, `brand-soft`,\n `accent`, `success`, `warning`, and `danger`.\n- Use `glass` as a boolean prop or attribute for glass surfaces when supported.\n- Preserve semantic labels, keyboard paths, focus states, and native form attributes.\n\n## Figma Handoff\n\nUse `exportThemeFigmaVariables` from `@santi020k/lumen-core/figma` when a design workflow needs\nFigma variable-friendly color values, and `exportThemeDesignTokens` when exporting standard design\ntoken JSON for token importers. See [docs/figma.md](figma.md) for the recommended Figma workflow.\n\n## When Editing This Repo\n\n- Shared contracts belong in `packages/core`.\n- Astro implementation and standalone CSS belong in `packages/astro`.\n- React-specific primitives belong in `packages/react`.\n- Web Component-specific primitives belong in `packages/elements`.\n- Update the relevant package README when public usage changes.\n- Add tests beside package code as `*.test.ts` when behavior, exported metadata, or component\n contracts change.\n",
32193
32298
  "readme": "<p align=\"center\">\n <a href=\"https://lumen.santi020k.com\">\n <img src=\"./apps/docs/public/logo.svg\" alt=\"Lumen UI\" width=\"239\" height=\"60\">\n </a>\n</p>\n\n<h1 align=\"center\">Lumen UI</h1>\n\n<p align=\"center\">\n A cross-platform UI system with 150+ web primitives and shared native foundations.\n</p>\n\n<p align=\"center\">\n <a href=\"https://lumen.santi020k.com\">Documentation</a>\n ·\n <a href=\"https://lumen.santi020k.com/docs/components\">Components</a>\n ·\n <a href=\"https://www.figma.com/community/file/1662337342676541513\">Figma library</a>\n ·\n <a href=\"https://github.com/santi020k/lumen/issues\">Issues</a>\n</p>\n\n<p align=\"center\">\n <a href=\"https://github.com/santi020k/lumen/actions\"><img alt=\"GitHub Actions\" src=\"https://img.shields.io/github/actions/workflow/status/santi020k/lumen/ci.yml?branch=main&style=flat-square\"></a>\n <a href=\"https://www.npmjs.com/package/@santi020k/lumen-astro\"><img alt=\"npm version\" src=\"https://img.shields.io/npm/v/@santi020k/lumen-astro?style=flat-square&label=npm\"></a>\n <a href=\"./LICENSE\"><img alt=\"MIT license\" src=\"https://img.shields.io/badge/license-MIT-111827?style=flat-square\"></a>\n</p>\n\nLumen gives applications a consistent visual language without tying them to one rendering model.\nIts packages share tokens, component contracts, styles, and interaction patterns while keeping each\nframework and platform's native authoring experience.\n\n- **Framework native:** Astro components, React primitives, and standards-based custom elements.\n- **Native foundations:** generated React Native, SwiftUI, and Jetpack Compose tokens preserve the\n same semantic roles without introducing DOM or CSS assumptions.\n- **Accessible by default:** semantic markup, keyboard paths, focus management, and reduced-motion\n support are built into the primitives.\n- **Standalone CSS:** no Tailwind configuration is required. Tailwind users get an explicit layer\n integration.\n- **Progressively enhanced:** Astro interactions use a small client runtime instead of requiring an\n application framework.\n- **Ready for design and AI workflows:** published Figma resources, a portable agent skill, an MCP\n server, `llms.txt`, and a machine-readable registry ship alongside the component system.\n- **Product-ready templates:** five responsive dashboard and application families are available as\n live previews and installable Astro, React, and Elements recipes.\n\n## Dashboards and templates\n\nExplore the [template gallery](https://lumen.santi020k.com/templates) for complete analytics,\nSaaS admin, commerce, project workspace, and authentication/onboarding experiences. Each family\nuses public Lumen primitives and semantic tokens, includes responsive and accessibility coverage,\nand ships through the CLI for all three framework targets:\n\n```bash\nlumen add analytics-dashboard\nlumen add commerce-dashboard --target react\nlumen add auth-onboarding --target elements\n```\n\n## Web quick start\n\nInstall the package for your framework:\n\n```bash\n# Astro\npnpm add @santi020k/lumen-astro\n\n# React\npnpm add @santi020k/lumen-react\n\n# Web Components\npnpm add @santi020k/lumen-elements\n```\n\n### Astro\n\nImport the stylesheet and mount `UIPrimitives` once in your root layout. The runtime enhances all\ninteractive Lumen markup on the page.\n\n```astro\n---\nimport '@santi020k/lumen-astro/styles.css'\nimport { UIPrimitives } from '@santi020k/lumen-astro'\n---\n\n<html lang=\"en\">\n <body>\n <slot />\n <UIPrimitives />\n </body>\n</html>\n```\n\n```astro\n---\nimport { Button, Card, Input } from '@santi020k/lumen-astro'\n---\n\n<Card>\n <label for=\"email\">Email</label>\n <Input id=\"email\" name=\"email\" type=\"email\" placeholder=\"you@example.com\" />\n <Button>Subscribe</Button>\n</Card>\n```\n\n### React\n\nLoad the stylesheet once from your app entry or global CSS:\n\n```tsx\nimport '@santi020k/lumen-react/styles.css'\nimport { Button, Card, Input } from '@santi020k/lumen-react'\n\nexport function SubscribeForm() {\n return (\n <Card>\n <label htmlFor=\"email\">Email</label>\n <Input id=\"email\" name=\"email\" type=\"email\" placeholder=\"you@example.com\" />\n <Button>Subscribe</Button>\n </Card>\n )\n}\n```\n\n### Web Components\n\nImport the styles and register the elements once:\n\n```html\n<style>\n @import \"@santi020k/lumen-elements/styles.css\";\n</style>\n\n<script type=\"module\">\n import { defineLumenElements } from '@santi020k/lumen-elements/define'\n\n defineLumenElements()\n</script>\n\n<lumen-card>\n <label for=\"email\">Email</label>\n <lumen-input id=\"email\" name=\"email\" type=\"email\" placeholder=\"you@example.com\"></lumen-input>\n <lumen-button>Subscribe</lumen-button>\n</lumen-card>\n```\n\nSee the [Web documentation](https://lumen.santi020k.com/docs/web) for installation, theming,\ncomponent examples, and API details.\n\nFor native applications, choose the [React Native](https://lumen.santi020k.com/docs/react-native),\n[Apple / SwiftUI](https://lumen.santi020k.com/docs/apple), or\n[Android / Compose](https://lumen.santi020k.com/docs/android) guide. The\n[shared foundations](https://lumen.santi020k.com/docs/foundations) explain the cross-platform token\nand component contract; repository contributors can also use the\n[cross-platform architecture](./docs/cross-platform.md) and\n[native component reference](./docs/native-components.md).\n\n> **Native platform status: Beta.** The shared token foundation is stable, while the React Native,\n> SwiftUI, and Compose component APIs are being validated through real applications and may evolve.\n> Review release notes when upgrading and share platform-specific feedback.\n\n## Native playgrounds\n\nThe repository includes three searchable, interactive galleries built from the real native\npackages:\n\n- [`apps/playground-react-native`](./apps/playground-react-native) runs through Expo on the web,\n iOS, and Android and includes EAS profiles for TestFlight, Android App Bundles, and APKs.\n- [`apps/playground-apple`](./apps/playground-apple) builds as an iOS Xcode app for devices and\n TestFlight, or as a macOS Swift Package executable for local exploration.\n- [`apps/playground-android`](./apps/playground-android) builds a native Compose application and a\n directly installable debug APK.\n\nSee the [playground workflow](./docs/playgrounds.md) for run, capture, and distribution commands.\n\n## Tailwind CSS\n\nKeep the layer prelude, Tailwind import, and Lumen stylesheet in the same shared CSS entry. Replace\n`lumen-astro` with the package for your framework.\n\n```css\n@import \"@santi020k/lumen-astro/layers.css\";\n@import \"tailwindcss\";\n@import \"@santi020k/lumen-astro/styles.css\";\n```\n\nThis order places Tailwind base styles before Lumen components and Tailwind utilities above Lumen\ncomponent defaults.\n\n## Packages\n\n| Package | Purpose |\n| --- | --- |\n| [`@santi020k/lumen-astro`](./packages/astro) | Reference implementation, Astro components, and progressive-enhancement runtime |\n| [`@santi020k/lumen-react`](./packages/react) | React components and behavior hooks |\n| [`@santi020k/lumen-react-hook-form`](./packages/react-hook-form) | Optional React Hook Form adapters for composite controls |\n| [`@santi020k/lumen-elements`](./packages/elements) | Standards-based Web Components |\n| [`@santi020k/lumen-core`](./packages/core) | Shared tokens, metadata, class helpers, and behavior contracts |\n| [`@santi020k/lumen-tokens`](./packages/tokens) | Canonical platform-neutral design token source |\n| [`@santi020k/lumen-react-native`](./packages/react-native) | React Native foundations and native primitives |\n| [`LumenUI`](./packages/swift) | Swift Package for SwiftUI foundations and native primitives |\n| [`lumen-compose`](./packages/compose) | Android library for Jetpack Compose foundations and native primitives |\n| [`@santi020k/lumen-icons-brand`](./packages/icons-brand) | Optional namespaced brand icons for the shared `Icon` API |\n| [`@santi020k/lumen`](./packages/lumen) | Umbrella package, CLI, registry metadata, and public package map |\n| [`@santi020k/lumen-mcp`](./packages/mcp) | MCP server for component discovery and source retrieval |\n\n## AI and design workflows\n\nInstall the portable Lumen skill in Codex, Claude Code, Cursor, Windsurf, and other compatible\ncoding agents:\n\n```bash\nnpx skills add santi020k/lumen --skill lumen-ui\n```\n\nThe skill teaches agents how to select, compose, theme, and verify Lumen primitives. Pair it with\n[`@santi020k/lumen-mcp`](./packages/mcp) when an agent needs to search the live catalog or retrieve\ncurrent source, props, tokens, and usage rules.\n\nAdditional machine-readable surfaces include:\n\n- [`llms.txt`](./llms.txt) for a concise project map.\n- [`docs/ai-usage.md`](./docs/ai-usage.md) for downstream generation examples.\n- [`docs/styling-contract.md`](./docs/styling-contract.md) for stable parts and component variables.\n- [`docs/import-and-icon-performance.md`](./docs/import-and-icon-performance.md) for repeatable import and icon evidence.\n- [`docs/consumer-regression-fixtures.md`](./docs/consumer-regression-fixtures.md) for production-shaped local coverage.\n- [`docs/project-adoption.md`](./docs/project-adoption.md) for the sibling-project audit and staged\n web and native migration strategy.\n- [`registry/lumen.registry.json`](./registry/lumen.registry.json) for recipes and installable file\n groups.\n- [`docs/figma.md`](./docs/figma.md) for Figma variables and Code Connect workflows.\n- [`docs/cross-platform.md`](./docs/cross-platform.md) for the native architecture, support tiers,\n and component-parity policy.\n\n## Contributing\n\nLumen is a pnpm workspace. Astro is the reference implementation; shared contracts belong in\n`packages/core`, while framework-specific behavior stays in its adapter package.\n\n```bash\npnpm install\npnpm run dev\npnpm run validate\n```\n\nRead [`CONTRIBUTING.md`](./CONTRIBUTING.md) before opening a pull request. User-visible package\nchanges require a changeset.\n\n## Community\n\nLumen is free and MIT licensed while the project focuses on adoption, public examples, and\nreal-world feedback.\n\n- Explore task-oriented [guides](https://lumen.santi020k.com/guides).\n- Share a public project in the [community showcase](https://lumen.santi020k.com/community).\n- Evaluate the system with the open [team adoption path](https://lumen.santi020k.com/teams).\n- Tell us what would improve your next build through the\n [adoption survey](https://github.com/santi020k/lumen/issues/new?template=adoption-feedback.yml).\n\nMaintainers can use the [exposure playbook](./docs/exposure-playbook.md) for publishing cadence,\ncampaign measurement, video scripts, and launch copy.\n\n## License\n\nLumen is available under the [MIT License](./LICENSE).\n"
32194
32299
  },
32195
32300
  "meta": {
32196
- "catalogHash": "c683fe44418e6ee45acbd061dfbf434f305d4a6b4c24054934da1046f4a2084b",
32301
+ "catalogHash": "02fdac6cca80508301905223659ae7164ddc65adc9a9d61327e51d9fa45ce68f",
32197
32302
  "componentCount": 153,
32198
- "nativeComponentCount": 38,
32303
+ "nativeComponentCount": 41,
32199
32304
  "packages": [
32200
32305
  "@santi020k/lumen-astro",
32201
32306
  "@santi020k/lumen-react",
@@ -32207,12 +32312,12 @@
32207
32312
  "com.santi020k:lumen-compose"
32208
32313
  ],
32209
32314
  "packageVersions": {
32210
- "@santi020k/lumen-astro": "1.2.0",
32211
- "@santi020k/lumen-core": "1.2.0",
32212
- "@santi020k/lumen-elements": "1.2.0",
32213
- "@santi020k/lumen-mcp": "1.2.0",
32214
- "@santi020k/lumen-react": "1.2.0",
32215
- "@santi020k/lumen-react-native": "0.1.0",
32315
+ "@santi020k/lumen-astro": "1.3.0",
32316
+ "@santi020k/lumen-core": "1.3.0",
32317
+ "@santi020k/lumen-elements": "1.3.0",
32318
+ "@santi020k/lumen-mcp": "1.3.0",
32319
+ "@santi020k/lumen-react": "1.3.0",
32320
+ "@santi020k/lumen-react-native": "0.2.0",
32216
32321
  "@santi020k/lumen-tokens": "0.1.0",
32217
32322
  "LumenUI": "workspace",
32218
32323
  "com.santi020k:lumen-compose": "workspace"
@@ -32220,7 +32325,7 @@
32220
32325
  "registryName": "lumen",
32221
32326
  "registryVersion": 1,
32222
32327
  "schemaVersion": 4,
32223
- "serverVersion": "1.2.0"
32328
+ "serverVersion": "1.3.0"
32224
32329
  },
32225
32330
  "nativeComponents": [
32226
32331
  {
@@ -32248,7 +32353,7 @@
32248
32353
  "example": "LumenTheme(darkTheme = false) {\n AppContent()\n}",
32249
32354
  "exportName": "LumenTheme",
32250
32355
  "importStatement": "import com.santi020k.lumen.LumenTheme",
32251
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
32356
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
32252
32357
  "language": "kotlin",
32253
32358
  "packageName": "com.santi020k:lumen-compose",
32254
32359
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -32298,7 +32403,7 @@
32298
32403
  "example": "AppContent()\n .lumenTheme(.light)",
32299
32404
  "exportName": ".lumenTheme",
32300
32405
  "importStatement": "import LumenUI",
32301
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
32406
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
32302
32407
  "language": "swift",
32303
32408
  "packageName": "LumenUI",
32304
32409
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -32347,7 +32452,7 @@
32347
32452
  "example": "LumenText(\n \"Welcome back\",\n variant = LumenTextVariant.Title,\n tone = LumenTextTone.Default\n)",
32348
32453
  "exportName": "LumenText",
32349
32454
  "importStatement": "import com.santi020k.lumen.LumenText",
32350
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
32455
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
32351
32456
  "language": "kotlin",
32352
32457
  "packageName": "com.santi020k:lumen-compose",
32353
32458
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -32421,7 +32526,7 @@
32421
32526
  "example": "LumenText(\n \"Welcome back\",\n variant: .title,\n tone: .default\n)",
32422
32527
  "exportName": "LumenText",
32423
32528
  "importStatement": "import LumenUI",
32424
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
32529
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
32425
32530
  "language": "swift",
32426
32531
  "packageName": "LumenUI",
32427
32532
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -32470,7 +32575,7 @@
32470
32575
  "example": "LumenSurface(\n tone = LumenSurfaceTone.Muted,\n padding = LumenSurfacePadding.Lg\n) {\n LumenText(\"Workspace\")\n}",
32471
32576
  "exportName": "LumenSurface",
32472
32577
  "importStatement": "import com.santi020k.lumen.LumenSurface",
32473
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
32578
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
32474
32579
  "language": "kotlin",
32475
32580
  "packageName": "com.santi020k:lumen-compose",
32476
32581
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -32544,7 +32649,7 @@
32544
32649
  "example": "LumenSurface(tone: .muted, padding: .lg) {\n LumenText(\"Workspace\")\n}",
32545
32650
  "exportName": "LumenSurface",
32546
32651
  "importStatement": "import LumenUI",
32547
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
32652
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
32548
32653
  "language": "swift",
32549
32654
  "packageName": "LumenUI",
32550
32655
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -32593,11 +32698,11 @@
32593
32698
  "example": "LumenIcon(\n imageVector = Icons.Default.Search,\n contentDescription = \"Search\",\n size = LumenIconSize.Md\n)",
32594
32699
  "exportName": "LumenIcon",
32595
32700
  "importStatement": "import com.santi020k.lumen.LumenIcon",
32596
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
32701
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
32597
32702
  "language": "kotlin",
32598
32703
  "packageName": "com.santi020k:lumen-compose",
32599
32704
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
32600
- "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/Primitives.kt",
32705
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt",
32601
32706
  "symbol": "LumenIcon"
32602
32707
  },
32603
32708
  "react-native": {
@@ -32667,7 +32772,7 @@
32667
32772
  "example": "LumenIcon(\n systemName: \"magnifyingglass\",\n size: .md,\n label: \"Search\"\n)",
32668
32773
  "exportName": "LumenIcon",
32669
32774
  "importStatement": "import LumenUI",
32670
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
32775
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
32671
32776
  "language": "swift",
32672
32777
  "packageName": "LumenUI",
32673
32778
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -32728,7 +32833,7 @@
32728
32833
  "example": "LumenIconButton(\n imageVector = Icons.Default.Settings,\n contentDescription = \"Settings\",\n onClick = ::openSettings\n)",
32729
32834
  "exportName": "LumenIconButton",
32730
32835
  "importStatement": "import com.santi020k.lumen.LumenIconButton",
32731
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
32836
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
32732
32837
  "language": "kotlin",
32733
32838
  "packageName": "com.santi020k:lumen-compose",
32734
32839
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -32826,7 +32931,7 @@
32826
32931
  "example": "LumenIconButton(\n systemName: \"gearshape\",\n label: \"Settings\",\n action: openSettings\n)",
32827
32932
  "exportName": "LumenIconButton",
32828
32933
  "importStatement": "import LumenUI",
32829
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
32934
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
32830
32935
  "language": "swift",
32831
32936
  "packageName": "LumenUI",
32832
32937
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -32887,7 +32992,7 @@
32887
32992
  "example": "LumenButton(\n onClick = ::continueFlow,\n intent = LumenButtonIntent.Primary\n) {\n Text(\"Continue\")\n}",
32888
32993
  "exportName": "LumenButton",
32889
32994
  "importStatement": "import com.santi020k.lumen.LumenButton",
32890
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
32995
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
32891
32996
  "language": "kotlin",
32892
32997
  "packageName": "com.santi020k:lumen-compose",
32893
32998
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -32985,7 +33090,7 @@
32985
33090
  "example": "LumenButton(\n \"Continue\",\n intent: .primary,\n action: continueFlow\n)",
32986
33091
  "exportName": "LumenButton",
32987
33092
  "importStatement": "import LumenUI",
32988
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
33093
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
32989
33094
  "language": "swift",
32990
33095
  "packageName": "LumenUI",
32991
33096
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -33046,7 +33151,7 @@
33046
33151
  "example": "LumenTextField(\n value = email,\n onValueChange = { email = it },\n label = \"Email address\"\n)",
33047
33152
  "exportName": "LumenTextField",
33048
33153
  "importStatement": "import com.santi020k.lumen.LumenTextField",
33049
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
33154
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
33050
33155
  "language": "kotlin",
33051
33156
  "packageName": "com.santi020k:lumen-compose",
33052
33157
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -33144,7 +33249,7 @@
33144
33249
  "example": "LumenTextField(\n \"Email address\",\n text: $email\n)",
33145
33250
  "exportName": "LumenTextField",
33146
33251
  "importStatement": "import LumenUI",
33147
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
33252
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
33148
33253
  "language": "swift",
33149
33254
  "packageName": "LumenUI",
33150
33255
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -33187,7 +33292,7 @@
33187
33292
  "example": "LumenBadge(\n text = \"Active\",\n tone = LumenBadgeTone.Success\n)",
33188
33293
  "exportName": "LumenBadge",
33189
33294
  "importStatement": "import com.santi020k.lumen.LumenBadge",
33190
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
33295
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
33191
33296
  "language": "kotlin",
33192
33297
  "packageName": "com.santi020k:lumen-compose",
33193
33298
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -33249,7 +33354,7 @@
33249
33354
  "example": "LumenBadge(\"Active\", tone: .success)",
33250
33355
  "exportName": "LumenBadge",
33251
33356
  "importStatement": "import LumenUI",
33252
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
33357
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
33253
33358
  "language": "swift",
33254
33359
  "packageName": "LumenUI",
33255
33360
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -33286,7 +33391,7 @@
33286
33391
  "example": "LumenDivider()",
33287
33392
  "exportName": "LumenDivider",
33288
33393
  "importStatement": "import com.santi020k.lumen.LumenDivider",
33289
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
33394
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
33290
33395
  "language": "kotlin",
33291
33396
  "packageName": "com.santi020k:lumen-compose",
33292
33397
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -33336,7 +33441,7 @@
33336
33441
  "example": "LumenDivider()",
33337
33442
  "exportName": "LumenDivider",
33338
33443
  "importStatement": "import LumenUI",
33339
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
33444
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
33340
33445
  "language": "swift",
33341
33446
  "packageName": "LumenUI",
33342
33447
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -33379,7 +33484,7 @@
33379
33484
  "example": "LumenSpinner(label = \"Loading projects\")",
33380
33485
  "exportName": "LumenSpinner",
33381
33486
  "importStatement": "import com.santi020k.lumen.LumenSpinner",
33382
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
33487
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
33383
33488
  "language": "kotlin",
33384
33489
  "packageName": "com.santi020k:lumen-compose",
33385
33490
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -33441,7 +33546,7 @@
33441
33546
  "example": "LumenSpinner(label: \"Loading projects\")",
33442
33547
  "exportName": "LumenSpinner",
33443
33548
  "importStatement": "import LumenUI",
33444
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
33549
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
33445
33550
  "language": "swift",
33446
33551
  "packageName": "LumenUI",
33447
33552
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -33490,7 +33595,7 @@
33490
33595
  "example": "LumenCard(variant = LumenCardVariant.Muted) {\n LumenText(\"Team workspace\")\n}",
33491
33596
  "exportName": "LumenCard",
33492
33597
  "importStatement": "import com.santi020k.lumen.LumenCard",
33493
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
33598
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
33494
33599
  "language": "kotlin",
33495
33600
  "packageName": "com.santi020k:lumen-compose",
33496
33601
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -33564,7 +33669,7 @@
33564
33669
  "example": "LumenCard(variant: .muted) {\n LumenText(\"Team workspace\")\n}",
33565
33670
  "exportName": "LumenCard",
33566
33671
  "importStatement": "import LumenUI",
33567
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
33672
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
33568
33673
  "language": "swift",
33569
33674
  "packageName": "LumenUI",
33570
33675
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -33613,7 +33718,7 @@
33613
33718
  "example": "LumenAlert(variant = LumenAlertVariant.Success) {\n Text(\"Profile synced\")\n}",
33614
33719
  "exportName": "LumenAlert",
33615
33720
  "importStatement": "import com.santi020k.lumen.LumenAlert",
33616
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
33721
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
33617
33722
  "language": "kotlin",
33618
33723
  "packageName": "com.santi020k:lumen-compose",
33619
33724
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -33687,7 +33792,7 @@
33687
33792
  "example": "LumenAlert(variant: .success) {\n Text(\"Profile synced\")\n}",
33688
33793
  "exportName": "LumenAlert",
33689
33794
  "importStatement": "import LumenUI",
33690
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
33795
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
33691
33796
  "language": "swift",
33692
33797
  "packageName": "LumenUI",
33693
33798
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -33736,7 +33841,7 @@
33736
33841
  "example": "LumenProgress(\n value = 72f,\n label = \"Profile completion\"\n)",
33737
33842
  "exportName": "LumenProgress",
33738
33843
  "importStatement": "import com.santi020k.lumen.LumenProgress",
33739
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
33844
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
33740
33845
  "language": "kotlin",
33741
33846
  "packageName": "com.santi020k:lumen-compose",
33742
33847
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -33810,7 +33915,7 @@
33810
33915
  "example": "LumenProgress(\n value: 72,\n label: \"Profile completion\"\n)",
33811
33916
  "exportName": "LumenProgress",
33812
33917
  "importStatement": "import LumenUI",
33813
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
33918
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
33814
33919
  "language": "swift",
33815
33920
  "packageName": "LumenUI",
33816
33921
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -33859,7 +33964,7 @@
33859
33964
  "example": "LumenSkeleton(\n height = 16.dp,\n label = \"Loading profile\"\n)",
33860
33965
  "exportName": "LumenSkeleton",
33861
33966
  "importStatement": "import com.santi020k.lumen.LumenSkeleton",
33862
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
33967
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
33863
33968
  "language": "kotlin",
33864
33969
  "packageName": "com.santi020k:lumen-compose",
33865
33970
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -33933,7 +34038,7 @@
33933
34038
  "example": "LumenSkeleton(\n height: 16,\n label: \"Loading profile\"\n)",
33934
34039
  "exportName": "LumenSkeleton",
33935
34040
  "importStatement": "import LumenUI",
33936
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
34041
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
33937
34042
  "language": "swift",
33938
34043
  "packageName": "LumenUI",
33939
34044
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -33994,7 +34099,7 @@
33994
34099
  "example": "LumenDisclosure(\n title = \"Implementation notes\",\n expanded = expanded,\n onExpandedChange = ::setExpanded\n) {\n LumenText(\"Native details\")\n}",
33995
34100
  "exportName": "LumenDisclosure",
33996
34101
  "importStatement": "import com.santi020k.lumen.LumenDisclosure",
33997
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
34102
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
33998
34103
  "language": "kotlin",
33999
34104
  "packageName": "com.santi020k:lumen-compose",
34000
34105
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -34092,7 +34197,7 @@
34092
34197
  "example": "LumenDisclosure(\n \"Implementation notes\",\n isExpanded: $isExpanded\n) {\n LumenText(\"Native details\")\n}",
34093
34198
  "exportName": "LumenDisclosure",
34094
34199
  "importStatement": "import LumenUI",
34095
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
34200
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
34096
34201
  "language": "swift",
34097
34202
  "packageName": "LumenUI",
34098
34203
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -34141,7 +34246,7 @@
34141
34246
  "example": "LumenAvatar(\n fallback = \"SM\",\n size = LumenAvatarSize.Lg,\n label = \"Santiago Molina\"\n)",
34142
34247
  "exportName": "LumenAvatar",
34143
34248
  "importStatement": "import com.santi020k.lumen.LumenAvatar",
34144
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
34249
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
34145
34250
  "language": "kotlin",
34146
34251
  "packageName": "com.santi020k:lumen-compose",
34147
34252
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -34215,7 +34320,7 @@
34215
34320
  "example": "LumenAvatar(\n fallback: \"SM\",\n size: .lg,\n label: \"Santiago Molina\"\n)",
34216
34321
  "exportName": "LumenAvatar",
34217
34322
  "importStatement": "import LumenUI",
34218
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
34323
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
34219
34324
  "language": "swift",
34220
34325
  "packageName": "LumenUI",
34221
34326
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -34270,7 +34375,7 @@
34270
34375
  "example": "LumenTextarea(\n value = notes,\n onValueChange = ::setNotes,\n label = \"Release notes\",\n description = \"Summarize the visible changes.\"\n)",
34271
34376
  "exportName": "LumenTextarea",
34272
34377
  "importStatement": "import com.santi020k.lumen.LumenTextarea",
34273
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
34378
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
34274
34379
  "language": "kotlin",
34275
34380
  "packageName": "com.santi020k:lumen-compose",
34276
34381
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -34356,7 +34461,7 @@
34356
34461
  "example": "LumenTextarea(\n \"Release notes\",\n text: $notes,\n description: \"Summarize the visible changes.\"\n)",
34357
34462
  "exportName": "LumenTextarea",
34358
34463
  "importStatement": "import LumenUI",
34359
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
34464
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
34360
34465
  "language": "swift",
34361
34466
  "packageName": "LumenUI",
34362
34467
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -34411,7 +34516,7 @@
34411
34516
  "example": "LumenFieldGroup(\n label = \"Notification channels\",\n description = \"Choose every channel the team should use.\",\n required = true\n) {\n NotificationControls()\n}",
34412
34517
  "exportName": "LumenFieldGroup",
34413
34518
  "importStatement": "import com.santi020k.lumen.LumenFieldGroup",
34414
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
34519
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
34415
34520
  "language": "kotlin",
34416
34521
  "packageName": "com.santi020k:lumen-compose",
34417
34522
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -34497,7 +34602,7 @@
34497
34602
  "example": "LumenFieldGroup(\n \"Notification channels\",\n description: \"Choose every channel the team should use.\",\n required: true\n) {\n NotificationControls()\n}",
34498
34603
  "exportName": "LumenFieldGroup",
34499
34604
  "importStatement": "import LumenUI",
34500
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
34605
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
34501
34606
  "language": "swift",
34502
34607
  "packageName": "LumenUI",
34503
34608
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -34552,7 +34657,7 @@
34552
34657
  "example": "LumenChip(\n label = \"Design\",\n selected = selected,\n onClick = { selected = !selected },\n onRemove = ::removeDesign\n)",
34553
34658
  "exportName": "LumenChip",
34554
34659
  "importStatement": "import com.santi020k.lumen.LumenChip",
34555
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
34660
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
34556
34661
  "language": "kotlin",
34557
34662
  "packageName": "com.santi020k:lumen-compose",
34558
34663
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -34638,7 +34743,7 @@
34638
34743
  "example": "LumenChip(\n \"Design\",\n selected: selected,\n removeLabel: \"Remove Design\",\n onPress: { selected.toggle() },\n onRemove: removeDesign\n)",
34639
34744
  "exportName": "LumenChip",
34640
34745
  "importStatement": "import LumenUI",
34641
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
34746
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
34642
34747
  "language": "swift",
34643
34748
  "packageName": "LumenUI",
34644
34749
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -34675,7 +34780,7 @@
34675
34780
  "example": "LumenButtonGroup {\n LumenButton(onClick = ::save) { Text(\"Save\") }\n LumenButton(onClick = ::cancel, intent = LumenButtonIntent.Secondary) { Text(\"Cancel\") }\n}",
34676
34781
  "exportName": "LumenButtonGroup",
34677
34782
  "importStatement": "import com.santi020k.lumen.LumenButtonGroup",
34678
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
34783
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
34679
34784
  "language": "kotlin",
34680
34785
  "packageName": "com.santi020k:lumen-compose",
34681
34786
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -34725,7 +34830,7 @@
34725
34830
  "example": "LumenButtonGroup {\n LumenButton(\"Save\", action: save)\n LumenButton(\"Cancel\", intent: .secondary, action: cancel)\n}",
34726
34831
  "exportName": "LumenButtonGroup",
34727
34832
  "importStatement": "import LumenUI",
34728
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
34833
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
34729
34834
  "language": "swift",
34730
34835
  "packageName": "LumenUI",
34731
34836
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -34780,7 +34885,7 @@
34780
34885
  "example": "LumenToast(\n title = \"Changes saved\",\n description = \"The workspace is up to date.\",\n variant = LumenBannerVariant.Success,\n onDismiss = ::dismissToast\n)",
34781
34886
  "exportName": "LumenToast",
34782
34887
  "importStatement": "import com.santi020k.lumen.LumenToast",
34783
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
34888
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
34784
34889
  "language": "kotlin",
34785
34890
  "packageName": "com.santi020k:lumen-compose",
34786
34891
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -34866,7 +34971,7 @@
34866
34971
  "example": "LumenToast(\n \"Changes saved\",\n description: \"The workspace is up to date.\",\n variant: .success,\n onDismiss: dismissToast\n)",
34867
34972
  "exportName": "LumenToast",
34868
34973
  "importStatement": "import LumenUI",
34869
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
34974
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
34870
34975
  "language": "swift",
34871
34976
  "packageName": "LumenUI",
34872
34977
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -34921,7 +35026,7 @@
34921
35026
  "example": "LumenToggle(\n label = \"Automatic updates\",\n checked = automaticUpdates,\n onCheckedChange = ::setAutomaticUpdates\n)",
34922
35027
  "exportName": "LumenToggle",
34923
35028
  "importStatement": "import com.santi020k.lumen.LumenToggle",
34924
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
35029
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
34925
35030
  "language": "kotlin",
34926
35031
  "packageName": "com.santi020k:lumen-compose",
34927
35032
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -35007,7 +35112,7 @@
35007
35112
  "example": "LumenToggle(\"Automatic updates\", isOn: $automaticUpdates)",
35008
35113
  "exportName": "LumenToggle",
35009
35114
  "importStatement": "import LumenUI",
35010
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
35115
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
35011
35116
  "language": "swift",
35012
35117
  "packageName": "LumenUI",
35013
35118
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -35056,7 +35161,7 @@
35056
35161
  "example": "LumenSettingsRow(\n title = \"Automatic updates\",\n description = \"Download stable updates automatically.\",\n control = {\n LumenToggle(\n label = \"Automatic updates\",\n checked = automaticUpdates,\n showLabel = false,\n onCheckedChange = ::setAutomaticUpdates\n )\n }\n)",
35057
35162
  "exportName": "LumenSettingsRow",
35058
35163
  "importStatement": "import com.santi020k.lumen.LumenSettingsRow",
35059
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
35164
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
35060
35165
  "language": "kotlin",
35061
35166
  "packageName": "com.santi020k:lumen-compose",
35062
35167
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -35130,7 +35235,7 @@
35130
35235
  "example": "LumenSettingsRow(\n \"Automatic updates\",\n description: \"Download stable updates automatically.\",\n systemName: \"arrow.triangle.2.circlepath\"\n) {\n LumenToggle(\"Automatic updates\", isOn: $automaticUpdates)\n .labelsHidden()\n}",
35131
35236
  "exportName": "LumenSettingsRow",
35132
35237
  "importStatement": "import LumenUI",
35133
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
35238
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
35134
35239
  "language": "swift",
35135
35240
  "packageName": "LumenUI",
35136
35241
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -35179,7 +35284,7 @@
35179
35284
  "example": "LumenPicker(\n label = \"Profile\",\n value = profile,\n options = profileOptions,\n onValueChange = ::setProfile\n)",
35180
35285
  "exportName": "LumenPicker",
35181
35286
  "importStatement": "import com.santi020k.lumen.LumenPicker",
35182
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
35287
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
35183
35288
  "language": "kotlin",
35184
35289
  "packageName": "com.santi020k:lumen-compose",
35185
35290
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -35216,7 +35321,7 @@
35216
35321
  "example": "LumenPicker(\"Profile\", selection: $profile, style: .segmented) {\n Text(\"Quiet\").tag(Profile.quiet)\n Text(\"Balanced\").tag(Profile.balanced)\n}",
35217
35322
  "exportName": "LumenPicker",
35218
35323
  "importStatement": "import LumenUI",
35219
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
35324
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
35220
35325
  "language": "swift",
35221
35326
  "packageName": "LumenUI",
35222
35327
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -35265,7 +35370,7 @@
35265
35370
  "example": "LumenSlider(\n label = \"Minimum speed\",\n value = minimumSpeed,\n onValueChange = ::setMinimumSpeed,\n valueRange = 1_000f..5_000f,\n steps = 39,\n valueLabel = \"${minimumSpeed.toInt()} RPM\"\n)",
35266
35371
  "exportName": "LumenSlider",
35267
35372
  "importStatement": "import com.santi020k.lumen.LumenSlider",
35268
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
35373
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
35269
35374
  "language": "kotlin",
35270
35375
  "packageName": "com.santi020k:lumen-compose",
35271
35376
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -35302,7 +35407,7 @@
35302
35407
  "example": "LumenSlider(\n \"Minimum speed\",\n value: $minimumSpeed,\n in: 1_000...5_000,\n step: 100,\n valueLabel: \"\\(Int(minimumSpeed)) RPM\"\n)",
35303
35408
  "exportName": "LumenSlider",
35304
35409
  "importStatement": "import LumenUI",
35305
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
35410
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
35306
35411
  "language": "swift",
35307
35412
  "packageName": "LumenUI",
35308
35413
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -35357,7 +35462,7 @@
35357
35462
  "example": "LumenSearchField(\n value = query,\n onValueChange = ::setQuery,\n prompt = \"Search workspaces\"\n)",
35358
35463
  "exportName": "LumenSearchField",
35359
35464
  "importStatement": "import com.santi020k.lumen.LumenSearchField",
35360
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
35465
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
35361
35466
  "language": "kotlin",
35362
35467
  "packageName": "com.santi020k:lumen-compose",
35363
35468
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -35443,7 +35548,7 @@
35443
35548
  "example": "LumenSearchField(\"Search workspaces\", text: $query)",
35444
35549
  "exportName": "LumenSearchField",
35445
35550
  "importStatement": "import LumenUI",
35446
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
35551
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
35447
35552
  "language": "swift",
35448
35553
  "packageName": "LumenUI",
35449
35554
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -35498,7 +35603,7 @@
35498
35603
  "example": "LumenCheckbox(\n label = \"Share analytics\",\n checked = sharesAnalytics,\n onCheckedChange = ::setSharesAnalytics\n)",
35499
35604
  "exportName": "LumenCheckbox",
35500
35605
  "importStatement": "import com.santi020k.lumen.LumenCheckbox",
35501
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
35606
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
35502
35607
  "language": "kotlin",
35503
35608
  "packageName": "com.santi020k:lumen-compose",
35504
35609
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -35584,7 +35689,7 @@
35584
35689
  "example": "LumenCheckbox(\n \"Share analytics\",\n isChecked: $sharesAnalytics\n)",
35585
35690
  "exportName": "LumenCheckbox",
35586
35691
  "importStatement": "import LumenUI",
35587
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
35692
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
35588
35693
  "language": "swift",
35589
35694
  "packageName": "LumenUI",
35590
35695
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -35633,7 +35738,7 @@
35633
35738
  "example": "LumenRadioGroup(\n label = \"Density\",\n options = densityOptions,\n value = density,\n onValueChange = ::setDensity\n)",
35634
35739
  "exportName": "LumenRadioGroup",
35635
35740
  "importStatement": "import com.santi020k.lumen.LumenRadioGroup",
35636
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
35741
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
35637
35742
  "language": "kotlin",
35638
35743
  "packageName": "com.santi020k:lumen-compose",
35639
35744
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -35707,7 +35812,7 @@
35707
35812
  "example": "LumenRadioGroup(\n \"Density\",\n selection: $density,\n options: densityOptions\n)",
35708
35813
  "exportName": "LumenRadioGroup",
35709
35814
  "importStatement": "import LumenUI",
35710
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
35815
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
35711
35816
  "language": "swift",
35712
35817
  "packageName": "LumenUI",
35713
35818
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -35762,7 +35867,7 @@
35762
35867
  "example": "LumenSegmentedControl(\n label = \"View\",\n options = viewOptions,\n value = view,\n onValueChange = ::setView\n)",
35763
35868
  "exportName": "LumenSegmentedControl",
35764
35869
  "importStatement": "import com.santi020k.lumen.LumenSegmentedControl",
35765
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
35870
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
35766
35871
  "language": "kotlin",
35767
35872
  "packageName": "com.santi020k:lumen-compose",
35768
35873
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -35848,7 +35953,7 @@
35848
35953
  "example": "LumenSegmentedControl(\n \"View\",\n selection: $view,\n options: viewOptions\n)",
35849
35954
  "exportName": "LumenSegmentedControl",
35850
35955
  "importStatement": "import LumenUI",
35851
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
35956
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
35852
35957
  "language": "swift",
35853
35958
  "packageName": "LumenUI",
35854
35959
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -35897,7 +36002,7 @@
35897
36002
  "example": "LumenEmptyState(\n title = \"No saved workspaces\",\n description = \"Create a workspace to get started.\",\n actions = { LumenButton(onClick = ::createWorkspace) { Text(\"Create\") } }\n)",
35898
36003
  "exportName": "LumenEmptyState",
35899
36004
  "importStatement": "import com.santi020k.lumen.LumenEmptyState",
35900
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
36005
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
35901
36006
  "language": "kotlin",
35902
36007
  "packageName": "com.santi020k:lumen-compose",
35903
36008
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -35971,7 +36076,7 @@
35971
36076
  "example": "LumenEmptyState(\n \"No saved workspaces\",\n systemName: \"rectangle.stack\",\n description: \"Create a workspace to get started.\"\n) {\n LumenButton(\"Create Workspace\", action: createWorkspace)\n}",
35972
36077
  "exportName": "LumenEmptyState",
35973
36078
  "importStatement": "import LumenUI",
35974
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
36079
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
35975
36080
  "language": "swift",
35976
36081
  "packageName": "LumenUI",
35977
36082
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -36014,7 +36119,7 @@
36014
36119
  "example": "LumenListRow(\n leading = { LumenAvatar(fallback = \"SM\") },\n trailing = { LumenBadge(\"Admin\") }\n) {\n LumenText(\"Santiago\", variant = LumenTextVariant.Label)\n}",
36015
36120
  "exportName": "LumenListRow",
36016
36121
  "importStatement": "import com.santi020k.lumen.LumenListRow",
36017
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
36122
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
36018
36123
  "language": "kotlin",
36019
36124
  "packageName": "com.santi020k:lumen-compose",
36020
36125
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -36076,7 +36181,7 @@
36076
36181
  "example": "LumenListRow {\n LumenAvatar(fallback: \"SM\")\n} content: {\n LumenText(\"Santiago\", variant: .label)\n} trailing: {\n LumenBadge(\"Admin\")\n}",
36077
36182
  "exportName": "LumenListRow",
36078
36183
  "importStatement": "import LumenUI",
36079
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
36184
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
36080
36185
  "language": "swift",
36081
36186
  "packageName": "LumenUI",
36082
36187
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -36131,7 +36236,7 @@
36131
36236
  "example": "LumenBanner(\n title = \"Workspace assigned automatically\",\n description = \"Moved the app to Documentation.\",\n variant = LumenBannerVariant.Accent,\n onDismiss = ::dismissAssignment\n)",
36132
36237
  "exportName": "LumenBanner",
36133
36238
  "importStatement": "import com.santi020k.lumen.LumenBanner",
36134
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
36239
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
36135
36240
  "language": "kotlin",
36136
36241
  "packageName": "com.santi020k:lumen-compose",
36137
36242
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -36217,7 +36322,7 @@
36217
36322
  "example": "LumenBanner(\n \"Workspace assigned automatically\",\n description: \"Moved Safari to Documentation.\",\n variant: .accent,\n onDismiss: dismissAssignment\n) {\n LumenButton(\"Undo\", intent: .quiet, size: .sm, action: undo)\n}",
36218
36323
  "exportName": "LumenBanner",
36219
36324
  "importStatement": "import LumenUI",
36220
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
36325
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
36221
36326
  "language": "swift",
36222
36327
  "packageName": "LumenUI",
36223
36328
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -36272,7 +36377,7 @@
36272
36377
  "example": "LumenStat(\n label = \"Open windows\",\n value = \"12\",\n detail = \"Across 3 workspaces\",\n tone = LumenMetricTone.Accent\n)",
36273
36378
  "exportName": "LumenStat",
36274
36379
  "importStatement": "import com.santi020k.lumen.LumenStat",
36275
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
36380
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
36276
36381
  "language": "kotlin",
36277
36382
  "packageName": "com.santi020k:lumen-compose",
36278
36383
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -36358,7 +36463,7 @@
36358
36463
  "example": "LumenStat(\n \"Open windows\",\n value: \"12\",\n detail: \"Across 3 workspaces\",\n systemName: \"macwindow\",\n tone: .accent\n)",
36359
36464
  "exportName": "LumenStat",
36360
36465
  "importStatement": "import LumenUI",
36361
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
36466
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
36362
36467
  "language": "swift",
36363
36468
  "packageName": "LumenUI",
36364
36469
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -36413,7 +36518,7 @@
36413
36518
  "example": "LumenGauge(\n label = \"Thermal pressure\",\n value = 48f,\n valueLabel = \"Fair\",\n tone = LumenMetricTone.Warning\n)",
36414
36519
  "exportName": "LumenGauge",
36415
36520
  "importStatement": "import com.santi020k.lumen.LumenGauge",
36416
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
36521
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
36417
36522
  "language": "kotlin",
36418
36523
  "packageName": "com.santi020k:lumen-compose",
36419
36524
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -36456,7 +36561,7 @@
36456
36561
  "example": "LumenGauge(\n \"Thermal pressure\",\n value: 48,\n valueLabel: \"Fair\",\n systemName: \"thermometer.medium\",\n tone: .warning\n)",
36457
36562
  "exportName": "LumenGauge",
36458
36563
  "importStatement": "import LumenUI",
36459
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
36564
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
36460
36565
  "language": "swift",
36461
36566
  "packageName": "LumenUI",
36462
36567
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -36505,7 +36610,7 @@
36505
36610
  "example": "LumenSectionHeader(\n title = \"Workspaces\",\n subtitle = \"Recently used\",\n count = \"4\"\n)",
36506
36611
  "exportName": "LumenSectionHeader",
36507
36612
  "importStatement": "import com.santi020k.lumen.LumenSectionHeader",
36508
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
36613
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
36509
36614
  "language": "kotlin",
36510
36615
  "packageName": "com.santi020k:lumen-compose",
36511
36616
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -36579,7 +36684,7 @@
36579
36684
  "example": "LumenSectionHeader(\n \"Workspaces\",\n subtitle: \"Recently used\",\n count: \"4\"\n) {\n LumenButton(\"Add\", intent: .quiet, size: .sm, action: addWorkspace)\n}",
36580
36685
  "exportName": "LumenSectionHeader",
36581
36686
  "importStatement": "import LumenUI",
36582
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
36687
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
36583
36688
  "language": "swift",
36584
36689
  "packageName": "LumenUI",
36585
36690
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -36622,7 +36727,7 @@
36622
36727
  "example": "LumenStatusBar(\n message = \"All changes saved\",\n tone = LumenMetricTone.Success\n)",
36623
36728
  "exportName": "LumenStatusBar",
36624
36729
  "importStatement": "import com.santi020k.lumen.LumenStatusBar",
36625
- "install": "Include packages/compose from a Lumen checkout or Git submodule, then add implementation(project(\":lumen-compose\")).",
36730
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
36626
36731
  "language": "kotlin",
36627
36732
  "packageName": "com.santi020k:lumen-compose",
36628
36733
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
@@ -36684,7 +36789,7 @@
36684
36789
  "example": "LumenStatusBar(\"All changes saved\", tone: .success) {\n Text(\"Just now\")\n}",
36685
36790
  "exportName": "LumenStatusBar",
36686
36791
  "importStatement": "import LumenUI",
36687
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
36792
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
36688
36793
  "language": "swift",
36689
36794
  "packageName": "LumenUI",
36690
36795
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -36696,6 +36801,176 @@
36696
36801
  "summary": "Present compact textual application status with optional trailing content.",
36697
36802
  "tier": "shared"
36698
36803
  },
36804
+ {
36805
+ "accessibility": "The native refresh gesture remains owned by the scroll container. Supply an accessibility label that describes the refreshed content.",
36806
+ "category": "Feedback",
36807
+ "contract": "Native pull-to-refresh state with semantic indicator colors",
36808
+ "guidance": "Attach to a React Native ScrollView or compatible list. Keep refresh state and completion in application code.",
36809
+ "id": "refresh-control",
36810
+ "implementations": {
36811
+ "react-native": {
36812
+ "api": [
36813
+ {
36814
+ "defaultValue": "Required",
36815
+ "description": "Reports whether the native indicator is active.",
36816
+ "name": "refreshing",
36817
+ "values": "boolean"
36818
+ },
36819
+ {
36820
+ "defaultValue": "Required",
36821
+ "description": "Starts the application refresh operation.",
36822
+ "name": "onRefresh",
36823
+ "values": "() => void"
36824
+ },
36825
+ {
36826
+ "defaultValue": "brand",
36827
+ "description": "Selects the semantic native indicator color.",
36828
+ "name": "indicatorTone",
36829
+ "values": "brand · accent · neutral"
36830
+ },
36831
+ {
36832
+ "defaultValue": "Application-provided",
36833
+ "description": "Names the content refreshed by the gesture.",
36834
+ "name": "accessibilityLabel",
36835
+ "values": "string"
36836
+ }
36837
+ ],
36838
+ "example": "<ScrollView\n refreshControl={(\n <LumenRefreshControl\n accessibilityLabel=\"Refresh projects\"\n refreshing={refreshing}\n onRefresh={refreshProjects}\n />\n )}\n>\n {content}\n</ScrollView>",
36839
+ "exportName": "LumenRefreshControl",
36840
+ "importStatement": "import { LumenRefreshControl } from '@santi020k/lumen-react-native'",
36841
+ "install": "pnpm add @santi020k/lumen-react-native",
36842
+ "language": "tsx",
36843
+ "packageName": "@santi020k/lumen-react-native",
36844
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
36845
+ "sourceFile": "packages/react-native/src/index.ts",
36846
+ "symbol": "LumenRefreshControl"
36847
+ }
36848
+ },
36849
+ "name": "Refresh control",
36850
+ "summary": "Apply Lumen semantic colors to React Native pull-to-refresh behavior.",
36851
+ "supportedPlatforms": [
36852
+ "iOS",
36853
+ "Android"
36854
+ ],
36855
+ "tier": "platform-specific"
36856
+ },
36857
+ {
36858
+ "accessibility": "The native picker retains its label and adjustable behavior. Supporting or validation text remains visible in the same contained group.",
36859
+ "category": "Forms",
36860
+ "contract": "Native date and time selection with bounds, supporting text, and validation messaging",
36861
+ "guidance": "Use for Apple date or time input that needs Lumen supporting and validation context. Keep calendar and locale behavior native.",
36862
+ "id": "date-field",
36863
+ "implementations": {
36864
+ "swiftui": {
36865
+ "api": [
36866
+ {
36867
+ "defaultValue": "Required",
36868
+ "description": "Labels the native date picker.",
36869
+ "name": "title",
36870
+ "values": "LocalizedStringKey"
36871
+ },
36872
+ {
36873
+ "defaultValue": "Required",
36874
+ "description": "Stores the selected native date value.",
36875
+ "name": "selection",
36876
+ "values": "Binding<Date>"
36877
+ },
36878
+ {
36879
+ "defaultValue": "date",
36880
+ "description": "Chooses the visible date and time parts.",
36881
+ "name": "components",
36882
+ "values": "date · dateAndTime · time"
36883
+ },
36884
+ {
36885
+ "defaultValue": "unbounded",
36886
+ "description": "Constrains selection using native picker bounds.",
36887
+ "name": "bounds",
36888
+ "values": "unbounded · closed · from · through"
36889
+ },
36890
+ {
36891
+ "defaultValue": "nil",
36892
+ "description": "Shows supporting text or a semantic danger validation message.",
36893
+ "name": "description / errorMessage",
36894
+ "values": "LocalizedStringKey?"
36895
+ }
36896
+ ],
36897
+ "example": "LumenDateField(\n \"Release date\",\n selection: $releaseDate,\n components: .dateAndTime,\n bounds: .from(.now),\n description: \"Choose when this version becomes available.\"\n)",
36898
+ "exportName": "LumenDateField",
36899
+ "importStatement": "import LumenUI",
36900
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
36901
+ "language": "swift",
36902
+ "packageName": "LumenUI",
36903
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
36904
+ "sourceFile": "packages/swift/Sources/LumenUI/AppleComponents.swift",
36905
+ "symbol": "LumenDateField"
36906
+ }
36907
+ },
36908
+ "name": "Date field",
36909
+ "summary": "Select an Apple-native date or time with bounds and validation context.",
36910
+ "supportedPlatforms": [
36911
+ "iOS",
36912
+ "macOS"
36913
+ ],
36914
+ "tier": "platform-specific"
36915
+ },
36916
+ {
36917
+ "accessibility": "The required content description names the icon-only action while the control preserves native Material button semantics.",
36918
+ "category": "Actions",
36919
+ "contract": "Material-native floating action with semantic intent and small or regular sizing",
36920
+ "guidance": "Use for one prominent Material screen action. Keep navigation bars and scaffold placement in application code.",
36921
+ "id": "floating-action-button",
36922
+ "implementations": {
36923
+ "compose": {
36924
+ "api": [
36925
+ {
36926
+ "defaultValue": "Required",
36927
+ "description": "Renders the native action glyph.",
36928
+ "name": "imageVector",
36929
+ "values": "ImageVector"
36930
+ },
36931
+ {
36932
+ "defaultValue": "Required",
36933
+ "description": "Names the icon-only action.",
36934
+ "name": "contentDescription",
36935
+ "values": "String"
36936
+ },
36937
+ {
36938
+ "defaultValue": "Required",
36939
+ "description": "Runs the primary screen action.",
36940
+ "name": "onClick",
36941
+ "values": "() -> Unit"
36942
+ },
36943
+ {
36944
+ "defaultValue": "brand",
36945
+ "description": "Selects a semantic action palette.",
36946
+ "name": "intent",
36947
+ "values": "brand · accent · danger"
36948
+ },
36949
+ {
36950
+ "defaultValue": "regular",
36951
+ "description": "Selects Material-native FAB geometry.",
36952
+ "name": "size",
36953
+ "values": "regular · small"
36954
+ }
36955
+ ],
36956
+ "example": "LumenFloatingActionButton(\n imageVector = Icons.Default.Add,\n contentDescription = \"Create project\",\n onClick = ::createProject\n)",
36957
+ "exportName": "LumenFloatingActionButton",
36958
+ "importStatement": "import com.santi020k.lumen.LumenFloatingActionButton",
36959
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
36960
+ "language": "kotlin",
36961
+ "packageName": "com.santi020k:lumen-compose",
36962
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
36963
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt",
36964
+ "symbol": "LumenFloatingActionButton"
36965
+ }
36966
+ },
36967
+ "name": "Floating action button",
36968
+ "summary": "Present a prominent Material-native action using Lumen semantic intent.",
36969
+ "supportedPlatforms": [
36970
+ "Android"
36971
+ ],
36972
+ "tier": "platform-specific"
36973
+ },
36699
36974
  {
36700
36975
  "accessibility": "Recording, current shortcut, validation error, cancel, change, and clear states all have visible native labels.",
36701
36976
  "category": "macOS utilities",
@@ -36733,7 +37008,7 @@
36733
37008
  "example": "LumenShortcutRecorder(\n \"Quick switch\",\n shortcut: $shortcut\n) { candidate in\n reserved.contains(candidate) ? \"Already in use.\" : nil\n}",
36734
37009
  "exportName": "LumenShortcutRecorder",
36735
37010
  "importStatement": "import LumenUI",
36736
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
37011
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
36737
37012
  "language": "swift",
36738
37013
  "packageName": "LumenUI",
36739
37014
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -36746,7 +37021,7 @@
36746
37021
  "supportedPlatforms": [
36747
37022
  "macOS"
36748
37023
  ],
36749
- "tier": "apple"
37024
+ "tier": "platform-specific"
36750
37025
  },
36751
37026
  {
36752
37027
  "accessibility": "Every symbol option has a readable label and selected state. Search and empty states use native controls and focus behavior.",
@@ -36785,7 +37060,7 @@
36785
37060
  "example": "LumenSymbolPickerButton(\n \"Workspace symbol\",\n selectedName: $symbolName\n)",
36786
37061
  "exportName": "LumenSymbolPicker / LumenSymbolPickerButton",
36787
37062
  "importStatement": "import LumenUI",
36788
- "install": "Add https://github.com/santi020k/lumen with Swift Package Manager and link the LumenUI product to the application target.",
37063
+ "install": "Add https://github.com/santi020k/lumen with Swift Package Manager, pin an exact or compatible release version, and link the LumenUI product to the application target.",
36789
37064
  "language": "swift",
36790
37065
  "packageName": "LumenUI",
36791
37066
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
@@ -36798,7 +37073,7 @@
36798
37073
  "supportedPlatforms": [
36799
37074
  "macOS"
36800
37075
  ],
36801
- "tier": "apple"
37076
+ "tier": "platform-specific"
36802
37077
  }
36803
37078
  ],
36804
37079
  "nativeSources": {
@@ -36806,12 +37081,13 @@
36806
37081
  "packages/compose/src/main/kotlin/com/santi020k/lumen/AdditionalComponents.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.RowScope\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.OutlinedTextFieldDefaults\nimport androidx.compose.material3.Slider\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.semantics.LiveRegionMode\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.liveRegion\nimport androidx.compose.ui.semantics.progressBarRangeInfo\nimport androidx.compose.ui.semantics.ProgressBarRangeInfo\nimport androidx.compose.ui.semantics.selected\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.semantics.stateDescription\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\n\nenum class LumenButtonGroupOrientation {\n Horizontal,\n Vertical\n}\n\n@Composable\nfun LumenButtonGroup(\n modifier: Modifier = Modifier,\n orientation: LumenButtonGroupOrientation = LumenButtonGroupOrientation.Horizontal,\n content: @Composable () -> Unit\n) {\n if (orientation == LumenButtonGroupOrientation.Horizontal) {\n Row(\n modifier = modifier,\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Sm),\n verticalAlignment = Alignment.CenterVertically\n ) { content() }\n } else {\n Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(LumenSpacing.Sm)) {\n content()\n }\n }\n}\n\n@Composable\nfun LumenFieldGroup(\n label: String,\n modifier: Modifier = Modifier,\n description: String? = null,\n errorMessage: String? = null,\n required: Boolean = false,\n content: @Composable () -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n\n Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(LumenSpacing.Sm)) {\n Text(\n text = if (required) \"$label *\" else label,\n color = colors.ink,\n style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n )\n if (description != null) {\n Text(description, color = colors.inkMuted, style = MaterialTheme.typography.bodySmall)\n }\n content()\n if (errorMessage != null) {\n Text(\n errorMessage,\n modifier = Modifier.semantics { liveRegion = LiveRegionMode.Polite },\n color = colors.danger,\n style = MaterialTheme.typography.bodySmall\n )\n }\n }\n}\n\n@Composable\nfun LumenTextarea(\n value: String,\n onValueChange: (String) -> Unit,\n label: String,\n modifier: Modifier = Modifier,\n description: String? = null,\n errorMessage: String? = null,\n enabled: Boolean = true,\n minLines: Int = 4\n) {\n val colors = LocalLumenTheme.current.colors\n\n OutlinedTextField(\n value = value,\n onValueChange = onValueChange,\n modifier = modifier.fillMaxWidth(),\n enabled = enabled,\n isError = errorMessage != null,\n minLines = minLines.coerceAtLeast(2),\n label = { Text(label) },\n supportingText = {\n val message = errorMessage ?: description\n if (message != null) Text(message)\n },\n shape = RoundedCornerShape(LumenRadius.Sm),\n colors = OutlinedTextFieldDefaults.colors(\n focusedContainerColor = colors.surface,\n unfocusedContainerColor = colors.surface,\n disabledContainerColor = colors.surface,\n focusedTextColor = colors.ink,\n unfocusedTextColor = colors.ink,\n focusedBorderColor = colors.brand,\n unfocusedBorderColor = colors.line,\n errorBorderColor = colors.danger,\n cursorColor = colors.brand\n )\n )\n}\n\n@Composable\nfun LumenChip(\n label: String,\n modifier: Modifier = Modifier,\n selected: Boolean = false,\n enabled: Boolean = true,\n removeLabel: String = \"Remove $label\",\n onClick: (() -> Unit)? = null,\n onRemove: (() -> Unit)? = null\n) {\n val colors = LocalLumenTheme.current.colors\n val interactionModifier = if (onClick != null) {\n Modifier.clickable(enabled = enabled, role = Role.Button, onClick = onClick)\n } else {\n Modifier\n }\n\n Surface(\n modifier = modifier\n .then(interactionModifier)\n .semantics { this.selected = selected },\n color = if (selected) colors.brandSoft else colors.surfaceMuted,\n contentColor = if (selected) colors.brand else colors.inkSoft,\n shape = RoundedCornerShape(LumenRadius.Full),\n border = BorderStroke(1.dp, if (selected) colors.brand else colors.line)\n ) {\n Row(\n modifier = Modifier\n .heightIn(min = 32.dp)\n .padding(horizontal = LumenSpacing.Md),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Xs),\n verticalAlignment = Alignment.CenterVertically\n ) {\n Text(label, style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.SemiBold))\n if (onRemove != null) {\n TextButton(enabled = enabled, onClick = onRemove) {\n Text(\"×\", modifier = Modifier.semantics { contentDescription = removeLabel })\n }\n }\n }\n }\n}\n\n@Composable\nfun LumenToast(\n title: String,\n modifier: Modifier = Modifier,\n description: String? = null,\n variant: LumenBannerVariant = LumenBannerVariant.Default,\n dismissLabel: String = \"Dismiss\",\n onDismiss: (() -> Unit)? = null,\n action: (@Composable RowScope.() -> Unit)? = null\n) {\n val colors = LocalLumenTheme.current.colors\n val palette = lumenBannerPalette(colors, variant)\n\n Surface(\n modifier = modifier.semantics { liveRegion = LiveRegionMode.Polite },\n color = palette.background,\n contentColor = colors.ink,\n shape = RoundedCornerShape(LumenRadius.Md),\n border = BorderStroke(1.dp, palette.border)\n ) {\n Row(\n modifier = Modifier.padding(horizontal = LumenSpacing.Lg, vertical = LumenSpacing.Md),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Md),\n verticalAlignment = Alignment.CenterVertically\n ) {\n Column(\n modifier = Modifier.weight(1f),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Xs)\n ) {\n Text(\n title,\n color = palette.accent,\n style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n )\n if (description != null) {\n Text(description, color = colors.inkSoft, style = MaterialTheme.typography.bodySmall)\n }\n }\n action?.invoke(this)\n if (onDismiss != null) {\n TextButton(onClick = onDismiss) { Text(dismissLabel, color = palette.accent) }\n }\n }\n }\n}\n\ndata class LumenPickerOption<T>(\n val value: T,\n val label: String,\n val enabled: Boolean = true\n)\n\n@Composable\nfun <T> LumenPicker(\n label: String,\n value: T,\n options: List<LumenPickerOption<T>>,\n onValueChange: (T) -> Unit,\n modifier: Modifier = Modifier,\n enabled: Boolean = true\n) {\n var expanded by remember { mutableStateOf(false) }\n val selectedLabel = options.firstOrNull { it.value == value }?.label ?: value.toString()\n\n Box(modifier = modifier) {\n Button(\n onClick = { expanded = true },\n enabled = enabled,\n modifier = Modifier.semantics {\n contentDescription = label\n stateDescription = selectedLabel\n }\n ) {\n Text(selectedLabel)\n }\n DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {\n options.forEach { option ->\n DropdownMenuItem(\n text = { Text(option.label) },\n enabled = option.enabled,\n onClick = {\n onValueChange(option.value)\n expanded = false\n }\n )\n }\n }\n }\n}\n\n@Composable\nfun LumenSlider(\n label: String,\n value: Float,\n onValueChange: (Float) -> Unit,\n modifier: Modifier = Modifier,\n valueRange: ClosedFloatingPointRange<Float> = 0f..1f,\n steps: Int = 0,\n valueLabel: String = value.toString(),\n enabled: Boolean = true\n) {\n Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(LumenSpacing.Xs)) {\n Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {\n Text(label)\n Text(valueLabel, color = LocalLumenTheme.current.colors.inkMuted)\n }\n Slider(\n value = value.coerceIn(valueRange.start, valueRange.endInclusive),\n onValueChange = onValueChange,\n enabled = enabled,\n valueRange = valueRange,\n steps = steps.coerceAtLeast(0),\n modifier = Modifier.semantics { stateDescription = valueLabel }\n )\n }\n}\n\n@Composable\nfun LumenGauge(\n label: String,\n value: Float,\n modifier: Modifier = Modifier,\n max: Float = 100f,\n valueLabel: String = value.toString(),\n tone: LumenMetricTone = LumenMetricTone.Brand,\n center: (@Composable () -> Unit)? = null\n) {\n val progress = LumenProgressValue.resolve(value, max)\n val colors = LocalLumenTheme.current.colors\n\n Box(\n modifier = modifier\n .size(72.dp)\n .semantics {\n contentDescription = label\n progressBarRangeInfo = ProgressBarRangeInfo(\n current = progress.value,\n range = 0f..progress.max,\n steps = 0\n )\n stateDescription = valueLabel\n },\n contentAlignment = Alignment.Center\n ) {\n CircularProgressIndicator(\n progress = { progress.value / progress.max },\n modifier = Modifier.size(72.dp),\n color = lumenMetricColor(colors, tone),\n trackColor = colors.surfaceStrong\n )\n if (center != null) center() else Text(valueLabel, style = MaterialTheme.typography.labelSmall)\n }\n}\n",
36807
37082
  "packages/compose/src/main/kotlin/com/santi020k/lumen/ContentComponents.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.semantics.ProgressBarRangeInfo\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.ui.semantics.clearAndSetSemantics\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.progressBarRangeInfo\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.semantics.stateDescription\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\n\nenum class LumenSkeletonShape {\n Circle,\n Rectangle,\n Text\n}\n\nfun resolveLumenSkeletonHeight(height: Dp): Dp = if (height.value.isFinite() && height > 0.dp) {\n height\n} else {\n 16.dp\n}\n\n@Composable\nfun LumenSkeleton(\n modifier: Modifier = Modifier,\n width: Dp? = null,\n height: Dp = 16.dp,\n shape: LumenSkeletonShape = LumenSkeletonShape.Text,\n label: String? = null\n) {\n val colors = LocalLumenTheme.current.colors\n val safeHeight = resolveLumenSkeletonHeight(height)\n val shapeModifier = when {\n shape == LumenSkeletonShape.Circle -> Modifier.size(safeHeight).clip(CircleShape)\n width != null -> Modifier.width(width).height(safeHeight)\n else -> Modifier.fillMaxWidth().height(safeHeight)\n }\n val cornerShape = when (shape) {\n LumenSkeletonShape.Circle -> CircleShape\n LumenSkeletonShape.Rectangle -> RoundedCornerShape(LumenRadius.Md)\n LumenSkeletonShape.Text -> RoundedCornerShape(LumenRadius.Sm)\n }\n val semanticsModifier = if (label == null) {\n Modifier.clearAndSetSemantics {}\n } else {\n Modifier.semantics {\n contentDescription = label\n progressBarRangeInfo = ProgressBarRangeInfo.Indeterminate\n }\n }\n\n Box(\n modifier = modifier\n .then(shapeModifier)\n .then(semanticsModifier)\n .clip(cornerShape)\n .background(colors.surfaceStrong.copy(alpha = 0.72f))\n )\n}\n\n@Composable\nfun LumenDisclosure(\n title: String,\n expanded: Boolean,\n onExpandedChange: (Boolean) -> Unit,\n modifier: Modifier = Modifier,\n description: String? = null,\n enabled: Boolean = true,\n graphic: (@Composable () -> Unit)? = null,\n content: @Composable () -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n\n Surface(\n modifier = modifier,\n color = colors.surface,\n contentColor = colors.ink,\n shape = RoundedCornerShape(LumenRadius.Md),\n border = androidx.compose.foundation.BorderStroke(1.dp, colors.line)\n ) {\n Column {\n Row(\n modifier = Modifier\n .fillMaxWidth()\n .heightIn(min = 48.dp)\n .clickable(\n enabled = enabled,\n role = Role.Button,\n onClick = { onExpandedChange(!expanded) }\n )\n .semantics(mergeDescendants = true) {\n contentDescription = title\n stateDescription = if (expanded) \"Expanded\" else \"Collapsed\"\n }\n .padding(horizontal = LumenSpacing.Lg, vertical = LumenSpacing.Md),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Md),\n verticalAlignment = Alignment.CenterVertically\n ) {\n graphic?.invoke()\n Column(\n modifier = Modifier.weight(1f),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Xs)\n ) {\n Text(\n text = title,\n color = colors.ink,\n style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n )\n if (description != null) {\n Text(text = description, color = colors.inkMuted, style = MaterialTheme.typography.bodySmall)\n }\n }\n Text(\n text = if (expanded) \"−\" else \"+\",\n modifier = Modifier.clearAndSetSemantics {},\n color = colors.inkSoft,\n style = MaterialTheme.typography.titleMedium\n )\n }\n if (expanded) {\n LumenDivider()\n Box(\n modifier = Modifier\n .fillMaxWidth()\n .padding(LumenSpacing.Lg)\n ) {\n content()\n }\n }\n }\n }\n}\n",
36808
37083
  "packages/compose/src/main/kotlin/com/santi020k/lumen/FormComponents.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.selection.toggleable\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.OutlinedTextFieldDefaults\nimport androidx.compose.material3.Switch\nimport androidx.compose.material3.SwitchDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.ui.semantics.clearAndSetSemantics\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\n\ndata class LumenSearchFieldState(val showClearAction: Boolean) {\n companion object {\n fun resolve(value: String, enabled: Boolean): LumenSearchFieldState = LumenSearchFieldState(\n showClearAction = enabled && value.isNotEmpty()\n )\n }\n}\n\n@Composable\nfun LumenToggle(\n label: String,\n checked: Boolean,\n onCheckedChange: (Boolean) -> Unit,\n modifier: Modifier = Modifier,\n description: String? = null,\n showLabel: Boolean = true,\n enabled: Boolean = true\n) {\n val colors = LocalLumenTheme.current.colors\n val widthModifier = if (showLabel) Modifier.fillMaxWidth() else Modifier\n\n Row(\n modifier = modifier\n .then(widthModifier)\n .heightIn(min = LumenButtonMetrics.resolve(LumenControlSize.Md).minHeight)\n .toggleable(\n value = checked,\n enabled = enabled,\n role = Role.Switch,\n onValueChange = onCheckedChange\n )\n .semantics(mergeDescendants = true) { contentDescription = label },\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Lg),\n verticalAlignment = Alignment.CenterVertically\n ) {\n if (showLabel) {\n Column(\n modifier = Modifier.weight(1f),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Xs)\n ) {\n Text(\n text = label,\n color = colors.ink,\n style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n )\n if (description != null) {\n Text(text = description, color = colors.inkMuted, style = MaterialTheme.typography.bodySmall)\n }\n }\n }\n Switch(\n checked = checked,\n onCheckedChange = null,\n modifier = Modifier.clearAndSetSemantics {},\n enabled = enabled,\n colors = SwitchDefaults.colors(\n checkedThumbColor = colors.onBrand,\n checkedTrackColor = colors.brandSolid,\n uncheckedThumbColor = colors.surface,\n uncheckedTrackColor = colors.surfaceStrong,\n uncheckedBorderColor = colors.line\n )\n )\n }\n}\n\n@Composable\nfun LumenSettingsRow(\n title: String,\n modifier: Modifier = Modifier,\n description: String? = null,\n graphic: (@Composable () -> Unit)? = null,\n control: @Composable () -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n\n Row(\n modifier = modifier\n .fillMaxWidth()\n .heightIn(min = 56.dp)\n .padding(vertical = LumenSpacing.Sm),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Lg),\n verticalAlignment = Alignment.CenterVertically\n ) {\n graphic?.invoke()\n Column(\n modifier = Modifier.weight(1f),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Xs)\n ) {\n Text(\n text = title,\n color = colors.ink,\n style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n )\n if (description != null) {\n Text(text = description, color = colors.inkMuted, style = MaterialTheme.typography.bodySmall)\n }\n }\n control()\n }\n}\n\n@Composable\nfun LumenSearchField(\n value: String,\n onValueChange: (String) -> Unit,\n modifier: Modifier = Modifier,\n prompt: String = \"Search\",\n clearLabel: String = \"Clear search\",\n enabled: Boolean = true,\n graphic: (@Composable () -> Unit)? = null\n) {\n val colors = LocalLumenTheme.current.colors\n val searchState = LumenSearchFieldState.resolve(value, enabled)\n val trailingIcon: (@Composable () -> Unit)? = if (searchState.showClearAction) {\n {\n TextButton(onClick = { onValueChange(\"\") }) {\n Text(clearLabel, color = colors.inkSoft, style = MaterialTheme.typography.labelSmall)\n }\n }\n } else {\n null\n }\n\n OutlinedTextField(\n value = value,\n onValueChange = onValueChange,\n modifier = modifier\n .fillMaxWidth()\n .heightIn(min = LumenButtonMetrics.resolve(LumenControlSize.Md).minHeight),\n enabled = enabled,\n singleLine = true,\n label = { Text(prompt) },\n leadingIcon = graphic,\n trailingIcon = trailingIcon,\n shape = RoundedCornerShape(LumenRadius.Sm),\n colors = OutlinedTextFieldDefaults.colors(\n focusedContainerColor = colors.surface,\n unfocusedContainerColor = colors.surface,\n disabledContainerColor = colors.surface,\n focusedTextColor = colors.ink,\n unfocusedTextColor = colors.ink,\n focusedBorderColor = colors.brand,\n unfocusedBorderColor = colors.line,\n cursorColor = colors.brand\n )\n )\n}\n",
36809
- "packages/compose/src/main/kotlin/com/santi020k/lumen/LumenTheme.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.runtime.staticCompositionLocalOf\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\n\n@Immutable\ndata class LumenThemeValues(\n val colors: LumenColorPalette,\n val isDark: Boolean\n)\n\nval LocalLumenTheme = staticCompositionLocalOf {\n LumenThemeValues(colors = LumenColors.Light, isDark = false)\n}\n\n@Composable\nfun LumenTheme(\n darkTheme: Boolean = isSystemInDarkTheme(),\n content: @Composable () -> Unit\n) {\n val values = if (darkTheme) {\n LumenThemeValues(colors = LumenColors.Dark, isDark = true)\n } else {\n LumenThemeValues(colors = LumenColors.Light, isDark = false)\n }\n\n val colorScheme = if (darkTheme) {\n darkColorScheme(\n primary = values.colors.brand,\n onPrimary = values.colors.onBrand,\n secondary = values.colors.accent,\n background = values.colors.canvas,\n onBackground = values.colors.ink,\n surface = values.colors.surface,\n onSurface = values.colors.ink,\n error = values.colors.danger,\n onError = values.colors.onDanger,\n outline = values.colors.line\n )\n } else {\n lightColorScheme(\n primary = values.colors.brand,\n onPrimary = values.colors.onBrand,\n secondary = values.colors.accent,\n background = values.colors.canvas,\n onBackground = values.colors.ink,\n surface = values.colors.surface,\n onSurface = values.colors.ink,\n error = values.colors.danger,\n onError = values.colors.onDanger,\n outline = values.colors.line\n )\n }\n\n androidx.compose.runtime.CompositionLocalProvider(LocalLumenTheme provides values) {\n MaterialTheme(colorScheme = colorScheme, content = content)\n }\n}\n",
36810
- "packages/compose/src/main/kotlin/com/santi020k/lumen/Primitives.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.RowScope\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.defaultMinSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.IconButtonDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.OutlinedTextFieldDefaults\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.semantics.stateDescription\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.VisualTransformation\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\n\nenum class LumenTextTone {\n Danger,\n Default,\n Muted,\n Soft,\n Success,\n Warning\n}\n\nenum class LumenTextVariant {\n Body,\n Caption,\n Label,\n Title\n}\n\n@Composable\nfun LumenText(\n text: String,\n modifier: Modifier = Modifier,\n variant: LumenTextVariant = LumenTextVariant.Body,\n tone: LumenTextTone = LumenTextTone.Default\n) {\n val theme = LocalLumenTheme.current\n val color = when (tone) {\n LumenTextTone.Danger -> theme.colors.danger\n LumenTextTone.Default -> theme.colors.ink\n LumenTextTone.Muted -> theme.colors.inkMuted\n LumenTextTone.Soft -> theme.colors.inkSoft\n LumenTextTone.Success -> theme.colors.success\n LumenTextTone.Warning -> theme.colors.warning\n }\n val style = when (variant) {\n LumenTextVariant.Body -> MaterialTheme.typography.bodyLarge\n LumenTextVariant.Caption -> MaterialTheme.typography.bodySmall\n LumenTextVariant.Label -> MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n LumenTextVariant.Title -> MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold)\n }\n\n Text(text = text, modifier = modifier, color = color, style = style)\n}\n\nenum class LumenSurfacePadding(val value: Dp) {\n None(0.dp),\n Sm(LumenSpacing.Sm),\n Md(LumenSpacing.Md),\n Lg(LumenSpacing.Lg)\n}\n\nenum class LumenSurfaceRadius(val value: Dp) {\n None(0.dp),\n Sm(LumenRadius.Sm),\n Md(LumenRadius.Md),\n Lg(LumenRadius.Lg)\n}\n\nenum class LumenSurfaceTone {\n Canvas,\n Muted,\n Strong,\n Surface\n}\n\n@Composable\nfun LumenSurface(\n modifier: Modifier = Modifier,\n tone: LumenSurfaceTone = LumenSurfaceTone.Surface,\n padding: LumenSurfacePadding = LumenSurfacePadding.Md,\n radius: LumenSurfaceRadius = LumenSurfaceRadius.Md,\n content: @Composable () -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n val color = when (tone) {\n LumenSurfaceTone.Canvas -> colors.canvas\n LumenSurfaceTone.Muted -> colors.surfaceMuted\n LumenSurfaceTone.Strong -> colors.surfaceStrong\n LumenSurfaceTone.Surface -> colors.surface\n }\n\n Surface(\n modifier = modifier,\n color = color,\n shape = RoundedCornerShape(radius.value)\n ) {\n androidx.compose.foundation.layout.Box(\n modifier = Modifier.padding(padding.value),\n contentAlignment = Alignment.TopStart\n ) {\n content()\n }\n }\n}\n\nenum class LumenButtonIntent {\n Danger,\n Primary,\n Quiet,\n Secondary\n}\n\nenum class LumenControlSize {\n Lg,\n Md,\n Sm\n}\n\nenum class LumenIconSize(val dimension: Dp) {\n Sm(16.dp),\n Md(20.dp),\n Lg(24.dp)\n}\n\n@Composable\nfun LumenIcon(\n imageVector: ImageVector,\n contentDescription: String? = null,\n modifier: Modifier = Modifier,\n size: LumenIconSize = LumenIconSize.Md,\n tint: Color = LocalLumenTheme.current.colors.ink\n) {\n Icon(\n imageVector = imageVector,\n contentDescription = contentDescription,\n modifier = modifier.size(size.dimension),\n tint = tint\n )\n}\n\n@Immutable\ndata class LumenIconButtonMetrics(\n val iconSize: LumenIconSize,\n val touchTarget: Dp\n) {\n companion object {\n fun resolve(size: LumenControlSize): LumenIconButtonMetrics = when (size) {\n LumenControlSize.Lg -> LumenIconButtonMetrics(LumenIconSize.Lg, 52.dp)\n LumenControlSize.Md -> LumenIconButtonMetrics(LumenIconSize.Md, 44.dp)\n LumenControlSize.Sm -> LumenIconButtonMetrics(LumenIconSize.Sm, 44.dp)\n }\n }\n}\n\n@Composable\nfun LumenIconButton(\n imageVector: ImageVector,\n contentDescription: String,\n onClick: () -> Unit,\n modifier: Modifier = Modifier,\n intent: LumenButtonIntent = LumenButtonIntent.Quiet,\n size: LumenControlSize = LumenControlSize.Md,\n enabled: Boolean = true\n) {\n val palette = buttonPalette(LocalLumenTheme.current.colors, intent)\n val metrics = LumenIconButtonMetrics.resolve(size)\n val shape = RoundedCornerShape(LumenRadius.Sm)\n\n IconButton(\n onClick = onClick,\n modifier = modifier\n .size(metrics.touchTarget)\n .clip(shape)\n .border(1.dp, palette.border, shape),\n enabled = enabled,\n colors = IconButtonDefaults.iconButtonColors(\n containerColor = palette.background,\n contentColor = palette.foreground,\n disabledContainerColor = palette.background.copy(alpha = 0.52f),\n disabledContentColor = palette.foreground.copy(alpha = 0.52f)\n )\n ) {\n LumenIcon(\n imageVector = imageVector,\n contentDescription = contentDescription,\n size = metrics.iconSize,\n tint = palette.foreground.copy(alpha = if (enabled) 1f else 0.52f)\n )\n }\n}\n\n@Immutable\ndata class LumenButtonMetrics(\n val horizontalPadding: Dp,\n val minHeight: Dp\n) {\n companion object {\n fun resolve(size: LumenControlSize): LumenButtonMetrics = when (size) {\n LumenControlSize.Lg -> LumenButtonMetrics(LumenSpacing.Xl, 52.dp)\n LumenControlSize.Md -> LumenButtonMetrics(LumenSpacing.Lg, 44.dp)\n LumenControlSize.Sm -> LumenButtonMetrics(LumenSpacing.Md, 36.dp)\n }\n }\n}\n\nprivate data class LumenButtonPalette(\n val background: Color,\n val border: Color,\n val foreground: Color\n)\n\nprivate fun buttonPalette(\n colors: LumenColorPalette,\n intent: LumenButtonIntent\n): LumenButtonPalette = when (intent) {\n LumenButtonIntent.Danger -> LumenButtonPalette(colors.danger, colors.danger, colors.onDanger)\n LumenButtonIntent.Primary -> LumenButtonPalette(colors.brandSolid, colors.brandSolid, colors.onBrand)\n LumenButtonIntent.Quiet -> LumenButtonPalette(Color.Transparent, Color.Transparent, colors.inkSoft)\n LumenButtonIntent.Secondary -> LumenButtonPalette(colors.surfaceMuted, colors.line, colors.ink)\n}\n\n@Composable\nfun LumenButton(\n onClick: () -> Unit,\n modifier: Modifier = Modifier,\n intent: LumenButtonIntent = LumenButtonIntent.Primary,\n size: LumenControlSize = LumenControlSize.Md,\n loading: Boolean = false,\n enabled: Boolean = true,\n content: @Composable RowScope.() -> Unit\n) {\n val palette = buttonPalette(LocalLumenTheme.current.colors, intent)\n val metrics = LumenButtonMetrics.resolve(size)\n val semanticsModifier = if (loading) {\n Modifier.semantics { stateDescription = \"Loading\" }\n } else {\n Modifier\n }\n\n Button(\n onClick = onClick,\n modifier = modifier\n .then(semanticsModifier)\n .defaultMinSize(minHeight = metrics.minHeight),\n enabled = enabled && !loading,\n shape = RoundedCornerShape(LumenRadius.Sm),\n border = BorderStroke(1.dp, palette.border),\n colors = ButtonDefaults.buttonColors(\n containerColor = palette.background,\n contentColor = palette.foreground,\n disabledContainerColor = palette.background.copy(alpha = 0.52f),\n disabledContentColor = palette.foreground.copy(alpha = 0.52f)\n ),\n contentPadding = PaddingValues(\n horizontal = metrics.horizontalPadding,\n vertical = LumenSpacing.Sm\n )\n ) {\n Row(\n horizontalArrangement = Arrangement.Center,\n verticalAlignment = Alignment.CenterVertically\n ) {\n if (loading) {\n CircularProgressIndicator(\n modifier = Modifier.size(16.dp),\n color = palette.foreground,\n strokeWidth = 2.dp\n )\n Spacer(modifier = Modifier.width(LumenSpacing.Sm))\n }\n content()\n }\n }\n}\n\n@Composable\nfun LumenTextField(\n value: String,\n onValueChange: (String) -> Unit,\n label: String,\n modifier: Modifier = Modifier,\n size: LumenControlSize = LumenControlSize.Md,\n error: Boolean = false,\n errorMessage: String? = null,\n enabled: Boolean = true,\n visualTransformation: VisualTransformation = VisualTransformation.None\n) {\n val colors = LocalLumenTheme.current.colors\n val metrics = LumenButtonMetrics.resolve(size)\n\n OutlinedTextField(\n value = value,\n onValueChange = onValueChange,\n modifier = modifier.defaultMinSize(minHeight = metrics.minHeight),\n enabled = enabled,\n isError = error,\n singleLine = true,\n label = { Text(label) },\n supportingText = errorMessage?.let { message -> ({ Text(message) }) },\n visualTransformation = visualTransformation,\n shape = RoundedCornerShape(LumenRadius.Sm),\n colors = OutlinedTextFieldDefaults.colors(\n focusedContainerColor = colors.surface,\n unfocusedContainerColor = colors.surface,\n disabledContainerColor = colors.surface,\n focusedTextColor = colors.ink,\n unfocusedTextColor = colors.ink,\n focusedBorderColor = colors.brand,\n unfocusedBorderColor = colors.line,\n errorBorderColor = colors.danger,\n cursorColor = colors.brand\n )\n )\n}\n\nenum class LumenBadgeTone {\n Accent,\n Danger,\n Neutral,\n Success,\n Warning\n}\n\n@Composable\nfun LumenBadge(\n text: String,\n modifier: Modifier = Modifier,\n tone: LumenBadgeTone = LumenBadgeTone.Neutral\n) {\n val colors = LocalLumenTheme.current.colors\n val foreground = when (tone) {\n LumenBadgeTone.Accent -> colors.accent\n LumenBadgeTone.Danger -> colors.danger\n LumenBadgeTone.Neutral -> colors.inkSoft\n LumenBadgeTone.Success -> colors.success\n LumenBadgeTone.Warning -> colors.warning\n }\n val background = if (tone == LumenBadgeTone.Neutral) {\n colors.surfaceMuted\n } else {\n foreground.copy(alpha = 0.12f)\n }\n\n Surface(\n modifier = modifier,\n color = background,\n contentColor = foreground,\n shape = RoundedCornerShape(LumenRadius.Full)\n ) {\n Text(\n text = text,\n modifier = Modifier.padding(horizontal = LumenSpacing.Sm, vertical = LumenSpacing.Xs),\n style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.SemiBold)\n )\n }\n}\n\n@Composable\nfun LumenDivider(modifier: Modifier = Modifier) {\n val colors = LocalLumenTheme.current.colors\n\n HorizontalDivider(modifier = modifier, color = colors.line)\n}\n\n@Composable\nfun LumenSpinner(\n modifier: Modifier = Modifier,\n label: String = \"Loading\"\n) {\n val colors = LocalLumenTheme.current.colors\n\n CircularProgressIndicator(\n modifier = modifier.semantics { contentDescription = label },\n color = colors.brand\n )\n}\n",
37084
+ "packages/compose/src/main/kotlin/com/santi020k/lumen/LumenTheme.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.ColorScheme\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Shapes\nimport androidx.compose.material3.Typography\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.runtime.staticCompositionLocalOf\nimport androidx.compose.ui.graphics.Color\n\n@Immutable\ndata class LumenThemeValues(\n val colors: LumenColorPalette,\n val isDark: Boolean\n)\n\n@Immutable\ndata class LumenMaterialColorOverrides(\n val brand: Color? = null,\n val accent: Color? = null,\n val success: Color? = null,\n val warning: Color? = null\n)\n\nval LocalLumenTheme = staticCompositionLocalOf {\n LumenThemeValues(colors = LumenColors.Light, isDark = false)\n}\n\nfun ColorScheme.toLumenColorPalette(\n fallback: LumenColorPalette,\n overrides: LumenMaterialColorOverrides = LumenMaterialColorOverrides()\n): LumenColorPalette = LumenColorPalette(\n canvas = background,\n surface = surface,\n surfaceMuted = surfaceVariant,\n surfaceStrong = surfaceContainerHigh,\n line = outline,\n ink = onBackground,\n inkSoft = onSurface,\n inkMuted = onSurfaceVariant,\n brand = overrides.brand ?: primary,\n brandSolid = overrides.brand ?: primary,\n brandSoft = primaryContainer,\n onBrand = onPrimary,\n accent = overrides.accent ?: secondary,\n success = overrides.success ?: fallback.success,\n warning = overrides.warning ?: fallback.warning,\n danger = error,\n onDanger = onError\n)\n\nfun LumenColorPalette.toMaterialColorScheme(isDark: Boolean): ColorScheme {\n return if (isDark) {\n darkColorScheme(\n primary = brand,\n onPrimary = onBrand,\n primaryContainer = brandSoft,\n onPrimaryContainer = ink,\n secondary = accent,\n background = canvas,\n onBackground = ink,\n surface = surface,\n onSurface = ink,\n surfaceVariant = surfaceMuted,\n onSurfaceVariant = inkMuted,\n surfaceContainerHigh = surfaceStrong,\n error = danger,\n onError = onDanger,\n outline = line\n )\n } else {\n lightColorScheme(\n primary = brand,\n onPrimary = onBrand,\n primaryContainer = brandSoft,\n onPrimaryContainer = ink,\n secondary = accent,\n background = canvas,\n onBackground = ink,\n surface = surface,\n onSurface = ink,\n surfaceVariant = surfaceMuted,\n onSurfaceVariant = inkMuted,\n surfaceContainerHigh = surfaceStrong,\n error = danger,\n onError = onDanger,\n outline = line\n )\n }\n}\n\n@Composable\nfun LumenTheme(\n darkTheme: Boolean = isSystemInDarkTheme(),\n values: LumenThemeValues? = null,\n materialColorScheme: ColorScheme? = null,\n materialColorOverrides: LumenMaterialColorOverrides = LumenMaterialColorOverrides(),\n typography: Typography = Typography(),\n shapes: Shapes = Shapes(),\n content: @Composable () -> Unit\n) {\n val defaultValues = if (darkTheme) {\n LumenThemeValues(colors = LumenColors.Dark, isDark = true)\n } else {\n LumenThemeValues(colors = LumenColors.Light, isDark = false)\n }\n val resolvedValues = values ?: materialColorScheme?.let { colorScheme ->\n LumenThemeValues(\n colors = colorScheme.toLumenColorPalette(defaultValues.colors, materialColorOverrides),\n isDark = darkTheme\n )\n } ?: defaultValues\n val resolvedColorScheme = materialColorScheme\n ?: resolvedValues.colors.toMaterialColorScheme(resolvedValues.isDark)\n\n CompositionLocalProvider(LocalLumenTheme provides resolvedValues) {\n MaterialTheme(\n colorScheme = resolvedColorScheme,\n typography = typography,\n shapes = shapes,\n content = content\n )\n }\n}\n",
37085
+ "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.SmallFloatingActionButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\n\nenum class LumenFloatingActionButtonIntent {\n Accent,\n Brand,\n Danger\n}\n\nenum class LumenFloatingActionButtonSize {\n Regular,\n Small\n}\n\n@Immutable\ndata class LumenFloatingActionButtonMetrics(\n val dimension: Dp,\n val iconSize: LumenIconSize\n) {\n companion object {\n fun resolve(size: LumenFloatingActionButtonSize): LumenFloatingActionButtonMetrics = when (size) {\n LumenFloatingActionButtonSize.Regular -> LumenFloatingActionButtonMetrics(56.dp, LumenIconSize.Lg)\n LumenFloatingActionButtonSize.Small -> LumenFloatingActionButtonMetrics(40.dp, LumenIconSize.Md)\n }\n }\n}\n\n@Immutable\ninternal data class LumenFloatingActionButtonPalette(\n val background: Color,\n val foreground: Color\n)\n\ninternal fun lumenFloatingActionButtonPalette(\n colors: LumenColorPalette,\n intent: LumenFloatingActionButtonIntent\n): LumenFloatingActionButtonPalette = when (intent) {\n LumenFloatingActionButtonIntent.Accent -> LumenFloatingActionButtonPalette(\n background = colors.accent,\n foreground = colors.canvas\n )\n LumenFloatingActionButtonIntent.Brand -> LumenFloatingActionButtonPalette(\n background = colors.brandSolid,\n foreground = colors.onBrand\n )\n LumenFloatingActionButtonIntent.Danger -> LumenFloatingActionButtonPalette(\n background = colors.danger,\n foreground = colors.onDanger\n )\n}\n\n/**\n * A Material-native floating action button using Lumen semantic colors and icon sizing.\n * The content description is required because floating actions are icon-only.\n */\n@Composable\nfun LumenFloatingActionButton(\n imageVector: ImageVector,\n contentDescription: String,\n onClick: () -> Unit,\n modifier: Modifier = Modifier,\n intent: LumenFloatingActionButtonIntent = LumenFloatingActionButtonIntent.Brand,\n size: LumenFloatingActionButtonSize = LumenFloatingActionButtonSize.Regular\n) {\n val palette = lumenFloatingActionButtonPalette(LocalLumenTheme.current.colors, intent)\n val metrics = LumenFloatingActionButtonMetrics.resolve(size)\n val content: @Composable () -> Unit = {\n LumenIcon(\n imageVector = imageVector,\n contentDescription = contentDescription,\n size = metrics.iconSize,\n tint = palette.foreground\n )\n }\n\n when (size) {\n LumenFloatingActionButtonSize.Regular -> FloatingActionButton(\n onClick = onClick,\n modifier = modifier,\n shape = RoundedCornerShape(LumenRadius.Lg),\n containerColor = palette.background,\n contentColor = palette.foreground,\n content = content\n )\n LumenFloatingActionButtonSize.Small -> SmallFloatingActionButton(\n onClick = onClick,\n modifier = modifier,\n shape = RoundedCornerShape(LumenRadius.Md),\n containerColor = palette.background,\n contentColor = palette.foreground,\n content = content\n )\n }\n}\n",
37086
+ "packages/compose/src/main/kotlin/com/santi020k/lumen/Primitives.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.RowScope\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.defaultMinSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.IconButtonDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.OutlinedTextFieldDefaults\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.semantics.stateDescription\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.VisualTransformation\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\n\nenum class LumenTextTone {\n Danger,\n Default,\n Muted,\n Soft,\n Success,\n Warning\n}\n\nenum class LumenTextVariant {\n Body,\n Caption,\n Label,\n Title\n}\n\n@Composable\nfun LumenText(\n text: String,\n modifier: Modifier = Modifier,\n variant: LumenTextVariant = LumenTextVariant.Body,\n tone: LumenTextTone = LumenTextTone.Default\n) {\n val theme = LocalLumenTheme.current\n val color = when (tone) {\n LumenTextTone.Danger -> theme.colors.danger\n LumenTextTone.Default -> theme.colors.ink\n LumenTextTone.Muted -> theme.colors.inkMuted\n LumenTextTone.Soft -> theme.colors.inkSoft\n LumenTextTone.Success -> theme.colors.success\n LumenTextTone.Warning -> theme.colors.warning\n }\n val style = when (variant) {\n LumenTextVariant.Body -> MaterialTheme.typography.bodyLarge\n LumenTextVariant.Caption -> MaterialTheme.typography.bodySmall\n LumenTextVariant.Label -> MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n LumenTextVariant.Title -> MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold)\n }\n\n Text(text = text, modifier = modifier, color = color, style = style)\n}\n\nenum class LumenSurfacePadding(val value: Dp) {\n None(0.dp),\n Sm(LumenSpacing.Sm),\n Md(LumenSpacing.Md),\n Lg(LumenSpacing.Lg)\n}\n\nenum class LumenSurfaceRadius(val value: Dp) {\n None(0.dp),\n Sm(LumenRadius.Sm),\n Md(LumenRadius.Md),\n Lg(LumenRadius.Lg)\n}\n\nenum class LumenSurfaceTone {\n Canvas,\n Muted,\n Strong,\n Surface\n}\n\n@Composable\nfun LumenSurface(\n modifier: Modifier = Modifier,\n tone: LumenSurfaceTone = LumenSurfaceTone.Surface,\n padding: LumenSurfacePadding = LumenSurfacePadding.Md,\n radius: LumenSurfaceRadius = LumenSurfaceRadius.Md,\n content: @Composable () -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n val color = when (tone) {\n LumenSurfaceTone.Canvas -> colors.canvas\n LumenSurfaceTone.Muted -> colors.surfaceMuted\n LumenSurfaceTone.Strong -> colors.surfaceStrong\n LumenSurfaceTone.Surface -> colors.surface\n }\n\n Surface(\n modifier = modifier,\n color = color,\n shape = RoundedCornerShape(radius.value)\n ) {\n androidx.compose.foundation.layout.Box(\n modifier = Modifier.padding(padding.value),\n contentAlignment = Alignment.TopStart\n ) {\n content()\n }\n }\n}\n\nenum class LumenButtonIntent {\n Danger,\n Primary,\n Quiet,\n Secondary\n}\n\nenum class LumenControlSize {\n Lg,\n Md,\n Sm\n}\n\nenum class LumenIconSize(val dimension: Dp) {\n Sm(16.dp),\n Md(20.dp),\n Lg(24.dp)\n}\n\n@Composable\nfun LumenIcon(\n imageVector: ImageVector,\n modifier: Modifier = Modifier,\n contentDescription: String? = null,\n size: LumenIconSize = LumenIconSize.Md,\n tint: Color = LocalLumenTheme.current.colors.ink\n) {\n Icon(\n imageVector = imageVector,\n contentDescription = contentDescription,\n modifier = modifier.size(size.dimension),\n tint = tint\n )\n}\n\n@Immutable\ndata class LumenIconButtonMetrics(\n val iconSize: LumenIconSize,\n val touchTarget: Dp\n) {\n companion object {\n fun resolve(size: LumenControlSize): LumenIconButtonMetrics = when (size) {\n LumenControlSize.Lg -> LumenIconButtonMetrics(LumenIconSize.Lg, 52.dp)\n LumenControlSize.Md -> LumenIconButtonMetrics(LumenIconSize.Md, 44.dp)\n LumenControlSize.Sm -> LumenIconButtonMetrics(LumenIconSize.Sm, 44.dp)\n }\n }\n}\n\n@Composable\nfun LumenIconButton(\n imageVector: ImageVector,\n contentDescription: String,\n onClick: () -> Unit,\n modifier: Modifier = Modifier,\n intent: LumenButtonIntent = LumenButtonIntent.Quiet,\n size: LumenControlSize = LumenControlSize.Md,\n enabled: Boolean = true\n) {\n val palette = buttonPalette(LocalLumenTheme.current.colors, intent)\n val metrics = LumenIconButtonMetrics.resolve(size)\n val shape = RoundedCornerShape(LumenRadius.Sm)\n\n IconButton(\n onClick = onClick,\n modifier = modifier\n .size(metrics.touchTarget)\n .clip(shape)\n .border(1.dp, palette.border, shape),\n enabled = enabled,\n colors = IconButtonDefaults.iconButtonColors(\n containerColor = palette.background,\n contentColor = palette.foreground,\n disabledContainerColor = palette.background.copy(alpha = 0.52f),\n disabledContentColor = palette.foreground.copy(alpha = 0.52f)\n )\n ) {\n LumenIcon(\n imageVector = imageVector,\n contentDescription = contentDescription,\n size = metrics.iconSize,\n tint = palette.foreground.copy(alpha = if (enabled) 1f else 0.52f)\n )\n }\n}\n\n@Immutable\ndata class LumenButtonMetrics(\n val horizontalPadding: Dp,\n val minHeight: Dp\n) {\n companion object {\n fun resolve(size: LumenControlSize): LumenButtonMetrics = when (size) {\n LumenControlSize.Lg -> LumenButtonMetrics(LumenSpacing.Xl, 52.dp)\n LumenControlSize.Md -> LumenButtonMetrics(LumenSpacing.Lg, 44.dp)\n LumenControlSize.Sm -> LumenButtonMetrics(LumenSpacing.Md, 36.dp)\n }\n }\n}\n\nprivate data class LumenButtonPalette(\n val background: Color,\n val border: Color,\n val foreground: Color\n)\n\nprivate fun buttonPalette(\n colors: LumenColorPalette,\n intent: LumenButtonIntent\n): LumenButtonPalette = when (intent) {\n LumenButtonIntent.Danger -> LumenButtonPalette(colors.danger, colors.danger, colors.onDanger)\n LumenButtonIntent.Primary -> LumenButtonPalette(colors.brandSolid, colors.brandSolid, colors.onBrand)\n LumenButtonIntent.Quiet -> LumenButtonPalette(Color.Transparent, Color.Transparent, colors.inkSoft)\n LumenButtonIntent.Secondary -> LumenButtonPalette(colors.surfaceMuted, colors.line, colors.ink)\n}\n\n@Composable\nfun LumenButton(\n onClick: () -> Unit,\n modifier: Modifier = Modifier,\n intent: LumenButtonIntent = LumenButtonIntent.Primary,\n size: LumenControlSize = LumenControlSize.Md,\n loading: Boolean = false,\n enabled: Boolean = true,\n content: @Composable RowScope.() -> Unit\n) {\n val palette = buttonPalette(LocalLumenTheme.current.colors, intent)\n val metrics = LumenButtonMetrics.resolve(size)\n val semanticsModifier = if (loading) {\n Modifier.semantics { stateDescription = \"Loading\" }\n } else {\n Modifier\n }\n\n Button(\n onClick = onClick,\n modifier = modifier\n .then(semanticsModifier)\n .defaultMinSize(minHeight = metrics.minHeight),\n enabled = enabled && !loading,\n shape = RoundedCornerShape(LumenRadius.Sm),\n border = BorderStroke(1.dp, palette.border),\n colors = ButtonDefaults.buttonColors(\n containerColor = palette.background,\n contentColor = palette.foreground,\n disabledContainerColor = palette.background.copy(alpha = 0.52f),\n disabledContentColor = palette.foreground.copy(alpha = 0.52f)\n ),\n contentPadding = PaddingValues(\n horizontal = metrics.horizontalPadding,\n vertical = LumenSpacing.Sm\n )\n ) {\n Row(\n horizontalArrangement = Arrangement.Center,\n verticalAlignment = Alignment.CenterVertically\n ) {\n if (loading) {\n CircularProgressIndicator(\n modifier = Modifier.size(16.dp),\n color = palette.foreground,\n strokeWidth = 2.dp\n )\n Spacer(modifier = Modifier.width(LumenSpacing.Sm))\n }\n content()\n }\n }\n}\n\n@Composable\nfun LumenTextField(\n value: String,\n onValueChange: (String) -> Unit,\n label: String,\n modifier: Modifier = Modifier,\n size: LumenControlSize = LumenControlSize.Md,\n error: Boolean = false,\n errorMessage: String? = null,\n enabled: Boolean = true,\n visualTransformation: VisualTransformation = VisualTransformation.None\n) {\n val colors = LocalLumenTheme.current.colors\n val metrics = LumenButtonMetrics.resolve(size)\n\n OutlinedTextField(\n value = value,\n onValueChange = onValueChange,\n modifier = modifier.defaultMinSize(minHeight = metrics.minHeight),\n enabled = enabled,\n isError = error,\n singleLine = true,\n label = { Text(label) },\n supportingText = errorMessage?.let { message -> ({ Text(message) }) },\n visualTransformation = visualTransformation,\n shape = RoundedCornerShape(LumenRadius.Sm),\n colors = OutlinedTextFieldDefaults.colors(\n focusedContainerColor = colors.surface,\n unfocusedContainerColor = colors.surface,\n disabledContainerColor = colors.surface,\n focusedTextColor = colors.ink,\n unfocusedTextColor = colors.ink,\n focusedBorderColor = colors.brand,\n unfocusedBorderColor = colors.line,\n errorBorderColor = colors.danger,\n cursorColor = colors.brand\n )\n )\n}\n\nenum class LumenBadgeTone {\n Accent,\n Danger,\n Neutral,\n Success,\n Warning\n}\n\n@Composable\nfun LumenBadge(\n text: String,\n modifier: Modifier = Modifier,\n tone: LumenBadgeTone = LumenBadgeTone.Neutral\n) {\n val colors = LocalLumenTheme.current.colors\n val foreground = when (tone) {\n LumenBadgeTone.Accent -> colors.accent\n LumenBadgeTone.Danger -> colors.danger\n LumenBadgeTone.Neutral -> colors.inkSoft\n LumenBadgeTone.Success -> colors.success\n LumenBadgeTone.Warning -> colors.warning\n }\n val background = if (tone == LumenBadgeTone.Neutral) {\n colors.surfaceMuted\n } else {\n foreground.copy(alpha = 0.12f)\n }\n\n Surface(\n modifier = modifier,\n color = background,\n contentColor = foreground,\n shape = RoundedCornerShape(LumenRadius.Full)\n ) {\n Text(\n text = text,\n modifier = Modifier.padding(horizontal = LumenSpacing.Sm, vertical = LumenSpacing.Xs),\n style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.SemiBold)\n )\n }\n}\n\n@Composable\nfun LumenDivider(modifier: Modifier = Modifier) {\n val colors = LocalLumenTheme.current.colors\n\n HorizontalDivider(modifier = modifier, color = colors.line)\n}\n\n@Composable\nfun LumenSpinner(\n modifier: Modifier = Modifier,\n label: String = \"Loading\"\n) {\n val colors = LocalLumenTheme.current.colors\n\n CircularProgressIndicator(\n modifier = modifier.semantics { contentDescription = label },\n color = colors.brand\n )\n}\n",
36811
37087
  "packages/compose/src/main/kotlin/com/santi020k/lumen/SelectionComponents.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.selection.selectable\nimport androidx.compose.foundation.selection.selectableGroup\nimport androidx.compose.foundation.selection.toggleable\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Checkbox\nimport androidx.compose.material3.CheckboxDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.RadioButton\nimport androidx.compose.material3.RadioButtonDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.ui.semantics.clearAndSetSemantics\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\n\n@Immutable\ndata class LumenSelectionOption(\n val value: String,\n val label: String,\n val description: String? = null,\n val enabled: Boolean = true\n)\n\n@Composable\nfun LumenCheckbox(\n label: String,\n checked: Boolean,\n onCheckedChange: (Boolean) -> Unit,\n modifier: Modifier = Modifier,\n description: String? = null,\n enabled: Boolean = true\n) {\n val colors = LocalLumenTheme.current.colors\n\n Row(\n modifier = modifier\n .fillMaxWidth()\n .heightIn(min = LumenButtonMetrics.resolve(LumenControlSize.Md).minHeight)\n .toggleable(\n value = checked,\n enabled = enabled,\n role = Role.Checkbox,\n onValueChange = onCheckedChange\n )\n .semantics(mergeDescendants = true) { contentDescription = label }\n .padding(vertical = LumenSpacing.Sm),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Md),\n verticalAlignment = Alignment.Top\n ) {\n Checkbox(\n checked = checked,\n onCheckedChange = null,\n modifier = Modifier.clearAndSetSemantics {},\n enabled = enabled,\n colors = CheckboxDefaults.colors(\n checkedColor = colors.brandSolid,\n uncheckedColor = colors.line,\n checkmarkColor = colors.onBrand,\n disabledCheckedColor = colors.brandSolid.copy(alpha = 0.52f),\n disabledUncheckedColor = colors.line.copy(alpha = 0.52f)\n )\n )\n Column(\n modifier = Modifier.weight(1f).padding(top = 2.dp),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Xs)\n ) {\n Text(\n text = label,\n color = colors.ink,\n style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n )\n if (description != null) {\n Text(text = description, color = colors.inkMuted, style = MaterialTheme.typography.bodySmall)\n }\n }\n }\n}\n\n@Composable\nfun LumenRadioGroup(\n label: String,\n options: List<LumenSelectionOption>,\n value: String,\n onValueChange: (String) -> Unit,\n modifier: Modifier = Modifier\n) {\n val colors = LocalLumenTheme.current.colors\n\n Column(\n modifier = modifier\n .fillMaxWidth()\n .selectableGroup()\n .semantics { contentDescription = label },\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Sm)\n ) {\n Text(\n text = label,\n color = colors.ink,\n style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n )\n options.forEach { option ->\n val selected = value == option.value\n\n Row(\n modifier = Modifier\n .fillMaxWidth()\n .heightIn(min = LumenButtonMetrics.resolve(LumenControlSize.Md).minHeight)\n .selectable(\n selected = selected,\n enabled = option.enabled,\n role = Role.RadioButton,\n onClick = { onValueChange(option.value) }\n )\n .padding(vertical = LumenSpacing.Sm),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Md),\n verticalAlignment = Alignment.Top\n ) {\n RadioButton(\n selected = selected,\n onClick = null,\n modifier = Modifier.clearAndSetSemantics {},\n enabled = option.enabled,\n colors = RadioButtonDefaults.colors(\n selectedColor = colors.brandSolid,\n unselectedColor = colors.line,\n disabledSelectedColor = colors.brandSolid.copy(alpha = 0.52f),\n disabledUnselectedColor = colors.line.copy(alpha = 0.52f)\n )\n )\n Column(\n modifier = Modifier.weight(1f).padding(top = 2.dp),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Xs)\n ) {\n Text(text = option.label, color = colors.ink, style = MaterialTheme.typography.bodyMedium)\n if (option.description != null) {\n Text(\n text = option.description,\n color = colors.inkMuted,\n style = MaterialTheme.typography.bodySmall\n )\n }\n }\n }\n }\n }\n}\n\n@Composable\nfun LumenSegmentedControl(\n label: String,\n options: List<LumenSelectionOption>,\n value: String,\n onValueChange: (String) -> Unit,\n modifier: Modifier = Modifier,\n showLabel: Boolean = true\n) {\n val colors = LocalLumenTheme.current.colors\n\n Column(\n modifier = modifier.fillMaxWidth(),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Sm)\n ) {\n if (showLabel) {\n Text(\n text = label,\n color = colors.ink,\n style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n )\n }\n Row(\n modifier = Modifier\n .fillMaxWidth()\n .clip(RoundedCornerShape(LumenRadius.Sm))\n .background(colors.surfaceMuted)\n .border(1.dp, colors.line, RoundedCornerShape(LumenRadius.Sm))\n .padding(2.dp)\n .selectableGroup()\n .semantics { contentDescription = label },\n verticalAlignment = Alignment.CenterVertically\n ) {\n options.forEach { option ->\n val selected = value == option.value\n\n Box(\n modifier = Modifier\n .weight(1f)\n .heightIn(min = 40.dp)\n .clip(RoundedCornerShape(LumenRadius.Sm))\n .background(if (selected) colors.surface else androidx.compose.ui.graphics.Color.Transparent)\n .selectable(\n selected = selected,\n enabled = option.enabled,\n role = Role.RadioButton,\n onClick = { onValueChange(option.value) }\n )\n .padding(horizontal = LumenSpacing.Sm, vertical = LumenSpacing.Sm),\n contentAlignment = Alignment.Center\n ) {\n Text(\n text = option.label,\n color = if (selected) colors.brand else colors.inkSoft,\n fontWeight = if (selected) FontWeight.Bold else FontWeight.SemiBold,\n maxLines = 1,\n overflow = TextOverflow.Ellipsis,\n style = MaterialTheme.typography.labelSmall\n )\n }\n }\n }\n }\n}\n",
36812
37088
  "packages/compose/src/main/kotlin/com/santi020k/lumen/SharedComponents.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.painter.Painter\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.semantics.ProgressBarRangeInfo\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.ui.semantics.clearAndSetSemantics\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.progressBarRangeInfo\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\n\nenum class LumenCardVariant {\n Accent,\n Default,\n Destructive,\n Muted,\n Success,\n Warning\n}\n\n@Immutable\ndata class LumenCardPalette(\n val background: Color,\n val border: Color\n)\n\nfun lumenCardPalette(colors: LumenColorPalette, variant: LumenCardVariant): LumenCardPalette {\n if (variant == LumenCardVariant.Default) return LumenCardPalette(colors.surface, colors.line)\n\n if (variant == LumenCardVariant.Muted) return LumenCardPalette(colors.surfaceMuted, colors.line)\n\n val accent = when (variant) {\n LumenCardVariant.Accent -> colors.accent\n LumenCardVariant.Destructive -> colors.danger\n LumenCardVariant.Success -> colors.success\n LumenCardVariant.Warning -> colors.warning\n LumenCardVariant.Default, LumenCardVariant.Muted -> colors.line\n }\n\n return LumenCardPalette(\n background = accent.copy(alpha = 0.06f),\n border = accent.copy(alpha = 0.24f)\n )\n}\n\n@Composable\nfun LumenCard(\n modifier: Modifier = Modifier,\n variant: LumenCardVariant = LumenCardVariant.Default,\n onClick: (() -> Unit)? = null,\n enabled: Boolean = true,\n content: @Composable () -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n val palette = lumenCardPalette(colors, variant)\n val interactionModifier = if (onClick == null) {\n Modifier\n } else {\n Modifier.clickable(enabled = enabled, role = Role.Button, onClick = onClick)\n }\n\n Surface(\n modifier = modifier.then(interactionModifier),\n color = palette.background,\n contentColor = colors.ink,\n shape = RoundedCornerShape(LumenRadius.Lg),\n border = androidx.compose.foundation.BorderStroke(1.dp, palette.border)\n ) {\n Box(modifier = Modifier.padding(LumenSpacing.Xl)) {\n content()\n }\n }\n}\n\nenum class LumenAlertVariant {\n Default,\n Destructive,\n Success,\n Warning\n}\n\nprivate data class LumenAlertPalette(\n val background: Color,\n val border: Color,\n val foreground: Color\n)\n\nprivate fun alertPalette(\n colors: LumenColorPalette,\n variant: LumenAlertVariant\n): LumenAlertPalette = when (variant) {\n LumenAlertVariant.Default -> LumenAlertPalette(colors.surface, colors.line, colors.ink)\n LumenAlertVariant.Destructive -> LumenAlertPalette(\n colors.danger.copy(alpha = 0.08f),\n colors.danger.copy(alpha = 0.36f),\n colors.danger\n )\n LumenAlertVariant.Success -> LumenAlertPalette(\n colors.success.copy(alpha = 0.08f),\n colors.success.copy(alpha = 0.36f),\n colors.success\n )\n LumenAlertVariant.Warning -> LumenAlertPalette(\n colors.warning.copy(alpha = 0.10f),\n colors.warning.copy(alpha = 0.42f),\n colors.warning\n )\n}\n\n@Composable\nfun LumenAlert(\n modifier: Modifier = Modifier,\n variant: LumenAlertVariant = LumenAlertVariant.Default,\n content: @Composable () -> Unit\n) {\n val palette = alertPalette(LocalLumenTheme.current.colors, variant)\n\n Surface(\n modifier = modifier,\n color = palette.background,\n contentColor = palette.foreground,\n shape = RoundedCornerShape(LumenRadius.Md),\n border = androidx.compose.foundation.BorderStroke(1.dp, palette.border)\n ) {\n Box(\n modifier = Modifier.padding(horizontal = LumenSpacing.Lg, vertical = LumenSpacing.Md)\n ) {\n content()\n }\n }\n}\n\n@Immutable\ndata class LumenProgressValue(\n val max: Float,\n val value: Float\n) {\n val fraction: Float get() = value / max\n\n companion object {\n fun resolve(value: Float, max: Float): LumenProgressValue {\n val safeMax = if (max.isFinite() && max > 0f) max else 100f\n val finiteValue = if (value.isFinite()) value else 0f\n\n return LumenProgressValue(\n max = safeMax,\n value = finiteValue.coerceIn(0f, safeMax)\n )\n }\n }\n}\n\n@Composable\nfun LumenProgress(\n value: Float,\n modifier: Modifier = Modifier,\n max: Float = 100f,\n label: String? = null\n) {\n val colors = LocalLumenTheme.current.colors\n val progress = LumenProgressValue.resolve(value, max)\n val semanticsModifier = Modifier.semantics {\n progressBarRangeInfo = ProgressBarRangeInfo(progress.value, 0f..progress.max)\n if (label != null) contentDescription = label\n }\n\n Box(\n modifier = modifier\n .then(semanticsModifier)\n .fillMaxWidth()\n .height(LumenSpacing.Sm)\n .clip(RoundedCornerShape(LumenRadius.Full))\n .background(colors.surfaceStrong)\n ) {\n Box(\n modifier = Modifier\n .fillMaxWidth(progress.fraction)\n .fillMaxHeight()\n .background(colors.brandSolid)\n )\n }\n}\n\nenum class LumenAvatarSize(val dimension: Dp) {\n Sm(32.dp),\n Md(40.dp),\n Lg(56.dp)\n}\n\n@Composable\nfun LumenAvatar(\n modifier: Modifier = Modifier,\n painter: Painter? = null,\n fallback: String = \"?\",\n size: LumenAvatarSize = LumenAvatarSize.Md,\n label: String? = null\n) {\n val colors = LocalLumenTheme.current.colors\n\n Box(\n modifier = modifier\n .size(size.dimension)\n .clip(CircleShape)\n .background(colors.surfaceMuted)\n .border(1.dp, colors.line, CircleShape)\n .clearAndSetSemantics {\n if (label != null) contentDescription = label\n },\n contentAlignment = Alignment.Center\n ) {\n if (painter == null) {\n Text(\n text = fallback,\n color = colors.ink,\n fontWeight = FontWeight.Bold\n )\n } else {\n Image(\n painter = painter,\n contentDescription = null,\n modifier = Modifier.fillMaxWidth().fillMaxHeight(),\n contentScale = ContentScale.Crop\n )\n }\n }\n}\n",
36813
37089
  "packages/compose/src/main/kotlin/com/santi020k/lumen/StructuredComponents.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.IntrinsicSize\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.widthIn\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.heading\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\n\nenum class LumenBannerVariant {\n Accent,\n Default,\n Destructive,\n Success,\n Warning\n}\n\nenum class LumenMetricTone {\n Accent,\n Brand,\n Danger,\n Neutral,\n Success,\n Warning\n}\n\n@Immutable\ndata class LumenBannerPalette(\n val accent: Color,\n val background: Color,\n val border: Color\n)\n\nfun lumenMetricColor(colors: LumenColorPalette, tone: LumenMetricTone): Color = when (tone) {\n LumenMetricTone.Accent -> colors.accent\n LumenMetricTone.Brand -> colors.brand\n LumenMetricTone.Danger -> colors.danger\n LumenMetricTone.Neutral -> colors.inkMuted\n LumenMetricTone.Success -> colors.success\n LumenMetricTone.Warning -> colors.warning\n}\n\nfun lumenBannerPalette(\n colors: LumenColorPalette,\n variant: LumenBannerVariant\n): LumenBannerPalette {\n val tone = when (variant) {\n LumenBannerVariant.Accent -> LumenMetricTone.Accent\n LumenBannerVariant.Default -> LumenMetricTone.Brand\n LumenBannerVariant.Destructive -> LumenMetricTone.Danger\n LumenBannerVariant.Success -> LumenMetricTone.Success\n LumenBannerVariant.Warning -> LumenMetricTone.Warning\n }\n val accent = lumenMetricColor(colors, tone)\n\n return LumenBannerPalette(\n accent = accent,\n background = if (variant == LumenBannerVariant.Default) colors.surface else accent.copy(alpha = 0.08f),\n border = if (variant == LumenBannerVariant.Default) colors.line else accent.copy(alpha = 0.32f)\n )\n}\n\n@Composable\nfun LumenEmptyState(\n title: String,\n modifier: Modifier = Modifier,\n description: String? = null,\n graphic: (@Composable () -> Unit)? = null,\n actions: (@Composable () -> Unit)? = null\n) {\n val colors = LocalLumenTheme.current.colors\n\n Column(\n modifier = modifier\n .fillMaxWidth()\n .widthIn(max = 440.dp)\n .padding(LumenSpacing.Xl),\n horizontalAlignment = Alignment.CenterHorizontally,\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Lg)\n ) {\n if (graphic != null) {\n Box(\n modifier = Modifier\n .size(56.dp)\n .clip(CircleShape)\n .background(colors.surfaceMuted),\n contentAlignment = Alignment.Center\n ) {\n graphic()\n }\n }\n\n Column(\n horizontalAlignment = Alignment.CenterHorizontally,\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Sm)\n ) {\n Text(\n text = title,\n color = colors.ink,\n style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold),\n textAlign = TextAlign.Center\n )\n\n if (description != null) {\n Text(\n text = description,\n color = colors.inkMuted,\n style = MaterialTheme.typography.bodyMedium,\n textAlign = TextAlign.Center\n )\n }\n }\n\n actions?.invoke()\n }\n}\n\n@Composable\nfun LumenListRow(\n modifier: Modifier = Modifier,\n leading: (@Composable () -> Unit)? = null,\n trailing: (@Composable () -> Unit)? = null,\n content: @Composable () -> Unit\n) {\n Row(\n modifier = modifier\n .fillMaxWidth()\n .heightIn(min = 56.dp)\n .padding(horizontal = LumenSpacing.Lg, vertical = LumenSpacing.Md),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Md),\n verticalAlignment = Alignment.CenterVertically\n ) {\n leading?.invoke()\n\n Box(modifier = Modifier.weight(1f)) {\n content()\n }\n\n trailing?.invoke()\n }\n}\n\n@Composable\nfun LumenBanner(\n title: String,\n modifier: Modifier = Modifier,\n description: String? = null,\n variant: LumenBannerVariant = LumenBannerVariant.Default,\n dismissLabel: String = \"Dismiss\",\n onDismiss: (() -> Unit)? = null,\n graphic: (@Composable () -> Unit)? = null,\n actions: (@Composable () -> Unit)? = null\n) {\n val colors = LocalLumenTheme.current.colors\n val palette = lumenBannerPalette(colors, variant)\n val shape = RoundedCornerShape(LumenRadius.Md)\n\n Surface(\n modifier = modifier,\n color = palette.background,\n contentColor = colors.ink,\n shape = shape,\n border = BorderStroke(1.dp, palette.border)\n ) {\n Row(\n modifier = Modifier.height(IntrinsicSize.Min)\n ) {\n Box(\n modifier = Modifier\n .width(3.dp)\n .fillMaxHeight()\n .background(palette.accent)\n )\n Row(\n modifier = Modifier\n .weight(1f)\n .padding(horizontal = LumenSpacing.Lg, vertical = LumenSpacing.Md),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Md),\n verticalAlignment = Alignment.CenterVertically\n ) {\n graphic?.invoke()\n\n Column(\n modifier = Modifier.weight(1f),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Xs)\n ) {\n Text(\n text = title,\n color = colors.ink,\n style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold)\n )\n\n if (description != null) {\n Text(\n text = description,\n color = colors.inkSoft,\n style = MaterialTheme.typography.bodySmall\n )\n }\n }\n\n actions?.invoke()\n\n if (onDismiss != null) {\n TextButton(onClick = onDismiss) {\n Text(dismissLabel, color = palette.accent)\n }\n }\n }\n }\n }\n}\n\n@Composable\nfun LumenStat(\n label: String,\n value: String,\n modifier: Modifier = Modifier,\n detail: String? = null,\n tone: LumenMetricTone = LumenMetricTone.Brand,\n graphic: (@Composable () -> Unit)? = null\n) {\n val colors = LocalLumenTheme.current.colors\n val accent = lumenMetricColor(colors, tone)\n val accessibleText = listOfNotNull(label, value, detail).joinToString(\", \")\n\n Column(\n modifier = modifier\n .semantics(mergeDescendants = true) { contentDescription = accessibleText }\n .background(accent.copy(alpha = 0.06f), RoundedCornerShape(LumenRadius.Md))\n .border(1.dp, accent.copy(alpha = 0.18f), RoundedCornerShape(LumenRadius.Md))\n .padding(LumenSpacing.Lg),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Sm)\n ) {\n graphic?.invoke()\n\n Text(\n text = value,\n color = colors.ink,\n style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold)\n )\n Text(\n text = label,\n color = colors.inkSoft,\n style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.SemiBold)\n )\n\n if (detail != null) {\n Text(text = detail, color = colors.inkMuted, style = MaterialTheme.typography.bodySmall)\n }\n }\n}\n\n@Composable\nfun LumenSectionHeader(\n title: String,\n modifier: Modifier = Modifier,\n subtitle: String? = null,\n count: String? = null,\n actions: (@Composable () -> Unit)? = null\n) {\n val colors = LocalLumenTheme.current.colors\n\n Row(\n modifier = modifier.fillMaxWidth(),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Md),\n verticalAlignment = Alignment.Top\n ) {\n Column(\n modifier = Modifier.weight(1f),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Xs)\n ) {\n Row(\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Sm),\n verticalAlignment = Alignment.CenterVertically\n ) {\n Text(\n text = title,\n modifier = Modifier.semantics { heading() },\n color = colors.ink,\n style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold)\n )\n if (count != null) LumenBadge(count)\n }\n\n if (subtitle != null) {\n Text(text = subtitle, color = colors.inkMuted, style = MaterialTheme.typography.bodySmall)\n }\n }\n\n actions?.invoke()\n }\n}\n\n@Composable\nfun LumenStatusBar(\n message: String,\n modifier: Modifier = Modifier,\n tone: LumenMetricTone = LumenMetricTone.Neutral,\n trailing: (@Composable () -> Unit)? = null\n) {\n val colors = LocalLumenTheme.current.colors\n\n Row(\n modifier = modifier\n .fillMaxWidth()\n .heightIn(min = 36.dp)\n .background(colors.surfaceMuted)\n .padding(horizontal = LumenSpacing.Lg),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Sm),\n verticalAlignment = Alignment.CenterVertically\n ) {\n Spacer(\n modifier = Modifier\n .size(7.dp)\n .clip(CircleShape)\n .background(lumenMetricColor(colors, tone))\n )\n Text(\n text = message,\n modifier = Modifier.weight(1f),\n color = colors.inkSoft,\n maxLines = 1,\n overflow = TextOverflow.Ellipsis,\n style = MaterialTheme.typography.bodySmall\n )\n trailing?.invoke()\n }\n}\n",
36814
- "packages/compose/src/main/kotlin/com/santi020k/lumen/Tokens.generated.kt": "// This file is generated by scripts/generate-platform-tokens.mjs.\npackage com.santi020k.lumen\n\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\n\ndata class LumenColorPalette(\n val canvas: Color,\n val surface: Color,\n val surfaceMuted: Color,\n val surfaceStrong: Color,\n val line: Color,\n val ink: Color,\n val inkSoft: Color,\n val inkMuted: Color,\n val brand: Color,\n val brandSolid: Color,\n val brandSoft: Color,\n val onBrand: Color,\n val accent: Color,\n val success: Color,\n val warning: Color,\n val danger: Color,\n val onDanger: Color\n)\n\nobject LumenColors {\n val Light = LumenColorPalette(\n canvas = Color(0xFFFFFFFF),\n surface = Color(0xFFFFFFFF),\n surfaceMuted = Color(0xFFF3F4F6),\n surfaceStrong = Color(0xFFE5E7EB),\n line = Color(0xFFD7DAE0),\n ink = Color(0xFF0F1729),\n inkSoft = Color(0xFF454F5F),\n inkMuted = Color(0xFF6C7889),\n brand = Color(0xFF2463EB),\n brandSolid = Color(0xFF1452D7),\n brandSoft = Color(0xFFF0F5FF),\n onBrand = Color(0xFFFFFFFF),\n accent = Color(0xFF16A286),\n success = Color(0xFF1B9D4A),\n warning = Color(0xFFF59F0A),\n danger = Color(0xFFEF4343),\n onDanger = Color(0xFF000000)\n )\n\n val Dark = LumenColorPalette(\n canvas = Color(0xFF101219),\n surface = Color(0xFF171A21),\n surfaceMuted = Color(0xFF20242C),\n surfaceStrong = Color(0xFF303540),\n line = Color(0xFF393F4C),\n ink = Color(0xFFF3F5F7),\n inkSoft = Color(0xFFBCC3CD),\n inkMuted = Color(0xFF949CA8),\n brand = Color(0xFF29B4F5),\n brandSolid = Color(0xFF087AAF),\n brandSoft = Color(0xFF07354B),\n onBrand = Color(0xFFFFFFFF),\n accent = Color(0xFF1AC19A),\n success = Color(0xFF24C25E),\n warning = Color(0xFFF6A823),\n danger = Color(0xFFF15B5B),\n onDanger = Color(0xFF101219)\n )\n}\n\nobject LumenSpacing {\n val Zero = 0f.dp\n val Xs = 4f.dp\n val Sm = 8f.dp\n val Md = 12f.dp\n val Lg = 16f.dp\n val Xl = 24f.dp\n val Size2xl = 32f.dp\n val Size3xl = 48f.dp\n}\n\nobject LumenRadius {\n val Sm = 6f.dp\n val Md = 10f.dp\n val Lg = 12f.dp\n val Full = 9999f.dp\n}\n\nobject LumenTypography {\n val XsSize = 12f.sp\n val SmSize = 14f.sp\n val MdSize = 16f.sp\n val LgSize = 18f.sp\n val XlSize = 20f.sp\n val Size2xlSize = 24f.sp\n const val RegularWeight = 400\n const val MediumWeight = 500\n const val SemiboldWeight = 600\n const val BoldWeight = 700\n}\n\ndata class LumenCubicBezier(\n val x1: Float,\n val y1: Float,\n val x2: Float,\n val y2: Float\n)\n\nobject LumenMotion {\n const val FastDurationMillis = 120\n const val StandardDurationMillis = 160\n const val SlowDurationMillis = 300\n val StandardEasing = LumenCubicBezier(0.32f, 0.72f, 0f, 1f)\n val EmphasizedEasing = LumenCubicBezier(0.22f, 1f, 0.36f, 1f)\n}\n\nobject LumenElevation {\n val Resting = 1f.dp\n val Raised = 3f.dp\n val Overlay = 6f.dp\n}\n"
37090
+ "packages/compose/src/main/kotlin/com/santi020k/lumen/Tokens.generated.kt": "// This file is generated by scripts/generate-platform-tokens.mjs.\npackage com.santi020k.lumen\n\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\n\n@Immutable\ndata class LumenColorPalette(\n val canvas: Color,\n val surface: Color,\n val surfaceMuted: Color,\n val surfaceStrong: Color,\n val line: Color,\n val ink: Color,\n val inkSoft: Color,\n val inkMuted: Color,\n val brand: Color,\n val brandSolid: Color,\n val brandSoft: Color,\n val onBrand: Color,\n val accent: Color,\n val success: Color,\n val warning: Color,\n val danger: Color,\n val onDanger: Color\n)\n\nobject LumenColors {\n val Light = LumenColorPalette(\n canvas = Color(0xFFFFFFFF),\n surface = Color(0xFFFFFFFF),\n surfaceMuted = Color(0xFFF3F4F6),\n surfaceStrong = Color(0xFFE5E7EB),\n line = Color(0xFFD7DAE0),\n ink = Color(0xFF0F1729),\n inkSoft = Color(0xFF454F5F),\n inkMuted = Color(0xFF6C7889),\n brand = Color(0xFF2463EB),\n brandSolid = Color(0xFF1452D7),\n brandSoft = Color(0xFFF0F5FF),\n onBrand = Color(0xFFFFFFFF),\n accent = Color(0xFF16A286),\n success = Color(0xFF1B9D4A),\n warning = Color(0xFFF59F0A),\n danger = Color(0xFFEF4343),\n onDanger = Color(0xFF000000)\n )\n\n val Dark = LumenColorPalette(\n canvas = Color(0xFF101219),\n surface = Color(0xFF171A21),\n surfaceMuted = Color(0xFF20242C),\n surfaceStrong = Color(0xFF303540),\n line = Color(0xFF393F4C),\n ink = Color(0xFFF3F5F7),\n inkSoft = Color(0xFFBCC3CD),\n inkMuted = Color(0xFF949CA8),\n brand = Color(0xFF29B4F5),\n brandSolid = Color(0xFF087AAF),\n brandSoft = Color(0xFF07354B),\n onBrand = Color(0xFFFFFFFF),\n accent = Color(0xFF1AC19A),\n success = Color(0xFF24C25E),\n warning = Color(0xFFF6A823),\n danger = Color(0xFFF15B5B),\n onDanger = Color(0xFF101219)\n )\n}\n\nobject LumenSpacing {\n val Zero = 0f.dp\n val Xs = 4f.dp\n val Sm = 8f.dp\n val Md = 12f.dp\n val Lg = 16f.dp\n val Xl = 24f.dp\n val Size2xl = 32f.dp\n val Size3xl = 48f.dp\n}\n\nobject LumenRadius {\n val Sm = 6f.dp\n val Md = 10f.dp\n val Lg = 12f.dp\n val Full = 9999f.dp\n}\n\nobject LumenTypography {\n val XsSize = 12f.sp\n val SmSize = 14f.sp\n val MdSize = 16f.sp\n val LgSize = 18f.sp\n val XlSize = 20f.sp\n val Size2xlSize = 24f.sp\n const val RegularWeight = 400\n const val MediumWeight = 500\n const val SemiboldWeight = 600\n const val BoldWeight = 700\n}\n\n@Immutable\ndata class LumenCubicBezier(\n val x1: Float,\n val y1: Float,\n val x2: Float,\n val y2: Float\n)\n\nobject LumenMotion {\n const val FastDurationMillis = 120\n const val StandardDurationMillis = 160\n const val SlowDurationMillis = 300\n val StandardEasing = LumenCubicBezier(0.32f, 0.72f, 0f, 1f)\n val EmphasizedEasing = LumenCubicBezier(0.22f, 1f, 0.36f, 1f)\n}\n\nobject LumenElevation {\n val Resting = 1f.dp\n val Raised = 3f.dp\n val Overlay = 6f.dp\n}\n"
36815
37091
  },
36816
37092
  "react-native": {
36817
37093
  "packages/react-native/src/additional-components.tsx": "import {\n type ReactElement,\n type ReactNode,\n type Ref\n} from 'react'\nimport {\n type HostInstance,\n Pressable,\n type PressableProps,\n Text,\n TextInput,\n type TextInputInstance,\n type TextInputProps,\n type TextStyle,\n View,\n type ViewProps\n} from 'react-native'\n\nimport { resolveLumenButtonOpacity } from './recipes.js'\nimport { type LumenAlertVariant, resolveLumenAlertColors } from './shared-recipes.js'\nimport { useLumenTheme } from './theme-context.js'\n\nconst resolveOptional = <Value,>(value: Value | undefined, fallback: Value): Value => value ?? fallback\n\nconst resolveTextareaBorderColor = (\n errorMessage: string | undefined,\n dangerColor: string,\n lineColor: string\n): string => errorMessage ? dangerColor : lineColor\n\nconst resolveTextareaOpacity = (editable: boolean): number => editable ? 1 : 0.52\n\nconst LumenTextareaMessage = ({\n color,\n fontSize,\n message\n}: {\n color: string\n fontSize: number\n message: string | undefined\n}): ReactElement | null => message ? <Text style={{ color, fontSize }}>{message}</Text> : null\n\nexport interface LumenToastProps extends ViewProps {\n action?: ReactNode\n description?: string\n dismissLabel?: string\n onDismiss?: () => void\n ref?: Ref<HostInstance>\n title: string\n variant?: LumenAlertVariant\n}\n\nexport const LumenToast = ({\n action,\n description,\n dismissLabel = 'Dismiss',\n onDismiss,\n ref,\n style,\n title,\n variant = 'default',\n ...props\n}: LumenToastProps): ReactElement => {\n const theme = useLumenTheme()\n const colors = resolveLumenAlertColors(theme.colors, variant)\n\n return (\n <View\n ref={ref}\n {...props}\n accessibilityRole=\"alert\"\n style={[\n {\n alignItems: 'center',\n backgroundColor: colors.backgroundColor,\n borderColor: colors.borderColor,\n borderRadius: theme.radii.md,\n borderWidth: 1,\n flexDirection: 'row',\n gap: theme.spacing.md,\n paddingHorizontal: theme.spacing.lg,\n paddingVertical: theme.spacing.md\n },\n style\n ]}\n >\n <View style={{ flex: 1, gap: theme.spacing.xs }}>\n <Text\n style={{\n color: colors.color,\n fontSize: theme.fontSizes.sm,\n fontWeight: String(theme.fontWeights.semibold) as TextStyle['fontWeight']\n }}\n >\n {title}\n </Text>\n {description ?\n <Text style={{ color: theme.colors.inkSoft, fontSize: theme.fontSizes.xs }}>{description}</Text> :\n null}\n </View>\n {action}\n {onDismiss ?\n (\n <Pressable\n accessibilityLabel={dismissLabel}\n accessibilityRole=\"button\"\n onPress={onDismiss}\n style={({ pressed }) => ({\n opacity: resolveLumenButtonOpacity(false, pressed),\n padding: theme.spacing.sm\n })}\n >\n <Text style={{ color: colors.color, fontSize: theme.fontSizes.xs }}>×</Text>\n </Pressable>\n ) :\n null}\n </View>\n )\n}\n\nexport interface LumenTextareaProps extends Omit<\n TextInputProps,\n 'multiline' | 'onChangeText' | 'value'\n> {\n description?: string\n errorMessage?: string\n label: string\n onChangeText: (value: string) => void\n ref?: Ref<TextInputInstance>\n value: string\n}\n\nexport const LumenTextarea = ({\n description,\n editable = true,\n errorMessage,\n label,\n numberOfLines = 4,\n onChangeText,\n placeholder,\n placeholderTextColor,\n ref,\n style,\n value,\n ...props\n}: LumenTextareaProps): ReactElement => {\n const theme = useLumenTheme()\n\n const supportingColor = resolveTextareaBorderColor(\n errorMessage,\n theme.colors.danger,\n theme.colors.inkMuted\n )\n\n const supportingMessage = resolveOptional(errorMessage, description)\n\n return (\n <View style={{ gap: theme.spacing.xs }}>\n <Text\n style={{\n color: theme.colors.ink,\n fontSize: theme.fontSizes.sm,\n fontWeight: String(theme.fontWeights.semibold) as TextStyle['fontWeight']\n }}\n >\n {label}\n </Text>\n <TextInput\n ref={ref}\n {...props}\n accessibilityHint={resolveOptional(errorMessage, description)}\n accessibilityLabel={resolveOptional(props.accessibilityLabel, label)}\n editable={editable}\n multiline\n numberOfLines={numberOfLines}\n onChangeText={onChangeText}\n placeholder={placeholder}\n placeholderTextColor={resolveOptional(placeholderTextColor, theme.colors.inkMuted)}\n style={[\n {\n backgroundColor: theme.colors.surface,\n borderColor: resolveTextareaBorderColor(\n errorMessage,\n theme.colors.danger,\n theme.colors.line\n ),\n borderRadius: theme.radii.sm,\n borderWidth: 1,\n color: theme.colors.ink,\n fontSize: theme.fontSizes.sm,\n minHeight: 112,\n opacity: resolveTextareaOpacity(editable),\n padding: theme.spacing.md,\n textAlignVertical: 'top'\n },\n style\n ]}\n value={value}\n />\n <LumenTextareaMessage\n color={supportingColor}\n fontSize={theme.fontSizes.xs}\n message={supportingMessage}\n />\n </View>\n )\n}\n\nexport interface LumenChipProps extends Omit<PressableProps, 'children'> {\n label: string\n onRemove?: () => void\n ref?: Ref<HostInstance>\n removeLabel?: string\n selected?: boolean\n}\n\nexport const LumenChip = ({\n disabled = false,\n label,\n onPress,\n onRemove,\n ref,\n removeLabel = `Remove ${label}`,\n selected = false,\n style,\n ...props\n}: LumenChipProps): ReactElement => {\n const theme = useLumenTheme()\n\n const content = (\n <>\n <Text\n style={{\n color: selected ? theme.colors.brand : theme.colors.inkSoft,\n fontSize: theme.fontSizes.xs,\n fontWeight: String(theme.fontWeights.semibold) as TextStyle['fontWeight']\n }}\n >\n {label}\n </Text>\n {onRemove ?\n (\n <Pressable\n accessibilityLabel={removeLabel}\n accessibilityRole=\"button\"\n disabled={disabled}\n hitSlop={6}\n onPress={event => {\n event.stopPropagation()\n\n onRemove()\n }}\n >\n <Text style={{ color: theme.colors.inkMuted, fontSize: theme.fontSizes.xs }}>×</Text>\n </Pressable>\n ) :\n null}\n </>\n )\n\n const baseStyle = {\n alignItems: 'center' as const,\n alignSelf: 'flex-start' as const,\n backgroundColor: selected ? theme.colors.brandSoft : theme.colors.surfaceMuted,\n borderColor: selected ? theme.colors.brand : theme.colors.line,\n borderRadius: theme.radii.full,\n borderWidth: 1,\n flexDirection: 'row' as const,\n gap: theme.spacing.xs,\n minHeight: 32,\n opacity: disabled ? 0.52 : 1,\n paddingHorizontal: theme.spacing.md\n }\n\n if (!onPress) {\n return <View ref={ref} {...props} style={[baseStyle, style as ViewProps['style']]}>{content}</View>\n }\n\n return (\n <Pressable\n ref={ref}\n {...props}\n accessibilityRole=\"button\"\n accessibilityState={{ ...props.accessibilityState, disabled, selected }}\n disabled={disabled}\n onPress={onPress}\n style={state => [\n baseStyle,\n { opacity: resolveLumenButtonOpacity(disabled, state.pressed) },\n typeof style === 'function' ? style(state) : style\n ]}\n >\n {content}\n </Pressable>\n )\n}\n\nexport interface LumenFieldGroupProps extends ViewProps {\n children: ReactNode\n description?: string\n errorMessage?: string\n label: string\n ref?: Ref<HostInstance>\n required?: boolean\n}\n\nexport const LumenFieldGroup = ({\n children,\n description,\n errorMessage,\n label,\n ref,\n required = false,\n style,\n ...props\n}: LumenFieldGroupProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <View ref={ref} {...props} style={[{ gap: theme.spacing.sm }, style]}>\n <Text\n style={{\n color: theme.colors.ink,\n fontSize: theme.fontSizes.sm,\n fontWeight: String(theme.fontWeights.semibold) as TextStyle['fontWeight']\n }}\n >\n {label}\n {required ? ' *' : ''}\n </Text>\n {description ?\n <Text style={{ color: theme.colors.inkMuted, fontSize: theme.fontSizes.xs }}>{description}</Text> :\n null}\n {children}\n {errorMessage ?\n (\n <Text accessibilityRole=\"alert\" style={{ color: theme.colors.danger, fontSize: theme.fontSizes.xs }}>\n {errorMessage}\n </Text>\n ) :\n null}\n </View>\n )\n}\n\nexport type LumenButtonGroupOrientation = 'horizontal' | 'vertical'\n\nexport interface LumenButtonGroupProps extends ViewProps {\n children: ReactNode\n orientation?: LumenButtonGroupOrientation\n ref?: Ref<HostInstance>\n}\n\nexport const LumenButtonGroup = ({\n children,\n orientation = 'horizontal',\n ref,\n style,\n ...props\n}: LumenButtonGroupProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <View\n ref={ref}\n {...props}\n style={[\n {\n alignItems: orientation === 'horizontal' ? 'center' : 'stretch',\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\n gap: theme.spacing.sm\n },\n style\n ]}\n >\n {children}\n </View>\n )\n}\n",
@@ -36822,7 +37098,10 @@
36822
37098
  "packages/react-native/src/form-recipes.test.ts": "import { describe, expect, test } from 'vitest'\n\nimport { resolveLumenSearchFieldState } from './form-recipes.js'\n\ndescribe('Lumen React Native form component recipes', () => {\n test('shows the clear action only for editable non-empty queries', () => {\n expect(resolveLumenSearchFieldState('lumen', true)).toEqual({\n opacity: 1,\n showClearAction: true\n })\n expect(resolveLumenSearchFieldState('', true).showClearAction).toBe(false)\n expect(resolveLumenSearchFieldState('lumen', false)).toEqual({\n opacity: 0.52,\n showClearAction: false\n })\n })\n})\n",
36823
37099
  "packages/react-native/src/form-recipes.ts": "export interface LumenSearchFieldState {\n opacity: number\n showClearAction: boolean\n}\n\nexport const resolveLumenSearchFieldState = (\n value: string,\n editable: boolean\n): LumenSearchFieldState => ({\n opacity: editable ? 1 : 0.52,\n showClearAction: editable && value.length > 0\n})\n",
36824
37100
  "packages/react-native/src/index.test.ts": "import { describe, expect, test } from 'vitest'\n\nimport {\n createLumenTheme,\n lumenDarkTheme,\n lumenLightTheme\n} from './theme.js'\nimport { lumenColorTokens } from './tokens.generated.js'\n\ndescribe('Lumen React Native foundations', () => {\n test('provides matching semantic roles for light and dark themes', () => {\n expect(Object.keys(lumenColorTokens.light)).toEqual(\n Object.keys(lumenColorTokens.dark)\n )\n expect(lumenLightTheme.colors.brand).toBe('#2463EB')\n expect(lumenDarkTheme.colors.brand).toBe('#29B4F5')\n })\n\n test('creates a complete theme for the requested scheme', () => {\n const theme = createLumenTheme('dark')\n\n expect(theme.scheme).toBe('dark')\n expect(theme.spacing.lg).toBe(16)\n expect(theme.radii.md).toBe(10)\n expect(theme.durations.standard).toBe(160)\n })\n})\n",
36825
- "packages/react-native/src/index.ts": "export {\n LumenButtonGroup,\n type LumenButtonGroupOrientation,\n type LumenButtonGroupProps,\n LumenChip,\n type LumenChipProps,\n LumenFieldGroup,\n type LumenFieldGroupProps,\n LumenTextarea,\n type LumenTextareaProps,\n LumenToast,\n type LumenToastProps\n} from './additional-components.js'\nexport {\n LumenDisclosure,\n type LumenDisclosureProps,\n LumenSkeleton,\n type LumenSkeletonProps,\n type LumenSkeletonShape\n} from './content-components.js'\nexport {\n LumenSearchField,\n type LumenSearchFieldProps,\n LumenSettingsRow,\n type LumenSettingsRowProps,\n LumenToggle,\n type LumenToggleProps\n} from './form-components.js'\nexport {\n LumenBadge,\n type LumenBadgeProps,\n LumenButton,\n type LumenButtonIntent,\n type LumenButtonProps,\n type LumenControlSize,\n LumenDivider,\n type LumenDividerProps,\n LumenIcon,\n LumenIconButton,\n type LumenIconButtonProps,\n type LumenIconGraphic,\n type LumenIconGraphicProps,\n type LumenIconProps,\n type LumenIconSize,\n LumenSpinner,\n type LumenSpinnerProps,\n LumenSurface,\n type LumenSurfacePadding,\n type LumenSurfaceProps,\n type LumenSurfaceRadius,\n type LumenSurfaceTone,\n LumenText,\n LumenTextField,\n type LumenTextFieldProps,\n type LumenTextProps,\n type LumenTextTone,\n type LumenTextVariant\n} from './primitives.js'\nexport {\n LumenProvider,\n type LumenProviderProps\n} from './provider.js'\nexport {\n LumenCheckbox,\n type LumenCheckboxProps,\n LumenRadioGroup,\n type LumenRadioGroupProps,\n LumenSegmentedControl,\n type LumenSegmentedControlProps,\n type LumenSelectionOption } from './selection-components.js'\nexport {\n LumenAlert,\n LumenAlertDescription,\n type LumenAlertDescriptionProps,\n type LumenAlertProps,\n LumenAlertTitle,\n type LumenAlertTitleProps,\n LumenAvatar,\n type LumenAvatarProps,\n LumenCard,\n type LumenCardProps,\n LumenProgress,\n type LumenProgressProps\n} from './shared-components.js'\nexport {\n type LumenAlertVariant,\n type LumenAvatarSize,\n type LumenCardVariant\n} from './shared-recipes.js'\nexport {\n LumenBanner,\n type LumenBannerProps,\n LumenEmptyState,\n type LumenEmptyStateProps,\n LumenListRow,\n type LumenListRowProps,\n LumenSectionHeader,\n type LumenSectionHeaderProps,\n LumenStat,\n type LumenStatProps,\n LumenStatusBar,\n type LumenStatusBarProps\n} from './structured-components.js'\nexport {\n type LumenBannerVariant,\n type LumenMetricTone\n} from './structured-recipes.js'\nexport {\n createLumenTheme,\n lumenDarkTheme,\n lumenLightTheme,\n type LumenTheme\n} from './theme.js'\nexport { useLumenTheme } from './theme-context.js'\nexport {\n type LumenColorScheme,\n lumenColorTokens,\n lumenDurations,\n lumenEasings,\n lumenElevation,\n lumenFontFamilies,\n lumenFontSizes,\n lumenFontWeights,\n lumenRadii,\n type LumenSemanticColor,\n lumenSpacing } from './tokens.generated.js'\n",
37101
+ "packages/react-native/src/index.ts": "export {\n LumenButtonGroup,\n type LumenButtonGroupOrientation,\n type LumenButtonGroupProps,\n LumenChip,\n type LumenChipProps,\n LumenFieldGroup,\n type LumenFieldGroupProps,\n LumenTextarea,\n type LumenTextareaProps,\n LumenToast,\n type LumenToastProps\n} from './additional-components.js'\nexport {\n LumenDisclosure,\n type LumenDisclosureProps,\n LumenSkeleton,\n type LumenSkeletonProps,\n type LumenSkeletonShape\n} from './content-components.js'\nexport {\n LumenSearchField,\n type LumenSearchFieldProps,\n LumenSettingsRow,\n type LumenSettingsRowProps,\n LumenToggle,\n type LumenToggleProps\n} from './form-components.js'\nexport {\n LumenRefreshControl,\n type LumenRefreshControlProps\n} from './platform-components.js'\nexport { type LumenRefreshIndicatorTone } from './platform-recipes.js'\nexport {\n LumenBadge,\n type LumenBadgeProps,\n LumenButton,\n type LumenButtonIntent,\n type LumenButtonProps,\n type LumenControlSize,\n LumenDivider,\n type LumenDividerProps,\n LumenIcon,\n LumenIconButton,\n type LumenIconButtonProps,\n type LumenIconGraphic,\n type LumenIconGraphicProps,\n type LumenIconProps,\n type LumenIconSize,\n LumenSpinner,\n type LumenSpinnerProps,\n LumenSurface,\n type LumenSurfacePadding,\n type LumenSurfaceProps,\n type LumenSurfaceRadius,\n type LumenSurfaceTone,\n LumenText,\n LumenTextField,\n type LumenTextFieldProps,\n type LumenTextProps,\n type LumenTextTone,\n type LumenTextVariant\n} from './primitives.js'\nexport {\n LumenProvider,\n type LumenProviderProps\n} from './provider.js'\nexport {\n LumenCheckbox,\n type LumenCheckboxProps,\n LumenRadioGroup,\n type LumenRadioGroupProps,\n LumenSegmentedControl,\n type LumenSegmentedControlProps,\n type LumenSelectionOption } from './selection-components.js'\nexport {\n LumenAlert,\n LumenAlertDescription,\n type LumenAlertDescriptionProps,\n type LumenAlertProps,\n LumenAlertTitle,\n type LumenAlertTitleProps,\n LumenAvatar,\n type LumenAvatarProps,\n LumenCard,\n type LumenCardProps,\n LumenProgress,\n type LumenProgressProps\n} from './shared-components.js'\nexport {\n type LumenAlertVariant,\n type LumenAvatarSize,\n type LumenCardVariant\n} from './shared-recipes.js'\nexport {\n LumenBanner,\n type LumenBannerProps,\n LumenEmptyState,\n type LumenEmptyStateProps,\n LumenListRow,\n type LumenListRowProps,\n LumenSectionHeader,\n type LumenSectionHeaderProps,\n LumenStat,\n type LumenStatProps,\n LumenStatusBar,\n type LumenStatusBarProps\n} from './structured-components.js'\nexport {\n type LumenBannerVariant,\n type LumenMetricTone\n} from './structured-recipes.js'\nexport {\n createLumenTheme,\n lumenDarkTheme,\n lumenLightTheme,\n type LumenTheme\n} from './theme.js'\nexport { useLumenTheme } from './theme-context.js'\nexport {\n type LumenColorScheme,\n lumenColorTokens,\n lumenDurations,\n lumenEasings,\n lumenElevation,\n lumenFontFamilies,\n lumenFontSizes,\n lumenFontWeights,\n lumenRadii,\n type LumenSemanticColor,\n lumenSpacing } from './tokens.generated.js'\n",
37102
+ "packages/react-native/src/platform-components.tsx": "import { type ComponentRef, type ReactElement, type Ref } from 'react'\nimport {\n RefreshControl,\n type RefreshControlProps\n} from 'react-native'\n\nimport {\n type LumenRefreshIndicatorTone,\n resolveLumenRefreshColors\n} from './platform-recipes.js'\nimport { useLumenTheme } from './theme-context.js'\n\nexport interface LumenRefreshControlProps extends Omit<\n RefreshControlProps,\n 'accessibilityLabel' | 'colors' | 'onRefresh' | 'progressBackgroundColor' | 'tintColor' | 'titleColor'\n> {\n accessibilityLabel: string\n indicatorTone?: LumenRefreshIndicatorTone\n onRefresh: NonNullable<RefreshControlProps['onRefresh']>\n ref?: Ref<ComponentRef<typeof RefreshControl>>\n}\n\n/**\n * A token-aware native pull-to-refresh indicator for React Native scroll containers.\n */\nexport const LumenRefreshControl = ({\n indicatorTone = 'brand',\n ref,\n ...props\n}: LumenRefreshControlProps): ReactElement => {\n const theme = useLumenTheme()\n const colors = resolveLumenRefreshColors(theme.colors, indicatorTone)\n\n return (\n <RefreshControl\n ref={ref}\n {...props}\n colors={[colors.indicator]}\n progressBackgroundColor={colors.surface}\n tintColor={colors.indicator}\n titleColor={colors.title}\n />\n )\n}\n",
37103
+ "packages/react-native/src/platform-recipes.test.ts": "import { describe, expect, test } from 'vitest'\n\nimport { resolveLumenRefreshColors } from './platform-recipes.js'\nimport { lumenDarkTheme, lumenLightTheme } from './theme.js'\n\ndescribe('React Native platform recipes', () => {\n test('maps refresh indicators to semantic theme colors', () => {\n expect(resolveLumenRefreshColors(lumenLightTheme.colors, 'brand')).toEqual({\n indicator: lumenLightTheme.colors.brand,\n surface: lumenLightTheme.colors.surface,\n title: lumenLightTheme.colors.inkMuted\n })\n expect(resolveLumenRefreshColors(lumenDarkTheme.colors, 'accent').indicator).toBe(\n lumenDarkTheme.colors.accent\n )\n expect(resolveLumenRefreshColors(lumenDarkTheme.colors, 'neutral').indicator).toBe(\n lumenDarkTheme.colors.inkSoft\n )\n })\n})\n",
37104
+ "packages/react-native/src/platform-recipes.ts": "import type { LumenTheme } from './theme.js'\n\nexport type LumenRefreshIndicatorTone = 'accent' | 'brand' | 'neutral'\n\nexport interface LumenRefreshColors {\n indicator: string\n surface: string\n title: string\n}\n\nconst resolveIndicatorColor = (\n colors: LumenTheme['colors'],\n tone: LumenRefreshIndicatorTone\n): string => {\n if (tone === 'accent') return colors.accent\n\n if (tone === 'neutral') return colors.inkSoft\n\n return colors.brand\n}\n\nexport const resolveLumenRefreshColors = (\n colors: LumenTheme['colors'],\n tone: LumenRefreshIndicatorTone\n): LumenRefreshColors => ({\n indicator: resolveIndicatorColor(colors, tone),\n surface: colors.surface,\n title: colors.inkMuted\n})\n",
36826
37105
  "packages/react-native/src/primitives.tsx": "import {\n type ComponentType,\n type ReactElement,\n type ReactNode,\n type Ref\n} from 'react'\nimport {\n ActivityIndicator,\n type ActivityIndicatorProps,\n type ColorValue,\n type HostInstance,\n Pressable,\n type PressableProps,\n Text,\n TextInput,\n type TextInputInstance,\n type TextInputProps,\n type TextProps,\n type TextStyle,\n View,\n type ViewProps\n} from 'react-native'\n\nimport {\n resolveLumenButtonColors,\n resolveLumenButtonOpacity,\n resolveLumenButtonSize,\n resolveLumenControlMinHeight,\n resolveLumenIconButtonSize,\n resolveLumenIconSize,\n resolveLumenPressableStyle,\n resolveLumenSurfaceColor,\n resolveLumenTextColor,\n resolveLumenTextStyle\n} from './recipes.js'\nimport { useLumenTheme } from './theme-context.js'\n\nexport type LumenTextTone =\n | 'danger' |\n 'default' |\n 'muted' |\n 'soft' |\n 'success' |\n 'warning'\nexport type LumenTextVariant = 'body' | 'caption' | 'label' | 'title'\n\nexport interface LumenTextProps extends TextProps {\n ref?: Ref<HostInstance>\n tone?: LumenTextTone\n variant?: LumenTextVariant\n}\n\nexport const LumenText = ({\n ref,\n style,\n tone = 'default',\n variant = 'body',\n ...props\n}: LumenTextProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <Text\n ref={ref}\n {...props}\n style={[\n { color: resolveLumenTextColor(theme.colors, tone) },\n resolveLumenTextStyle(theme, variant),\n style\n ]}\n />\n )\n}\n\nexport type LumenSurfacePadding = 'lg' | 'md' | 'none' | 'sm'\nexport type LumenSurfaceRadius = 'lg' | 'md' | 'none' | 'sm'\nexport type LumenSurfaceTone = 'canvas' | 'muted' | 'strong' | 'surface'\n\nexport interface LumenSurfaceProps extends ViewProps {\n padding?: LumenSurfacePadding\n radius?: LumenSurfaceRadius\n ref?: Ref<HostInstance>\n tone?: LumenSurfaceTone\n}\n\nexport const LumenSurface = ({\n padding = 'md',\n radius = 'md',\n ref,\n style,\n tone = 'surface',\n ...props\n}: LumenSurfaceProps): ReactElement => {\n const theme = useLumenTheme()\n const backgroundColor = resolveLumenSurfaceColor(theme, tone)\n const paddingValue = padding === 'none' ? 0 : theme.spacing[padding]\n const radiusValue = radius === 'none' ? 0 : theme.radii[radius]\n\n return (\n <View\n ref={ref}\n {...props}\n style={[{ backgroundColor, borderRadius: radiusValue, padding: paddingValue }, style]}\n />\n )\n}\n\nexport type LumenButtonIntent = 'danger' | 'primary' | 'quiet' | 'secondary'\nexport type LumenControlSize = 'lg' | 'md' | 'sm'\nexport type LumenIconSize = 'lg' | 'md' | 'sm'\n\nexport interface LumenIconGraphicProps {\n color?: ColorValue\n size?: number\n strokeWidth?: number\n}\n\nexport type LumenIconGraphic = ComponentType<LumenIconGraphicProps>\n\nexport interface LumenIconProps extends Omit<ViewProps, 'children'> {\n color?: ColorValue\n decorative?: boolean\n icon: LumenIconGraphic\n label?: string\n ref?: Ref<HostInstance>\n size?: LumenIconSize\n strokeWidth?: number\n}\n\nexport const LumenIcon = ({\n color,\n decorative,\n icon: Graphic,\n label,\n ref,\n size = 'md',\n strokeWidth = 2,\n style,\n ...props\n}: LumenIconProps): ReactElement => {\n const theme = useLumenTheme()\n const dimension = resolveLumenIconSize(size)\n const isDecorative = decorative ?? !label\n\n return (\n <View\n ref={ref}\n {...props}\n accessible={!isDecorative}\n accessibilityElementsHidden={isDecorative}\n accessibilityLabel={isDecorative ? undefined : label}\n accessibilityRole={isDecorative ? undefined : 'image'}\n importantForAccessibility={isDecorative ? 'no' : 'yes'}\n style={[\n {\n alignItems: 'center',\n height: dimension,\n justifyContent: 'center',\n width: dimension\n },\n style\n ]}\n >\n <Graphic\n color={color ?? theme.colors.ink}\n size={dimension}\n strokeWidth={strokeWidth}\n />\n </View>\n )\n}\n\nexport interface LumenIconButtonProps extends Omit<PressableProps, 'children'> {\n icon: LumenIconGraphic\n intent?: LumenButtonIntent\n label: string\n ref?: Ref<HostInstance>\n size?: LumenControlSize\n strokeWidth?: number\n}\n\nexport const LumenIconButton = ({\n accessibilityState,\n disabled = false,\n icon,\n intent = 'quiet',\n label,\n ref,\n size = 'md',\n strokeWidth = 2,\n style,\n ...props\n}: LumenIconButtonProps): ReactElement => {\n const theme = useLumenTheme()\n const colors = resolveLumenButtonColors(theme.colors, intent)\n const metrics = resolveLumenIconButtonSize(size)\n\n return (\n <Pressable\n ref={ref}\n {...props}\n accessibilityLabel={label}\n accessibilityRole=\"button\"\n accessibilityState={{ ...accessibilityState, disabled }}\n disabled={disabled}\n style={state => [\n {\n alignItems: 'center',\n backgroundColor: colors.backgroundColor,\n borderColor: colors.borderColor,\n borderRadius: theme.radii.sm,\n borderWidth: 1,\n height: metrics.touchTarget,\n justifyContent: 'center',\n opacity: resolveLumenButtonOpacity(disabled, state.pressed),\n width: metrics.touchTarget\n },\n resolveLumenPressableStyle(style, state)\n ]}\n >\n <LumenIcon\n color={colors.color}\n decorative\n icon={icon}\n size={metrics.iconSize}\n strokeWidth={strokeWidth}\n />\n </Pressable>\n )\n}\n\nexport interface LumenButtonProps extends Omit<PressableProps, 'children'> {\n children: ReactNode\n intent?: LumenButtonIntent\n loading?: boolean\n ref?: Ref<HostInstance>\n size?: LumenControlSize\n}\n\nexport const LumenButton = ({\n accessibilityState,\n children,\n disabled = false,\n intent = 'primary',\n loading = false,\n ref,\n size = 'md',\n style,\n ...props\n}: LumenButtonProps): ReactElement => {\n const theme = useLumenTheme()\n const colors = resolveLumenButtonColors(theme.colors, intent)\n const dimensions = resolveLumenButtonSize(theme, size)\n const isDisabled = disabled || loading\n\n return (\n <Pressable\n ref={ref}\n accessibilityRole=\"button\"\n {...props}\n accessibilityState={{\n ...accessibilityState,\n busy: loading,\n disabled: isDisabled\n }}\n disabled={isDisabled}\n style={state => [\n {\n alignItems: 'center',\n backgroundColor: colors.backgroundColor,\n borderColor: colors.borderColor,\n borderRadius: theme.radii.sm,\n borderWidth: 1,\n flexDirection: 'row',\n gap: theme.spacing.sm,\n justifyContent: 'center',\n opacity: resolveLumenButtonOpacity(isDisabled, state.pressed)\n },\n dimensions.container,\n resolveLumenPressableStyle(style, state)\n ]}\n >\n {loading ? <ActivityIndicator color={colors.color} size=\"small\" /> : null}\n <Text\n style={[\n {\n color: colors.color,\n fontWeight: String(theme.fontWeights.semibold) as TextStyle['fontWeight']\n },\n dimensions.label\n ]}\n >\n {children}\n </Text>\n </Pressable>\n )\n}\n\nexport interface LumenTextFieldProps extends TextInputProps {\n error?: boolean\n ref?: Ref<TextInputInstance>\n size?: LumenControlSize\n}\n\nexport const LumenTextField = ({\n editable = true,\n error = false,\n placeholderTextColor,\n ref,\n size = 'md',\n style,\n ...props\n}: LumenTextFieldProps): ReactElement => {\n const theme = useLumenTheme()\n const minHeight = resolveLumenControlMinHeight(size)\n const fontSize = size === 'lg' ? theme.fontSizes.md : theme.fontSizes.sm\n\n return (\n <TextInput\n ref={ref}\n {...props}\n aria-invalid={error || undefined}\n editable={editable}\n placeholderTextColor={placeholderTextColor ?? theme.colors.inkMuted}\n style={[\n {\n backgroundColor: theme.colors.surface,\n borderColor: error ? theme.colors.danger : theme.colors.line,\n borderRadius: theme.radii.sm,\n borderWidth: 1,\n color: theme.colors.ink,\n fontSize,\n minHeight,\n opacity: editable ? 1 : 0.52,\n paddingHorizontal: theme.spacing.md,\n paddingVertical: theme.spacing.sm\n },\n style\n ]}\n />\n )\n}\n\nexport interface LumenBadgeProps extends ViewProps {\n children: ReactNode\n ref?: Ref<HostInstance>\n tone?: 'accent' | 'danger' | 'neutral' | 'success' | 'warning'\n}\n\nexport const LumenBadge = ({\n children,\n ref,\n style,\n tone = 'neutral',\n ...props\n}: LumenBadgeProps): ReactElement => {\n const theme = useLumenTheme()\n const color = tone === 'neutral' ? theme.colors.inkSoft : theme.colors[tone]\n const backgroundColor = tone === 'neutral' ? theme.colors.surfaceMuted : `${color}1F`\n\n return (\n <View\n ref={ref}\n {...props}\n style={[\n {\n alignSelf: 'flex-start',\n backgroundColor,\n borderRadius: theme.radii.full,\n paddingHorizontal: theme.spacing.sm,\n paddingVertical: theme.spacing.xs\n },\n style\n ]}\n >\n <Text style={{ color, fontSize: theme.fontSizes.xs, fontWeight: '600' }}>\n {children}\n </Text>\n </View>\n )\n}\n\nexport interface LumenDividerProps extends ViewProps {\n ref?: Ref<HostInstance>\n}\n\nexport const LumenDivider = ({\n ref,\n style,\n ...props\n}: LumenDividerProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <View\n ref={ref}\n accessibilityElementsHidden\n importantForAccessibility=\"no\"\n {...props}\n style={[{ backgroundColor: theme.colors.line, height: 1 }, style]}\n />\n )\n}\n\nexport interface LumenSpinnerProps extends ActivityIndicatorProps {\n ref?: Ref<HostInstance>\n}\n\nexport const LumenSpinner = ({\n color,\n ref,\n ...props\n}: LumenSpinnerProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <ActivityIndicator\n ref={ref}\n accessibilityRole=\"progressbar\"\n color={color ?? theme.colors.brand}\n {...props}\n />\n )\n}\n",
36827
37106
  "packages/react-native/src/provider.tsx": "import {\n type ReactNode,\n useMemo } from 'react'\nimport { useColorScheme } from 'react-native'\n\nimport {\n createLumenTheme,\n type LumenTheme\n} from './theme.js'\nimport { LumenThemeContext } from './theme-context.js'\nimport type { LumenColorScheme } from './tokens.generated.js'\n\nexport interface LumenProviderProps {\n children: ReactNode\n scheme?: LumenColorScheme | 'system'\n theme?: LumenTheme\n}\n\nexport const LumenProvider = ({\n children,\n scheme = 'system',\n theme\n}: LumenProviderProps) => {\n const systemScheme = useColorScheme()\n const resolvedScheme = scheme === 'system' ? systemScheme ?? 'light' : scheme\n\n const value = useMemo(\n () => theme ?? createLumenTheme(resolvedScheme),\n [resolvedScheme, theme]\n )\n\n return (\n <LumenThemeContext value={value}>\n {children}\n </LumenThemeContext>\n )\n}\n",
36828
37107
  "packages/react-native/src/recipes.test.ts": "import { describe, expect, test } from 'vitest'\n\nimport {\n resolveLumenButtonColors,\n resolveLumenButtonOpacity,\n resolveLumenButtonSize,\n resolveLumenControlMinHeight,\n resolveLumenIconButtonSize,\n resolveLumenIconSize,\n resolveLumenSurfaceColor,\n resolveLumenTextColor\n} from './recipes.js'\nimport {\n lumenDarkTheme,\n lumenLightTheme\n} from './theme.js'\n\ndescribe('Lumen React Native primitive recipes', () => {\n test('maps shared button intents to semantic theme roles', () => {\n expect(resolveLumenButtonColors(lumenLightTheme.colors, 'primary')).toEqual({\n backgroundColor: lumenLightTheme.colors.brandSolid,\n borderColor: lumenLightTheme.colors.brandSolid,\n color: lumenLightTheme.colors.onBrand\n })\n expect(resolveLumenButtonColors(lumenDarkTheme.colors, 'danger').color).toBe(\n lumenDarkTheme.colors.onDanger\n )\n })\n\n test('keeps touch targets and disabled feedback predictable', () => {\n expect(resolveLumenButtonSize(lumenLightTheme, 'md').container.minHeight).toBe(44)\n expect(resolveLumenControlMinHeight('lg')).toBe(52)\n expect(resolveLumenButtonOpacity(true, false)).toBe(0.52)\n expect(resolveLumenButtonOpacity(false, true)).toBe(0.84)\n expect(resolveLumenIconSize('sm')).toBe(16)\n expect(resolveLumenIconSize('md')).toBe(20)\n expect(resolveLumenIconSize('lg')).toBe(24)\n expect(resolveLumenIconButtonSize('sm')).toEqual({ iconSize: 'sm', touchTarget: 44 })\n expect(resolveLumenIconButtonSize('lg')).toEqual({ iconSize: 'lg', touchTarget: 52 })\n })\n\n test('maps content and surfaces without platform-specific color literals', () => {\n expect(resolveLumenTextColor(lumenDarkTheme.colors, 'muted')).toBe(\n lumenDarkTheme.colors.inkMuted\n )\n expect(resolveLumenSurfaceColor(lumenDarkTheme, 'strong')).toBe(\n lumenDarkTheme.colors.surfaceStrong\n )\n })\n})\n",
@@ -36842,16 +37121,17 @@
36842
37121
  },
36843
37122
  "swiftui": {
36844
37123
  "packages/swift/Sources/LumenUI/AdditionalComponents.swift": "import SwiftUI\n\npublic enum LumenButtonGroupOrientation: Sendable {\n case horizontal\n case vertical\n}\n\npublic struct LumenButtonGroup<Content: View>: View {\n private let content: Content\n private let orientation: LumenButtonGroupOrientation\n\n public init(\n orientation: LumenButtonGroupOrientation = .horizontal,\n @ViewBuilder content: () -> Content\n ) {\n self.orientation = orientation\n self.content = content()\n }\n\n public var body: some View {\n Group {\n if orientation == .horizontal {\n HStack(spacing: LumenSpacing.sm) { content }\n } else {\n VStack(alignment: .leading, spacing: LumenSpacing.sm) { content }\n }\n }\n .accessibilityElement(children: .contain)\n }\n}\n\npublic struct LumenFieldGroup<Content: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let content: Content\n private let description: LocalizedStringKey?\n private let errorMessage: LocalizedStringKey?\n private let required: Bool\n private let title: LocalizedStringKey\n\n public init(\n _ title: LocalizedStringKey,\n description: LocalizedStringKey? = nil,\n errorMessage: LocalizedStringKey? = nil,\n required: Bool = false,\n @ViewBuilder content: () -> Content\n ) {\n self.title = title\n self.description = description\n self.errorMessage = errorMessage\n self.required = required\n self.content = content()\n }\n\n public var body: some View {\n VStack(alignment: .leading, spacing: LumenSpacing.sm) {\n HStack(spacing: LumenSpacing.xs) {\n Text(title)\n .font(.callout.weight(.semibold))\n .foregroundStyle(theme.colors.ink)\n if required {\n Text(\"*\").foregroundStyle(theme.colors.danger).accessibilityLabel(\"Required\")\n }\n }\n if let description {\n Text(description)\n .font(.caption)\n .foregroundStyle(theme.colors.inkMuted)\n }\n content\n if let errorMessage {\n Text(errorMessage)\n .font(.caption)\n .foregroundStyle(theme.colors.danger)\n }\n }\n .accessibilityElement(children: .contain)\n }\n}\n\npublic struct LumenTextarea: View {\n @Binding private var text: String\n @Environment(\\.isEnabled) private var isEnabled\n @Environment(\\.lumenTheme) private var theme\n\n private let description: LocalizedStringKey?\n private let errorMessage: LocalizedStringKey?\n private let minHeight: CGFloat\n private let title: LocalizedStringKey\n\n public init(\n _ title: LocalizedStringKey,\n text: Binding<String>,\n description: LocalizedStringKey? = nil,\n errorMessage: LocalizedStringKey? = nil,\n minHeight: CGFloat = 112\n ) {\n self.title = title\n _text = text\n self.description = description\n self.errorMessage = errorMessage\n self.minHeight = minHeight\n }\n\n public var body: some View {\n VStack(alignment: .leading, spacing: LumenSpacing.xs) {\n Text(title)\n .font(.callout.weight(.semibold))\n .foregroundStyle(theme.colors.ink)\n TextEditor(text: $text)\n .scrollContentBackground(.hidden)\n .font(.callout)\n .foregroundStyle(theme.colors.ink)\n .frame(minHeight: minHeight)\n .padding(LumenSpacing.sm)\n .background(theme.colors.surface)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous)\n .stroke(errorMessage == nil ? theme.colors.line : theme.colors.danger, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous))\n .opacity(isEnabled ? 1 : 0.52)\n .accessibilityLabel(title)\n .accessibilityValue(errorMessage ?? LocalizedStringKey(text))\n if let errorMessage {\n Text(errorMessage).font(.caption).foregroundStyle(theme.colors.danger)\n } else if let description {\n Text(description).font(.caption).foregroundStyle(theme.colors.inkMuted)\n }\n }\n }\n}\n\npublic struct LumenChip: View {\n @Environment(\\.isEnabled) private var isEnabled\n @Environment(\\.lumenTheme) private var theme\n\n private let label: LocalizedStringKey\n private let onPress: (() -> Void)?\n private let onRemove: (() -> Void)?\n private let removeLabel: LocalizedStringKey\n private let selected: Bool\n\n public init(\n _ label: LocalizedStringKey,\n selected: Bool = false,\n removeLabel: LocalizedStringKey = \"Remove\",\n onPress: (() -> Void)? = nil,\n onRemove: (() -> Void)? = nil\n ) {\n self.label = label\n self.selected = selected\n self.removeLabel = removeLabel\n self.onPress = onPress\n self.onRemove = onRemove\n }\n\n public var body: some View {\n HStack(spacing: LumenSpacing.xs) {\n if let onPress {\n Button(action: onPress) {\n Text(label).font(.caption.weight(.semibold))\n }\n .buttonStyle(.plain)\n .accessibilityAddTraits(selected ? .isSelected : [])\n } else {\n Text(label).font(.caption.weight(.semibold))\n }\n if let onRemove {\n Button(action: onRemove) {\n Image(systemName: \"xmark\").font(.caption2.weight(.bold))\n }\n .buttonStyle(.plain)\n .accessibilityLabel(removeLabel)\n }\n }\n .foregroundStyle(selected ? theme.colors.brand : theme.colors.inkSoft)\n .padding(.horizontal, LumenSpacing.md)\n .frame(minHeight: 32)\n .background(selected ? theme.colors.brandSoft : theme.colors.surfaceMuted)\n .overlay {\n Capsule().stroke(selected ? theme.colors.brand : theme.colors.line, lineWidth: 1)\n }\n .clipShape(Capsule())\n .opacity(isEnabled ? 1 : 0.52)\n .accessibilityElement(children: .contain)\n }\n}\n\npublic struct LumenToast<Actions: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let actions: Actions\n private let description: LocalizedStringKey?\n private let onDismiss: (() -> Void)?\n private let title: LocalizedStringKey\n private let variant: LumenBannerVariant\n\n public init(\n _ title: LocalizedStringKey,\n description: LocalizedStringKey? = nil,\n variant: LumenBannerVariant = .default,\n onDismiss: (() -> Void)? = nil,\n @ViewBuilder actions: () -> Actions\n ) {\n self.title = title\n self.description = description\n self.variant = variant\n self.onDismiss = onDismiss\n self.actions = actions()\n }\n\n public var body: some View {\n HStack(spacing: LumenSpacing.md) {\n VStack(alignment: .leading, spacing: LumenSpacing.xs) {\n Text(title).font(.callout.weight(.semibold)).foregroundStyle(accentColor)\n if let description {\n Text(description).font(.caption).foregroundStyle(theme.colors.inkSoft)\n }\n }\n .frame(maxWidth: .infinity, alignment: .leading)\n actions\n if let onDismiss {\n LumenIconButton(\n systemName: \"xmark\",\n label: \"Dismiss\",\n intent: .quiet,\n size: .sm,\n action: onDismiss\n )\n }\n }\n .padding(.horizontal, LumenSpacing.lg)\n .padding(.vertical, LumenSpacing.md)\n .background(backgroundColor)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous)\n .stroke(borderColor, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous))\n .accessibilityElement(children: .contain)\n }\n\n private var accentColor: Color {\n switch variant {\n case .accent: theme.colors.accent\n case .default: theme.colors.brand\n case .destructive: theme.colors.danger\n case .success: theme.colors.success\n case .warning: theme.colors.warning\n }\n }\n\n private var backgroundColor: Color {\n variant == .default ? theme.colors.surface : accentColor.opacity(0.08)\n }\n\n private var borderColor: Color {\n variant == .default ? theme.colors.line : accentColor.opacity(0.32)\n }\n}\n\npublic extension LumenToast where Actions == EmptyView {\n init(\n _ title: LocalizedStringKey,\n description: LocalizedStringKey? = nil,\n variant: LumenBannerVariant = .default,\n onDismiss: (() -> Void)? = nil\n ) {\n self.init(title, description: description, variant: variant, onDismiss: onDismiss) {\n EmptyView()\n }\n }\n}\n",
37124
+ "packages/swift/Sources/LumenUI/AppleComponents.swift": "#if os(iOS) || os(macOS)\nimport SwiftUI\n\npublic enum LumenDateFieldComponents: Sendable {\n case date\n case dateAndTime\n case time\n\n fileprivate var displayedComponents: DatePickerComponents {\n switch self {\n case .date:\n [.date]\n case .dateAndTime:\n [.date, .hourAndMinute]\n case .time:\n [.hourAndMinute]\n }\n }\n}\n\npublic enum LumenDateFieldBounds: Equatable, Sendable {\n case closed(ClosedRange<Date>)\n case from(Date)\n case through(Date)\n case unbounded\n\n public func clamped(_ value: Date) -> Date {\n switch self {\n case let .closed(range):\n min(max(value, range.lowerBound), range.upperBound)\n case let .from(lowerBound):\n max(value, lowerBound)\n case let .through(upperBound):\n min(value, upperBound)\n case .unbounded:\n value\n }\n }\n}\n\n/// A token-aware Apple date input with native picker behavior and Lumen validation messaging.\npublic struct LumenDateField: View {\n @Binding private var selection: Date\n @Environment(\\.lumenTheme) private var theme\n\n private let bounds: LumenDateFieldBounds\n private let components: LumenDateFieldComponents\n private let description: LocalizedStringKey?\n private let errorMessage: LocalizedStringKey?\n private let title: LocalizedStringKey\n\n public init(\n _ title: LocalizedStringKey,\n selection: Binding<Date>,\n components: LumenDateFieldComponents = .date,\n bounds: LumenDateFieldBounds = .unbounded,\n description: LocalizedStringKey? = nil,\n errorMessage: LocalizedStringKey? = nil\n ) {\n self.title = title\n _selection = selection\n self.components = components\n self.bounds = bounds\n self.description = description\n self.errorMessage = errorMessage\n }\n\n public var body: some View {\n VStack(alignment: .leading, spacing: LumenSpacing.sm) {\n datePicker\n .tint(theme.colors.brandSolid)\n\n if let errorMessage {\n Text(errorMessage)\n .font(.caption)\n .foregroundStyle(theme.colors.danger)\n .fixedSize(horizontal: false, vertical: true)\n } else if let description {\n Text(description)\n .font(.caption)\n .foregroundStyle(theme.colors.inkMuted)\n .fixedSize(horizontal: false, vertical: true)\n }\n }\n .accessibilityElement(children: .contain)\n }\n\n @ViewBuilder\n private var datePicker: some View {\n switch bounds {\n case let .closed(range):\n DatePicker(\n title,\n selection: boundedSelection,\n in: range,\n displayedComponents: components.displayedComponents\n )\n case let .from(lowerBound):\n DatePicker(\n title,\n selection: boundedSelection,\n in: lowerBound...,\n displayedComponents: components.displayedComponents\n )\n case let .through(upperBound):\n DatePicker(\n title,\n selection: boundedSelection,\n in: ...upperBound,\n displayedComponents: components.displayedComponents\n )\n case .unbounded:\n DatePicker(\n title,\n selection: boundedSelection,\n displayedComponents: components.displayedComponents\n )\n }\n }\n\n private var boundedSelection: Binding<Date> {\n Binding(\n get: { bounds.clamped(selection) },\n set: { selection = bounds.clamped($0) }\n )\n }\n}\n#endif\n",
36845
37125
  "packages/swift/Sources/LumenUI/ContentComponents.swift": "#if os(iOS) || os(macOS)\nimport SwiftUI\n\npublic enum LumenSkeletonShape: Sendable {\n case circle\n case rectangle\n case text\n}\n\npublic struct LumenSkeleton: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let height: CGFloat\n private let label: String?\n private let shape: LumenSkeletonShape\n private let width: CGFloat?\n\n public init(\n width: CGFloat? = nil,\n height: CGFloat = 16,\n shape: LumenSkeletonShape = .text,\n label: String? = nil\n ) {\n self.width = width\n self.height = height.isFinite && height > 0 ? height : 16\n self.shape = shape\n self.label = label\n }\n\n public var body: some View {\n skeletonShape\n .foregroundStyle(theme.colors.surfaceStrong.opacity(0.72))\n .frame(maxWidth: fillsAvailableWidth ? .infinity : nil, alignment: .leading)\n .frame(width: resolvedWidth, height: height)\n .accessibilityElement(children: .ignore)\n .accessibilityHidden(label == nil)\n .accessibilityLabel(Text(label ?? \"\"))\n .accessibilityValue(Text(label == nil ? \"\" : \"Loading\"))\n }\n\n private var fillsAvailableWidth: Bool {\n width == nil && shape != .circle\n }\n\n private var resolvedWidth: CGFloat? {\n shape == .circle ? height : width\n }\n\n @ViewBuilder\n private var skeletonShape: some View {\n switch shape {\n case .circle:\n Circle()\n case .rectangle:\n RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous)\n case .text:\n RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous)\n }\n }\n}\n\npublic struct LumenDisclosure<Content: View>: View {\n @Binding private var isExpanded: Bool\n @Environment(\\.lumenTheme) private var theme\n\n private let content: Content\n private let description: String?\n private let title: String\n\n public init(\n _ title: String,\n isExpanded: Binding<Bool>,\n description: String? = nil,\n @ViewBuilder content: () -> Content\n ) {\n self.title = title\n _isExpanded = isExpanded\n self.description = description\n self.content = content()\n }\n\n public var body: some View {\n DisclosureGroup(isExpanded: $isExpanded) {\n content\n .frame(maxWidth: .infinity, alignment: .leading)\n .padding(.top, LumenSpacing.md)\n } label: {\n VStack(alignment: .leading, spacing: LumenSpacing.xs) {\n Text(title)\n .font(.callout.weight(.semibold))\n .foregroundStyle(theme.colors.ink)\n\n if let description {\n Text(description)\n .font(.caption)\n .foregroundStyle(theme.colors.inkMuted)\n .fixedSize(horizontal: false, vertical: true)\n }\n }\n }\n .tint(theme.colors.brandSolid)\n .padding(.horizontal, LumenSpacing.lg)\n .padding(.vertical, LumenSpacing.md)\n .background(theme.colors.surface)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous)\n .stroke(theme.colors.line, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous))\n }\n}\n#endif\n",
36846
37126
  "packages/swift/Sources/LumenUI/FormControls.swift": "#if os(iOS) || os(macOS)\nimport SwiftUI\n\npublic struct LumenToggle<Label: View>: View {\n @Binding private var isOn: Bool\n @Environment(\\.lumenTheme) private var theme\n\n private let label: Label\n\n public init(\n isOn: Binding<Bool>,\n @ViewBuilder label: () -> Label\n ) {\n _isOn = isOn\n self.label = label()\n }\n\n public var body: some View {\n Toggle(isOn: $isOn) {\n label\n }\n .toggleStyle(.switch)\n .tint(theme.colors.brandSolid)\n }\n}\n\npublic extension LumenToggle where Label == Text {\n init(_ title: LocalizedStringKey, isOn: Binding<Bool>) {\n self.init(isOn: isOn) {\n Text(title)\n }\n }\n}\n\npublic struct LumenSettingsRow<Control: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let control: Control\n private let description: LocalizedStringKey?\n private let systemName: String?\n private let title: LocalizedStringKey\n\n public init(\n _ title: LocalizedStringKey,\n description: LocalizedStringKey? = nil,\n systemName: String? = nil,\n @ViewBuilder control: () -> Control\n ) {\n self.title = title\n self.description = description\n self.systemName = systemName\n self.control = control()\n }\n\n public var body: some View {\n HStack(alignment: .center, spacing: LumenSpacing.lg) {\n if let systemName {\n LumenIcon(systemName: systemName, size: .sm, color: theme.colors.inkSoft)\n }\n\n VStack(alignment: .leading, spacing: LumenSpacing.xs) {\n Text(title)\n .font(.callout.weight(.semibold))\n .foregroundStyle(theme.colors.ink)\n\n if let description {\n Text(description)\n .font(.caption)\n .foregroundStyle(theme.colors.inkMuted)\n .fixedSize(horizontal: false, vertical: true)\n }\n }\n .frame(maxWidth: .infinity, alignment: .leading)\n\n control\n .fixedSize(horizontal: true, vertical: false)\n }\n .accessibilityElement(children: .contain)\n }\n}\n\npublic enum LumenPickerStyle: Sendable {\n case automatic\n case menu\n case segmented\n}\n\npublic struct LumenPicker<SelectionValue: Hashable, Content: View>: View {\n @Binding private var selection: SelectionValue\n @Environment(\\.lumenTheme) private var theme\n\n private let content: Content\n private let showsLabel: Bool\n private let style: LumenPickerStyle\n private let title: LocalizedStringKey\n\n public init(\n _ title: LocalizedStringKey,\n selection: Binding<SelectionValue>,\n style: LumenPickerStyle = .automatic,\n showsLabel: Bool = true,\n @ViewBuilder content: () -> Content\n ) {\n self.title = title\n _selection = selection\n self.style = style\n self.showsLabel = showsLabel\n self.content = content()\n }\n\n public var body: some View {\n styledPicker\n .tint(theme.colors.brandSolid)\n }\n\n @ViewBuilder\n private var styledPicker: some View {\n switch style {\n case .automatic:\n picker\n case .menu:\n #if os(watchOS)\n picker\n #else\n picker.pickerStyle(.menu)\n #endif\n case .segmented:\n picker.pickerStyle(.segmented)\n }\n }\n\n @ViewBuilder\n private var picker: some View {\n let picker = Picker(title, selection: $selection) {\n content\n }\n\n if showsLabel {\n picker\n } else {\n picker.labelsHidden()\n }\n }\n}\n\npublic struct LumenSliderConfiguration: Equatable, Sendable {\n public let bounds: ClosedRange<Double>\n public let step: Double?\n\n public static func resolve(\n bounds: ClosedRange<Double>,\n step: Double?\n ) -> LumenSliderConfiguration {\n let safeStep = step.flatMap { value in\n value.isFinite && value > 0 ? value : nil\n }\n\n return LumenSliderConfiguration(bounds: bounds, step: safeStep)\n }\n}\n\npublic struct LumenSlider: View {\n @Binding private var value: Double\n @Environment(\\.lumenTheme) private var theme\n\n private let configuration: LumenSliderConfiguration\n private let label: LocalizedStringKey\n private let valueLabel: String?\n\n public init(\n _ label: LocalizedStringKey,\n value: Binding<Double>,\n in bounds: ClosedRange<Double>,\n step: Double? = nil,\n valueLabel: String? = nil\n ) {\n self.label = label\n _value = value\n configuration = LumenSliderConfiguration.resolve(bounds: bounds, step: step)\n self.valueLabel = valueLabel\n }\n\n public var body: some View {\n VStack(alignment: .leading, spacing: LumenSpacing.sm) {\n HStack(alignment: .firstTextBaseline, spacing: LumenSpacing.md) {\n Text(label)\n .font(.callout.weight(.semibold))\n .foregroundStyle(theme.colors.ink)\n\n Spacer(minLength: LumenSpacing.md)\n\n if let valueLabel {\n Text(valueLabel)\n .font(.caption.monospacedDigit())\n .foregroundStyle(theme.colors.inkMuted)\n }\n }\n\n slider\n .tint(theme.colors.brandSolid)\n }\n }\n\n @ViewBuilder\n private var slider: some View {\n if let step = configuration.step {\n Slider(\n value: $value,\n in: configuration.bounds,\n step: step,\n label: { Text(label) }\n )\n } else {\n Slider(\n value: $value,\n in: configuration.bounds,\n label: { Text(label) }\n )\n }\n }\n}\n\npublic struct LumenSearchField: View {\n @Binding private var text: String\n @Environment(\\.isEnabled) private var isEnabled\n @Environment(\\.lumenControlDensity) private var density\n @Environment(\\.lumenTheme) private var theme\n\n private let prompt: String\n\n public init(_ prompt: String = \"Search\", text: Binding<String>) {\n self.prompt = prompt\n _text = text\n }\n\n public var body: some View {\n HStack(spacing: LumenSpacing.sm) {\n LumenIcon(systemName: \"magnifyingglass\", size: .sm, color: theme.colors.inkMuted)\n\n TextField(prompt, text: $text)\n .textFieldStyle(.plain)\n .foregroundStyle(theme.colors.ink)\n\n if !text.isEmpty {\n Button {\n text = \"\"\n } label: {\n Image(systemName: \"xmark.circle.fill\")\n .foregroundStyle(theme.colors.inkMuted)\n }\n .buttonStyle(.plain)\n .accessibilityLabel(\"Clear search\")\n }\n }\n .frame(minHeight: LumenButtonMetrics.resolve(.md, density: density).minHeight)\n .padding(.horizontal, LumenSpacing.md)\n .background(theme.colors.surface)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous)\n .stroke(theme.colors.line, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous))\n .opacity(isEnabled ? 1 : 0.52)\n }\n}\n#endif\n",
36847
- "packages/swift/Sources/LumenUI/LumenTheme.swift": "import SwiftUI\n\npublic enum LumenColorScheme: Sendable {\n case dark\n case light\n}\n\npublic struct LumenTheme: Sendable {\n public let colors: LumenColorPalette\n public let scheme: LumenColorScheme\n\n public init(colors: LumenColorPalette, scheme: LumenColorScheme) {\n self.colors = colors\n self.scheme = scheme\n }\n\n public static let light = LumenTheme(colors: LumenColors.light, scheme: .light)\n public static let dark = LumenTheme(colors: LumenColors.dark, scheme: .dark)\n}\n\nprivate struct LumenThemeKey: EnvironmentKey {\n static let defaultValue = LumenTheme.light\n}\n\npublic extension EnvironmentValues {\n var lumenTheme: LumenTheme {\n get { self[LumenThemeKey.self] }\n set { self[LumenThemeKey.self] = newValue }\n }\n}\n\npublic extension View {\n func lumenTheme(_ theme: LumenTheme) -> some View {\n environment(\\.lumenTheme, theme)\n .preferredColorScheme(theme.scheme == .dark ? .dark : .light)\n }\n}\n",
37127
+ "packages/swift/Sources/LumenUI/LumenTheme.swift": "import SwiftUI\n\npublic enum LumenColorScheme: Sendable {\n case dark\n case light\n}\n\npublic struct LumenTheme: Sendable {\n public let colors: LumenColorPalette\n public let scheme: LumenColorScheme\n\n public init(colors: LumenColorPalette, scheme: LumenColorScheme) {\n self.colors = colors\n self.scheme = scheme\n }\n\n public static let light = LumenTheme(colors: LumenColors.light, scheme: .light)\n public static let dark = LumenTheme(colors: LumenColors.dark, scheme: .dark)\n}\n\nextension LumenTheme {\n func resolvedPreferredColorScheme(enforceColorScheme: Bool) -> ColorScheme? {\n guard enforceColorScheme else { return nil }\n\n return scheme == .dark ? .dark : .light\n }\n}\n\nprivate struct LumenThemeKey: EnvironmentKey {\n static let defaultValue = LumenTheme.light\n}\n\npublic extension EnvironmentValues {\n var lumenTheme: LumenTheme {\n get { self[LumenThemeKey.self] }\n set { self[LumenThemeKey.self] = newValue }\n }\n}\n\npublic extension View {\n func lumenTheme(_ theme: LumenTheme, enforceColorScheme: Bool = true) -> some View {\n environment(\\.lumenTheme, theme)\n .preferredColorScheme(theme.resolvedPreferredColorScheme(enforceColorScheme: enforceColorScheme))\n }\n}\n",
36848
37128
  "packages/swift/Sources/LumenUI/MacComponents.swift": "#if os(macOS)\nimport AppKit\nimport SwiftUI\n\npublic struct LumenShortcut: Hashable {\n public let display: String\n public let keyCode: UInt16\n public let modifiers: NSEvent.ModifierFlags\n\n public init(\n keyCode: UInt16,\n modifiers: NSEvent.ModifierFlags,\n display: String? = nil\n ) {\n self.keyCode = keyCode\n self.modifiers = modifiers.intersection(.deviceIndependentFlagsMask)\n self.display = display ?? Self.format(\n keyCode: keyCode,\n modifiers: modifiers,\n characters: nil\n )\n }\n\n public func hash(into hasher: inout Hasher) {\n hasher.combine(keyCode)\n hasher.combine(modifiers.rawValue)\n }\n\n public static func == (lhs: LumenShortcut, rhs: LumenShortcut) -> Bool {\n lhs.keyCode == rhs.keyCode && lhs.modifiers.rawValue == rhs.modifiers.rawValue\n }\n\n static func from(_ event: NSEvent) -> LumenShortcut {\n LumenShortcut(\n keyCode: event.keyCode,\n modifiers: event.modifierFlags,\n display: format(\n keyCode: event.keyCode,\n modifiers: event.modifierFlags,\n characters: event.charactersIgnoringModifiers\n )\n )\n }\n\n static func format(\n keyCode: UInt16,\n modifiers: NSEvent.ModifierFlags,\n characters: String?\n ) -> String {\n let flags = modifiers.intersection(.deviceIndependentFlagsMask)\n var parts: [String] = []\n\n if flags.contains(.control) { parts.append(\"⌃\") }\n if flags.contains(.option) { parts.append(\"⌥\") }\n if flags.contains(.shift) { parts.append(\"⇧\") }\n if flags.contains(.command) { parts.append(\"⌘\") }\n\n let specialKeys: [UInt16: String] = [\n 36: \"↩\",\n 48: \"⇥\",\n 49: \"Space\",\n 51: \"⌫\",\n 115: \"↖\",\n 116: \"⇞\",\n 119: \"↘\",\n 121: \"⇟\",\n 123: \"←\",\n 124: \"→\",\n 125: \"↓\",\n 126: \"↑\",\n ]\n\n if let specialKey = specialKeys[keyCode] {\n parts.append(specialKey)\n } else if let characters, let character = characters.first {\n parts.append(String(character).uppercased())\n } else {\n parts.append(\"Key \\(keyCode)\")\n }\n\n return parts.joined()\n }\n}\n\n@MainActor\nprivate final class LumenShortcutRecorderState: ObservableObject {\n @Published var errorMessage: String?\n @Published var isRecording = false\n var monitor: Any?\n}\n\npublic struct LumenShortcutRecorder: View {\n @Binding private var shortcut: LumenShortcut?\n @Environment(\\.lumenTheme) private var theme\n @StateObject private var state = LumenShortcutRecorderState()\n\n private let label: LocalizedStringKey\n private let validation: ((LumenShortcut) -> String?)?\n\n public init(\n _ label: LocalizedStringKey,\n shortcut: Binding<LumenShortcut?>,\n validation: ((LumenShortcut) -> String?)? = nil\n ) {\n self.label = label\n _shortcut = shortcut\n self.validation = validation\n }\n\n public var body: some View {\n VStack(alignment: .leading, spacing: LumenSpacing.sm) {\n HStack(spacing: LumenSpacing.md) {\n Text(label)\n .font(.callout.weight(.semibold))\n .foregroundStyle(theme.colors.ink)\n\n Spacer(minLength: LumenSpacing.md)\n\n if state.isRecording {\n Text(\"Press keys…\")\n .font(.caption.monospaced())\n .foregroundStyle(theme.colors.inkMuted)\n\n LumenButton(\"Cancel\", intent: .quiet, size: .sm) {\n stopRecording()\n }\n } else {\n if let shortcut {\n Text(shortcut.display)\n .font(.caption.weight(.semibold).monospaced())\n .foregroundStyle(theme.colors.ink)\n .padding(.horizontal, LumenSpacing.sm)\n .padding(.vertical, LumenSpacing.xs)\n .background(theme.colors.surfaceStrong)\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.sm))\n .accessibilityLabel(\"Current shortcut \\(shortcut.display)\")\n }\n\n LumenButton(shortcut == nil ? \"Record…\" : \"Change…\", intent: .secondary, size: .sm) {\n startRecording()\n }\n\n if shortcut != nil {\n LumenButton(\"Clear\", intent: .quiet, size: .sm) {\n shortcut = nil\n state.errorMessage = nil\n }\n }\n }\n }\n\n if let errorMessage = state.errorMessage {\n HStack(spacing: LumenSpacing.sm) {\n LumenIcon(\n systemName: \"exclamationmark.triangle\",\n size: .sm,\n color: theme.colors.warning\n )\n Text(errorMessage)\n .font(.caption)\n .foregroundStyle(theme.colors.warning)\n }\n .accessibilityElement(children: .combine)\n }\n }\n .onDisappear {\n stopRecording()\n }\n }\n\n private func startRecording() {\n guard state.monitor == nil else { return }\n\n state.errorMessage = nil\n state.isRecording = true\n state.monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in\n handle(event)\n return nil\n }\n }\n\n private func stopRecording() {\n if let monitor = state.monitor {\n NSEvent.removeMonitor(monitor)\n }\n state.monitor = nil\n state.isRecording = false\n }\n\n private func handle(_ event: NSEvent) {\n defer { stopRecording() }\n\n if event.keyCode == 53 {\n return\n }\n\n let shortcut = LumenShortcut.from(event)\n let commandModifiers: NSEvent.ModifierFlags = [.command, .control, .option, .shift]\n\n guard !shortcut.modifiers.intersection(commandModifiers).isEmpty else {\n state.errorMessage = \"Include at least one modifier key.\"\n return\n }\n\n if let validationMessage = validation?(shortcut) {\n state.errorMessage = validationMessage\n return\n }\n\n self.shortcut = shortcut\n state.errorMessage = nil\n }\n}\n\npublic struct LumenSymbolOption: Hashable, Identifiable, Sendable {\n public let category: String\n public let label: String\n public let name: String\n\n public var id: String { name }\n\n public init(name: String, label: String, category: String) {\n self.name = name\n self.label = label\n self.category = category\n }\n\n public static let common: [LumenSymbolOption] = [\n LumenSymbolOption(name: \"folder\", label: \"Folder\", category: \"Files\"),\n LumenSymbolOption(name: \"doc\", label: \"Document\", category: \"Files\"),\n LumenSymbolOption(name: \"tray\", label: \"Tray\", category: \"Files\"),\n LumenSymbolOption(name: \"archivebox\", label: \"Archive\", category: \"Files\"),\n LumenSymbolOption(name: \"desktopcomputer\", label: \"Desktop\", category: \"Devices\"),\n LumenSymbolOption(name: \"laptopcomputer\", label: \"Laptop\", category: \"Devices\"),\n LumenSymbolOption(name: \"display\", label: \"Display\", category: \"Devices\"),\n LumenSymbolOption(name: \"iphone\", label: \"Phone\", category: \"Devices\"),\n LumenSymbolOption(name: \"hammer\", label: \"Build\", category: \"Work\"),\n LumenSymbolOption(name: \"wrench.and.screwdriver\", label: \"Tools\", category: \"Work\"),\n LumenSymbolOption(name: \"briefcase\", label: \"Business\", category: \"Work\"),\n LumenSymbolOption(name: \"chart.bar\", label: \"Analytics\", category: \"Work\"),\n LumenSymbolOption(name: \"person.2\", label: \"People\", category: \"People\"),\n LumenSymbolOption(name: \"bubble.left.and.bubble.right\", label: \"Messages\", category: \"People\"),\n LumenSymbolOption(name: \"video\", label: \"Video\", category: \"People\"),\n LumenSymbolOption(name: \"calendar\", label: \"Calendar\", category: \"Planning\"),\n LumenSymbolOption(name: \"checklist\", label: \"Tasks\", category: \"Planning\"),\n LumenSymbolOption(name: \"clock\", label: \"Time\", category: \"Planning\"),\n LumenSymbolOption(name: \"flag\", label: \"Milestone\", category: \"Planning\"),\n LumenSymbolOption(name: \"star\", label: \"Favorite\", category: \"General\"),\n LumenSymbolOption(name: \"heart\", label: \"Health\", category: \"General\"),\n LumenSymbolOption(name: \"house\", label: \"Home\", category: \"General\"),\n LumenSymbolOption(name: \"gearshape\", label: \"Settings\", category: \"General\"),\n ]\n}\n\npublic struct LumenSymbolPicker: View {\n @Binding private var selectedName: String\n @Environment(\\.lumenTheme) private var theme\n @State private var query = \"\"\n\n private let options: [LumenSymbolOption]\n private let title: LocalizedStringKey\n\n public init(\n _ title: LocalizedStringKey = \"Choose a symbol\",\n selectedName: Binding<String>,\n options: [LumenSymbolOption] = LumenSymbolOption.common\n ) {\n self.title = title\n _selectedName = selectedName\n self.options = options\n }\n\n public var body: some View {\n VStack(alignment: .leading, spacing: LumenSpacing.md) {\n Text(title)\n .font(.headline)\n .foregroundStyle(theme.colors.ink)\n\n LumenSearchField(\"Search symbols\", text: $query)\n\n LumenDivider()\n\n if matchingOptions.isEmpty {\n LumenEmptyState(\n \"No symbols found\",\n systemName: \"magnifyingglass\",\n description: \"Try a different search term.\"\n )\n } else {\n ScrollView {\n LazyVStack(alignment: .leading, spacing: LumenSpacing.lg) {\n ForEach(categories, id: \\.self) { category in\n VStack(alignment: .leading, spacing: LumenSpacing.sm) {\n Text(category)\n .font(.caption.weight(.semibold))\n .foregroundStyle(theme.colors.inkMuted)\n\n LazyVGrid(columns: columns, alignment: .leading, spacing: LumenSpacing.sm) {\n ForEach(matchingOptions.filter { $0.category == category }) { option in\n symbolButton(option)\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n private var categories: [String] {\n options.map(\\.category).reduce(into: []) { result, category in\n if !result.contains(category) && matchingOptions.contains(where: { $0.category == category }) {\n result.append(category)\n }\n }\n }\n\n private var columns: [GridItem] {\n [GridItem(.adaptive(minimum: 64, maximum: 88), spacing: LumenSpacing.sm)]\n }\n\n private var matchingOptions: [LumenSymbolOption] {\n guard !query.isEmpty else { return options }\n\n return options.filter {\n $0.label.localizedCaseInsensitiveContains(query)\n || $0.name.localizedCaseInsensitiveContains(query)\n || $0.category.localizedCaseInsensitiveContains(query)\n }\n }\n\n private func symbolButton(_ option: LumenSymbolOption) -> some View {\n let isSelected = selectedName == option.name\n\n return Button {\n selectedName = option.name\n } label: {\n VStack(spacing: LumenSpacing.xs) {\n LumenIcon(\n systemName: option.name,\n size: .md,\n color: isSelected ? theme.colors.brand : theme.colors.inkSoft\n )\n Text(option.label)\n .font(.caption2)\n .lineLimit(1)\n }\n .frame(maxWidth: .infinity, minHeight: 56)\n .padding(.horizontal, LumenSpacing.xs)\n .background(isSelected ? theme.colors.brandSoft : theme.colors.surfaceMuted)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous)\n .stroke(isSelected ? theme.colors.brand : theme.colors.line, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous))\n }\n .buttonStyle(.plain)\n .help(option.label)\n .accessibilityLabel(option.label)\n .accessibilityAddTraits(isSelected ? .isSelected : [])\n }\n}\n\npublic struct LumenSymbolPickerButton: View {\n @Binding private var selectedName: String\n @Environment(\\.lumenTheme) private var theme\n @State private var isPresented = false\n\n private let label: LocalizedStringKey\n private let options: [LumenSymbolOption]\n\n public init(\n _ label: LocalizedStringKey = \"Choose a symbol\",\n selectedName: Binding<String>,\n options: [LumenSymbolOption] = LumenSymbolOption.common\n ) {\n self.label = label\n _selectedName = selectedName\n self.options = options\n }\n\n public var body: some View {\n Button {\n isPresented = true\n } label: {\n ZStack(alignment: .bottomTrailing) {\n LumenIcon(systemName: selectedName, size: .lg, color: theme.colors.brand)\n .frame(width: 42, height: 42)\n .background(theme.colors.brandSoft)\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous))\n\n Image(systemName: \"pencil.circle.fill\")\n .font(.system(size: 14, weight: .semibold))\n .foregroundStyle(theme.colors.onBrand, theme.colors.brandSolid)\n .offset(x: 4, y: 4)\n }\n }\n .buttonStyle(.plain)\n .accessibilityLabel(label)\n .help(Text(label))\n .popover(isPresented: $isPresented, arrowEdge: .bottom) {\n LumenSymbolPicker(label, selectedName: $selectedName, options: options)\n .padding(LumenSpacing.lg)\n .frame(width: 400, height: 480)\n .lumenTheme(theme)\n }\n }\n}\n#endif\n",
36849
37129
  "packages/swift/Sources/LumenUI/Platform.swift": "import SwiftUI\n\n/// The amount of space Lumen controls reserve for pointer or touch interaction.\npublic enum LumenControlDensity: Equatable, Sendable {\n /// Compact controls sized for pointer-first macOS interfaces.\n case compact\n /// Larger controls sized for touch-first iOS interfaces.\n case regular\n\n public static var platformDefault: LumenControlDensity {\n #if os(macOS) || targetEnvironment(macCatalyst)\n .compact\n #else\n .regular\n #endif\n }\n}\n\nprivate struct LumenControlDensityKey: EnvironmentKey {\n static let defaultValue = LumenControlDensity.platformDefault\n}\n\npublic extension EnvironmentValues {\n var lumenControlDensity: LumenControlDensity {\n get { self[LumenControlDensityKey.self] }\n set { self[LumenControlDensityKey.self] = newValue }\n }\n}\n\npublic extension View {\n /// Overrides Lumen's automatic macOS or iOS control density for this view hierarchy.\n func lumenControlDensity(_ density: LumenControlDensity) -> some View {\n environment(\\.lumenControlDensity, density)\n }\n}\n",
36850
37130
  "packages/swift/Sources/LumenUI/Primitives.swift": "import SwiftUI\n\npublic enum LumenTextTone: Sendable {\n case danger\n case `default`\n case muted\n case soft\n case success\n case warning\n}\n\npublic enum LumenTextVariant: Sendable {\n case body\n case caption\n case label\n case title\n}\n\npublic struct LumenText: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let content: LocalizedStringKey\n private let tone: LumenTextTone\n private let variant: LumenTextVariant\n\n public init(\n _ content: LocalizedStringKey,\n variant: LumenTextVariant = .body,\n tone: LumenTextTone = .default\n ) {\n self.content = content\n self.variant = variant\n self.tone = tone\n }\n\n public var body: some View {\n Text(content)\n .font(font)\n .foregroundStyle(foregroundColor)\n }\n\n private var font: Font {\n switch variant {\n case .body: .body\n case .caption: .caption\n case .label: .callout.weight(.semibold)\n case .title: .title2.weight(.bold)\n }\n }\n\n private var foregroundColor: Color {\n switch tone {\n case .danger: theme.colors.danger\n case .default: theme.colors.ink\n case .muted: theme.colors.inkMuted\n case .soft: theme.colors.inkSoft\n case .success: theme.colors.success\n case .warning: theme.colors.warning\n }\n }\n}\n\npublic enum LumenSurfacePadding: Sendable {\n case lg\n case md\n case none\n case sm\n\n var value: CGFloat {\n switch self {\n case .lg: LumenSpacing.lg\n case .md: LumenSpacing.md\n case .none: 0\n case .sm: LumenSpacing.sm\n }\n }\n}\n\npublic enum LumenSurfaceRadius: Sendable {\n case lg\n case md\n case none\n case sm\n\n var value: CGFloat {\n switch self {\n case .lg: LumenRadius.lg\n case .md: LumenRadius.md\n case .none: 0\n case .sm: LumenRadius.sm\n }\n }\n}\n\npublic enum LumenSurfaceTone: Sendable {\n case canvas\n case muted\n case strong\n case surface\n}\n\npublic struct LumenSurface<Content: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let content: Content\n private let padding: LumenSurfacePadding\n private let radius: LumenSurfaceRadius\n private let tone: LumenSurfaceTone\n\n public init(\n tone: LumenSurfaceTone = .surface,\n padding: LumenSurfacePadding = .md,\n radius: LumenSurfaceRadius = .md,\n @ViewBuilder content: () -> Content\n ) {\n self.content = content()\n self.padding = padding\n self.radius = radius\n self.tone = tone\n }\n\n public var body: some View {\n content\n .padding(padding.value)\n .background(backgroundColor)\n .clipShape(RoundedRectangle(cornerRadius: radius.value, style: .continuous))\n }\n\n private var backgroundColor: Color {\n switch tone {\n case .canvas: theme.colors.canvas\n case .muted: theme.colors.surfaceMuted\n case .strong: theme.colors.surfaceStrong\n case .surface: theme.colors.surface\n }\n }\n}\n\npublic enum LumenButtonIntent: Sendable {\n case danger\n case primary\n case quiet\n case secondary\n}\n\npublic enum LumenControlSize: Sendable {\n case lg\n case md\n case sm\n}\n\npublic enum LumenIconSize: Sendable {\n case lg\n case md\n case sm\n\n var dimension: CGFloat {\n switch self {\n case .lg: 24\n case .md: 20\n case .sm: 16\n }\n }\n}\n\npublic struct LumenIcon: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let color: Color?\n private let label: LocalizedStringKey?\n private let size: LumenIconSize\n private let systemName: String\n\n public init(\n systemName: String,\n size: LumenIconSize = .md,\n color: Color? = nil,\n label: LocalizedStringKey? = nil\n ) {\n self.systemName = systemName\n self.size = size\n self.color = color\n self.label = label\n }\n\n public var body: some View {\n Image(systemName: systemName)\n .font(.system(size: size.dimension, weight: .medium))\n .foregroundStyle(color ?? theme.colors.ink)\n .frame(width: size.dimension, height: size.dimension)\n .accessibilityHidden(label == nil)\n .accessibilityLabel(label ?? \"\")\n }\n}\n\npublic struct LumenIconButtonMetrics: Equatable, Sendable {\n public let iconSize: LumenIconSize\n public let touchTarget: CGFloat\n\n public static func resolve(\n _ size: LumenControlSize,\n density: LumenControlDensity = .platformDefault\n ) -> LumenIconButtonMetrics {\n switch (density, size) {\n case (.compact, .lg): LumenIconButtonMetrics(iconSize: .lg, touchTarget: 40)\n case (.compact, .md): LumenIconButtonMetrics(iconSize: .md, touchTarget: 32)\n case (.compact, .sm): LumenIconButtonMetrics(iconSize: .sm, touchTarget: 28)\n case (.regular, .lg): LumenIconButtonMetrics(iconSize: .lg, touchTarget: 52)\n case (.regular, .md): LumenIconButtonMetrics(iconSize: .md, touchTarget: 44)\n case (.regular, .sm): LumenIconButtonMetrics(iconSize: .sm, touchTarget: 44)\n }\n }\n}\n\npublic struct LumenIconButtonStyle: ButtonStyle {\n @Environment(\\.lumenControlDensity) private var density\n @Environment(\\.isEnabled) private var isEnabled\n @Environment(\\.lumenTheme) private var theme\n\n private let intent: LumenButtonIntent\n private let size: LumenControlSize\n\n public init(intent: LumenButtonIntent = .quiet, size: LumenControlSize = .md) {\n self.intent = intent\n self.size = size\n }\n\n public func makeBody(configuration: Configuration) -> some View {\n let metrics = LumenIconButtonMetrics.resolve(size, density: density)\n\n configuration.label\n .frame(width: metrics.touchTarget, height: metrics.touchTarget)\n .background(backgroundColor)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous)\n .stroke(borderColor, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous))\n .opacity(isEnabled ? (configuration.isPressed ? 0.84 : 1) : 0.52)\n }\n\n private var backgroundColor: Color {\n switch intent {\n case .danger: theme.colors.danger\n case .primary: theme.colors.brandSolid\n case .quiet: .clear\n case .secondary: theme.colors.surfaceMuted\n }\n }\n\n private var borderColor: Color {\n switch intent {\n case .danger: theme.colors.danger\n case .primary: theme.colors.brandSolid\n case .quiet: .clear\n case .secondary: theme.colors.line\n }\n }\n}\n\npublic struct LumenIconButton: View {\n @Environment(\\.lumenControlDensity) private var density\n @Environment(\\.lumenTheme) private var theme\n\n private let action: () -> Void\n private let disabled: Bool\n private let intent: LumenButtonIntent\n private let label: LocalizedStringKey\n private let size: LumenControlSize\n private let systemName: String\n\n public init(\n systemName: String,\n label: LocalizedStringKey,\n intent: LumenButtonIntent = .quiet,\n size: LumenControlSize = .md,\n disabled: Bool = false,\n action: @escaping () -> Void\n ) {\n self.systemName = systemName\n self.label = label\n self.intent = intent\n self.size = size\n self.disabled = disabled\n self.action = action\n }\n\n public var body: some View {\n let metrics = LumenIconButtonMetrics.resolve(size, density: density)\n\n Button(action: action) {\n LumenIcon(\n systemName: systemName,\n size: metrics.iconSize,\n color: foregroundColor\n )\n }\n .buttonStyle(LumenIconButtonStyle(intent: intent, size: size))\n .disabled(disabled)\n .accessibilityLabel(label)\n }\n\n private var foregroundColor: Color {\n switch intent {\n case .danger: theme.colors.onDanger\n case .primary: theme.colors.onBrand\n case .quiet: theme.colors.inkSoft\n case .secondary: theme.colors.ink\n }\n }\n}\n\npublic struct LumenButtonMetrics: Equatable, Sendable {\n public let horizontalPadding: CGFloat\n public let minHeight: CGFloat\n\n public static func resolve(\n _ size: LumenControlSize,\n density: LumenControlDensity = .platformDefault\n ) -> LumenButtonMetrics {\n switch (density, size) {\n case (.compact, .lg):\n LumenButtonMetrics(horizontalPadding: LumenSpacing.lg, minHeight: 40)\n case (.compact, .md):\n LumenButtonMetrics(horizontalPadding: LumenSpacing.md, minHeight: 32)\n case (.compact, .sm):\n LumenButtonMetrics(horizontalPadding: LumenSpacing.md, minHeight: 28)\n case (.regular, .lg):\n LumenButtonMetrics(horizontalPadding: LumenSpacing.xl, minHeight: 52)\n case (.regular, .md):\n LumenButtonMetrics(horizontalPadding: LumenSpacing.lg, minHeight: 44)\n case (.regular, .sm):\n LumenButtonMetrics(horizontalPadding: LumenSpacing.md, minHeight: 36)\n }\n }\n}\n\npublic struct LumenButtonStyle: ButtonStyle {\n @Environment(\\.lumenControlDensity) private var density\n @Environment(\\.isEnabled) private var isEnabled\n @Environment(\\.lumenTheme) private var theme\n\n private let intent: LumenButtonIntent\n private let size: LumenControlSize\n\n public init(\n intent: LumenButtonIntent = .primary,\n size: LumenControlSize = .md\n ) {\n self.intent = intent\n self.size = size\n }\n\n public func makeBody(configuration: Configuration) -> some View {\n let metrics = LumenButtonMetrics.resolve(size, density: density)\n\n configuration.label\n .font(size == .lg ? .body.weight(.semibold) : .callout.weight(.semibold))\n .foregroundStyle(foregroundColor)\n .frame(minHeight: metrics.minHeight)\n .padding(.horizontal, metrics.horizontalPadding)\n .background(backgroundColor)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous)\n .stroke(borderColor, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous))\n .opacity(isEnabled ? (configuration.isPressed ? 0.84 : 1) : 0.52)\n }\n\n private var backgroundColor: Color {\n switch intent {\n case .danger: theme.colors.danger\n case .primary: theme.colors.brandSolid\n case .quiet: .clear\n case .secondary: theme.colors.surfaceMuted\n }\n }\n\n private var borderColor: Color {\n switch intent {\n case .danger: theme.colors.danger\n case .primary: theme.colors.brandSolid\n case .quiet: .clear\n case .secondary: theme.colors.line\n }\n }\n\n private var foregroundColor: Color {\n switch intent {\n case .danger: theme.colors.onDanger\n case .primary: theme.colors.onBrand\n case .quiet: theme.colors.inkSoft\n case .secondary: theme.colors.ink\n }\n }\n}\n\npublic struct LumenButton<Label: View>: View {\n private let action: () -> Void\n private let disabled: Bool\n private let intent: LumenButtonIntent\n private let label: Label\n private let loading: Bool\n private let size: LumenControlSize\n\n public init(\n intent: LumenButtonIntent = .primary,\n size: LumenControlSize = .md,\n loading: Bool = false,\n disabled: Bool = false,\n action: @escaping () -> Void,\n @ViewBuilder label: () -> Label\n ) {\n self.action = action\n self.disabled = disabled\n self.intent = intent\n self.label = label()\n self.loading = loading\n self.size = size\n }\n\n public var body: some View {\n Button(action: action) {\n HStack(spacing: LumenSpacing.sm) {\n if loading {\n ProgressView()\n .controlSize(.small)\n }\n label\n }\n }\n .buttonStyle(LumenButtonStyle(intent: intent, size: size))\n .disabled(disabled || loading)\n .accessibilityValue(loading ? \"Loading\" : \"\")\n }\n}\n\npublic extension LumenButton where Label == Text {\n init(\n _ title: LocalizedStringKey,\n intent: LumenButtonIntent = .primary,\n size: LumenControlSize = .md,\n loading: Bool = false,\n disabled: Bool = false,\n action: @escaping () -> Void\n ) {\n self.init(\n intent: intent,\n size: size,\n loading: loading,\n disabled: disabled,\n action: action\n ) {\n Text(title)\n }\n }\n}\n\npublic struct LumenTextField: View {\n @Binding private var text: String\n @Environment(\\.isEnabled) private var isEnabled\n @Environment(\\.lumenControlDensity) private var density\n @Environment(\\.lumenTheme) private var theme\n\n private let error: Bool\n private let errorMessage: String?\n private let size: LumenControlSize\n private let title: String\n\n public init(\n _ title: String,\n text: Binding<String>,\n size: LumenControlSize = .md,\n error: Bool = false,\n errorMessage: String? = nil\n ) {\n self.title = title\n _text = text\n self.size = size\n self.error = error\n self.errorMessage = errorMessage\n }\n\n public var body: some View {\n TextField(title, text: $text)\n .textFieldStyle(.plain)\n .font(size == .lg ? .body : .callout)\n .foregroundStyle(theme.colors.ink)\n .frame(minHeight: LumenButtonMetrics.resolve(size, density: density).minHeight)\n .padding(.horizontal, LumenSpacing.md)\n .background(theme.colors.surface)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous)\n .stroke(error ? theme.colors.danger : theme.colors.line, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous))\n .opacity(isEnabled ? 1 : 0.52)\n .accessibilityValue(error ? errorMessage ?? \"Invalid value\" : text)\n }\n}\n\npublic enum LumenBadgeTone: Sendable {\n case accent\n case danger\n case neutral\n case success\n case warning\n}\n\npublic struct LumenBadge: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let content: LocalizedStringKey\n private let tone: LumenBadgeTone\n\n public init(\n _ content: LocalizedStringKey,\n tone: LumenBadgeTone = .neutral\n ) {\n self.content = content\n self.tone = tone\n }\n\n public var body: some View {\n Text(content)\n .font(.caption.weight(.semibold))\n .foregroundStyle(foregroundColor)\n .padding(.horizontal, LumenSpacing.sm)\n .padding(.vertical, LumenSpacing.xs)\n .background(backgroundColor)\n .clipShape(Capsule())\n }\n\n private var foregroundColor: Color {\n switch tone {\n case .accent: theme.colors.accent\n case .danger: theme.colors.danger\n case .neutral: theme.colors.inkSoft\n case .success: theme.colors.success\n case .warning: theme.colors.warning\n }\n }\n\n private var backgroundColor: Color {\n tone == .neutral ? theme.colors.surfaceMuted : foregroundColor.opacity(0.12)\n }\n}\n\npublic struct LumenDivider: View {\n @Environment(\\.lumenTheme) private var theme\n\n public init() {}\n\n public var body: some View {\n Rectangle()\n .fill(theme.colors.line)\n .frame(height: 1)\n .accessibilityHidden(true)\n }\n}\n\npublic struct LumenSpinner: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let label: LocalizedStringKey\n\n public init(_ label: LocalizedStringKey = \"Loading\") {\n self.label = label\n }\n\n public var body: some View {\n ProgressView()\n .tint(theme.colors.brand)\n .accessibilityLabel(label)\n }\n}\n",
36851
37131
  "packages/swift/Sources/LumenUI/SelectionControls.swift": "#if os(iOS) || os(macOS)\nimport SwiftUI\n\npublic struct LumenSelectionOption<Value: Hashable>: Identifiable {\n public let description: String?\n public let id: Value\n public let isDisabled: Bool\n public let title: String\n\n public init(\n _ title: String,\n value: Value,\n description: String? = nil,\n isDisabled: Bool = false\n ) {\n self.title = title\n id = value\n self.description = description\n self.isDisabled = isDisabled\n }\n}\n\npublic struct LumenCheckbox: View {\n @Binding private var isChecked: Bool\n @Environment(\\.isEnabled) private var isEnabled\n @Environment(\\.lumenTheme) private var theme\n\n private let description: String?\n private let label: String\n\n public init(_ label: String, isChecked: Binding<Bool>, description: String? = nil) {\n self.label = label\n _isChecked = isChecked\n self.description = description\n }\n\n public var body: some View {\n Button { isChecked.toggle() } label: {\n HStack(alignment: .top, spacing: LumenSpacing.md) {\n Image(systemName: isChecked ? \"checkmark.square.fill\" : \"square\")\n .font(.system(size: 20, weight: .semibold))\n .foregroundStyle(isChecked ? theme.colors.brandSolid : theme.colors.inkMuted)\n .frame(width: 24, height: 24)\n .accessibilityHidden(true)\n\n VStack(alignment: .leading, spacing: LumenSpacing.xs) {\n Text(label).font(.callout.weight(.semibold)).foregroundStyle(theme.colors.ink)\n if let description {\n Text(description)\n .font(.caption)\n .foregroundStyle(theme.colors.inkMuted)\n .fixedSize(horizontal: false, vertical: true)\n }\n }\n .frame(maxWidth: .infinity, alignment: .leading)\n }\n .contentShape(Rectangle())\n }\n .buttonStyle(.plain)\n .opacity(isEnabled ? 1 : 0.52)\n .accessibilityLabel(Text(label))\n .accessibilityValue(Text(isChecked ? \"Checked\" : \"Not checked\"))\n }\n}\n\npublic struct LumenRadioGroup<Value: Hashable>: View {\n @Binding private var selection: Value\n @Environment(\\.lumenTheme) private var theme\n\n private let label: String\n private let options: [LumenSelectionOption<Value>]\n\n public init(_ label: String, selection: Binding<Value>, options: [LumenSelectionOption<Value>]) {\n self.label = label\n _selection = selection\n self.options = options\n }\n\n public var body: some View {\n VStack(alignment: .leading, spacing: LumenSpacing.sm) {\n Text(label).font(.callout.weight(.semibold)).foregroundStyle(theme.colors.ink)\n ForEach(options) { option in\n let isSelected = selection == option.id\n\n Button { selection = option.id } label: {\n HStack(alignment: .top, spacing: LumenSpacing.md) {\n Image(systemName: isSelected ? \"largecircle.fill.circle\" : \"circle\")\n .font(.system(size: 20, weight: .semibold))\n .foregroundStyle(isSelected ? theme.colors.brandSolid : theme.colors.inkMuted)\n .frame(width: 24, height: 24)\n .accessibilityHidden(true)\n VStack(alignment: .leading, spacing: LumenSpacing.xs) {\n Text(option.title).font(.callout).foregroundStyle(theme.colors.ink)\n if let description = option.description {\n Text(description)\n .font(.caption)\n .foregroundStyle(theme.colors.inkMuted)\n .fixedSize(horizontal: false, vertical: true)\n }\n }\n .frame(maxWidth: .infinity, alignment: .leading)\n }\n .contentShape(Rectangle())\n }\n .buttonStyle(.plain)\n .disabled(option.isDisabled)\n .opacity(option.isDisabled ? 0.52 : 1)\n .accessibilityLabel(Text(option.title))\n .accessibilityValue(Text(isSelected ? \"Selected\" : \"Not selected\"))\n .accessibilityAddTraits(isSelected ? .isSelected : [])\n }\n }\n .accessibilityElement(children: .contain)\n .accessibilityLabel(Text(label))\n }\n}\n\npublic struct LumenSegmentedControl<Value: Hashable>: View {\n @Binding private var selection: Value\n @Environment(\\.lumenTheme) private var theme\n\n private let label: String\n private let options: [LumenSelectionOption<Value>]\n private let showsLabel: Bool\n\n public init(\n _ label: String,\n selection: Binding<Value>,\n options: [LumenSelectionOption<Value>],\n showsLabel: Bool = true\n ) {\n self.label = label\n _selection = selection\n self.options = options\n self.showsLabel = showsLabel\n }\n\n public var body: some View {\n VStack(alignment: .leading, spacing: LumenSpacing.sm) {\n if showsLabel {\n Text(label).font(.callout.weight(.semibold)).foregroundStyle(theme.colors.ink)\n }\n HStack(spacing: 2) {\n ForEach(options) { option in\n let isSelected = selection == option.id\n\n Button { selection = option.id } label: {\n Text(option.title)\n .font(.caption.weight(isSelected ? .bold : .semibold))\n .lineLimit(1)\n .frame(maxWidth: .infinity, minHeight: 36)\n .padding(.horizontal, LumenSpacing.sm)\n .foregroundStyle(isSelected ? theme.colors.brand : theme.colors.inkSoft)\n .background(isSelected ? theme.colors.surface : .clear)\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous))\n }\n .buttonStyle(.plain)\n .disabled(option.isDisabled)\n .opacity(option.isDisabled ? 0.52 : 1)\n .accessibilityLabel(Text(option.title))\n .accessibilityValue(Text(isSelected ? \"Selected\" : \"Not selected\"))\n .accessibilityAddTraits(isSelected ? .isSelected : [])\n }\n }\n .padding(2)\n .background(theme.colors.surfaceMuted)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous)\n .stroke(theme.colors.line, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.sm, style: .continuous))\n .accessibilityElement(children: .contain)\n .accessibilityLabel(Text(label))\n }\n }\n}\n#endif\n",
36852
37132
  "packages/swift/Sources/LumenUI/SharedComponents.swift": "import SwiftUI\n\npublic enum LumenCardVariant: Sendable {\n case accent\n case `default`\n case destructive\n case muted\n case success\n case warning\n}\n\npublic struct LumenCard<Content: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let action: (() -> Void)?\n private let content: Content\n private let variant: LumenCardVariant\n\n public init(\n variant: LumenCardVariant = .default,\n action: (() -> Void)? = nil,\n @ViewBuilder content: () -> Content\n ) {\n self.variant = variant\n self.action = action\n self.content = content()\n }\n\n public var body: some View {\n Group {\n if let action {\n Button(action: action) {\n cardSurface\n }\n .buttonStyle(LumenCardButtonStyle())\n } else {\n cardSurface\n }\n }\n }\n\n private var cardSurface: some View {\n content\n .frame(maxWidth: .infinity, alignment: .leading)\n .padding(LumenSpacing.xl)\n .background(backgroundColor)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.lg, style: .continuous)\n .stroke(borderColor, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.lg, style: .continuous))\n }\n\n private var accentColor: Color? {\n switch variant {\n case .accent: theme.colors.accent\n case .default, .muted: nil\n case .destructive: theme.colors.danger\n case .success: theme.colors.success\n case .warning: theme.colors.warning\n }\n }\n\n private var backgroundColor: Color {\n switch variant {\n case .default: theme.colors.surface\n case .muted: theme.colors.surfaceMuted\n case .accent, .destructive, .success, .warning:\n accentColor?.opacity(0.06) ?? theme.colors.surface\n }\n }\n\n private var borderColor: Color {\n accentColor?.opacity(0.24) ?? theme.colors.line\n }\n}\n\nprivate struct LumenCardButtonStyle: ButtonStyle {\n func makeBody(configuration: Configuration) -> some View {\n configuration.label\n .opacity(configuration.isPressed ? 0.84 : 1)\n .scaleEffect(configuration.isPressed ? 0.995 : 1)\n }\n}\n\npublic enum LumenAlertVariant: Sendable {\n case `default`\n case destructive\n case success\n case warning\n}\n\npublic struct LumenAlert<Content: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let content: Content\n private let variant: LumenAlertVariant\n\n public init(\n variant: LumenAlertVariant = .default,\n @ViewBuilder content: () -> Content\n ) {\n self.variant = variant\n self.content = content()\n }\n\n public var body: some View {\n content\n .frame(maxWidth: .infinity, alignment: .leading)\n .padding(.horizontal, LumenSpacing.lg)\n .padding(.vertical, LumenSpacing.md)\n .foregroundStyle(foregroundColor)\n .background(backgroundColor)\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous)\n .stroke(borderColor, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous))\n }\n\n private var accentColor: Color {\n switch variant {\n case .default: theme.colors.ink\n case .destructive: theme.colors.danger\n case .success: theme.colors.success\n case .warning: theme.colors.warning\n }\n }\n\n private var backgroundColor: Color {\n variant == .default ? theme.colors.surface : accentColor.opacity(0.08)\n }\n\n private var borderColor: Color {\n variant == .default ? theme.colors.line : accentColor.opacity(0.36)\n }\n\n private var foregroundColor: Color {\n variant == .default ? theme.colors.ink : accentColor\n }\n}\n\npublic struct LumenProgressValue: Equatable, Sendable {\n public let max: Double\n public let value: Double\n\n public var fraction: Double { value / max }\n\n public static func resolve(value: Double, max: Double) -> LumenProgressValue {\n let safeMax = max.isFinite && max > 0 ? max : 100\n let finiteValue = value.isFinite ? value : 0\n let safeValue = min(safeMax, Swift.max(0, finiteValue))\n\n return LumenProgressValue(max: safeMax, value: safeValue)\n }\n}\n\npublic struct LumenProgress: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let label: String?\n private let progress: LumenProgressValue\n\n public init(\n value: Double = 0,\n max: Double = 100,\n label: String? = nil\n ) {\n progress = LumenProgressValue.resolve(value: value, max: max)\n self.label = label\n }\n\n public var body: some View {\n GeometryReader { geometry in\n ZStack(alignment: .leading) {\n Capsule().fill(theme.colors.surfaceStrong)\n Capsule()\n .fill(theme.colors.brandSolid)\n .frame(width: geometry.size.width * progress.fraction)\n }\n }\n .frame(height: LumenSpacing.sm)\n .accessibilityElement(children: .ignore)\n .accessibilityLabel(Text(label ?? \"Progress\"))\n .accessibilityValue(Text(\"\\(Int(progress.fraction * 100)) percent\"))\n }\n}\n\npublic enum LumenAvatarSize: Sendable {\n case lg\n case md\n case sm\n\n var dimension: CGFloat {\n switch self {\n case .lg: 56\n case .md: 40\n case .sm: 32\n }\n }\n}\n\npublic struct LumenAvatar: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let fallback: String\n private let image: Image?\n private let label: String?\n private let size: LumenAvatarSize\n\n public init(\n image: Image? = nil,\n fallback: String = \"?\",\n size: LumenAvatarSize = .md,\n label: String? = nil\n ) {\n self.image = image\n self.fallback = fallback\n self.size = size\n self.label = label\n }\n\n public var body: some View {\n ZStack {\n Circle().fill(theme.colors.surfaceMuted)\n\n if let image {\n image\n .resizable()\n .scaledToFill()\n } else {\n Text(fallback)\n .font(fallbackFont)\n .foregroundStyle(theme.colors.ink)\n }\n }\n .frame(width: size.dimension, height: size.dimension)\n .clipShape(Circle())\n .overlay {\n Circle().stroke(theme.colors.line, lineWidth: 1)\n }\n .accessibilityElement(children: .ignore)\n .accessibilityHidden(label == nil)\n .accessibilityLabel(Text(label ?? \"\"))\n }\n\n private var fallbackFont: Font {\n size == .lg ? .headline.weight(.bold) : .callout.weight(.bold)\n }\n}\n",
36853
37133
  "packages/swift/Sources/LumenUI/StructuredComponents.swift": "import SwiftUI\n\npublic struct LumenEmptyState<Actions: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let actions: Actions\n private let description: LocalizedStringKey?\n private let systemName: String\n private let title: LocalizedStringKey\n\n public init(\n _ title: LocalizedStringKey,\n systemName: String,\n description: LocalizedStringKey? = nil,\n @ViewBuilder actions: () -> Actions\n ) {\n self.title = title\n self.systemName = systemName\n self.description = description\n self.actions = actions()\n }\n\n public var body: some View {\n VStack(spacing: LumenSpacing.lg) {\n LumenIcon(systemName: systemName, size: .lg, color: theme.colors.inkMuted)\n .padding(LumenSpacing.md)\n .background(theme.colors.surfaceMuted, in: Circle())\n\n VStack(spacing: LumenSpacing.sm) {\n Text(title)\n .font(.headline)\n .foregroundStyle(theme.colors.ink)\n\n if let description {\n Text(description)\n .font(.callout)\n .foregroundStyle(theme.colors.inkMuted)\n .multilineTextAlignment(.center)\n .fixedSize(horizontal: false, vertical: true)\n }\n }\n\n actions\n }\n .frame(maxWidth: 440)\n .padding(LumenSpacing.xl)\n .frame(maxWidth: .infinity, maxHeight: .infinity)\n .accessibilityElement(children: .contain)\n }\n}\n\npublic extension LumenEmptyState where Actions == EmptyView {\n init(\n _ title: LocalizedStringKey,\n systemName: String,\n description: LocalizedStringKey? = nil\n ) {\n self.init(title, systemName: systemName, description: description) {\n EmptyView()\n }\n }\n}\n\npublic struct LumenListRow<Leading: View, Content: View, Trailing: View>: View {\n private let content: Content\n private let leading: Leading\n private let trailing: Trailing\n\n public init(\n @ViewBuilder leading: () -> Leading,\n @ViewBuilder content: () -> Content,\n @ViewBuilder trailing: () -> Trailing\n ) {\n self.leading = leading()\n self.content = content()\n self.trailing = trailing()\n }\n\n public var body: some View {\n HStack(alignment: .center, spacing: LumenSpacing.md) {\n leading\n .fixedSize()\n\n content\n .frame(maxWidth: .infinity, alignment: .leading)\n\n trailing\n .fixedSize(horizontal: true, vertical: false)\n }\n .padding(.horizontal, LumenSpacing.lg)\n .padding(.vertical, LumenSpacing.md)\n .contentShape(Rectangle())\n .accessibilityElement(children: .contain)\n }\n}\n\npublic extension LumenListRow where Trailing == EmptyView {\n init(\n @ViewBuilder leading: () -> Leading,\n @ViewBuilder content: () -> Content\n ) {\n self.init(leading: leading, content: content) {\n EmptyView()\n }\n }\n}\n\npublic enum LumenBannerVariant: Sendable {\n case accent\n case `default`\n case destructive\n case success\n case warning\n}\n\npublic struct LumenBanner<Actions: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let actions: Actions\n private let description: LocalizedStringKey?\n private let onDismiss: (() -> Void)?\n private let systemName: String?\n private let title: LocalizedStringKey\n private let variant: LumenBannerVariant\n\n public init(\n _ title: LocalizedStringKey,\n description: LocalizedStringKey? = nil,\n systemName: String? = nil,\n variant: LumenBannerVariant = .default,\n onDismiss: (() -> Void)? = nil,\n @ViewBuilder actions: () -> Actions\n ) {\n self.title = title\n self.description = description\n self.systemName = systemName\n self.variant = variant\n self.onDismiss = onDismiss\n self.actions = actions()\n }\n\n public var body: some View {\n HStack(alignment: .center, spacing: LumenSpacing.md) {\n LumenIcon(\n systemName: systemName ?? defaultSystemName,\n size: .md,\n color: accentColor\n )\n\n VStack(alignment: .leading, spacing: LumenSpacing.xs) {\n Text(title)\n .font(.callout.weight(.semibold))\n .foregroundStyle(theme.colors.ink)\n\n if let description {\n Text(description)\n .font(.caption)\n .foregroundStyle(theme.colors.inkSoft)\n .fixedSize(horizontal: false, vertical: true)\n }\n }\n .frame(maxWidth: .infinity, alignment: .leading)\n\n actions\n\n if let onDismiss {\n LumenIconButton(\n systemName: \"xmark\",\n label: \"Dismiss\",\n intent: .quiet,\n size: .sm,\n action: onDismiss\n )\n }\n }\n .padding(.horizontal, LumenSpacing.lg)\n .padding(.vertical, LumenSpacing.md)\n .background(backgroundColor)\n .overlay(alignment: .leading) {\n Rectangle()\n .fill(accentColor)\n .frame(width: 3)\n }\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous)\n .stroke(borderColor, lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous))\n .accessibilityElement(children: .contain)\n }\n\n private var accentColor: Color {\n switch variant {\n case .accent: theme.colors.accent\n case .default: theme.colors.brand\n case .destructive: theme.colors.danger\n case .success: theme.colors.success\n case .warning: theme.colors.warning\n }\n }\n\n private var backgroundColor: Color {\n variant == .default ? theme.colors.surface : accentColor.opacity(0.08)\n }\n\n private var borderColor: Color {\n variant == .default ? theme.colors.line : accentColor.opacity(0.32)\n }\n\n private var defaultSystemName: String {\n switch variant {\n case .accent: \"sparkles\"\n case .default: \"info.circle\"\n case .destructive: \"xmark.octagon\"\n case .success: \"checkmark.circle\"\n case .warning: \"exclamationmark.triangle\"\n }\n }\n}\n\npublic extension LumenBanner where Actions == EmptyView {\n init(\n _ title: LocalizedStringKey,\n description: LocalizedStringKey? = nil,\n systemName: String? = nil,\n variant: LumenBannerVariant = .default,\n onDismiss: (() -> Void)? = nil\n ) {\n self.init(\n title,\n description: description,\n systemName: systemName,\n variant: variant,\n onDismiss: onDismiss\n ) {\n EmptyView()\n }\n }\n}\n\npublic enum LumenMetricTone: Sendable {\n case accent\n case brand\n case danger\n case neutral\n case success\n case warning\n}\n\npublic struct LumenStat: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let detail: LocalizedStringKey?\n private let label: LocalizedStringKey\n private let systemName: String?\n private let tone: LumenMetricTone\n private let value: String\n\n public init(\n _ label: LocalizedStringKey,\n value: String,\n detail: LocalizedStringKey? = nil,\n systemName: String? = nil,\n tone: LumenMetricTone = .brand\n ) {\n self.label = label\n self.value = value\n self.detail = detail\n self.systemName = systemName\n self.tone = tone\n }\n\n public var body: some View {\n VStack(alignment: .leading, spacing: LumenSpacing.sm) {\n if let systemName {\n LumenIcon(systemName: systemName, size: .sm, color: accentColor)\n }\n\n Text(value)\n .font(.title2.weight(.bold))\n .monospacedDigit()\n .foregroundStyle(theme.colors.ink)\n\n Text(label)\n .font(.caption.weight(.semibold))\n .foregroundStyle(theme.colors.inkSoft)\n\n if let detail {\n Text(detail)\n .font(.caption)\n .foregroundStyle(theme.colors.inkMuted)\n }\n }\n .frame(maxWidth: .infinity, alignment: .leading)\n .padding(LumenSpacing.lg)\n .background(accentColor.opacity(0.06))\n .overlay {\n RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous)\n .stroke(accentColor.opacity(0.18), lineWidth: 1)\n }\n .clipShape(RoundedRectangle(cornerRadius: LumenRadius.md, style: .continuous))\n .accessibilityElement(children: .combine)\n }\n\n private var accentColor: Color {\n metricColor(tone, theme: theme)\n }\n}\n\npublic struct LumenGauge: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let label: LocalizedStringKey\n private let progress: LumenProgressValue\n private let systemName: String?\n private let tone: LumenMetricTone\n private let valueLabel: String\n\n public init(\n _ label: LocalizedStringKey,\n value: Double,\n max: Double = 100,\n valueLabel: String,\n systemName: String? = nil,\n tone: LumenMetricTone = .brand\n ) {\n self.label = label\n progress = LumenProgressValue.resolve(value: value, max: max)\n self.valueLabel = valueLabel\n self.systemName = systemName\n self.tone = tone\n }\n\n public var body: some View {\n VStack(spacing: LumenSpacing.md) {\n ZStack {\n Circle()\n .stroke(theme.colors.surfaceStrong, lineWidth: 10)\n\n Circle()\n .trim(from: 0, to: progress.fraction)\n .stroke(accentColor, style: StrokeStyle(lineWidth: 10, lineCap: .round))\n .rotationEffect(.degrees(-90))\n\n VStack(spacing: LumenSpacing.xs) {\n if let systemName {\n LumenIcon(systemName: systemName, size: .md, color: accentColor)\n }\n Text(valueLabel)\n .font(.headline.monospacedDigit())\n .foregroundStyle(theme.colors.ink)\n }\n }\n .frame(width: 108, height: 108)\n\n Text(label)\n .font(.caption.weight(.semibold))\n .foregroundStyle(theme.colors.inkSoft)\n }\n .accessibilityElement(children: .ignore)\n .accessibilityLabel(Text(label))\n .accessibilityValue(Text(valueLabel))\n }\n\n private var accentColor: Color {\n metricColor(tone, theme: theme)\n }\n}\n\npublic struct LumenSectionHeader<Actions: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let actions: Actions\n private let count: String?\n private let subtitle: LocalizedStringKey?\n private let title: LocalizedStringKey\n\n public init(\n _ title: LocalizedStringKey,\n subtitle: LocalizedStringKey? = nil,\n count: String? = nil,\n @ViewBuilder actions: () -> Actions\n ) {\n self.title = title\n self.subtitle = subtitle\n self.count = count\n self.actions = actions()\n }\n\n public var body: some View {\n HStack(alignment: .top, spacing: LumenSpacing.md) {\n VStack(alignment: .leading, spacing: LumenSpacing.xs) {\n HStack(alignment: .firstTextBaseline, spacing: LumenSpacing.sm) {\n Text(title)\n .font(.headline)\n .foregroundStyle(theme.colors.ink)\n\n if let count {\n LumenBadge(LocalizedStringKey(count), tone: .neutral)\n .monospacedDigit()\n }\n }\n\n if let subtitle {\n Text(subtitle)\n .font(.caption)\n .foregroundStyle(theme.colors.inkMuted)\n }\n }\n .frame(maxWidth: .infinity, alignment: .leading)\n\n actions\n }\n .accessibilityElement(children: .contain)\n }\n}\n\npublic extension LumenSectionHeader where Actions == EmptyView {\n init(\n _ title: LocalizedStringKey,\n subtitle: LocalizedStringKey? = nil,\n count: String? = nil\n ) {\n self.init(title, subtitle: subtitle, count: count) {\n EmptyView()\n }\n }\n}\n\npublic struct LumenStatusBar<Trailing: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let message: LocalizedStringKey\n private let tone: LumenMetricTone\n private let trailing: Trailing\n\n public init(\n _ message: LocalizedStringKey,\n tone: LumenMetricTone = .neutral,\n @ViewBuilder trailing: () -> Trailing\n ) {\n self.message = message\n self.tone = tone\n self.trailing = trailing()\n }\n\n public var body: some View {\n HStack(spacing: LumenSpacing.sm) {\n Circle()\n .fill(metricColor(tone, theme: theme))\n .frame(width: 7, height: 7)\n .accessibilityHidden(true)\n\n Text(message)\n .lineLimit(1)\n\n Spacer(minLength: LumenSpacing.md)\n\n trailing\n }\n .font(.caption)\n .foregroundStyle(theme.colors.inkSoft)\n .padding(.horizontal, LumenSpacing.lg)\n .frame(minHeight: 36)\n .background(theme.colors.surfaceMuted)\n .accessibilityElement(children: .contain)\n }\n}\n\npublic extension LumenStatusBar where Trailing == EmptyView {\n init(_ message: LocalizedStringKey, tone: LumenMetricTone = .neutral) {\n self.init(message, tone: tone) {\n EmptyView()\n }\n }\n}\n\nprivate func metricColor(_ tone: LumenMetricTone, theme: LumenTheme) -> Color {\n switch tone {\n case .accent: theme.colors.accent\n case .brand: theme.colors.brand\n case .danger: theme.colors.danger\n case .neutral: theme.colors.inkMuted\n case .success: theme.colors.success\n case .warning: theme.colors.warning\n }\n}\n",
36854
- "packages/swift/Sources/LumenUI/Tokens.generated.swift": "// This file is generated by scripts/generate-platform-tokens.mjs.\nimport SwiftUI\n\npublic struct LumenColorPalette: Sendable {\n public let canvas: Color\n public let surface: Color\n public let surfaceMuted: Color\n public let surfaceStrong: Color\n public let line: Color\n public let ink: Color\n public let inkSoft: Color\n public let inkMuted: Color\n public let brand: Color\n public let brandSolid: Color\n public let brandSoft: Color\n public let onBrand: Color\n public let accent: Color\n public let success: Color\n public let warning: Color\n public let danger: Color\n public let onDanger: Color\n}\n\npublic enum LumenColors {\n public static let light = LumenColorPalette(\n canvas: Color(red: 1.000000, green: 1.000000, blue: 1.000000),\n surface: Color(red: 1.000000, green: 1.000000, blue: 1.000000),\n surfaceMuted: Color(red: 0.952941, green: 0.956863, blue: 0.964706),\n surfaceStrong: Color(red: 0.898039, green: 0.905882, blue: 0.921569),\n line: Color(red: 0.843137, green: 0.854902, blue: 0.878431),\n ink: Color(red: 0.058824, green: 0.090196, blue: 0.160784),\n inkSoft: Color(red: 0.270588, green: 0.309804, blue: 0.372549),\n inkMuted: Color(red: 0.423529, green: 0.470588, blue: 0.537255),\n brand: Color(red: 0.141176, green: 0.388235, blue: 0.921569),\n brandSolid: Color(red: 0.078431, green: 0.321569, blue: 0.843137),\n brandSoft: Color(red: 0.941176, green: 0.960784, blue: 1.000000),\n onBrand: Color(red: 1.000000, green: 1.000000, blue: 1.000000),\n accent: Color(red: 0.086275, green: 0.635294, blue: 0.525490),\n success: Color(red: 0.105882, green: 0.615686, blue: 0.290196),\n warning: Color(red: 0.960784, green: 0.623529, blue: 0.039216),\n danger: Color(red: 0.937255, green: 0.262745, blue: 0.262745),\n onDanger: Color(red: 0.000000, green: 0.000000, blue: 0.000000)\n )\n\n public static let dark = LumenColorPalette(\n canvas: Color(red: 0.062745, green: 0.070588, blue: 0.098039),\n surface: Color(red: 0.090196, green: 0.101961, blue: 0.129412),\n surfaceMuted: Color(red: 0.125490, green: 0.141176, blue: 0.172549),\n surfaceStrong: Color(red: 0.188235, green: 0.207843, blue: 0.250980),\n line: Color(red: 0.223529, green: 0.247059, blue: 0.298039),\n ink: Color(red: 0.952941, green: 0.960784, blue: 0.968627),\n inkSoft: Color(red: 0.737255, green: 0.764706, blue: 0.803922),\n inkMuted: Color(red: 0.580392, green: 0.611765, blue: 0.658824),\n brand: Color(red: 0.160784, green: 0.705882, blue: 0.960784),\n brandSolid: Color(red: 0.031373, green: 0.478431, blue: 0.686275),\n brandSoft: Color(red: 0.027451, green: 0.207843, blue: 0.294118),\n onBrand: Color(red: 1.000000, green: 1.000000, blue: 1.000000),\n accent: Color(red: 0.101961, green: 0.756863, blue: 0.603922),\n success: Color(red: 0.141176, green: 0.760784, blue: 0.368627),\n warning: Color(red: 0.964706, green: 0.658824, blue: 0.137255),\n danger: Color(red: 0.945098, green: 0.356863, blue: 0.356863),\n onDanger: Color(red: 0.062745, green: 0.070588, blue: 0.098039)\n )\n}\n\npublic enum LumenSpacing {\n public static let zero: CGFloat = 0\n public static let xs: CGFloat = 4\n public static let sm: CGFloat = 8\n public static let md: CGFloat = 12\n public static let lg: CGFloat = 16\n public static let xl: CGFloat = 24\n public static let size2xl: CGFloat = 32\n public static let size3xl: CGFloat = 48\n}\n\npublic enum LumenRadius {\n public static let sm: CGFloat = 6\n public static let md: CGFloat = 10\n public static let lg: CGFloat = 12\n public static let full: CGFloat = 9999\n}\n\npublic enum LumenTypography {\n public static let xsSize: CGFloat = 12\n public static let smSize: CGFloat = 14\n public static let mdSize: CGFloat = 16\n public static let lgSize: CGFloat = 18\n public static let xlSize: CGFloat = 20\n public static let size2xlSize: CGFloat = 24\n public static let regularWeight: Font.Weight = .regular\n public static let mediumWeight: Font.Weight = .medium\n public static let semiboldWeight: Font.Weight = .semibold\n public static let boldWeight: Font.Weight = .bold\n}\n\npublic struct LumenCubicBezier: Equatable, Sendable {\n public let x1: Double\n public let y1: Double\n public let x2: Double\n public let y2: Double\n\n public init(x1: Double, y1: Double, x2: Double, y2: Double) {\n self.x1 = x1\n self.y1 = y1\n self.x2 = x2\n self.y2 = y2\n }\n}\n\npublic enum LumenMotion {\n public static let fastDuration: Double = 0.12\n public static let standardDuration: Double = 0.16\n public static let slowDuration: Double = 0.30\n public static let standardEasing = LumenCubicBezier(x1: 0.32, y1: 0.72, x2: 0, y2: 1)\n public static let emphasizedEasing = LumenCubicBezier(x1: 0.22, y1: 1, x2: 0.36, y2: 1)\n}\n\npublic enum LumenElevation {\n public static let resting: CGFloat = 1\n public static let raised: CGFloat = 3\n public static let overlay: CGFloat = 6\n}\n"
37134
+ "packages/swift/Sources/LumenUI/Tokens.generated.swift": "// This file is generated by scripts/generate-platform-tokens.mjs.\nimport SwiftUI\n\npublic struct LumenColorPalette: Sendable {\n public let canvas: Color\n public let surface: Color\n public let surfaceMuted: Color\n public let surfaceStrong: Color\n public let line: Color\n public let ink: Color\n public let inkSoft: Color\n public let inkMuted: Color\n public let brand: Color\n public let brandSolid: Color\n public let brandSoft: Color\n public let onBrand: Color\n public let accent: Color\n public let success: Color\n public let warning: Color\n public let danger: Color\n public let onDanger: Color\n\n public init(\n canvas: Color,\n surface: Color,\n surfaceMuted: Color,\n surfaceStrong: Color,\n line: Color,\n ink: Color,\n inkSoft: Color,\n inkMuted: Color,\n brand: Color,\n brandSolid: Color,\n brandSoft: Color,\n onBrand: Color,\n accent: Color,\n success: Color,\n warning: Color,\n danger: Color,\n onDanger: Color\n ) {\n self.canvas = canvas\n self.surface = surface\n self.surfaceMuted = surfaceMuted\n self.surfaceStrong = surfaceStrong\n self.line = line\n self.ink = ink\n self.inkSoft = inkSoft\n self.inkMuted = inkMuted\n self.brand = brand\n self.brandSolid = brandSolid\n self.brandSoft = brandSoft\n self.onBrand = onBrand\n self.accent = accent\n self.success = success\n self.warning = warning\n self.danger = danger\n self.onDanger = onDanger\n }\n}\n\npublic enum LumenColors {\n public static let light = LumenColorPalette(\n canvas: Color(red: 1.000000, green: 1.000000, blue: 1.000000),\n surface: Color(red: 1.000000, green: 1.000000, blue: 1.000000),\n surfaceMuted: Color(red: 0.952941, green: 0.956863, blue: 0.964706),\n surfaceStrong: Color(red: 0.898039, green: 0.905882, blue: 0.921569),\n line: Color(red: 0.843137, green: 0.854902, blue: 0.878431),\n ink: Color(red: 0.058824, green: 0.090196, blue: 0.160784),\n inkSoft: Color(red: 0.270588, green: 0.309804, blue: 0.372549),\n inkMuted: Color(red: 0.423529, green: 0.470588, blue: 0.537255),\n brand: Color(red: 0.141176, green: 0.388235, blue: 0.921569),\n brandSolid: Color(red: 0.078431, green: 0.321569, blue: 0.843137),\n brandSoft: Color(red: 0.941176, green: 0.960784, blue: 1.000000),\n onBrand: Color(red: 1.000000, green: 1.000000, blue: 1.000000),\n accent: Color(red: 0.086275, green: 0.635294, blue: 0.525490),\n success: Color(red: 0.105882, green: 0.615686, blue: 0.290196),\n warning: Color(red: 0.960784, green: 0.623529, blue: 0.039216),\n danger: Color(red: 0.937255, green: 0.262745, blue: 0.262745),\n onDanger: Color(red: 0.000000, green: 0.000000, blue: 0.000000)\n )\n\n public static let dark = LumenColorPalette(\n canvas: Color(red: 0.062745, green: 0.070588, blue: 0.098039),\n surface: Color(red: 0.090196, green: 0.101961, blue: 0.129412),\n surfaceMuted: Color(red: 0.125490, green: 0.141176, blue: 0.172549),\n surfaceStrong: Color(red: 0.188235, green: 0.207843, blue: 0.250980),\n line: Color(red: 0.223529, green: 0.247059, blue: 0.298039),\n ink: Color(red: 0.952941, green: 0.960784, blue: 0.968627),\n inkSoft: Color(red: 0.737255, green: 0.764706, blue: 0.803922),\n inkMuted: Color(red: 0.580392, green: 0.611765, blue: 0.658824),\n brand: Color(red: 0.160784, green: 0.705882, blue: 0.960784),\n brandSolid: Color(red: 0.031373, green: 0.478431, blue: 0.686275),\n brandSoft: Color(red: 0.027451, green: 0.207843, blue: 0.294118),\n onBrand: Color(red: 1.000000, green: 1.000000, blue: 1.000000),\n accent: Color(red: 0.101961, green: 0.756863, blue: 0.603922),\n success: Color(red: 0.141176, green: 0.760784, blue: 0.368627),\n warning: Color(red: 0.964706, green: 0.658824, blue: 0.137255),\n danger: Color(red: 0.945098, green: 0.356863, blue: 0.356863),\n onDanger: Color(red: 0.062745, green: 0.070588, blue: 0.098039)\n )\n}\n\npublic enum LumenSpacing {\n public static let zero: CGFloat = 0\n public static let xs: CGFloat = 4\n public static let sm: CGFloat = 8\n public static let md: CGFloat = 12\n public static let lg: CGFloat = 16\n public static let xl: CGFloat = 24\n public static let size2xl: CGFloat = 32\n public static let size3xl: CGFloat = 48\n}\n\npublic enum LumenRadius {\n public static let sm: CGFloat = 6\n public static let md: CGFloat = 10\n public static let lg: CGFloat = 12\n public static let full: CGFloat = 9999\n}\n\npublic enum LumenTypography {\n public static let xsSize: CGFloat = 12\n public static let smSize: CGFloat = 14\n public static let mdSize: CGFloat = 16\n public static let lgSize: CGFloat = 18\n public static let xlSize: CGFloat = 20\n public static let size2xlSize: CGFloat = 24\n public static let regularWeight: Font.Weight = .regular\n public static let mediumWeight: Font.Weight = .medium\n public static let semiboldWeight: Font.Weight = .semibold\n public static let boldWeight: Font.Weight = .bold\n}\n\npublic struct LumenCubicBezier: Equatable, Sendable {\n public let x1: Double\n public let y1: Double\n public let x2: Double\n public let y2: Double\n\n public init(x1: Double, y1: Double, x2: Double, y2: Double) {\n self.x1 = x1\n self.y1 = y1\n self.x2 = x2\n self.y2 = y2\n }\n}\n\npublic enum LumenMotion {\n public static let fastDuration: Double = 0.12\n public static let standardDuration: Double = 0.16\n public static let slowDuration: Double = 0.30\n public static let standardEasing = LumenCubicBezier(x1: 0.32, y1: 0.72, x2: 0, y2: 1)\n public static let emphasizedEasing = LumenCubicBezier(x1: 0.22, y1: 1, x2: 0.36, y2: 1)\n}\n\npublic enum LumenElevation {\n public static let resting: CGFloat = 1\n public static let raised: CGFloat = 3\n public static let overlay: CGFloat = 6\n}\n"
36855
37135
  }
36856
37136
  },
36857
37137
  "recipes": [