@santi020k/lumen-mcp 1.3.0 → 1.5.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.
@@ -9,6 +9,7 @@
9
9
  "Attachment": "8b6f84195c49932d047bee9a6ed76fc5bd708d99eea2609ca7a6cd702dbcf9d4",
10
10
  "Autocomplete": "b89d75014a5bad6fe8218ec0ea56af5f50bb1c4b1d7741342539e1835e9b97ea",
11
11
  "Avatar": "311767692b20b03119196ef27210d585c7424ac4f92ed5e71cdc59cfae979c58",
12
+ "Backdrop": "07df835d13a0b94064c4a354cd624023c047d05b0b450d5c2e2fed4a7ceac279",
12
13
  "BackToTop": "48da5a02c213cdd20dfc21301937982108a100c9364b3710893ab3b65aba3c1f",
13
14
  "Badge": "853992e0fe8baca97426399ccfbc492c3384001928b705363416b937bf03d9fa",
14
15
  "BarChart": "077260e2ffe472e8e6efd995dbbd9bb60a19b7aec1f1391ef2df30f3852a86de",
@@ -53,9 +54,11 @@
53
54
  "FloatingBadge": "04f46638085a6ab1f6aead011a9a71c1dc53a0e771437b0e69a06dd4b6c17403",
54
55
  "Form": "00180eb0c646100cff0a1ba1ca1ec9f4fc80cb064d02e5bb285888a158de37c1",
55
56
  "FormattedDate": "53db4de0b1c4503e458638824bdf000c3254463c76d3d701791062e30255e2c8",
57
+ "Graphic": "e0aee1756b667569a51df051bbe62f52db2ee1317a82e2ebc00c8375f8944075",
56
58
  "HoverCard": "fc6d0d3f45211f1f1bc7628a601a95ddc76839d980bd1e01478a6b6318f103b4",
57
59
  "Icon": "f55d57961c7d46f2d865777ab59150893ac1bf16d8a1f873b45eede1376cf261",
58
60
  "Image": "0da04cc0862b901c4091c42b471026352017618692e6b528d597a06e07d41a38",
61
+ "Illustration": "81f92665df919bd9097233f65df61260a68ae2926f041d12c3f3e2879162d447",
59
62
  "Input": "16b77d4c44257612108d2790296f9f43813aaa3a7a903b3bf2d8f62a46074705",
60
63
  "InputGroup": "1e77e49544647c9aa14c1f8fa3b6b9be56fed7064c296e46ef53190b88bf8f80",
61
64
  "InputOTP": "b0b1b7a6e4d61d7dd24360fd8393b3d9129408e97227df6be9d6d2f807f2775b",
@@ -156,47 +159,68 @@
156
159
  "VisuallyHidden": "17808de2f795be62dbeb1f8c649fedd7dda5acbd8af3bbc1d1bbec20e788e3b3"
157
160
  },
158
161
  "nativeComponents": {
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",
162
+ "Theme": "d842af0deb21b8cda2e0174a7ddb430f4a4221b3367831eafe5577033e3ad7e7",
163
+ "Text": "a0ec8310022ffbce337e6438cd1b578970ba079dd0f869c3b19a271685240cbd",
164
+ "Surface": "d9e04e1d8c8b587bbb8ca4a34778dc1ed0bd28cb711b1a1d80cc28490fde80dd",
165
+ "Icon": "cc7097cde28d269c99574b10cae8ff0e505df242ecc58ba5b00b9412172658c3",
166
+ "Icon button": "fa306c12c2f8dda4f4c6d333dc8043574d85a0f2409c8c68126eb2152700f631",
167
+ "Button": "bb139a12f9f3cef1cadd347d33aa6a7e0192ebe5c90c785a1767cd831aa7565b",
168
+ "Text field": "8492a9b49db28d731ad29faf9cfbe1d6f2aa3b650302f9270698f60741760a6d",
169
+ "Badge": "93cadf1f9d49fcb46b24149301ff34e6180f30079c5209fa27ba44eb810f1fa8",
170
+ "Divider": "96f26b9bcc7118748598f15b256f551aa9645520e517111bd127bfbc6513a56d",
171
+ "Spinner": "66df46d36c01aa11214d764830e6490232ff70311e9f16987be60739b112f799",
172
+ "Card": "ca307cd01d6f1e2f199335e8a620a923869df4ec2c22329821ca7767705495af",
173
+ "Alert": "135cd171d5f98b4f3291f0da242b22e032fd06fe7199cfc9ef0e2c26cb7b0b4c",
174
+ "Progress": "9b2201033ebfcc87ca6548e89501aa965b46281854830bd42ce9f96a6c195fc0",
175
+ "Skeleton": "1a434c444602b1ceff149e995056b9e48bec516daa567feb77b69d677aaf6e83",
176
+ "Disclosure": "ef9284c05a877ec8726f6e4669d825668be871bc39a9c7d1ddb872aabe39140b",
177
+ "Avatar": "f9204d30580573f5e4dfd24c9606b558b57ed5858edb84687b9769a445b74b82",
178
+ "Textarea": "fdcfce4f52590b51f44865ce30d58a5a1a797b6307608fe8758327a5b4d33daa",
179
+ "Field group": "ee06223db232ff00a35e9162379cb4026b80ba49e4e5630783303e3fcf6d466f",
180
+ "Chip": "58efbce779f98e5e782f958acebabd54ff3a3cc53f1824491d828f36b6b7ad53",
181
+ "Button group": "e104fb26def3551a226dd2a8a17063feecf41ce7363f40ef0e69385d632af374",
182
+ "Toast": "cab5d2c2751c897dc678ce2bd9c71af6ddbdac409ad615864f88853c1e0cee57",
180
183
  "Toggle": "b2e7e0723a55a6118cf20f978a4f880d814440107827e509b11e8c3c92b00f4b",
181
184
  "Settings row": "8bc392c37d446ba1ed276556ac57910335d8dab7bc013e56fc9383819a48baa5",
182
185
  "Picker": "ed96336bb1fec3088a1aafebfcb7f0091352de1d47c666c0c54b67ebb3a472c0",
183
186
  "Slider": "95d3f6367e952633325dc1214a240424b0299fa4ce1457eadcbc995489ed0828",
184
187
  "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",
188
+ "Checkbox": "22028a1e1a7517fb2bdd6962eae040d067827c3be53af242cc551fbdb4996c79",
189
+ "Radio group": "b2098339feb47b9fe572bd888b91b256fa9f4ee0a7a50302a604e36108c2104b",
190
+ "Segmented control": "92f975ee8211ebf1a86d7e372e0d1f1cfd39d45f33f9bb8a43167c82becb5f9d",
191
+ "Navigation bar": "0ea9eaa8d6e90c55c65623fc7ab72591858416487691bb87c3330242a9fe0f4b",
192
+ "Empty state": "134a0cd1c653e5f3b07378ceb11db7987c97259221d283e589bc4c12deb7f1a0",
193
+ "List row": "fe64c26bd290a8664330fc7c7ec1aa6804d039b2a127678a7b1e43c83e922f6b",
194
+ "Banner": "1d7e36125b6700cd1dd6fe643f3a84f41cc4bd87e24e9e5a8739491beef332d1",
195
+ "Stat": "d41b2004c65666060e279694d9fa3be1cf9aa35d46cb4d2d80ceffdbf78278f6",
192
196
  "Gauge": "d8bb93ae076c2fe70e2dbc149cf6d99bdb2199c176dff0f6ccd0016eb1b62b49",
193
- "Section header": "dfdadc03ca5d89b0e19d197edce1e2d8e83e2d39381aa7dcbc328b7d53d52b7c",
194
- "Status bar": "a1b36ae346299e01bcac31b852aa1cf84d46aef3fda333b321b2610e7792a289",
195
- "Refresh control": "37a3e0a9d085780b9d3dead61fee2849108ae4f33e6d0814ddf680c4f5829b7c",
196
- "Date field": "2397d6907ac230497077d3b388b330fc43680f6e7cadd2faef3480a71d719ec3",
197
- "Floating action button": "614a972743528ef2df98f9a64281eef4cbed1725c42b8c86da534645be15e8f6",
197
+ "Section header": "4cbcc77c23f5626d80a675ac5cd0d6fd46328ecdd7fc579735dcf88fbeb313ad",
198
+ "Status bar": "781566e2fc6239bddf46dc6b5d5d23fc28af008f1b8b903c3bc03dba60c4e798",
199
+ "Collapsible navigation bar": "dca8d0fb545832bb6972198e1faf4db540d92c5b550bb77c94953849fd1d72cb",
200
+ "Navigation accessory": "56b7905d0265e5d89f509294090f9a7a15b8fceccca9b4607b407b3e2229e4c6",
201
+ "Navigation bar scroll behavior": "32617167b0c8411442223810609b17929558ca623a03ff4f19c3bf846e55abda",
202
+ "Navigation bar accessory": "c67dac67188494302cbd99700f57074ca6a022498121b2c366a39c06b7a89bbc",
203
+ "Adaptive navigation scaffold": "4360aa0c46eb761bd93c0d03e614321e30ce791d12982b064c2e7dc55aa37f89",
204
+ "Refresh control": "cc40b1e03afb06de40e0c4fdb01c4d40113f92823fdb91c2135fed706878a2ee",
205
+ "Date field": "64fcae6b6339db3f57c072d0e183d5c93e1b95759a600a3a345ee76d9a0dfdcc",
206
+ "Floating action button": "52eadc73e99b66fed25009e03812f0b06168de8539ced006c5b5eea8b6f7a98c",
207
+ "Link": "80389c18ef528e7b0f32c34b2ccd7464bbec84444192d96bfd3b10410ffd8790",
208
+ "Tab bar minimization": "5306ad157bc439c9813701e63a8ddea7ac53f9fa5ad5d1c593f5a5a71d4fc59e",
209
+ "Tab accessory": "d9fe38854a63220e6d8f2ba8c28ecd847069ea3469f146060fa2f920b431c329",
198
210
  "Shortcut recorder": "a5a2fa237d2efe604431bc35d46f063328fdc0fd8c0485d53d1692b1f0a76926",
199
- "Symbol picker": "57b3383a74549aa62dd97dfa627464818e4b65d5b8c130ecb3697059668fbb27"
211
+ "Symbol picker": "57b3383a74549aa62dd97dfa627464818e4b65d5b8c130ecb3697059668fbb27",
212
+ "Graphic": "284672046f3a3d5e83834aca7f2847d52dff2a47c81da2ee2e927a3477eb24be",
213
+ "Backdrop": "65d9bf433bbaee2402bbfe1518d593df36fc6d0197f29c69a3ec68380a5598b4",
214
+ "Illustration": "ac5ade1e6774c32628e4c67029e47e234cffa2499a29535b619efd611751ec4b",
215
+ "Alert dialog": "bf28384464575d8c01120ffc1f97e8d1af6bfc3e0fa75fcc0a729633fa78f33b",
216
+ "Sheet": "64571d907efb9e125411ae5557a94bc2cd51ad10af1baaaa0411fb06223e6748",
217
+ "Menu": "19db0b34a54f2a298dcb7a74e09dd0e803639a82d53548ce216c8647bcc3a6ba",
218
+ "Share button": "b756d1f682b85e6a2a430565343774a66cf95047e2d702d837f0f5e9fc0c6c19",
219
+ "Wearable action": "d033694a60052eeaa5a6ad6fddf438dc67647b281ccf5ad11e0b58fbcf3bea52",
220
+ "Wearable progress": "2b0d4fc506d6ac626b77086212f2358ad2f90ab9c15b84125338a7da07995193",
221
+ "Wearable status": "a81185ab80524b0ab2d0e648d52c5f85606598c5e767693122f242f868a5924f",
222
+ "Wearable metric": "5e1f0850333a1997a0322ae5937ee0e43e24ef0acc598227d067d2639aff5f7d",
223
+ "Wearable list row": "56d6e690bb2ccb84037c6580e96ab7dcca44f2b8e3812d6105a91f85acd2ab67"
200
224
  },
201
225
  "recipes": {
202
226
  "all-components": "517dfb03b8ca5483f5412a00ca2a151763d8d4429a44e398d4d74733793f0fd7",
@@ -1764,6 +1788,204 @@
1764
1788
  ],
1765
1789
  "runtimeEvents": []
1766
1790
  },
1791
+ {
1792
+ "apiReference": [
1793
+ {
1794
+ "attribute": "variant",
1795
+ "defaultValue": "\"aurora\"",
1796
+ "description": "Selects the ambient background pattern.",
1797
+ "values": "\"aurora\" | \"dots\" | \"grid\" | \"rays\""
1798
+ },
1799
+ {
1800
+ "attribute": "tone",
1801
+ "defaultValue": "\"brand\"",
1802
+ "description": "Uses a semantic Lumen color for the pattern.",
1803
+ "values": "\"brand\" | \"accent\" | \"neutral\""
1804
+ },
1805
+ {
1806
+ "attribute": "intensity",
1807
+ "defaultValue": "\"medium\"",
1808
+ "description": "Controls decoration opacity without fading child content.",
1809
+ "values": "\"subtle\" | \"medium\" | \"strong\""
1810
+ },
1811
+ {
1812
+ "attribute": "children",
1813
+ "defaultValue": "required",
1814
+ "description": "Keeps meaningful content above the decorative layer.",
1815
+ "values": "rendered content"
1816
+ },
1817
+ {
1818
+ "attribute": "class, className",
1819
+ "defaultValue": "\"\"",
1820
+ "description": "Merges custom classes with the generated ui-* root classes.",
1821
+ "values": "string"
1822
+ },
1823
+ {
1824
+ "attribute": "...native attributes",
1825
+ "defaultValue": "-",
1826
+ "description": "Forwards standard attributes to the root element unless the component consumes them.",
1827
+ "values": "HTML attributes"
1828
+ }
1829
+ ],
1830
+ "astroSource": "---\nimport type { HTMLAttributes } from 'astro/types'\n\nimport { resolveLumenAstroProps } from '@santi020k/lumen-core'\n\ninterface Props extends HTMLAttributes<'div'> {\n class?: string | null\n className?: string\n intensity?: 'medium' | 'strong' | 'subtle'\n tone?: 'accent' | 'brand' | 'neutral'\n variant?: 'aurora' | 'dots' | 'grid' | 'rays'\n}\n\nconst {\n class: classProp = '',\n className = '',\n intensity = 'medium',\n tone = 'brand',\n variant = 'aurora',\n ...rest\n} = Astro.props\n\nconst { classList, passthrough } = resolveLumenAstroProps(rest, [\n 'ui-backdrop',\n `ui-backdrop--${variant}`,\n `ui-backdrop--${tone}`,\n `ui-backdrop--${intensity}`\n], classProp, className)\n---\n\n<div class:list={classList} {...passthrough}>\n <div class=\"ui-backdrop__content\">\n <slot />\n </div>\n</div>\n",
1831
+ "category": "Brand",
1832
+ "collections": [],
1833
+ "dependencies": [
1834
+ "styles"
1835
+ ],
1836
+ "description": "Adds a portable ambient pattern behind meaningful content.",
1837
+ "behavior": {
1838
+ "astro": "none",
1839
+ "elements": "registered-element",
1840
+ "react": "component"
1841
+ },
1842
+ "files": [
1843
+ "packages/astro/components/Backdrop.astro",
1844
+ "packages/astro/styles/lumen.css",
1845
+ "packages/react/src/components.tsx"
1846
+ ],
1847
+ "frameworkDetails": {
1848
+ "astro": {
1849
+ "available": true,
1850
+ "example": "<Backdrop intensity=\"medium\" tone=\"brand\" variant=\"aurora\"><Card variant=\"glass\">Project overview</Card></Backdrop>",
1851
+ "importStatement": "import { Backdrop, Card } from '@santi020k/lumen-astro'",
1852
+ "language": "astro",
1853
+ "packageName": "@santi020k/lumen-astro",
1854
+ "props": [
1855
+ {
1856
+ "name": "class",
1857
+ "optional": true,
1858
+ "type": "string | null"
1859
+ },
1860
+ {
1861
+ "name": "className",
1862
+ "optional": true,
1863
+ "type": "string"
1864
+ },
1865
+ {
1866
+ "name": "intensity",
1867
+ "optional": true,
1868
+ "type": "'medium' | 'strong' | 'subtle'"
1869
+ },
1870
+ {
1871
+ "name": "tone",
1872
+ "optional": true,
1873
+ "type": "'accent' | 'brand' | 'neutral'"
1874
+ },
1875
+ {
1876
+ "name": "variant",
1877
+ "optional": true,
1878
+ "type": "'aurora' | 'dots' | 'grid' | 'rays'"
1879
+ }
1880
+ ],
1881
+ "propsExtends": "HTMLAttributes<'div'>",
1882
+ "source": "---\nimport type { HTMLAttributes } from 'astro/types'\n\nimport { resolveLumenAstroProps } from '@santi020k/lumen-core'\n\ninterface Props extends HTMLAttributes<'div'> {\n class?: string | null\n className?: string\n intensity?: 'medium' | 'strong' | 'subtle'\n tone?: 'accent' | 'brand' | 'neutral'\n variant?: 'aurora' | 'dots' | 'grid' | 'rays'\n}\n\nconst {\n class: classProp = '',\n className = '',\n intensity = 'medium',\n tone = 'brand',\n variant = 'aurora',\n ...rest\n} = Astro.props\n\nconst { classList, passthrough } = resolveLumenAstroProps(rest, [\n 'ui-backdrop',\n `ui-backdrop--${variant}`,\n `ui-backdrop--${tone}`,\n `ui-backdrop--${intensity}`\n], classProp, className)\n---\n\n<div class:list={classList} {...passthrough}>\n <div class=\"ui-backdrop__content\">\n <slot />\n </div>\n</div>\n",
1883
+ "styleImport": "import '@santi020k/lumen-astro/styles.css'"
1884
+ },
1885
+ "elements": {
1886
+ "attributes": [
1887
+ "intensity",
1888
+ "tone",
1889
+ "variant",
1890
+ "children",
1891
+ "class, className",
1892
+ "...native attributes"
1893
+ ],
1894
+ "available": true,
1895
+ "behavior": {
1896
+ "mode": "registration",
1897
+ "setup": "Call defineLumenElements() once before rendering Lumen custom elements."
1898
+ },
1899
+ "example": "<lumen-backdrop intensity=\"medium\" tone=\"brand\" variant=\"aurora\"><lumen-card variant=\"glass\">Project overview</lumen-card></lumen-backdrop>",
1900
+ "importStatement": "import { defineLumenElements } from '@santi020k/lumen-elements/define'",
1901
+ "language": "html",
1902
+ "packageName": "@santi020k/lumen-elements",
1903
+ "props": [],
1904
+ "propsExtends": "HTMLElement attributes",
1905
+ "registration": "defineLumenElements()",
1906
+ "source": "Backdrop: {\n attributeClasses: {\n intensity: {\n medium: 'ui-backdrop--medium',\n strong: 'ui-backdrop--strong',\n subtle: 'ui-backdrop--subtle'\n },\n tone: {\n accent: 'ui-backdrop--accent',\n brand: 'ui-backdrop--brand',\n neutral: 'ui-backdrop--neutral'\n },\n variant: {\n aurora: 'ui-backdrop--aurora',\n dots: 'ui-backdrop--dots',\n grid: 'ui-backdrop--grid',\n rays: 'ui-backdrop--rays'\n }\n },\n baseClassName: 'ui-backdrop',\n defaults: { intensity: 'medium', tone: 'brand', variant: 'aurora' },\n tagName: 'lumen-backdrop'\n }",
1907
+ "styleImport": "import '@santi020k/lumen-elements/styles.css'",
1908
+ "tagName": "lumen-backdrop"
1909
+ },
1910
+ "react": {
1911
+ "available": true,
1912
+ "example": "<Backdrop intensity=\"medium\" tone=\"brand\" variant=\"aurora\"><Card variant=\"glass\">Project overview</Card></Backdrop>",
1913
+ "importStatement": "import { Backdrop, Card } from '@santi020k/lumen-react'",
1914
+ "language": "tsx",
1915
+ "packageName": "@santi020k/lumen-react",
1916
+ "props": [
1917
+ {
1918
+ "name": "intensity",
1919
+ "optional": true,
1920
+ "type": "'medium' | 'strong' | 'subtle'"
1921
+ },
1922
+ {
1923
+ "name": "tone",
1924
+ "optional": true,
1925
+ "type": "'accent' | 'brand' | 'neutral'"
1926
+ },
1927
+ {
1928
+ "name": "variant",
1929
+ "optional": true,
1930
+ "type": "'aurora' | 'dots' | 'grid' | 'rays'"
1931
+ }
1932
+ ],
1933
+ "propsExtends": "ComponentPropsWithoutRef<'div'>",
1934
+ "source": "export interface BackdropProps extends ComponentPropsWithoutRef<'div'> {\n intensity?: 'medium' | 'strong' | 'subtle'\n tone?: 'accent' | 'brand' | 'neutral'\n variant?: 'aurora' | 'dots' | 'grid' | 'rays'\n}\n\nexport const Backdrop = ({\n children,\n className,\n intensity = 'medium',\n tone = 'brand',\n variant = 'aurora',\n ...props\n}: BackdropProps) => (\n <div\n className={composeClassName(\n 'ui-backdrop',\n `ui-backdrop--${variant}`,\n `ui-backdrop--${tone}`,\n `ui-backdrop--${intensity}`,\n className\n )}\n {...props}\n >\n <div className=\"ui-backdrop__content\">{children}</div>\n </div>\n)",
1935
+ "styleImport": "import '@santi020k/lumen-react/styles.css'"
1936
+ }
1937
+ },
1938
+ "frameworks": {
1939
+ "astro": true,
1940
+ "elements": true,
1941
+ "react": true
1942
+ },
1943
+ "guidance": null,
1944
+ "kebab": "backdrop",
1945
+ "keyboardInteractions": [],
1946
+ "keywords": [
1947
+ "adds",
1948
+ "ambient",
1949
+ "backdrop",
1950
+ "behind",
1951
+ "brand",
1952
+ "content",
1953
+ "meaningful",
1954
+ "pattern",
1955
+ "portable"
1956
+ ],
1957
+ "name": "Backdrop",
1958
+ "props": [
1959
+ {
1960
+ "name": "class",
1961
+ "optional": true,
1962
+ "type": "string | null"
1963
+ },
1964
+ {
1965
+ "name": "className",
1966
+ "optional": true,
1967
+ "type": "string"
1968
+ },
1969
+ {
1970
+ "name": "intensity",
1971
+ "optional": true,
1972
+ "type": "'medium' | 'strong' | 'subtle'"
1973
+ },
1974
+ {
1975
+ "name": "tone",
1976
+ "optional": true,
1977
+ "type": "'accent' | 'brand' | 'neutral'"
1978
+ },
1979
+ {
1980
+ "name": "variant",
1981
+ "optional": true,
1982
+ "type": "'aurora' | 'dots' | 'grid' | 'rays'"
1983
+ }
1984
+ ],
1985
+ "propsExtends": "HTMLAttributes<'div'>",
1986
+ "recipes": [],
1987
+ "runtimeEvents": []
1988
+ },
1767
1989
  {
1768
1990
  "apiReference": [
1769
1991
  {
@@ -11088,6 +11310,222 @@
11088
11310
  "recipes": [],
11089
11311
  "runtimeEvents": []
11090
11312
  },
11313
+ {
11314
+ "apiReference": [
11315
+ {
11316
+ "attribute": "variant",
11317
+ "defaultValue": "\"orbit\"",
11318
+ "description": "Selects a portable decorative pattern around application-provided artwork.",
11319
+ "values": "\"glow\" | \"grid\" | \"orbit\""
11320
+ },
11321
+ {
11322
+ "attribute": "tone",
11323
+ "defaultValue": "\"brand\"",
11324
+ "description": "Uses the matching semantic Lumen color without introducing a product palette.",
11325
+ "values": "\"brand\" | \"accent\" | \"neutral\""
11326
+ },
11327
+ {
11328
+ "attribute": "size",
11329
+ "defaultValue": "\"md\"",
11330
+ "description": "Uses the shared 160, 240, or 320 unit composition size.",
11331
+ "values": "\"sm\" | \"md\" | \"lg\""
11332
+ },
11333
+ {
11334
+ "attribute": "label",
11335
+ "defaultValue": "-",
11336
+ "description": "Exposes the complete composition as one named image; without it the graphic is decorative.",
11337
+ "values": "string"
11338
+ },
11339
+ {
11340
+ "attribute": "class, className",
11341
+ "defaultValue": "\"\"",
11342
+ "description": "Merges custom classes with the generated ui-* root classes.",
11343
+ "values": "string"
11344
+ },
11345
+ {
11346
+ "attribute": "...native attributes",
11347
+ "defaultValue": "-",
11348
+ "description": "Forwards standard attributes to the root element unless the component consumes them.",
11349
+ "values": "HTML attributes"
11350
+ }
11351
+ ],
11352
+ "astroSource": "---\nimport type { HTMLAttributes } from 'astro/types'\n\nimport { resolveLumenAstroProps } from '@santi020k/lumen-core'\n\ninterface Props extends HTMLAttributes<'div'> {\n class?: string | null\n className?: string\n label?: string\n size?: 'lg' | 'md' | 'sm'\n tone?: 'accent' | 'brand' | 'neutral'\n variant?: 'glow' | 'grid' | 'orbit'\n}\n\nconst {\n class: classProp = '',\n className = '',\n label,\n size = 'md',\n tone = 'brand',\n variant = 'orbit',\n ...rest\n} = Astro.props\n\nconst { classList, passthrough } = resolveLumenAstroProps(rest, [\n 'ui-graphic',\n `ui-graphic--${variant}`,\n `ui-graphic--${tone}`,\n `ui-graphic--${size}`\n], classProp, className)\n---\n\n<div\n aria-hidden={label ? undefined : 'true'}\n aria-label={label}\n class:list={classList}\n role={label ? 'img' : undefined}\n {...passthrough}\n>\n <div class=\"ui-graphic__content\">\n <slot />\n </div>\n</div>\n",
11353
+ "category": "Brand",
11354
+ "collections": [],
11355
+ "dependencies": [
11356
+ "styles"
11357
+ ],
11358
+ "description": "Frames product-owned artwork with portable glow, grid, or orbit treatments.",
11359
+ "behavior": {
11360
+ "astro": "none",
11361
+ "elements": "registered-element",
11362
+ "react": "component"
11363
+ },
11364
+ "files": [
11365
+ "packages/astro/components/Graphic.astro",
11366
+ "packages/astro/styles/lumen.css",
11367
+ "packages/react/src/components.tsx"
11368
+ ],
11369
+ "frameworkDetails": {
11370
+ "astro": {
11371
+ "available": true,
11372
+ "example": "<Graphic size=\"lg\" tone=\"accent\" variant=\"orbit\"><Icon name=\"sparkles\" decorative size=\"xl\" /></Graphic>",
11373
+ "importStatement": "import { Graphic, Icon } from '@santi020k/lumen-astro'",
11374
+ "language": "astro",
11375
+ "packageName": "@santi020k/lumen-astro",
11376
+ "props": [
11377
+ {
11378
+ "name": "class",
11379
+ "optional": true,
11380
+ "type": "string | null"
11381
+ },
11382
+ {
11383
+ "name": "className",
11384
+ "optional": true,
11385
+ "type": "string"
11386
+ },
11387
+ {
11388
+ "name": "label",
11389
+ "optional": true,
11390
+ "type": "string"
11391
+ },
11392
+ {
11393
+ "name": "size",
11394
+ "optional": true,
11395
+ "type": "'lg' | 'md' | 'sm'"
11396
+ },
11397
+ {
11398
+ "name": "tone",
11399
+ "optional": true,
11400
+ "type": "'accent' | 'brand' | 'neutral'"
11401
+ },
11402
+ {
11403
+ "name": "variant",
11404
+ "optional": true,
11405
+ "type": "'glow' | 'grid' | 'orbit'"
11406
+ }
11407
+ ],
11408
+ "propsExtends": "HTMLAttributes<'div'>",
11409
+ "source": "---\nimport type { HTMLAttributes } from 'astro/types'\n\nimport { resolveLumenAstroProps } from '@santi020k/lumen-core'\n\ninterface Props extends HTMLAttributes<'div'> {\n class?: string | null\n className?: string\n label?: string\n size?: 'lg' | 'md' | 'sm'\n tone?: 'accent' | 'brand' | 'neutral'\n variant?: 'glow' | 'grid' | 'orbit'\n}\n\nconst {\n class: classProp = '',\n className = '',\n label,\n size = 'md',\n tone = 'brand',\n variant = 'orbit',\n ...rest\n} = Astro.props\n\nconst { classList, passthrough } = resolveLumenAstroProps(rest, [\n 'ui-graphic',\n `ui-graphic--${variant}`,\n `ui-graphic--${tone}`,\n `ui-graphic--${size}`\n], classProp, className)\n---\n\n<div\n aria-hidden={label ? undefined : 'true'}\n aria-label={label}\n class:list={classList}\n role={label ? 'img' : undefined}\n {...passthrough}\n>\n <div class=\"ui-graphic__content\">\n <slot />\n </div>\n</div>\n",
11410
+ "styleImport": "import '@santi020k/lumen-astro/styles.css'"
11411
+ },
11412
+ "elements": {
11413
+ "attributes": [
11414
+ "size",
11415
+ "tone",
11416
+ "variant",
11417
+ "label",
11418
+ "class, className",
11419
+ "...native attributes"
11420
+ ],
11421
+ "available": true,
11422
+ "behavior": {
11423
+ "mode": "registration",
11424
+ "setup": "Call defineLumenElements() once before rendering Lumen custom elements."
11425
+ },
11426
+ "example": "<lumen-graphic size=\"lg\" tone=\"accent\" variant=\"orbit\"><lumen-icon name=\"sparkles\" decorative size=\"xl\"></lumen-icon></lumen-graphic>",
11427
+ "importStatement": "import { defineLumenElements } from '@santi020k/lumen-elements/define'",
11428
+ "language": "html",
11429
+ "packageName": "@santi020k/lumen-elements",
11430
+ "props": [],
11431
+ "propsExtends": "HTMLElement attributes",
11432
+ "registration": "defineLumenElements()",
11433
+ "source": "Graphic: {\n attributeClasses: {\n size: {\n lg: 'ui-graphic--lg',\n md: 'ui-graphic--md',\n sm: 'ui-graphic--sm'\n },\n tone: {\n accent: 'ui-graphic--accent',\n brand: 'ui-graphic--brand',\n neutral: 'ui-graphic--neutral'\n },\n variant: {\n glow: 'ui-graphic--glow',\n grid: 'ui-graphic--grid',\n orbit: 'ui-graphic--orbit'\n }\n },\n baseClassName: 'ui-graphic',\n defaults: { size: 'md', tone: 'brand', variant: 'orbit' },\n tagName: 'lumen-graphic'\n }",
11434
+ "styleImport": "import '@santi020k/lumen-elements/styles.css'",
11435
+ "tagName": "lumen-graphic"
11436
+ },
11437
+ "react": {
11438
+ "available": true,
11439
+ "example": "<Graphic size=\"lg\" tone=\"accent\" variant=\"orbit\"><Icon name=\"sparkles\" decorative size=\"xl\" /></Graphic>",
11440
+ "importStatement": "import { Graphic, Icon } from '@santi020k/lumen-react'",
11441
+ "language": "tsx",
11442
+ "packageName": "@santi020k/lumen-react",
11443
+ "props": [
11444
+ {
11445
+ "name": "label",
11446
+ "optional": true,
11447
+ "type": "string"
11448
+ },
11449
+ {
11450
+ "name": "size",
11451
+ "optional": true,
11452
+ "type": "'lg' | 'md' | 'sm'"
11453
+ },
11454
+ {
11455
+ "name": "tone",
11456
+ "optional": true,
11457
+ "type": "'accent' | 'brand' | 'neutral'"
11458
+ },
11459
+ {
11460
+ "name": "variant",
11461
+ "optional": true,
11462
+ "type": "'glow' | 'grid' | 'orbit'"
11463
+ }
11464
+ ],
11465
+ "propsExtends": "ComponentPropsWithoutRef<'div'>",
11466
+ "source": "export interface GraphicProps extends ComponentPropsWithoutRef<'div'> {\n label?: string\n size?: 'lg' | 'md' | 'sm'\n tone?: 'accent' | 'brand' | 'neutral'\n variant?: 'glow' | 'grid' | 'orbit'\n}\n\nexport const Graphic = ({\n children,\n className,\n label,\n size = 'md',\n tone = 'brand',\n variant = 'orbit',\n ...props\n}: GraphicProps) => (\n <div\n aria-hidden={label ? undefined : true}\n aria-label={label}\n className={composeClassName(\n 'ui-graphic',\n `ui-graphic--${variant}`,\n `ui-graphic--${tone}`,\n `ui-graphic--${size}`,\n className\n )}\n role={label ? 'img' : undefined}\n {...props}\n >\n <div className=\"ui-graphic__content\">{children}</div>\n </div>\n)",
11467
+ "styleImport": "import '@santi020k/lumen-react/styles.css'"
11468
+ }
11469
+ },
11470
+ "frameworks": {
11471
+ "astro": true,
11472
+ "elements": true,
11473
+ "react": true
11474
+ },
11475
+ "guidance": null,
11476
+ "kebab": "graphic",
11477
+ "keyboardInteractions": [],
11478
+ "keywords": [
11479
+ "artwork",
11480
+ "brand",
11481
+ "frames",
11482
+ "glow",
11483
+ "graphic",
11484
+ "grid",
11485
+ "orbit",
11486
+ "owned",
11487
+ "portable",
11488
+ "product",
11489
+ "treatments",
11490
+ "with"
11491
+ ],
11492
+ "name": "Graphic",
11493
+ "props": [
11494
+ {
11495
+ "name": "class",
11496
+ "optional": true,
11497
+ "type": "string | null"
11498
+ },
11499
+ {
11500
+ "name": "className",
11501
+ "optional": true,
11502
+ "type": "string"
11503
+ },
11504
+ {
11505
+ "name": "label",
11506
+ "optional": true,
11507
+ "type": "string"
11508
+ },
11509
+ {
11510
+ "name": "size",
11511
+ "optional": true,
11512
+ "type": "'lg' | 'md' | 'sm'"
11513
+ },
11514
+ {
11515
+ "name": "tone",
11516
+ "optional": true,
11517
+ "type": "'accent' | 'brand' | 'neutral'"
11518
+ },
11519
+ {
11520
+ "name": "variant",
11521
+ "optional": true,
11522
+ "type": "'glow' | 'grid' | 'orbit'"
11523
+ }
11524
+ ],
11525
+ "propsExtends": "HTMLAttributes<'div'>",
11526
+ "recipes": [],
11527
+ "runtimeEvents": []
11528
+ },
11091
11529
  {
11092
11530
  "apiReference": [
11093
11531
  {
@@ -11736,6 +12174,222 @@
11736
12174
  "recipes": [],
11737
12175
  "runtimeEvents": []
11738
12176
  },
12177
+ {
12178
+ "apiReference": [
12179
+ {
12180
+ "attribute": "variant",
12181
+ "defaultValue": "\"empty\"",
12182
+ "description": "Selects a built-in semantic scene.",
12183
+ "values": "\"empty\" | \"success\" | \"error\" | \"offline\""
12184
+ },
12185
+ {
12186
+ "attribute": "tone",
12187
+ "defaultValue": "\"auto\"",
12188
+ "description": "Uses the variant semantic color automatically or an explicit portable tone.",
12189
+ "values": "\"auto\" | \"brand\" | \"accent\" | \"neutral\""
12190
+ },
12191
+ {
12192
+ "attribute": "size",
12193
+ "defaultValue": "\"md\"",
12194
+ "description": "Uses the shared 96, 128, or 176 unit illustration size.",
12195
+ "values": "\"sm\" | \"md\" | \"lg\""
12196
+ },
12197
+ {
12198
+ "attribute": "label",
12199
+ "defaultValue": "-",
12200
+ "description": "Exposes the illustration as one named image; without it the artwork is decorative.",
12201
+ "values": "string"
12202
+ },
12203
+ {
12204
+ "attribute": "class, className",
12205
+ "defaultValue": "\"\"",
12206
+ "description": "Merges custom classes with the generated ui-* root classes.",
12207
+ "values": "string"
12208
+ },
12209
+ {
12210
+ "attribute": "...native attributes",
12211
+ "defaultValue": "-",
12212
+ "description": "Forwards standard attributes to the root element unless the component consumes them.",
12213
+ "values": "HTML attributes"
12214
+ }
12215
+ ],
12216
+ "astroSource": "---\nimport type { HTMLAttributes } from 'astro/types'\n\nimport { resolveLumenAstroProps } from '@santi020k/lumen-core'\n\ninterface Props extends HTMLAttributes<'span'> {\n class?: string | null\n className?: string\n label?: string\n size?: 'lg' | 'md' | 'sm'\n tone?: 'accent' | 'auto' | 'brand' | 'neutral'\n variant?: 'empty' | 'error' | 'offline' | 'success'\n}\n\nconst {\n class: classProp = '',\n className = '',\n label,\n size = 'md',\n tone = 'auto',\n variant = 'empty',\n ...rest\n} = Astro.props\n\nconst { classList, passthrough } = resolveLumenAstroProps(rest, [\n 'ui-illustration',\n `ui-illustration--${variant}`,\n `ui-illustration--${tone}`,\n `ui-illustration--${size}`\n], classProp, className)\n---\n\n<span\n aria-hidden={label ? undefined : 'true'}\n aria-label={label}\n class:list={classList}\n role={label ? 'img' : undefined}\n {...passthrough}\n>\n <svg aria-hidden=\"true\" fill=\"none\" viewBox=\"0 0 120 120\">\n <circle class=\"ui-illustration__wash\" cx=\"60\" cy=\"60\" r=\"48\" />\n {variant === 'empty' && (\n <g>\n <path d=\"M32 48h56l-7 35H39l-7-35Z\" />\n <path d=\"M43 48 49 36h22l6 12M47 66h26\" />\n </g>\n )}\n {variant === 'success' && (\n <g>\n <circle cx=\"60\" cy=\"60\" r=\"28\" />\n <path d=\"m45 60 10 10 21-23\" />\n </g>\n )}\n {variant === 'error' && (\n <g>\n <circle cx=\"60\" cy=\"60\" r=\"28\" />\n <path d=\"m49 49 22 22m0-22L49 71\" />\n </g>\n )}\n {variant === 'offline' && (\n <g>\n <path d=\"M37 74h44a14 14 0 0 0 1-28 23 23 0 0 0-42-4 16 16 0 0 0-3 32Z\" />\n <path d=\"m38 34 44 52\" />\n </g>\n )}\n </svg>\n</span>\n",
12217
+ "category": "Feedback",
12218
+ "collections": [],
12219
+ "dependencies": [
12220
+ "styles"
12221
+ ],
12222
+ "description": "Shows a built-in semantic scene for empty, success, error, or offline states.",
12223
+ "behavior": {
12224
+ "astro": "none",
12225
+ "elements": "registered-element",
12226
+ "react": "component"
12227
+ },
12228
+ "files": [
12229
+ "packages/astro/components/Illustration.astro",
12230
+ "packages/astro/styles/lumen.css",
12231
+ "packages/react/src/components.tsx"
12232
+ ],
12233
+ "frameworkDetails": {
12234
+ "astro": {
12235
+ "available": true,
12236
+ "example": "<Illustration label=\"No projects yet\" size=\"md\" variant=\"empty\" />",
12237
+ "importStatement": "import { Illustration } from '@santi020k/lumen-astro'",
12238
+ "language": "astro",
12239
+ "packageName": "@santi020k/lumen-astro",
12240
+ "props": [
12241
+ {
12242
+ "name": "class",
12243
+ "optional": true,
12244
+ "type": "string | null"
12245
+ },
12246
+ {
12247
+ "name": "className",
12248
+ "optional": true,
12249
+ "type": "string"
12250
+ },
12251
+ {
12252
+ "name": "label",
12253
+ "optional": true,
12254
+ "type": "string"
12255
+ },
12256
+ {
12257
+ "name": "size",
12258
+ "optional": true,
12259
+ "type": "'lg' | 'md' | 'sm'"
12260
+ },
12261
+ {
12262
+ "name": "tone",
12263
+ "optional": true,
12264
+ "type": "'accent' | 'auto' | 'brand' | 'neutral'"
12265
+ },
12266
+ {
12267
+ "name": "variant",
12268
+ "optional": true,
12269
+ "type": "'empty' | 'error' | 'offline' | 'success'"
12270
+ }
12271
+ ],
12272
+ "propsExtends": "HTMLAttributes<'span'>",
12273
+ "source": "---\nimport type { HTMLAttributes } from 'astro/types'\n\nimport { resolveLumenAstroProps } from '@santi020k/lumen-core'\n\ninterface Props extends HTMLAttributes<'span'> {\n class?: string | null\n className?: string\n label?: string\n size?: 'lg' | 'md' | 'sm'\n tone?: 'accent' | 'auto' | 'brand' | 'neutral'\n variant?: 'empty' | 'error' | 'offline' | 'success'\n}\n\nconst {\n class: classProp = '',\n className = '',\n label,\n size = 'md',\n tone = 'auto',\n variant = 'empty',\n ...rest\n} = Astro.props\n\nconst { classList, passthrough } = resolveLumenAstroProps(rest, [\n 'ui-illustration',\n `ui-illustration--${variant}`,\n `ui-illustration--${tone}`,\n `ui-illustration--${size}`\n], classProp, className)\n---\n\n<span\n aria-hidden={label ? undefined : 'true'}\n aria-label={label}\n class:list={classList}\n role={label ? 'img' : undefined}\n {...passthrough}\n>\n <svg aria-hidden=\"true\" fill=\"none\" viewBox=\"0 0 120 120\">\n <circle class=\"ui-illustration__wash\" cx=\"60\" cy=\"60\" r=\"48\" />\n {variant === 'empty' && (\n <g>\n <path d=\"M32 48h56l-7 35H39l-7-35Z\" />\n <path d=\"M43 48 49 36h22l6 12M47 66h26\" />\n </g>\n )}\n {variant === 'success' && (\n <g>\n <circle cx=\"60\" cy=\"60\" r=\"28\" />\n <path d=\"m45 60 10 10 21-23\" />\n </g>\n )}\n {variant === 'error' && (\n <g>\n <circle cx=\"60\" cy=\"60\" r=\"28\" />\n <path d=\"m49 49 22 22m0-22L49 71\" />\n </g>\n )}\n {variant === 'offline' && (\n <g>\n <path d=\"M37 74h44a14 14 0 0 0 1-28 23 23 0 0 0-42-4 16 16 0 0 0-3 32Z\" />\n <path d=\"m38 34 44 52\" />\n </g>\n )}\n </svg>\n</span>\n",
12274
+ "styleImport": "import '@santi020k/lumen-astro/styles.css'"
12275
+ },
12276
+ "elements": {
12277
+ "attributes": [
12278
+ "size",
12279
+ "tone",
12280
+ "variant",
12281
+ "label",
12282
+ "class, className",
12283
+ "...native attributes"
12284
+ ],
12285
+ "available": true,
12286
+ "behavior": {
12287
+ "mode": "registration",
12288
+ "setup": "Call defineLumenElements() once before rendering Lumen custom elements."
12289
+ },
12290
+ "example": "<lumen-illustration label=\"No projects yet\" size=\"md\" variant=\"empty\"></lumen-illustration>",
12291
+ "importStatement": "import { defineLumenElements } from '@santi020k/lumen-elements/define'",
12292
+ "language": "html",
12293
+ "packageName": "@santi020k/lumen-elements",
12294
+ "props": [],
12295
+ "propsExtends": "HTMLElement attributes",
12296
+ "registration": "defineLumenElements()",
12297
+ "source": "Illustration: {\n attributeClasses: {\n size: {\n lg: 'ui-illustration--lg',\n md: 'ui-illustration--md',\n sm: 'ui-illustration--sm'\n },\n tone: {\n accent: 'ui-illustration--accent',\n auto: 'ui-illustration--auto',\n brand: 'ui-illustration--brand',\n neutral: 'ui-illustration--neutral'\n },\n variant: {\n empty: 'ui-illustration--empty',\n error: 'ui-illustration--error',\n offline: 'ui-illustration--offline',\n success: 'ui-illustration--success'\n }\n },\n baseClassName: 'ui-illustration',\n defaults: { size: 'md', tone: 'auto', variant: 'empty' },\n tagName: 'lumen-illustration'\n }",
12298
+ "styleImport": "import '@santi020k/lumen-elements/styles.css'",
12299
+ "tagName": "lumen-illustration"
12300
+ },
12301
+ "react": {
12302
+ "available": true,
12303
+ "example": "<Illustration label=\"No projects yet\" size=\"md\" variant=\"empty\" />",
12304
+ "importStatement": "import { Illustration } from '@santi020k/lumen-react'",
12305
+ "language": "tsx",
12306
+ "packageName": "@santi020k/lumen-react",
12307
+ "props": [
12308
+ {
12309
+ "name": "label",
12310
+ "optional": true,
12311
+ "type": "string"
12312
+ },
12313
+ {
12314
+ "name": "size",
12315
+ "optional": true,
12316
+ "type": "'lg' | 'md' | 'sm'"
12317
+ },
12318
+ {
12319
+ "name": "tone",
12320
+ "optional": true,
12321
+ "type": "'accent' | 'auto' | 'brand' | 'neutral'"
12322
+ },
12323
+ {
12324
+ "name": "variant",
12325
+ "optional": true,
12326
+ "type": "'empty' | 'error' | 'offline' | 'success'"
12327
+ }
12328
+ ],
12329
+ "propsExtends": "ComponentPropsWithoutRef<'span'>",
12330
+ "source": "export interface IllustrationProps extends ComponentPropsWithoutRef<'span'> {\n label?: string\n size?: 'lg' | 'md' | 'sm'\n tone?: 'accent' | 'auto' | 'brand' | 'neutral'\n variant?: 'empty' | 'error' | 'offline' | 'success'\n}\n\nexport const Illustration = ({\n className,\n label,\n size = 'md',\n tone = 'auto',\n variant = 'empty',\n ...props\n}: IllustrationProps) => (\n <span\n aria-hidden={label ? undefined : true}\n aria-label={label}\n className={composeClassName(\n 'ui-illustration',\n `ui-illustration--${variant}`,\n `ui-illustration--${tone}`,\n `ui-illustration--${size}`,\n className\n )}\n role={label ? 'img' : undefined}\n {...props}\n >\n <svg aria-hidden=\"true\" fill=\"none\" viewBox=\"0 0 120 120\">\n <circle className=\"ui-illustration__wash\" cx=\"60\" cy=\"60\" r=\"48\" />\n {variant === 'empty' && (\n <g>\n <path d=\"M32 48h56l-7 35H39l-7-35Z\" />\n <path d=\"M43 48 49 36h22l6 12M47 66h26\" />\n </g>\n )}\n {variant === 'success' && (\n <g>\n <circle cx=\"60\" cy=\"60\" r=\"28\" />\n <path d=\"m45 60 10 10 21-23\" />\n </g>\n )}\n {variant === 'error' && (\n <g>\n <circle cx=\"60\" cy=\"60\" r=\"28\" />\n <path d=\"m49 49 22 22m0-22L49 71\" />\n </g>\n )}\n {variant === 'offline' && (\n <g>\n <path d=\"M37 74h44a14 14 0 0 0 1-28 23 23 0 0 0-42-4 16 16 0 0 0-3 32Z\" />\n <path d=\"m38 34 44 52\" />\n </g>\n )}\n </svg>\n </span>\n)",
12331
+ "styleImport": "import '@santi020k/lumen-react/styles.css'"
12332
+ }
12333
+ },
12334
+ "frameworks": {
12335
+ "astro": true,
12336
+ "elements": true,
12337
+ "react": true
12338
+ },
12339
+ "guidance": null,
12340
+ "kebab": "illustration",
12341
+ "keyboardInteractions": [],
12342
+ "keywords": [
12343
+ "built",
12344
+ "empty",
12345
+ "error",
12346
+ "feedback",
12347
+ "for",
12348
+ "illustration",
12349
+ "offline",
12350
+ "scene",
12351
+ "semantic",
12352
+ "shows",
12353
+ "states",
12354
+ "success"
12355
+ ],
12356
+ "name": "Illustration",
12357
+ "props": [
12358
+ {
12359
+ "name": "class",
12360
+ "optional": true,
12361
+ "type": "string | null"
12362
+ },
12363
+ {
12364
+ "name": "className",
12365
+ "optional": true,
12366
+ "type": "string"
12367
+ },
12368
+ {
12369
+ "name": "label",
12370
+ "optional": true,
12371
+ "type": "string"
12372
+ },
12373
+ {
12374
+ "name": "size",
12375
+ "optional": true,
12376
+ "type": "'lg' | 'md' | 'sm'"
12377
+ },
12378
+ {
12379
+ "name": "tone",
12380
+ "optional": true,
12381
+ "type": "'accent' | 'auto' | 'brand' | 'neutral'"
12382
+ },
12383
+ {
12384
+ "name": "variant",
12385
+ "optional": true,
12386
+ "type": "'empty' | 'error' | 'offline' | 'success'"
12387
+ }
12388
+ ],
12389
+ "propsExtends": "HTMLAttributes<'span'>",
12390
+ "recipes": [],
12391
+ "runtimeEvents": []
12392
+ },
11739
12393
  {
11740
12394
  "apiReference": [
11741
12395
  {
@@ -32294,13 +32948,13 @@
32294
32948
  }
32295
32949
  ],
32296
32950
  "docs": {
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",
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"
32951
+ "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.4.0` | `com.santi020k.lumen` | Not applicable |\n| Wear OS / Compose | Maven Central `com.santi020k:lumen-compose-wear:0.4.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.4.0` from Maven Central and wrap content in `LumenTheme`. Native\nadapters do not load CSS or the Astro runtime.\n\nFor watchOS, use the focused `LumenWatch*` contracts from the same Swift package. For Wear OS,\ninstall `com.santi020k:lumen-compose-wear:0.4.0` and wrap wearable content in `LumenWearTheme`\ninside the application-owned Wear Material theme. Keep complications, tiles, rotary input,\nAlways On behavior, haptics, synchronization, navigation, and health or safety logic app-owned.\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`Graphic`, `Backdrop`, `Illustration`, `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 doctor-native --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",
32952
+ "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://github.com/santi020k/lumen/actions/workflows/codeql.yml\"><img alt=\"CodeQL\" src=\"https://github.com/santi020k/lumen/actions/workflows/codeql.yml/badge.svg\"></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`, a machine-readable registry, and a cross-platform release manifest ship\n 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| [`lumen-compose-wear`](./packages/compose/wear) | Focused Wear OS primitives for at-a-glance round-screen actions, progress, status, metrics, and rows |\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/graphics.md`](./docs/graphics.md) for portable decorative artwork across web and native platforms.\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- [`docs/native-patterns.md`](./docs/native-patterns.md) for developed native compositions and the\n boundary between Lumen presentation and platform-owned application structure.\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"
32299
32953
  },
32300
32954
  "meta": {
32301
- "catalogHash": "02fdac6cca80508301905223659ae7164ddc65adc9a9d61327e51d9fa45ce68f",
32302
- "componentCount": 153,
32303
- "nativeComponentCount": 41,
32955
+ "catalogHash": "b218bf14d64e7d7b5fea64afcf8894cd7a90dd030ca38c97d6831328f8f93b01",
32956
+ "componentCount": 156,
32957
+ "nativeComponentCount": 62,
32304
32958
  "packages": [
32305
32959
  "@santi020k/lumen-astro",
32306
32960
  "@santi020k/lumen-react",
@@ -32312,20 +32966,21 @@
32312
32966
  "com.santi020k:lumen-compose"
32313
32967
  ],
32314
32968
  "packageVersions": {
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",
32969
+ "@santi020k/lumen-astro": "1.4.0",
32970
+ "@santi020k/lumen-core": "1.4.0",
32971
+ "@santi020k/lumen-elements": "1.4.0",
32972
+ "@santi020k/lumen-mcp": "1.5.0",
32973
+ "@santi020k/lumen-react": "1.4.0",
32974
+ "@santi020k/lumen-react-native": "0.4.0",
32321
32975
  "@santi020k/lumen-tokens": "0.1.0",
32322
32976
  "LumenUI": "workspace",
32323
- "com.santi020k:lumen-compose": "workspace"
32977
+ "com.santi020k:lumen-compose": "workspace",
32978
+ "com.santi020k:lumen-compose-wear": "workspace"
32324
32979
  },
32325
32980
  "registryName": "lumen",
32326
32981
  "registryVersion": 1,
32327
- "schemaVersion": 4,
32328
- "serverVersion": "1.3.0"
32982
+ "schemaVersion": 5,
32983
+ "serverVersion": "1.5.0"
32329
32984
  },
32330
32985
  "nativeComponents": [
32331
32986
  {
@@ -32702,7 +33357,7 @@
32702
33357
  "language": "kotlin",
32703
33358
  "packageName": "com.santi020k:lumen-compose",
32704
33359
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
32705
- "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt",
33360
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
32706
33361
  "symbol": "LumenIcon"
32707
33362
  },
32708
33363
  "react-native": {
@@ -33722,7 +34377,7 @@
33722
34377
  "language": "kotlin",
33723
34378
  "packageName": "com.santi020k:lumen-compose",
33724
34379
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
33725
- "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/SharedComponents.kt",
34380
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
33726
34381
  "symbol": "LumenAlert"
33727
34382
  },
33728
34383
  "react-native": {
@@ -33796,7 +34451,7 @@
33796
34451
  "language": "swift",
33797
34452
  "packageName": "LumenUI",
33798
34453
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
33799
- "sourceFile": "packages/swift/Sources/LumenUI/SharedComponents.swift",
34454
+ "sourceFile": "packages/swift/Sources/LumenUI/OverlayComponents.swift",
33800
34455
  "symbol": "LumenAlert"
33801
34456
  }
33802
34457
  },
@@ -35965,6 +36620,147 @@
35965
36620
  "summary": "Choose one value from a compact row of short peer options.",
35966
36621
  "tier": "shared"
35967
36622
  },
36623
+ {
36624
+ "accessibility": "The group has a readable navigation label and every destination exposes selected and disabled state through native tab semantics.",
36625
+ "category": "Navigation",
36626
+ "contract": "Controlled selection from peer app destinations with re-selection callbacks and optional dot, text, or count badges",
36627
+ "guidance": "Use for a small set of peer app destinations. Keep hierarchical navigation, history, deep links, restoration, and screen rendering in the application router or native navigation stack.",
36628
+ "id": "navigation-bar",
36629
+ "implementations": {
36630
+ "compose": {
36631
+ "api": [
36632
+ {
36633
+ "defaultValue": "Required",
36634
+ "description": "Provides destination values, short labels, native icons, disabled state, and optional dot, text, or capped count badges.",
36635
+ "name": "items",
36636
+ "values": "List<LumenNavigationItem<Value>>"
36637
+ },
36638
+ {
36639
+ "defaultValue": "Required",
36640
+ "description": "Stores the active destination.",
36641
+ "name": "selectedValue",
36642
+ "values": "Value"
36643
+ },
36644
+ {
36645
+ "defaultValue": "Required",
36646
+ "description": "Requests a destination change without owning navigation history.",
36647
+ "name": "onValueChange",
36648
+ "values": "(Value) -> Unit"
36649
+ },
36650
+ {
36651
+ "defaultValue": "null",
36652
+ "description": "Handles a second activation of the selected destination, such as scrolling to top or popping its stack.",
36653
+ "name": "onReselect",
36654
+ "values": "((Value) -> Unit)?"
36655
+ },
36656
+ {
36657
+ "defaultValue": "Primary navigation",
36658
+ "description": "Names the destination group for assistive technology.",
36659
+ "name": "accessibilityLabel",
36660
+ "values": "String"
36661
+ }
36662
+ ],
36663
+ "example": "LumenNavigationBar(\n items = destinations,\n selectedValue = destination,\n onValueChange = ::setDestination,\n onReselect = ::scrollDestinationToTop\n)",
36664
+ "exportName": "LumenNavigationBar",
36665
+ "importStatement": "import com.santi020k.lumen.LumenNavigationBar",
36666
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
36667
+ "language": "kotlin",
36668
+ "packageName": "com.santi020k:lumen-compose",
36669
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
36670
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt",
36671
+ "symbol": "LumenNavigationBar"
36672
+ },
36673
+ "react-native": {
36674
+ "api": [
36675
+ {
36676
+ "defaultValue": "Required",
36677
+ "description": "Provides destination values, short labels, native icons, disabled state, and optional dot, text, or capped count badges.",
36678
+ "name": "items",
36679
+ "values": "readonly LumenNavigationItem[]"
36680
+ },
36681
+ {
36682
+ "defaultValue": "Required",
36683
+ "description": "Stores the active destination.",
36684
+ "name": "value",
36685
+ "values": "string"
36686
+ },
36687
+ {
36688
+ "defaultValue": "Required",
36689
+ "description": "Requests a destination change without owning navigation history.",
36690
+ "name": "onValueChange",
36691
+ "values": "(string) => void"
36692
+ },
36693
+ {
36694
+ "defaultValue": "undefined",
36695
+ "description": "Handles a second activation of the selected destination, such as scrolling to top or popping its stack.",
36696
+ "name": "onReselect",
36697
+ "values": "((value: string) => void)?"
36698
+ },
36699
+ {
36700
+ "defaultValue": "Primary navigation",
36701
+ "description": "Names the destination group for assistive technology.",
36702
+ "name": "accessibilityLabel",
36703
+ "values": "String"
36704
+ }
36705
+ ],
36706
+ "example": "<LumenNavigationBar\n items={destinations}\n value={destination}\n onValueChange={setDestination}\n onReselect={scrollDestinationToTop}\n/>",
36707
+ "exportName": "LumenNavigationBar",
36708
+ "importStatement": "import { LumenNavigationBar } from '@santi020k/lumen-react-native'",
36709
+ "install": "pnpm add @santi020k/lumen-react-native",
36710
+ "language": "tsx",
36711
+ "packageName": "@santi020k/lumen-react-native",
36712
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
36713
+ "sourceFile": "packages/react-native/src/index.ts",
36714
+ "symbol": "LumenNavigationBar"
36715
+ },
36716
+ "swiftui": {
36717
+ "api": [
36718
+ {
36719
+ "defaultValue": "Required",
36720
+ "description": "Provides destination values, short labels, native icons, disabled state, and optional dot, text, or capped count badges.",
36721
+ "name": "items",
36722
+ "values": "[LumenNavigationItem<Selection>]"
36723
+ },
36724
+ {
36725
+ "defaultValue": "Required",
36726
+ "description": "Stores the active destination.",
36727
+ "name": "selection",
36728
+ "values": "Binding<Selection>"
36729
+ },
36730
+ {
36731
+ "defaultValue": "Required",
36732
+ "description": "Requests a destination change without owning navigation history.",
36733
+ "name": "onValueChange",
36734
+ "values": "Binding setter"
36735
+ },
36736
+ {
36737
+ "defaultValue": "nil",
36738
+ "description": "Handles a second activation of the selected destination, such as scrolling to top or popping its stack.",
36739
+ "name": "onReselect",
36740
+ "values": "((Selection) -> Void)?"
36741
+ },
36742
+ {
36743
+ "defaultValue": "Primary navigation",
36744
+ "description": "Names the destination group for assistive technology.",
36745
+ "name": "label",
36746
+ "values": "String"
36747
+ }
36748
+ ],
36749
+ "example": "LumenNavigationBar(\n selection: $destination,\n items: destinations,\n onReselect: scrollDestinationToTop\n)",
36750
+ "exportName": "LumenNavigationBar",
36751
+ "importStatement": "import LumenUI",
36752
+ "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.",
36753
+ "language": "swift",
36754
+ "packageName": "LumenUI",
36755
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
36756
+ "sourceFile": "packages/swift/Sources/LumenUI/NavigationComponents.swift",
36757
+ "symbol": "LumenNavigationBar"
36758
+ }
36759
+ },
36760
+ "name": "Navigation bar",
36761
+ "summary": "Select among peer application destinations with native visuals and controlled state.",
36762
+ "tier": "shared"
36763
+ },
35968
36764
  {
35969
36765
  "accessibility": "The title, description, optional graphic, and recovery actions remain in a readable native order.",
35970
36766
  "category": "Feedback",
@@ -36801,6 +37597,256 @@
36801
37597
  "summary": "Present compact textual application status with optional trailing content.",
36802
37598
  "tier": "shared"
36803
37599
  },
37600
+ {
37601
+ "accessibility": "Hidden destinations stop receiving pointer input and are removed from the accessibility tree. The application keeps native list and router ownership.",
37602
+ "category": "Navigation",
37603
+ "contract": "Animated bottom navigation that hides after forward scroll travel and reveals on reverse travel or at the top",
37604
+ "guidance": "Use with the visibility hook when more content space is valuable during deliberate vertical scrolling. Keep safe areas, virtualization, and routing in application code.",
37605
+ "id": "collapsible-navigation-bar",
37606
+ "implementations": {
37607
+ "react-native": {
37608
+ "api": [
37609
+ {
37610
+ "defaultValue": "Required",
37611
+ "description": "Controls the animated expanded or collapsed state.",
37612
+ "name": "visible",
37613
+ "values": "boolean"
37614
+ },
37615
+ {
37616
+ "defaultValue": "Required",
37617
+ "description": "Defines peer destinations.",
37618
+ "name": "items",
37619
+ "values": "readonly LumenNavigationItem[]"
37620
+ },
37621
+ {
37622
+ "defaultValue": "Required",
37623
+ "description": "Identifies the selected destination.",
37624
+ "name": "value",
37625
+ "values": "string"
37626
+ },
37627
+ {
37628
+ "defaultValue": "Required",
37629
+ "description": "Requests destination selection.",
37630
+ "name": "onValueChange",
37631
+ "values": "(value: string) => void"
37632
+ },
37633
+ {
37634
+ "defaultValue": "undefined",
37635
+ "description": "Styles the animated outer container without replacing its collapse dimensions.",
37636
+ "name": "containerStyle",
37637
+ "values": "StyleProp<ViewStyle>"
37638
+ },
37639
+ {
37640
+ "defaultValue": "undefined",
37641
+ "description": "Adds compact application-owned content that collapses with the navigation bar.",
37642
+ "name": "accessory",
37643
+ "values": "ReactNode"
37644
+ }
37645
+ ],
37646
+ "example": "const navigation = useLumenNavigationBarVisibility()\n\n<FlatList\n data={items}\n onScroll={navigation.onScroll}\n scrollEventThrottle={16}\n renderItem={renderItem}\n/>\n<LumenCollapsibleNavigationBar\n items={destinations}\n value={destination}\n visible={navigation.visible}\n onValueChange={setDestination}\n/>",
37647
+ "exportName": "LumenCollapsibleNavigationBar",
37648
+ "importStatement": "import { LumenCollapsibleNavigationBar } from '@santi020k/lumen-react-native'",
37649
+ "install": "pnpm add @santi020k/lumen-react-native",
37650
+ "language": "tsx",
37651
+ "packageName": "@santi020k/lumen-react-native",
37652
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
37653
+ "sourceFile": "packages/react-native/src/index.ts",
37654
+ "symbol": "LumenCollapsibleNavigationBar"
37655
+ }
37656
+ },
37657
+ "name": "Collapsible navigation bar",
37658
+ "summary": "Animate a React Native destination bar in response to native vertical scroll travel.",
37659
+ "supportedPlatforms": [
37660
+ "iOS",
37661
+ "Android"
37662
+ ],
37663
+ "tier": "platform-specific"
37664
+ },
37665
+ {
37666
+ "accessibility": "The container preserves the semantics of its application-owned status and actions. When supplied to the collapsible bar, it leaves the accessibility tree with navigation.",
37667
+ "category": "Navigation",
37668
+ "contract": "Compact status or action content above a React Native bottom navigation bar",
37669
+ "guidance": "Use for one compact persistent activity such as playback, upload, recording, or an active call. Keep safe-area padding and domain state in the application.",
37670
+ "id": "navigation-accessory",
37671
+ "implementations": {
37672
+ "react-native": {
37673
+ "api": [
37674
+ {
37675
+ "defaultValue": "Required",
37676
+ "description": "Provides compact status or independently accessible actions.",
37677
+ "name": "children",
37678
+ "values": "ReactNode"
37679
+ },
37680
+ {
37681
+ "defaultValue": "undefined",
37682
+ "description": "Extends the fixed compact native container.",
37683
+ "name": "style",
37684
+ "values": "StyleProp<ViewStyle>"
37685
+ }
37686
+ ],
37687
+ "example": "<LumenCollapsibleNavigationBar\n accessory={(\n <LumenText variant=\"label\">Uploading 3 files</LumenText>\n )}\n items={destinations}\n value={destination}\n visible={navigation.visible}\n onValueChange={setDestination}\n/>",
37688
+ "exportName": "LumenNavigationAccessory",
37689
+ "importStatement": "import { LumenNavigationAccessory } from '@santi020k/lumen-react-native'",
37690
+ "install": "pnpm add @santi020k/lumen-react-native",
37691
+ "language": "tsx",
37692
+ "packageName": "@santi020k/lumen-react-native",
37693
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
37694
+ "sourceFile": "packages/react-native/src/index.ts",
37695
+ "symbol": "LumenNavigationAccessory"
37696
+ }
37697
+ },
37698
+ "name": "Navigation accessory",
37699
+ "summary": "Place compact application status or actions above React Native bottom navigation.",
37700
+ "supportedPlatforms": [
37701
+ "iOS",
37702
+ "Android"
37703
+ ],
37704
+ "tier": "platform-specific"
37705
+ },
37706
+ {
37707
+ "accessibility": "The behavior observes but does not consume nested-scroll input. After exit, the Material navigation bar is absent from layout and semantics.",
37708
+ "category": "Navigation",
37709
+ "contract": "Threshold-based nested-scroll visibility state for a Material navigation bar",
37710
+ "guidance": "Attach the modifier above a vertical lazy or scrollable child and pass the same state to the bottom bar. Keep Scaffold, navigation, and collection state application-owned.",
37711
+ "id": "navigation-bar-scroll-behavior",
37712
+ "implementations": {
37713
+ "compose": {
37714
+ "api": [
37715
+ {
37716
+ "defaultValue": "true",
37717
+ "description": "Sets the remembered initial visibility.",
37718
+ "name": "initiallyVisible",
37719
+ "values": "Boolean"
37720
+ },
37721
+ {
37722
+ "defaultValue": "16.dp",
37723
+ "description": "Sets deliberate travel required before visibility changes.",
37724
+ "name": "threshold",
37725
+ "values": "Dp"
37726
+ },
37727
+ {
37728
+ "defaultValue": "Available",
37729
+ "description": "Reveals navigation for application-owned events.",
37730
+ "name": "show()",
37731
+ "values": "function"
37732
+ },
37733
+ {
37734
+ "defaultValue": "Available",
37735
+ "description": "Hides navigation for application-owned events.",
37736
+ "name": "hide()",
37737
+ "values": "function"
37738
+ }
37739
+ ],
37740
+ "example": "val navigationScrollState = rememberLumenNavigationBarScrollState()\n\nScaffold(\n modifier = Modifier.lumenNavigationBarScrollBehavior(navigationScrollState),\n bottomBar = {\n LumenNavigationBar(\n items = destinations,\n selectedValue = destination,\n onValueChange = ::setDestination,\n scrollState = navigationScrollState\n )\n }\n) { padding ->\n LazyColumn(contentPadding = padding) { /* application content */ }\n}",
37741
+ "exportName": "LumenNavigationBarScrollState",
37742
+ "importStatement": "import com.santi020k.lumen.LumenNavigationBarScrollState",
37743
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
37744
+ "language": "kotlin",
37745
+ "packageName": "com.santi020k:lumen-compose",
37746
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
37747
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt",
37748
+ "symbol": "LumenNavigationBarScrollState"
37749
+ }
37750
+ },
37751
+ "name": "Navigation bar scroll behavior",
37752
+ "summary": "Coordinate Material bottom navigation visibility through the Compose nested-scroll chain.",
37753
+ "supportedPlatforms": [
37754
+ "Android"
37755
+ ],
37756
+ "tier": "platform-specific"
37757
+ },
37758
+ {
37759
+ "accessibility": "Application-owned content retains its native semantics and exits the layout together with hidden bottom navigation.",
37760
+ "category": "Navigation",
37761
+ "contract": "Compact token-aware content above Material bottom navigation coordinated with its scroll visibility",
37762
+ "guidance": "Place directly above LumenNavigationBar for one compact activity such as playback, upload, recording, or an active call. Keep domain state and actions application-owned.",
37763
+ "id": "navigation-bar-accessory",
37764
+ "implementations": {
37765
+ "compose": {
37766
+ "api": [
37767
+ {
37768
+ "defaultValue": "null",
37769
+ "description": "Coordinates entry and exit with scroll-responsive navigation.",
37770
+ "name": "scrollState",
37771
+ "values": "LumenNavigationBarScrollState?"
37772
+ },
37773
+ {
37774
+ "defaultValue": "Required",
37775
+ "description": "Provides compact status or independently accessible actions.",
37776
+ "name": "content",
37777
+ "values": "@Composable () -> Unit"
37778
+ }
37779
+ ],
37780
+ "example": "LumenNavigationBarAccessory(scrollState = navigationScrollState) {\n LumenText(\"Uploading 3 files\", variant = LumenTextVariant.Label)\n}",
37781
+ "exportName": "LumenNavigationBarAccessory",
37782
+ "importStatement": "import com.santi020k.lumen.LumenNavigationBarAccessory",
37783
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
37784
+ "language": "kotlin",
37785
+ "packageName": "com.santi020k:lumen-compose",
37786
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
37787
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt",
37788
+ "symbol": "LumenNavigationBarAccessory"
37789
+ }
37790
+ },
37791
+ "name": "Navigation bar accessory",
37792
+ "summary": "Place compact application status or actions above Material bottom navigation.",
37793
+ "supportedPlatforms": [
37794
+ "Android"
37795
+ ],
37796
+ "tier": "platform-specific"
37797
+ },
37798
+ {
37799
+ "accessibility": "Material supplies navigation bar or rail semantics, selected and disabled state, keyboard traversal, and badge placement while destination content remains application-owned.",
37800
+ "category": "Navigation",
37801
+ "contract": "Material adaptive navigation that switches between bottom bar and rail from window size and device posture",
37802
+ "guidance": "Use as the primary app surface when navigation should change between a bottom bar and rail across phones, tablets, folding devices, split screen, and desktop windows. Keep routing and destination state controlled.",
37803
+ "id": "adaptive-navigation-scaffold",
37804
+ "implementations": {
37805
+ "compose": {
37806
+ "api": [
37807
+ {
37808
+ "defaultValue": "Required",
37809
+ "description": "Provides controlled destinations, badges, icons, and disabled state.",
37810
+ "name": "items",
37811
+ "values": "List<LumenNavigationItem<Value>>"
37812
+ },
37813
+ {
37814
+ "defaultValue": "Required",
37815
+ "description": "Identifies the active application-owned destination.",
37816
+ "name": "selectedValue",
37817
+ "values": "Value"
37818
+ },
37819
+ {
37820
+ "defaultValue": "Application-provided",
37821
+ "description": "Separates destination changes from repeated activation.",
37822
+ "name": "onValueChange / onReselect",
37823
+ "values": "callbacks"
37824
+ },
37825
+ {
37826
+ "defaultValue": "Required",
37827
+ "description": "Renders the selected destination without transferring router ownership.",
37828
+ "name": "content",
37829
+ "values": "@Composable () -> Unit"
37830
+ }
37831
+ ],
37832
+ "example": "LumenAdaptiveNavigationScaffold(\n items = destinations,\n selectedValue = destination,\n onValueChange = ::setDestination,\n onReselect = ::scrollDestinationToTop\n) {\n DestinationContent(destination)\n}",
37833
+ "exportName": "LumenAdaptiveNavigationScaffold",
37834
+ "importStatement": "import com.santi020k.lumen.LumenAdaptiveNavigationScaffold",
37835
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
37836
+ "language": "kotlin",
37837
+ "packageName": "com.santi020k:lumen-compose",
37838
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
37839
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt",
37840
+ "symbol": "LumenAdaptiveNavigationScaffold"
37841
+ }
37842
+ },
37843
+ "name": "Adaptive navigation scaffold",
37844
+ "summary": "Adapt Material primary navigation between bottom bar and rail as the window changes.",
37845
+ "supportedPlatforms": [
37846
+ "Android"
37847
+ ],
37848
+ "tier": "platform-specific"
37849
+ },
36804
37850
  {
36805
37851
  "accessibility": "The native refresh gesture remains owned by the scroll container. Supply an accessibility label that describes the refreshed content.",
36806
37852
  "category": "Feedback",
@@ -36812,296 +37858,1774 @@
36812
37858
  "api": [
36813
37859
  {
36814
37860
  "defaultValue": "Required",
36815
- "description": "Reports whether the native indicator is active.",
36816
- "name": "refreshing",
37861
+ "description": "Reports whether the native indicator is active.",
37862
+ "name": "refreshing",
37863
+ "values": "boolean"
37864
+ },
37865
+ {
37866
+ "defaultValue": "Required",
37867
+ "description": "Starts the application refresh operation.",
37868
+ "name": "onRefresh",
37869
+ "values": "() => void"
37870
+ },
37871
+ {
37872
+ "defaultValue": "brand",
37873
+ "description": "Selects the semantic native indicator color.",
37874
+ "name": "indicatorTone",
37875
+ "values": "brand · accent · neutral"
37876
+ },
37877
+ {
37878
+ "defaultValue": "Application-provided",
37879
+ "description": "Names the content refreshed by the gesture.",
37880
+ "name": "accessibilityLabel",
37881
+ "values": "string"
37882
+ }
37883
+ ],
37884
+ "example": "<ScrollView\n refreshControl={(\n <LumenRefreshControl\n accessibilityLabel=\"Refresh projects\"\n refreshing={refreshing}\n onRefresh={refreshProjects}\n />\n )}\n>\n {content}\n</ScrollView>",
37885
+ "exportName": "LumenRefreshControl",
37886
+ "importStatement": "import { LumenRefreshControl } from '@santi020k/lumen-react-native'",
37887
+ "install": "pnpm add @santi020k/lumen-react-native",
37888
+ "language": "tsx",
37889
+ "packageName": "@santi020k/lumen-react-native",
37890
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
37891
+ "sourceFile": "packages/react-native/src/index.ts",
37892
+ "symbol": "LumenRefreshControl"
37893
+ }
37894
+ },
37895
+ "name": "Refresh control",
37896
+ "summary": "Apply Lumen semantic colors to React Native pull-to-refresh behavior.",
37897
+ "supportedPlatforms": [
37898
+ "iOS",
37899
+ "Android"
37900
+ ],
37901
+ "tier": "platform-specific"
37902
+ },
37903
+ {
37904
+ "accessibility": "The native picker retains its label and adjustable behavior. Supporting or validation text remains visible in the same contained group.",
37905
+ "category": "Forms",
37906
+ "contract": "Native date and time selection with bounds, supporting text, and validation messaging",
37907
+ "guidance": "Use for Apple date or time input that needs Lumen supporting and validation context. Keep calendar and locale behavior native.",
37908
+ "id": "date-field",
37909
+ "implementations": {
37910
+ "swiftui": {
37911
+ "api": [
37912
+ {
37913
+ "defaultValue": "Required",
37914
+ "description": "Labels the native date picker.",
37915
+ "name": "title",
37916
+ "values": "LocalizedStringKey"
37917
+ },
37918
+ {
37919
+ "defaultValue": "Required",
37920
+ "description": "Stores the selected native date value.",
37921
+ "name": "selection",
37922
+ "values": "Binding<Date>"
37923
+ },
37924
+ {
37925
+ "defaultValue": "date",
37926
+ "description": "Chooses the visible date and time parts.",
37927
+ "name": "components",
37928
+ "values": "date · dateAndTime · time"
37929
+ },
37930
+ {
37931
+ "defaultValue": "unbounded",
37932
+ "description": "Constrains selection using native picker bounds.",
37933
+ "name": "bounds",
37934
+ "values": "unbounded · closed · from · through"
37935
+ },
37936
+ {
37937
+ "defaultValue": "nil",
37938
+ "description": "Shows supporting text or a semantic danger validation message.",
37939
+ "name": "description / errorMessage",
37940
+ "values": "LocalizedStringKey?"
37941
+ }
37942
+ ],
37943
+ "example": "LumenDateField(\n \"Release date\",\n selection: $releaseDate,\n components: .dateAndTime,\n bounds: .from(.now),\n description: \"Choose when this version becomes available.\"\n)",
37944
+ "exportName": "LumenDateField",
37945
+ "importStatement": "import LumenUI",
37946
+ "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.",
37947
+ "language": "swift",
37948
+ "packageName": "LumenUI",
37949
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
37950
+ "sourceFile": "packages/swift/Sources/LumenUI/AppleComponents.swift",
37951
+ "symbol": "LumenDateField"
37952
+ }
37953
+ },
37954
+ "name": "Date field",
37955
+ "summary": "Select an Apple-native date or time with bounds and validation context.",
37956
+ "supportedPlatforms": [
37957
+ "iOS",
37958
+ "macOS"
37959
+ ],
37960
+ "tier": "platform-specific"
37961
+ },
37962
+ {
37963
+ "accessibility": "The required content description names the icon-only action while the control preserves native Material button semantics.",
37964
+ "category": "Actions",
37965
+ "contract": "Material-native floating action with semantic intent, sizing, and optional hide or follow-navigation behavior",
37966
+ "guidance": "Use for one prominent Material screen action. Keep navigation bars and scaffold placement in application code.",
37967
+ "id": "floating-action-button",
37968
+ "implementations": {
37969
+ "compose": {
37970
+ "api": [
37971
+ {
37972
+ "defaultValue": "Required",
37973
+ "description": "Renders the native action glyph.",
37974
+ "name": "imageVector",
37975
+ "values": "ImageVector"
37976
+ },
37977
+ {
37978
+ "defaultValue": "Required",
37979
+ "description": "Names the icon-only action.",
37980
+ "name": "contentDescription",
37981
+ "values": "String"
37982
+ },
37983
+ {
37984
+ "defaultValue": "Required",
37985
+ "description": "Runs the primary screen action.",
37986
+ "name": "onClick",
37987
+ "values": "() -> Unit"
37988
+ },
37989
+ {
37990
+ "defaultValue": "brand",
37991
+ "description": "Selects a semantic action palette.",
37992
+ "name": "intent",
37993
+ "values": "brand · accent · danger"
37994
+ },
37995
+ {
37996
+ "defaultValue": "regular",
37997
+ "description": "Selects Material-native FAB geometry.",
37998
+ "name": "size",
37999
+ "values": "regular · small"
38000
+ },
38001
+ {
38002
+ "defaultValue": "alwaysVisible",
38003
+ "description": "Keeps, hides, or repositions the action as scroll-responsive navigation changes.",
38004
+ "name": "navigationBehavior",
38005
+ "values": "alwaysVisible · hideWithNavigation · followNavigation"
38006
+ },
38007
+ {
38008
+ "defaultValue": "null",
38009
+ "description": "Supplies the navigation visibility used by coordinated behavior.",
38010
+ "name": "scrollState",
38011
+ "values": "LumenNavigationBarScrollState?"
38012
+ }
38013
+ ],
38014
+ "example": "LumenFloatingActionButton(\n imageVector = Icons.Default.Add,\n contentDescription = \"Create project\",\n onClick = ::createProject,\n scrollState = navigationScrollState,\n navigationBehavior = LumenFloatingActionButtonNavigationBehavior.HideWithNavigation\n)",
38015
+ "exportName": "LumenFloatingActionButton",
38016
+ "importStatement": "import com.santi020k.lumen.LumenFloatingActionButton",
38017
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38018
+ "language": "kotlin",
38019
+ "packageName": "com.santi020k:lumen-compose",
38020
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38021
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt",
38022
+ "symbol": "LumenFloatingActionButton"
38023
+ }
38024
+ },
38025
+ "name": "Floating action button",
38026
+ "summary": "Present a prominent Material-native action using Lumen semantic intent.",
38027
+ "supportedPlatforms": [
38028
+ "Android"
38029
+ ],
38030
+ "tier": "platform-specific"
38031
+ },
38032
+ {
38033
+ "accessibility": "SwiftUI preserves the native link role, visible label, keyboard focus, disabled state, and URL-opening behavior.",
38034
+ "category": "Actions",
38035
+ "contract": "Token-aware URL action backed by SwiftUI native link opening",
38036
+ "guidance": "Use for external or system URL actions that need Lumen semantic treatment. Keep in-app routing and URL policy in application code.",
38037
+ "id": "link",
38038
+ "implementations": {
38039
+ "swiftui": {
38040
+ "api": [
38041
+ {
38042
+ "defaultValue": "Required",
38043
+ "description": "Provides visible link content.",
38044
+ "name": "label",
38045
+ "values": "LocalizedStringKey or custom View"
38046
+ },
38047
+ {
38048
+ "defaultValue": "Required",
38049
+ "description": "Provides the native URL destination.",
38050
+ "name": "destination",
38051
+ "values": "URL"
38052
+ },
38053
+ {
38054
+ "defaultValue": "false",
38055
+ "description": "Adds a decorative external-destination symbol without changing the accessible label.",
38056
+ "name": "showsExternalIndicator",
38057
+ "values": "Bool"
38058
+ }
38059
+ ],
38060
+ "example": "LumenLink(\n \"Read privacy policy\",\n destination: privacyPolicyURL,\n showsExternalIndicator: true\n)",
38061
+ "exportName": "LumenLink",
38062
+ "importStatement": "import LumenUI",
38063
+ "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.",
38064
+ "language": "swift",
38065
+ "packageName": "LumenUI",
38066
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38067
+ "sourceFile": "packages/swift/Sources/LumenUI/AppleComponents.swift",
38068
+ "symbol": "LumenLink"
38069
+ }
38070
+ },
38071
+ "name": "Link",
38072
+ "summary": "Open an Apple URL using native SwiftUI behavior and Lumen semantic styling.",
38073
+ "supportedPlatforms": [
38074
+ "iOS",
38075
+ "macOS"
38076
+ ],
38077
+ "tier": "platform-specific"
38078
+ },
38079
+ {
38080
+ "accessibility": "SwiftUI retains native tab focus, safe-area adjustment, animation, and reduced-motion behavior while the application owns selection and navigation state.",
38081
+ "category": "Navigation",
38082
+ "contract": "Availability-safe native tab-bar minimization with automatic, never, and scroll-direction policies",
38083
+ "guidance": "Apply to a native TabView. iOS 26 uses system minimization on iPhone; earlier supported releases retain the normal tab bar without a custom imitation.",
38084
+ "id": "tab-bar-minimization",
38085
+ "implementations": {
38086
+ "swiftui": {
38087
+ "api": [
38088
+ {
38089
+ "defaultValue": "Required",
38090
+ "description": "Selects the native minimization policy.",
38091
+ "name": "behavior",
38092
+ "values": "automatic · never · onScrollDown · onScrollUp"
38093
+ },
38094
+ {
38095
+ "defaultValue": "Required",
38096
+ "description": "Uses native minimization on iOS 26 and a no-op compatibility fallback before iOS 26.",
38097
+ "name": "availability",
38098
+ "values": "iOS 16 or newer"
38099
+ }
38100
+ ],
38101
+ "example": "TabView(selection: $selection) {\n FeedView().tabItem { Label(\"Feed\", systemImage: \"rectangle.stack\") }\n ProfileView().tabItem { Label(\"Profile\", systemImage: \"person\") }\n}\n.lumenTabBarMinimizeBehavior(.onScrollDown)",
38102
+ "exportName": "lumenTabBarMinimizeBehavior",
38103
+ "importStatement": "import LumenUI",
38104
+ "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.",
38105
+ "language": "swift",
38106
+ "packageName": "LumenUI",
38107
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38108
+ "sourceFile": "packages/swift/Sources/LumenUI/NavigationComponents.swift",
38109
+ "symbol": "lumenTabBarMinimizeBehavior"
38110
+ }
38111
+ },
38112
+ "name": "Tab bar minimization",
38113
+ "summary": "Let an iPhone native tab bar minimize and expand in response to scrolling.",
38114
+ "supportedPlatforms": [
38115
+ "iOS"
38116
+ ],
38117
+ "tier": "platform-specific"
38118
+ },
38119
+ {
38120
+ "accessibility": "Expanded and compact content preserve their own readable labels, controls, and traversal order while SwiftUI owns placement changes.",
38121
+ "category": "Navigation",
38122
+ "contract": "Adaptive expanded and compact content above or inline with a native tab bar",
38123
+ "guidance": "Use for a mini player, call, upload, recording, or persistent status. Keep accessory domain state and actions application-owned.",
38124
+ "id": "tab-accessory",
38125
+ "implementations": {
38126
+ "swiftui": {
38127
+ "api": [
38128
+ {
38129
+ "defaultValue": "Required",
38130
+ "description": "Provides content displayed above a regular-size tab bar.",
38131
+ "name": "expanded",
38132
+ "values": "@ViewBuilder () -> View"
38133
+ },
38134
+ {
38135
+ "defaultValue": "Required",
38136
+ "description": "Provides reduced content displayed inline with a minimized tab bar.",
38137
+ "name": "compact",
38138
+ "values": "@ViewBuilder () -> View"
38139
+ },
38140
+ {
38141
+ "defaultValue": "true",
38142
+ "description": "Controls whether the surrounding tab accessory modifier presents content.",
38143
+ "name": "isEnabled",
38144
+ "values": "Bool"
38145
+ }
38146
+ ],
38147
+ "example": "TabView {\n FeedView().tabItem { Label(\"Feed\", systemImage: \"rectangle.stack\") }\n}\n.lumenTabViewBottomAccessory {\n LumenTabAccessory {\n ExpandedUploadStatus()\n } compact: {\n CompactUploadStatus()\n }\n}",
38148
+ "exportName": "LumenTabAccessory",
38149
+ "importStatement": "import LumenUI",
38150
+ "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.",
38151
+ "language": "swift",
38152
+ "packageName": "LumenUI",
38153
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38154
+ "sourceFile": "packages/swift/Sources/LumenUI/NavigationComponents.swift",
38155
+ "symbol": "LumenTabAccessory"
38156
+ }
38157
+ },
38158
+ "name": "Tab accessory",
38159
+ "summary": "Adapt application-owned accessory content to expanded and inline tab-bar placement.",
38160
+ "supportedPlatforms": [
38161
+ "iOS"
38162
+ ],
38163
+ "tier": "platform-specific"
38164
+ },
38165
+ {
38166
+ "accessibility": "Recording, current shortcut, validation error, cancel, change, and clear states all have visible native labels.",
38167
+ "category": "macOS utilities",
38168
+ "contract": "Keyboard shortcut capture, validation, clear, and cancel states",
38169
+ "guidance": "Use only on macOS. Applications own conflict validation and command registration; Lumen handles capture and presentation.",
38170
+ "id": "shortcut-recorder",
38171
+ "implementations": {
38172
+ "swiftui": {
38173
+ "api": [
38174
+ {
38175
+ "defaultValue": "Required",
38176
+ "description": "Names the command being configured.",
38177
+ "name": "label",
38178
+ "values": "LocalizedStringKey"
38179
+ },
38180
+ {
38181
+ "defaultValue": "Required",
38182
+ "description": "Stores the current keyboard shortcut.",
38183
+ "name": "shortcut",
38184
+ "values": "Binding<LumenShortcut?>"
38185
+ },
38186
+ {
38187
+ "defaultValue": "nil",
38188
+ "description": "Returns an application conflict message or nil.",
38189
+ "name": "validation",
38190
+ "values": "((LumenShortcut) -> String?)?"
38191
+ },
38192
+ {
38193
+ "defaultValue": "Required",
38194
+ "description": "Uses NSEvent keyboard capture and macOS modifier glyphs.",
38195
+ "name": "platform",
38196
+ "values": "macOS"
38197
+ }
38198
+ ],
38199
+ "example": "LumenShortcutRecorder(\n \"Quick switch\",\n shortcut: $shortcut\n) { candidate in\n reserved.contains(candidate) ? \"Already in use.\" : nil\n}",
38200
+ "exportName": "LumenShortcutRecorder",
38201
+ "importStatement": "import LumenUI",
38202
+ "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.",
38203
+ "language": "swift",
38204
+ "packageName": "LumenUI",
38205
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38206
+ "sourceFile": "packages/swift/Sources/LumenUI/MacComponents.swift",
38207
+ "symbol": "LumenShortcutRecorder"
38208
+ }
38209
+ },
38210
+ "name": "Shortcut recorder",
38211
+ "summary": "Capture, validate, change, and clear native macOS keyboard shortcuts.",
38212
+ "supportedPlatforms": [
38213
+ "macOS"
38214
+ ],
38215
+ "tier": "platform-specific"
38216
+ },
38217
+ {
38218
+ "accessibility": "Every symbol option has a readable label and selected state. Search and empty states use native controls and focus behavior.",
38219
+ "category": "macOS utilities",
38220
+ "contract": "Searchable and categorized SF Symbols selection",
38221
+ "guidance": "Use only on macOS. Supply product-specific symbol options when the built-in common set is broader than the task requires.",
38222
+ "id": "symbol-picker",
38223
+ "implementations": {
38224
+ "swiftui": {
38225
+ "api": [
38226
+ {
38227
+ "defaultValue": "Choose a symbol",
38228
+ "description": "Names the picker or popover trigger.",
38229
+ "name": "title / label",
38230
+ "values": "LocalizedStringKey"
38231
+ },
38232
+ {
38233
+ "defaultValue": "Required",
38234
+ "description": "Stores the selected SF Symbol name.",
38235
+ "name": "selectedName",
38236
+ "values": "Binding<String>"
38237
+ },
38238
+ {
38239
+ "defaultValue": ".common",
38240
+ "description": "Provides labeled, categorized symbol choices.",
38241
+ "name": "options",
38242
+ "values": "[LumenSymbolOption]"
38243
+ },
38244
+ {
38245
+ "defaultValue": "Inline",
38246
+ "description": "Selects the full picker or compact popover trigger.",
38247
+ "name": "presentation",
38248
+ "values": "Inline picker / popover button"
38249
+ }
38250
+ ],
38251
+ "example": "LumenSymbolPickerButton(\n \"Workspace symbol\",\n selectedName: $symbolName\n)",
38252
+ "exportName": "LumenSymbolPicker / LumenSymbolPickerButton",
38253
+ "importStatement": "import LumenUI",
38254
+ "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.",
38255
+ "language": "swift",
38256
+ "packageName": "LumenUI",
38257
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38258
+ "sourceFile": "packages/swift/Sources/LumenUI/MacComponents.swift",
38259
+ "symbol": "LumenSymbolPicker"
38260
+ }
38261
+ },
38262
+ "name": "Symbol picker",
38263
+ "summary": "Search and choose from labeled, categorized SF Symbols in a native macOS picker.",
38264
+ "supportedPlatforms": [
38265
+ "macOS"
38266
+ ],
38267
+ "tier": "platform-specific"
38268
+ },
38269
+ {
38270
+ "accessibility": "Decorative graphics stay hidden from assistive technology; an optional label exposes the complete composition as one image.",
38271
+ "category": "Data display",
38272
+ "contract": "Token-aware glow, grid, and orbit presets around application-provided artwork",
38273
+ "guidance": "Use to frame meaningful application artwork with a restrained token-aware preset. Leave the label absent when the surrounding content already communicates the same meaning.",
38274
+ "id": "graphic",
38275
+ "implementations": {
38276
+ "compose": {
38277
+ "api": [
38278
+ {
38279
+ "defaultValue": "orbit",
38280
+ "description": "Selects the decorative composition.",
38281
+ "name": "variant",
38282
+ "values": "glow · grid · orbit"
38283
+ },
38284
+ {
38285
+ "defaultValue": "md",
38286
+ "description": "Selects a shared native dimension.",
38287
+ "name": "size",
38288
+ "values": "sm · md · lg"
38289
+ },
38290
+ {
38291
+ "defaultValue": "brand",
38292
+ "description": "Selects the semantic decoration color.",
38293
+ "name": "tone",
38294
+ "values": "brand · accent · neutral"
38295
+ },
38296
+ {
38297
+ "defaultValue": "null",
38298
+ "description": "Optionally exposes the composition as one labeled image.",
38299
+ "name": "label",
38300
+ "values": "String?"
38301
+ }
38302
+ ],
38303
+ "example": "LumenGraphic(\n variant = LumenGraphicVariant.Orbit,\n label = \"Project overview\"\n) {\n ProjectArtwork()\n}",
38304
+ "exportName": "LumenGraphic",
38305
+ "importStatement": "import com.santi020k.lumen.LumenGraphic",
38306
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38307
+ "language": "kotlin",
38308
+ "packageName": "com.santi020k:lumen-compose",
38309
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38310
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/ContentComponents.kt",
38311
+ "symbol": "LumenGraphic"
38312
+ },
38313
+ "react-native": {
38314
+ "api": [
38315
+ {
38316
+ "defaultValue": "orbit",
38317
+ "description": "Selects the decorative composition.",
38318
+ "name": "variant",
38319
+ "values": "glow · grid · orbit"
38320
+ },
38321
+ {
38322
+ "defaultValue": "md",
38323
+ "description": "Selects a shared native dimension.",
38324
+ "name": "size",
38325
+ "values": "sm · md · lg"
38326
+ },
38327
+ {
38328
+ "defaultValue": "brand",
38329
+ "description": "Selects the semantic decoration color.",
38330
+ "name": "tone",
38331
+ "values": "brand · accent · neutral"
38332
+ },
38333
+ {
38334
+ "defaultValue": "undefined",
38335
+ "description": "Optionally exposes the composition as one labeled image.",
38336
+ "name": "label",
38337
+ "values": "string"
38338
+ }
38339
+ ],
38340
+ "example": "<LumenGraphic variant=\"orbit\" label=\"Project overview\">\n <ProjectArtwork />\n</LumenGraphic>",
38341
+ "exportName": "LumenGraphic",
38342
+ "importStatement": "import { LumenGraphic } from '@santi020k/lumen-react-native'",
38343
+ "install": "pnpm add @santi020k/lumen-react-native",
38344
+ "language": "tsx",
38345
+ "packageName": "@santi020k/lumen-react-native",
38346
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38347
+ "sourceFile": "packages/react-native/src/content-components.tsx",
38348
+ "symbol": "LumenGraphic"
38349
+ },
38350
+ "swiftui": {
38351
+ "api": [
38352
+ {
38353
+ "defaultValue": "orbit",
38354
+ "description": "Selects the decorative composition.",
38355
+ "name": "variant",
38356
+ "values": "glow · grid · orbit"
38357
+ },
38358
+ {
38359
+ "defaultValue": "md",
38360
+ "description": "Selects a shared native dimension.",
38361
+ "name": "size",
38362
+ "values": "sm · md · lg"
38363
+ },
38364
+ {
38365
+ "defaultValue": "brand",
38366
+ "description": "Selects the semantic decoration color.",
38367
+ "name": "tone",
38368
+ "values": "brand · accent · neutral"
38369
+ },
38370
+ {
38371
+ "defaultValue": "nil",
38372
+ "description": "Optionally exposes the composition as one labeled image.",
38373
+ "name": "label",
38374
+ "values": "LocalizedStringKey?"
38375
+ }
38376
+ ],
38377
+ "example": "LumenGraphic(\n variant: .orbit,\n label: \"Project overview\"\n) {\n ProjectArtwork()\n}",
38378
+ "exportName": "LumenGraphic",
38379
+ "importStatement": "import LumenUI",
38380
+ "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.",
38381
+ "language": "swift",
38382
+ "packageName": "LumenUI",
38383
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38384
+ "sourceFile": "packages/swift/Sources/LumenUI/ContentComponents.swift",
38385
+ "symbol": "LumenGraphic"
38386
+ }
38387
+ },
38388
+ "name": "Graphic",
38389
+ "summary": "Frame application-provided artwork with token-aware glow, grid, or orbit decoration.",
38390
+ "tier": "shared"
38391
+ },
38392
+ {
38393
+ "accessibility": "The ambient layer has no accessibility semantics; child content keeps its native reading order, names, and controls.",
38394
+ "category": "Data display",
38395
+ "contract": "Ambient aurora, dots, grid, and rays behind application content with semantic tones and intensity",
38396
+ "guidance": "Use behind meaningful hero, empty-state, or highlighted content. Keep child surfaces and text on semantic tokens so contrast does not depend on the decoration.",
38397
+ "id": "backdrop",
38398
+ "implementations": {
38399
+ "compose": {
38400
+ "api": [
38401
+ {
38402
+ "defaultValue": "aurora",
38403
+ "description": "Selects the ambient pattern.",
38404
+ "name": "variant",
38405
+ "values": "aurora · dots · grid · rays"
38406
+ },
38407
+ {
38408
+ "defaultValue": "brand",
38409
+ "description": "Selects the semantic decoration color.",
38410
+ "name": "tone",
38411
+ "values": "brand · accent · neutral"
38412
+ },
38413
+ {
38414
+ "defaultValue": "medium",
38415
+ "description": "Controls only the decoration opacity.",
38416
+ "name": "intensity",
38417
+ "values": "subtle · medium · strong"
38418
+ },
38419
+ {
38420
+ "defaultValue": "Required",
38421
+ "description": "Renders meaningful content above the decorative layer.",
38422
+ "name": "content",
38423
+ "values": "Native view content"
38424
+ }
38425
+ ],
38426
+ "example": "LumenBackdrop(\n intensity = LumenBackdropIntensity.Medium,\n variant = LumenBackdropVariant.Aurora\n) {\n ProjectOverview()\n}",
38427
+ "exportName": "LumenBackdrop",
38428
+ "importStatement": "import com.santi020k.lumen.LumenBackdrop",
38429
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38430
+ "language": "kotlin",
38431
+ "packageName": "com.santi020k:lumen-compose",
38432
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38433
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/ContentComponents.kt",
38434
+ "symbol": "LumenBackdrop"
38435
+ },
38436
+ "react-native": {
38437
+ "api": [
38438
+ {
38439
+ "defaultValue": "aurora",
38440
+ "description": "Selects the ambient pattern.",
38441
+ "name": "variant",
38442
+ "values": "aurora · dots · grid · rays"
38443
+ },
38444
+ {
38445
+ "defaultValue": "brand",
38446
+ "description": "Selects the semantic decoration color.",
38447
+ "name": "tone",
38448
+ "values": "brand · accent · neutral"
38449
+ },
38450
+ {
38451
+ "defaultValue": "medium",
38452
+ "description": "Controls only the decoration opacity.",
38453
+ "name": "intensity",
38454
+ "values": "subtle · medium · strong"
38455
+ },
38456
+ {
38457
+ "defaultValue": "Required",
38458
+ "description": "Renders meaningful content above the decorative layer.",
38459
+ "name": "content",
38460
+ "values": "Native view content"
38461
+ }
38462
+ ],
38463
+ "example": "<LumenBackdrop intensity=\"medium\" variant=\"aurora\">\n <ProjectOverview />\n</LumenBackdrop>",
38464
+ "exportName": "LumenBackdrop",
38465
+ "importStatement": "import { LumenBackdrop } from '@santi020k/lumen-react-native'",
38466
+ "install": "pnpm add @santi020k/lumen-react-native",
38467
+ "language": "tsx",
38468
+ "packageName": "@santi020k/lumen-react-native",
38469
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38470
+ "sourceFile": "packages/react-native/src/content-components.tsx",
38471
+ "symbol": "LumenBackdrop"
38472
+ },
38473
+ "swiftui": {
38474
+ "api": [
38475
+ {
38476
+ "defaultValue": "aurora",
38477
+ "description": "Selects the ambient pattern.",
38478
+ "name": "variant",
38479
+ "values": "aurora · dots · grid · rays"
38480
+ },
38481
+ {
38482
+ "defaultValue": "brand",
38483
+ "description": "Selects the semantic decoration color.",
38484
+ "name": "tone",
38485
+ "values": "brand · accent · neutral"
38486
+ },
38487
+ {
38488
+ "defaultValue": "medium",
38489
+ "description": "Controls only the decoration opacity.",
38490
+ "name": "intensity",
38491
+ "values": "subtle · medium · strong"
38492
+ },
38493
+ {
38494
+ "defaultValue": "Required",
38495
+ "description": "Renders meaningful content above the decorative layer.",
38496
+ "name": "content",
38497
+ "values": "Native view content"
38498
+ }
38499
+ ],
38500
+ "example": "LumenBackdrop(\n intensity: .medium,\n variant: .aurora\n) {\n ProjectOverview()\n}",
38501
+ "exportName": "LumenBackdrop",
38502
+ "importStatement": "import LumenUI",
38503
+ "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.",
38504
+ "language": "swift",
38505
+ "packageName": "LumenUI",
38506
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38507
+ "sourceFile": "packages/swift/Sources/LumenUI/ContentComponents.swift",
38508
+ "symbol": "LumenBackdrop"
38509
+ }
38510
+ },
38511
+ "name": "Backdrop",
38512
+ "summary": "Place a token-aware ambient pattern behind native application content.",
38513
+ "tier": "shared"
38514
+ },
38515
+ {
38516
+ "accessibility": "Illustrations are decorative by default; an optional label exposes the complete scene as one image.",
38517
+ "category": "Feedback",
38518
+ "contract": "Built-in empty, success, error, and offline scenes with shared semantic colors and sizes",
38519
+ "guidance": "Use the built-in scene when its state matches the product message. Keep it decorative when the adjacent heading and description already communicate the same status.",
38520
+ "id": "illustration",
38521
+ "implementations": {
38522
+ "compose": {
38523
+ "api": [
38524
+ {
38525
+ "defaultValue": "empty",
38526
+ "description": "Selects the semantic scene.",
38527
+ "name": "variant",
38528
+ "values": "empty · success · error · offline"
38529
+ },
38530
+ {
38531
+ "defaultValue": "auto",
38532
+ "description": "Uses the state color automatically or an explicit portable tone.",
38533
+ "name": "tone",
38534
+ "values": "auto · brand · accent · neutral"
38535
+ },
38536
+ {
38537
+ "defaultValue": "md",
38538
+ "description": "Selects the shared 96, 128, or 176 unit dimension.",
38539
+ "name": "size",
38540
+ "values": "sm · md · lg"
38541
+ },
38542
+ {
38543
+ "defaultValue": "null",
38544
+ "description": "Optionally exposes the illustration as one labeled image.",
38545
+ "name": "label",
38546
+ "values": "String?"
38547
+ }
38548
+ ],
38549
+ "example": "LumenIllustration(\n variant = LumenIllustrationVariant.Success,\n label = \"Saved successfully\"\n)",
38550
+ "exportName": "LumenIllustration",
38551
+ "importStatement": "import com.santi020k.lumen.LumenIllustration",
38552
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38553
+ "language": "kotlin",
38554
+ "packageName": "com.santi020k:lumen-compose",
38555
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38556
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/ContentComponents.kt",
38557
+ "symbol": "LumenIllustration"
38558
+ },
38559
+ "react-native": {
38560
+ "api": [
38561
+ {
38562
+ "defaultValue": "empty",
38563
+ "description": "Selects the semantic scene.",
38564
+ "name": "variant",
38565
+ "values": "empty · success · error · offline"
38566
+ },
38567
+ {
38568
+ "defaultValue": "auto",
38569
+ "description": "Uses the state color automatically or an explicit portable tone.",
38570
+ "name": "tone",
38571
+ "values": "auto · brand · accent · neutral"
38572
+ },
38573
+ {
38574
+ "defaultValue": "md",
38575
+ "description": "Selects the shared 96, 128, or 176 unit dimension.",
38576
+ "name": "size",
38577
+ "values": "sm · md · lg"
38578
+ },
38579
+ {
38580
+ "defaultValue": "undefined",
38581
+ "description": "Optionally exposes the illustration as one labeled image.",
38582
+ "name": "label",
38583
+ "values": "string"
38584
+ }
38585
+ ],
38586
+ "example": "<LumenIllustration\n label=\"Saved successfully\"\n variant=\"success\"\n/>",
38587
+ "exportName": "LumenIllustration",
38588
+ "importStatement": "import { LumenIllustration } from '@santi020k/lumen-react-native'",
38589
+ "install": "pnpm add @santi020k/lumen-react-native",
38590
+ "language": "tsx",
38591
+ "packageName": "@santi020k/lumen-react-native",
38592
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38593
+ "sourceFile": "packages/react-native/src/content-components.tsx",
38594
+ "symbol": "LumenIllustration"
38595
+ },
38596
+ "swiftui": {
38597
+ "api": [
38598
+ {
38599
+ "defaultValue": "empty",
38600
+ "description": "Selects the semantic scene.",
38601
+ "name": "variant",
38602
+ "values": "empty · success · error · offline"
38603
+ },
38604
+ {
38605
+ "defaultValue": "auto",
38606
+ "description": "Uses the state color automatically or an explicit portable tone.",
38607
+ "name": "tone",
38608
+ "values": "auto · brand · accent · neutral"
38609
+ },
38610
+ {
38611
+ "defaultValue": "md",
38612
+ "description": "Selects the shared 96, 128, or 176 unit dimension.",
38613
+ "name": "size",
38614
+ "values": "sm · md · lg"
38615
+ },
38616
+ {
38617
+ "defaultValue": "nil",
38618
+ "description": "Optionally exposes the illustration as one labeled image.",
38619
+ "name": "label",
38620
+ "values": "String?"
38621
+ }
38622
+ ],
38623
+ "example": "LumenIllustration(\n variant: .success,\n label: \"Saved successfully\"\n)",
38624
+ "exportName": "LumenIllustration",
38625
+ "importStatement": "import LumenUI",
38626
+ "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.",
38627
+ "language": "swift",
38628
+ "packageName": "LumenUI",
38629
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38630
+ "sourceFile": "packages/swift/Sources/LumenUI/ContentComponents.swift",
38631
+ "symbol": "LumenIllustration"
38632
+ }
38633
+ },
38634
+ "name": "Illustration",
38635
+ "summary": "Render a built-in empty, success, error, or offline semantic scene.",
38636
+ "tier": "shared"
38637
+ },
38638
+ {
38639
+ "accessibility": "Native modal focus stays inside the confirmation surface and cancel and confirm remain independently named actions.",
38640
+ "category": "Feedback",
38641
+ "contract": "Controlled confirmation with cancel, confirm, destructive, disabled, and loading states",
38642
+ "guidance": "Use for short consequential confirmations. Keep visibility and mutation state in the application and reserve destructive styling for irreversible or difficult-to-recover actions.",
38643
+ "id": "alert-dialog",
38644
+ "implementations": {
38645
+ "compose": {
38646
+ "api": [
38647
+ {
38648
+ "defaultValue": "Required",
38649
+ "description": "Controls native modal presentation.",
38650
+ "name": "visible",
38651
+ "values": "Boolean"
38652
+ },
38653
+ {
38654
+ "defaultValue": "Description optional",
38655
+ "description": "Explains the decision.",
38656
+ "name": "title / description",
38657
+ "values": "Native localized text"
38658
+ },
38659
+ {
38660
+ "defaultValue": "Cancel label: Cancel",
38661
+ "description": "Names both actions.",
38662
+ "name": "confirmLabel / cancelLabel",
38663
+ "values": "Native localized text"
38664
+ },
38665
+ {
38666
+ "defaultValue": "false",
38667
+ "description": "Communicates whether the confirm action is destructive.",
38668
+ "name": "destructive",
38669
+ "values": "Boolean"
38670
+ },
38671
+ {
38672
+ "defaultValue": "true",
38673
+ "description": "Controls whether the confirm action can be activated.",
38674
+ "name": "confirmEnabled",
38675
+ "values": "Boolean"
38676
+ },
38677
+ {
38678
+ "defaultValue": "false",
38679
+ "description": "Communicates asynchronous confirmation progress.",
38680
+ "name": "confirmLoading",
38681
+ "values": "Boolean"
38682
+ }
38683
+ ],
38684
+ "example": "LumenAlertDialog(\n visible = showConfirmation,\n title = \"Delete report?\",\n confirmLabel = \"Delete\",\n destructive = true,\n onConfirm = ::deleteReport,\n onDismiss = { showConfirmation = false }\n)",
38685
+ "exportName": "LumenAlertDialog",
38686
+ "importStatement": "import com.santi020k.lumen.LumenAlertDialog",
38687
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38688
+ "language": "kotlin",
38689
+ "packageName": "com.santi020k:lumen-compose",
38690
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38691
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
38692
+ "symbol": "LumenAlertDialog"
38693
+ },
38694
+ "react-native": {
38695
+ "api": [
38696
+ {
38697
+ "defaultValue": "Required",
38698
+ "description": "Controls native modal presentation.",
38699
+ "name": "visible",
38700
+ "values": "boolean"
38701
+ },
38702
+ {
38703
+ "defaultValue": "Description optional",
38704
+ "description": "Explains the decision.",
38705
+ "name": "title / description",
38706
+ "values": "Native localized text"
38707
+ },
38708
+ {
38709
+ "defaultValue": "Cancel label: Cancel",
38710
+ "description": "Names both actions.",
38711
+ "name": "confirmLabel / cancelLabel",
38712
+ "values": "Native localized text"
38713
+ },
38714
+ {
38715
+ "defaultValue": "false",
38716
+ "description": "Communicates whether the confirm action is destructive.",
38717
+ "name": "destructive",
38718
+ "values": "boolean"
38719
+ },
38720
+ {
38721
+ "defaultValue": "false",
38722
+ "description": "Controls whether the confirm action can be activated.",
38723
+ "name": "confirmDisabled",
38724
+ "values": "boolean"
38725
+ },
38726
+ {
38727
+ "defaultValue": "false",
38728
+ "description": "Communicates asynchronous confirmation progress.",
38729
+ "name": "confirmLoading",
38730
+ "values": "boolean"
38731
+ }
38732
+ ],
38733
+ "example": "<LumenAlertDialog\n visible={showConfirmation}\n title=\"Delete report?\"\n confirmLabel=\"Delete\"\n destructive\n onConfirm={deleteReport}\n onDismiss={() => setShowConfirmation(false)}\n/>",
38734
+ "exportName": "LumenAlertDialog",
38735
+ "importStatement": "import { LumenAlertDialog } from '@santi020k/lumen-react-native'",
38736
+ "install": "pnpm add @santi020k/lumen-react-native",
38737
+ "language": "tsx",
38738
+ "packageName": "@santi020k/lumen-react-native",
38739
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38740
+ "sourceFile": "packages/react-native/src/index.ts",
38741
+ "symbol": "LumenAlertDialog"
38742
+ },
38743
+ "swiftui": {
38744
+ "api": [
38745
+ {
38746
+ "defaultValue": "Required",
38747
+ "description": "Controls native modal presentation.",
38748
+ "name": "isPresented",
38749
+ "values": "Binding<Bool>"
38750
+ },
38751
+ {
38752
+ "defaultValue": "Description optional",
38753
+ "description": "Explains the decision.",
38754
+ "name": "title / description",
38755
+ "values": "Native localized text"
38756
+ },
38757
+ {
38758
+ "defaultValue": "Cancel label: Cancel",
38759
+ "description": "Names both actions.",
38760
+ "name": "confirmLabel / cancelLabel",
38761
+ "values": "Native localized text"
38762
+ },
38763
+ {
38764
+ "defaultValue": "nil",
38765
+ "description": "Communicates whether the confirm action is destructive.",
38766
+ "name": "confirmRole",
38767
+ "values": "ButtonRole?"
38768
+ },
38769
+ {
38770
+ "defaultValue": "false",
38771
+ "description": "Controls whether the confirm action can be activated.",
38772
+ "name": "confirmDisabled",
38773
+ "values": "Bool"
38774
+ },
38775
+ {
38776
+ "defaultValue": "false",
38777
+ "description": "Communicates asynchronous confirmation progress.",
38778
+ "name": "confirmLoading",
38779
+ "values": "Bool"
38780
+ }
38781
+ ],
38782
+ "example": "content.lumenAlertDialog(\n isPresented: $showConfirmation,\n title: \"Delete report?\",\n confirmLabel: \"Delete\",\n confirmRole: .destructive,\n onConfirm: deleteReport\n)",
38783
+ "exportName": "lumenAlertDialog",
38784
+ "importStatement": "import LumenUI",
38785
+ "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.",
38786
+ "language": "swift",
38787
+ "packageName": "LumenUI",
38788
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38789
+ "sourceFile": "packages/swift/Sources/LumenUI/OverlayComponents.swift",
38790
+ "symbol": "lumenAlertDialog"
38791
+ }
38792
+ },
38793
+ "name": "Alert dialog",
38794
+ "summary": "Request a controlled native confirmation with explicit cancel and confirm states.",
38795
+ "tier": "shared"
38796
+ },
38797
+ {
38798
+ "accessibility": "The native modal presentation contains focus while preserving the semantics and reading order of application-owned content.",
38799
+ "category": "Layout",
38800
+ "contract": "Controlled supplemental surface with optional heading, description, actions, and native dismissal",
38801
+ "guidance": "Use for supplemental editing or detail that should not replace the current screen. Keep form state and dismissal decisions application-owned.",
38802
+ "id": "sheet",
38803
+ "implementations": {
38804
+ "compose": {
38805
+ "api": [
38806
+ {
38807
+ "defaultValue": "Required",
38808
+ "description": "Controls native sheet presentation.",
38809
+ "name": "visible",
38810
+ "values": "Boolean"
38811
+ },
38812
+ {
38813
+ "defaultValue": "nil / undefined",
38814
+ "description": "Provides optional heading context.",
38815
+ "name": "title / description",
38816
+ "values": "Native localized text"
38817
+ },
38818
+ {
38819
+ "defaultValue": "Required",
38820
+ "description": "Returns presentation ownership to the application.",
38821
+ "name": "onDismiss",
38822
+ "values": "() -> Void"
38823
+ },
38824
+ {
38825
+ "defaultValue": "Actions optional",
38826
+ "description": "Composes application-owned content and actions.",
38827
+ "name": "content / actions",
38828
+ "values": "Native view slots"
38829
+ }
38830
+ ],
38831
+ "example": "LumenSheet(\n visible = showEditor,\n onDismiss = { showEditor = false },\n title = \"Edit report\"\n) {\n ReportEditor()\n}",
38832
+ "exportName": "LumenSheet",
38833
+ "importStatement": "import com.santi020k.lumen.LumenSheet",
38834
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38835
+ "language": "kotlin",
38836
+ "packageName": "com.santi020k:lumen-compose",
38837
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38838
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
38839
+ "symbol": "LumenSheet"
38840
+ },
38841
+ "react-native": {
38842
+ "api": [
38843
+ {
38844
+ "defaultValue": "Required",
38845
+ "description": "Controls native sheet presentation.",
38846
+ "name": "visible",
36817
38847
  "values": "boolean"
36818
38848
  },
36819
38849
  {
36820
- "defaultValue": "Required",
36821
- "description": "Starts the application refresh operation.",
36822
- "name": "onRefresh",
36823
- "values": "() => void"
38850
+ "defaultValue": "nil / undefined",
38851
+ "description": "Provides optional heading context.",
38852
+ "name": "title / description",
38853
+ "values": "Native localized text"
36824
38854
  },
36825
38855
  {
36826
- "defaultValue": "brand",
36827
- "description": "Selects the semantic native indicator color.",
36828
- "name": "indicatorTone",
36829
- "values": "brand · accent · neutral"
38856
+ "defaultValue": "Required",
38857
+ "description": "Returns presentation ownership to the application.",
38858
+ "name": "onDismiss",
38859
+ "values": "() -> Void"
36830
38860
  },
36831
38861
  {
36832
- "defaultValue": "Application-provided",
36833
- "description": "Names the content refreshed by the gesture.",
36834
- "name": "accessibilityLabel",
36835
- "values": "string"
38862
+ "defaultValue": "Actions optional",
38863
+ "description": "Composes application-owned content and actions.",
38864
+ "name": "content / actions",
38865
+ "values": "Native view slots"
36836
38866
  }
36837
38867
  ],
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'",
38868
+ "example": "<LumenSheet\n visible={showEditor}\n title=\"Edit report\"\n onDismiss={() => setShowEditor(false)}\n>\n <ReportEditor />\n</LumenSheet>",
38869
+ "exportName": "LumenSheet",
38870
+ "importStatement": "import { LumenSheet } from '@santi020k/lumen-react-native'",
36841
38871
  "install": "pnpm add @santi020k/lumen-react-native",
36842
38872
  "language": "tsx",
36843
38873
  "packageName": "@santi020k/lumen-react-native",
36844
38874
  "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
36845
38875
  "sourceFile": "packages/react-native/src/index.ts",
36846
- "symbol": "LumenRefreshControl"
38876
+ "symbol": "LumenSheet"
38877
+ },
38878
+ "swiftui": {
38879
+ "api": [
38880
+ {
38881
+ "defaultValue": "Required",
38882
+ "description": "Controls native sheet presentation.",
38883
+ "name": "isPresented",
38884
+ "values": "Binding<Bool>"
38885
+ },
38886
+ {
38887
+ "defaultValue": "nil / undefined",
38888
+ "description": "Provides optional heading context.",
38889
+ "name": "title / description",
38890
+ "values": "Native localized text"
38891
+ },
38892
+ {
38893
+ "defaultValue": "Required",
38894
+ "description": "Returns presentation ownership to the application.",
38895
+ "name": "onDismiss",
38896
+ "values": "() -> Void"
38897
+ },
38898
+ {
38899
+ "defaultValue": "Actions optional",
38900
+ "description": "Composes application-owned content and actions.",
38901
+ "name": "content / actions",
38902
+ "values": "Native view slots"
38903
+ }
38904
+ ],
38905
+ "example": "content.lumenSheet(\n isPresented: $showEditor,\n title: \"Edit report\"\n) {\n ReportEditor()\n}",
38906
+ "exportName": "lumenSheet",
38907
+ "importStatement": "import LumenUI",
38908
+ "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.",
38909
+ "language": "swift",
38910
+ "packageName": "LumenUI",
38911
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38912
+ "sourceFile": "packages/swift/Sources/LumenUI/OverlayComponents.swift",
38913
+ "symbol": "lumenSheet"
36847
38914
  }
36848
38915
  },
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"
38916
+ "name": "Sheet",
38917
+ "summary": "Present supplemental application content in a controlled native sheet.",
38918
+ "tier": "shared"
36856
38919
  },
36857
38920
  {
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",
38921
+ "accessibility": "The trigger exposes expanded state and each native menu action reports its label, disabled state, and destructive role.",
38922
+ "category": "Actions",
38923
+ "contract": "Anchored action menu with labeled disabled and destructive item states",
38924
+ "guidance": "Use for a short set of contextual actions. Keep labels unique and concise, and do not hide a screen's only primary action inside a menu.",
38925
+ "id": "menu",
36863
38926
  "implementations": {
36864
- "swiftui": {
38927
+ "compose": {
36865
38928
  "api": [
36866
38929
  {
36867
38930
  "defaultValue": "Required",
36868
- "description": "Labels the native date picker.",
36869
- "name": "title",
36870
- "values": "LocalizedStringKey"
38931
+ "description": "Provides labeled actions and states.",
38932
+ "name": "items",
38933
+ "values": "Native Lumen menu item collection"
38934
+ },
38935
+ {
38936
+ "defaultValue": "Collapsed",
38937
+ "description": "Controls or reports native menu presentation.",
38938
+ "name": "expanded",
38939
+ "values": "Boolean"
38940
+ },
38941
+ {
38942
+ "defaultValue": "false",
38943
+ "description": "Keeps an unavailable item readable but inactive.",
38944
+ "name": "disabled",
38945
+ "values": "Boolean"
36871
38946
  },
38947
+ {
38948
+ "defaultValue": "false / nil",
38949
+ "description": "Marks a destructive action semantically and visually.",
38950
+ "name": "destructive / role",
38951
+ "values": "Destructive action role"
38952
+ }
38953
+ ],
38954
+ "example": "LumenMenu(\n expanded = showMenu,\n onDismissRequest = { showMenu = false },\n items = actions\n)",
38955
+ "exportName": "LumenMenu",
38956
+ "importStatement": "import com.santi020k.lumen.LumenMenu",
38957
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38958
+ "language": "kotlin",
38959
+ "packageName": "com.santi020k:lumen-compose",
38960
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38961
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
38962
+ "symbol": "LumenMenu"
38963
+ },
38964
+ "react-native": {
38965
+ "api": [
36872
38966
  {
36873
38967
  "defaultValue": "Required",
36874
- "description": "Stores the selected native date value.",
36875
- "name": "selection",
36876
- "values": "Binding<Date>"
38968
+ "description": "Provides labeled actions and states.",
38969
+ "name": "items",
38970
+ "values": "Native Lumen menu item collection"
36877
38971
  },
36878
38972
  {
36879
- "defaultValue": "date",
36880
- "description": "Chooses the visible date and time parts.",
36881
- "name": "components",
36882
- "values": "date · dateAndTime · time"
38973
+ "defaultValue": "Collapsed",
38974
+ "description": "Controls or reports native menu presentation.",
38975
+ "name": "Internal trigger state",
38976
+ "values": "Native Modal"
36883
38977
  },
36884
38978
  {
36885
- "defaultValue": "unbounded",
36886
- "description": "Constrains selection using native picker bounds.",
36887
- "name": "bounds",
36888
- "values": "unbounded · closed · from · through"
38979
+ "defaultValue": "false",
38980
+ "description": "Keeps an unavailable item readable but inactive.",
38981
+ "name": "disabled",
38982
+ "values": "Boolean"
36889
38983
  },
36890
38984
  {
36891
- "defaultValue": "nil",
36892
- "description": "Shows supporting text or a semantic danger validation message.",
36893
- "name": "description / errorMessage",
36894
- "values": "LocalizedStringKey?"
38985
+ "defaultValue": "false / nil",
38986
+ "description": "Marks a destructive action semantically and visually.",
38987
+ "name": "destructive / role",
38988
+ "values": "Destructive action role"
36895
38989
  }
36896
38990
  ],
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",
38991
+ "example": "<LumenMenu\n accessibilityLabel=\"More actions\"\n trigger={<MoreIcon />}\n items={actions}\n/>",
38992
+ "exportName": "LumenMenu",
38993
+ "importStatement": "import { LumenMenu } from '@santi020k/lumen-react-native'",
38994
+ "install": "pnpm add @santi020k/lumen-react-native",
38995
+ "language": "tsx",
38996
+ "packageName": "@santi020k/lumen-react-native",
38997
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38998
+ "sourceFile": "packages/react-native/src/index.ts",
38999
+ "symbol": "LumenMenu"
39000
+ },
39001
+ "swiftui": {
39002
+ "api": [
39003
+ {
39004
+ "defaultValue": "Required",
39005
+ "description": "Provides labeled actions and states.",
39006
+ "name": "items",
39007
+ "values": "Native Lumen menu item collection"
39008
+ },
39009
+ {
39010
+ "defaultValue": "Collapsed",
39011
+ "description": "Controls or reports native menu presentation.",
39012
+ "name": "Native Menu state",
39013
+ "values": "Native"
39014
+ },
39015
+ {
39016
+ "defaultValue": "false",
39017
+ "description": "Keeps an unavailable item readable but inactive.",
39018
+ "name": "disabled",
39019
+ "values": "Boolean"
39020
+ },
39021
+ {
39022
+ "defaultValue": "false / nil",
39023
+ "description": "Marks a destructive action semantically and visually.",
39024
+ "name": "destructive / role",
39025
+ "values": "Destructive action role"
39026
+ }
39027
+ ],
39028
+ "example": "LumenMenu(items: actions) {\n Label(\"More\", systemImage: \"ellipsis\")\n}",
39029
+ "exportName": "LumenMenu",
36899
39030
  "importStatement": "import LumenUI",
36900
39031
  "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
39032
  "language": "swift",
36902
39033
  "packageName": "LumenUI",
36903
39034
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
36904
- "sourceFile": "packages/swift/Sources/LumenUI/AppleComponents.swift",
36905
- "symbol": "LumenDateField"
39035
+ "sourceFile": "packages/swift/Sources/LumenUI/OverlayComponents.swift",
39036
+ "symbol": "LumenMenu"
36906
39037
  }
36907
39038
  },
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"
39039
+ "name": "Menu",
39040
+ "summary": "Present a native anchored action menu with shared item-state semantics.",
39041
+ "tier": "shared"
36915
39042
  },
36916
39043
  {
36917
- "accessibility": "The required content description names the icon-only action while the control preserves native Material button semantics.",
39044
+ "accessibility": "The trigger remains a native labeled button and destination selection is delegated to the operating system share surface.",
36918
39045
  "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",
39046
+ "contract": "Token-aware button that presents the operating system share surface for application-owned content",
39047
+ "guidance": "Use to share application-owned text, URLs, or files through the operating system. Keep content generation, file permissions, completion feedback, and error handling in the application.",
39048
+ "id": "share-button",
36922
39049
  "implementations": {
36923
39050
  "compose": {
36924
39051
  "api": [
36925
39052
  {
36926
39053
  "defaultValue": "Required",
36927
- "description": "Renders the native action glyph.",
36928
- "name": "imageVector",
36929
- "values": "ImageVector"
39054
+ "description": "Supplies application-owned share content.",
39055
+ "name": "payload",
39056
+ "values": "LumenSharePayload"
36930
39057
  },
36931
39058
  {
36932
39059
  "defaultValue": "Required",
36933
- "description": "Names the icon-only action.",
36934
- "name": "contentDescription",
39060
+ "description": "Configures the native share presentation.",
39061
+ "name": "chooserTitle",
36935
39062
  "values": "String"
36936
39063
  },
39064
+ {
39065
+ "defaultValue": "Share",
39066
+ "description": "Names the trigger action.",
39067
+ "name": "label",
39068
+ "values": "Native localized text"
39069
+ },
39070
+ {
39071
+ "defaultValue": "No-op",
39072
+ "description": "Lets the application report sharing outcomes where the platform exposes them.",
39073
+ "name": "onFailure",
39074
+ "values": "Platform callback"
39075
+ }
39076
+ ],
39077
+ "example": "LumenShareButton(\n payload = LumenSharePayload(text = reportText),\n chooserTitle = \"Share report\"\n)",
39078
+ "exportName": "LumenShareButton",
39079
+ "importStatement": "import com.santi020k.lumen.LumenShareButton",
39080
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
39081
+ "language": "kotlin",
39082
+ "packageName": "com.santi020k:lumen-compose",
39083
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
39084
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
39085
+ "symbol": "LumenShareButton"
39086
+ },
39087
+ "react-native": {
39088
+ "api": [
36937
39089
  {
36938
39090
  "defaultValue": "Required",
36939
- "description": "Runs the primary screen action.",
39091
+ "description": "Supplies application-owned share content.",
39092
+ "name": "content",
39093
+ "values": "ShareContent"
39094
+ },
39095
+ {
39096
+ "defaultValue": "undefined",
39097
+ "description": "Configures the native share presentation.",
39098
+ "name": "options",
39099
+ "values": "ShareOptions"
39100
+ },
39101
+ {
39102
+ "defaultValue": "Share",
39103
+ "description": "Names the trigger action.",
39104
+ "name": "label",
39105
+ "values": "Native localized text"
39106
+ },
39107
+ {
39108
+ "defaultValue": "No-op",
39109
+ "description": "Lets the application report sharing outcomes where the platform exposes them.",
39110
+ "name": "onError / onShared",
39111
+ "values": "Platform callback"
39112
+ }
39113
+ ],
39114
+ "example": "<LumenShareButton\n label=\"Share report\"\n content={{ message: reportText }}\n/>",
39115
+ "exportName": "LumenShareButton",
39116
+ "importStatement": "import { LumenShareButton } from '@santi020k/lumen-react-native'",
39117
+ "install": "pnpm add @santi020k/lumen-react-native",
39118
+ "language": "tsx",
39119
+ "packageName": "@santi020k/lumen-react-native",
39120
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
39121
+ "sourceFile": "packages/react-native/src/index.ts",
39122
+ "symbol": "LumenShareButton"
39123
+ },
39124
+ "swiftui": {
39125
+ "api": [
39126
+ {
39127
+ "defaultValue": "Required",
39128
+ "description": "Supplies application-owned share content.",
39129
+ "name": "item",
39130
+ "values": "Transferable"
39131
+ },
39132
+ {
39133
+ "defaultValue": "System-provided",
39134
+ "description": "Configures the native share presentation.",
39135
+ "name": "Native share title",
39136
+ "values": "System-provided"
39137
+ },
39138
+ {
39139
+ "defaultValue": "Share",
39140
+ "description": "Names the trigger action.",
39141
+ "name": "label",
39142
+ "values": "Native localized text"
39143
+ },
39144
+ {
39145
+ "defaultValue": "No-op",
39146
+ "description": "Lets the application report sharing outcomes where the platform exposes them.",
39147
+ "name": "Native ShareLink result",
39148
+ "values": "Platform callback"
39149
+ }
39150
+ ],
39151
+ "example": "LumenShareButton(\"Share report\", item: reportText)",
39152
+ "exportName": "LumenShareButton",
39153
+ "importStatement": "import LumenUI",
39154
+ "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.",
39155
+ "language": "swift",
39156
+ "packageName": "LumenUI",
39157
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
39158
+ "sourceFile": "packages/swift/Sources/LumenUI/OverlayComponents.swift",
39159
+ "symbol": "LumenShareButton"
39160
+ }
39161
+ },
39162
+ "name": "Share button",
39163
+ "summary": "Open the operating system share surface from a token-aware native action.",
39164
+ "tier": "shared"
39165
+ },
39166
+ {
39167
+ "accessibility": "Requires one concise action label, preserves native button semantics, and exposes disabled state without duplicating visible content.",
39168
+ "category": "Actions",
39169
+ "contract": "At-a-glance round primary action with semantic intent, disabled state, and wearable sizing",
39170
+ "guidance": "Use for one essential wrist action. Keep haptics, health or safety policy, synchronization, and command handling in the application.",
39171
+ "id": "wearable-action",
39172
+ "implementations": {
39173
+ "compose": {
39174
+ "api": [
39175
+ {
39176
+ "defaultValue": "Required",
39177
+ "description": "Names the essential action.",
39178
+ "name": "accessibilityLabel",
39179
+ "values": "Native localized text"
39180
+ },
39181
+ {
39182
+ "defaultValue": "Required",
39183
+ "description": "Runs application-owned behavior.",
36940
39184
  "name": "onClick",
36941
39185
  "values": "() -> Unit"
36942
39186
  },
36943
39187
  {
36944
39188
  "defaultValue": "brand",
36945
- "description": "Selects a semantic action palette.",
36946
- "name": "intent",
36947
- "values": "brand · accent · danger"
39189
+ "description": "Applies semantic intent.",
39190
+ "name": "tone",
39191
+ "values": "brand · accent · success · warning · danger · neutral"
36948
39192
  },
36949
39193
  {
36950
- "defaultValue": "regular",
36951
- "description": "Selects Material-native FAB geometry.",
36952
- "name": "size",
36953
- "values": "regular · small"
39194
+ "defaultValue": "120",
39195
+ "description": "Clamps the round target to wearable-safe bounds.",
39196
+ "name": "dimension",
39197
+ "values": "Native display units"
39198
+ },
39199
+ {
39200
+ "defaultValue": "true",
39201
+ "description": "Controls action and disabled presentation.",
39202
+ "name": "enabled",
39203
+ "values": "Boolean"
36954
39204
  }
36955
39205
  ],
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\")).",
39206
+ "example": "LumenWearActionButton(\n accessibilityLabel = \"Start contraction\",\n onClick = ::startContraction\n) { TimerLabel() }",
39207
+ "exportName": "LumenWearActionButton",
39208
+ "importStatement": "import com.santi020k.lumen.LumenWearActionButton",
39209
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":wear\")).",
36960
39210
  "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"
39211
+ "packageName": "com.santi020k:lumen-compose-wear",
39212
+ "setup": "Wrap wearable content in LumenWearTheme inside the application-owned Wear Material theme.",
39213
+ "sourceFile": "packages/compose/wear/src/main/kotlin/com/santi020k/lumen/wear/WearComponents.kt",
39214
+ "symbol": "LumenWearActionButton"
39215
+ },
39216
+ "swiftui": {
39217
+ "api": [
39218
+ {
39219
+ "defaultValue": "Required",
39220
+ "description": "Names the essential action.",
39221
+ "name": "accessibilityLabel",
39222
+ "values": "Native localized text"
39223
+ },
39224
+ {
39225
+ "defaultValue": "Required",
39226
+ "description": "Runs application-owned behavior.",
39227
+ "name": "action",
39228
+ "values": "() -> Unit"
39229
+ },
39230
+ {
39231
+ "defaultValue": "brand",
39232
+ "description": "Applies semantic intent.",
39233
+ "name": "tone",
39234
+ "values": "brand · accent · success · warning · danger · neutral"
39235
+ },
39236
+ {
39237
+ "defaultValue": "120",
39238
+ "description": "Clamps the round target to wearable-safe bounds.",
39239
+ "name": "dimension",
39240
+ "values": "Native display units"
39241
+ },
39242
+ {
39243
+ "defaultValue": "true",
39244
+ "description": "Controls action and disabled presentation.",
39245
+ "name": "enabled",
39246
+ "values": "Boolean"
39247
+ }
39248
+ ],
39249
+ "example": "LumenWatchActionButton(\"Start contraction\", action: startContraction) {\n TimerLabel()\n}",
39250
+ "exportName": "LumenWatchActionButton",
39251
+ "importStatement": "import LumenUI",
39252
+ "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.",
39253
+ "language": "swift",
39254
+ "packageName": "LumenUI",
39255
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
39256
+ "sourceFile": "packages/swift/Sources/LumenUI/WatchComponents.swift",
39257
+ "symbol": "LumenWatchActionButton"
36965
39258
  }
36966
39259
  },
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"
39260
+ "name": "Wearable action",
39261
+ "summary": "Present one at-a-glance, round primary action on watchOS or Wear OS.",
39262
+ "tier": "shared"
36973
39263
  },
36974
39264
  {
36975
- "accessibility": "Recording, current shortcut, validation error, cancel, change, and clear states all have visible native labels.",
36976
- "category": "macOS utilities",
36977
- "contract": "Keyboard shortcut capture, validation, clear, and cancel states",
36978
- "guidance": "Use only on macOS. Applications own conflict validation and command registration; Lumen handles capture and presentation.",
36979
- "id": "shortcut-recorder",
39265
+ "accessibility": "Exposes a normalized progress range while application-owned inner content retains its own readable semantics.",
39266
+ "category": "Data display",
39267
+ "contract": "Clamped circular progress surrounding app-owned wearable content",
39268
+ "guidance": "Use for short at-a-glance progress. Keep long-running background work, Always On policy, and timeline updates application-owned.",
39269
+ "id": "wearable-progress",
36980
39270
  "implementations": {
39271
+ "compose": {
39272
+ "api": [
39273
+ {
39274
+ "defaultValue": "Required",
39275
+ "description": "Provides current progress.",
39276
+ "name": "value",
39277
+ "values": "Finite numeric value"
39278
+ },
39279
+ {
39280
+ "defaultValue": "1",
39281
+ "description": "Defines the upper bound.",
39282
+ "name": "maximum",
39283
+ "values": "Positive numeric value"
39284
+ },
39285
+ {
39286
+ "defaultValue": "brand",
39287
+ "description": "Applies semantic progress color.",
39288
+ "name": "tone",
39289
+ "values": "brand · accent · success · warning · danger · neutral"
39290
+ },
39291
+ {
39292
+ "defaultValue": "4",
39293
+ "description": "Controls the clamped ring stroke.",
39294
+ "name": "lineWidth",
39295
+ "values": "Native display units"
39296
+ },
39297
+ {
39298
+ "defaultValue": "Required",
39299
+ "description": "Provides application-owned center content.",
39300
+ "name": "content",
39301
+ "values": "Native view slot"
39302
+ }
39303
+ ],
39304
+ "example": "LumenWearProgressRing(value = elapsed, maximum = threshold) {\n TimerLabel()\n}",
39305
+ "exportName": "LumenWearProgressRing",
39306
+ "importStatement": "import com.santi020k.lumen.LumenWearProgressRing",
39307
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":wear\")).",
39308
+ "language": "kotlin",
39309
+ "packageName": "com.santi020k:lumen-compose-wear",
39310
+ "setup": "Wrap wearable content in LumenWearTheme inside the application-owned Wear Material theme.",
39311
+ "sourceFile": "packages/compose/wear/src/main/kotlin/com/santi020k/lumen/wear/WearComponents.kt",
39312
+ "symbol": "LumenWearProgressRing"
39313
+ },
36981
39314
  "swiftui": {
36982
39315
  "api": [
36983
39316
  {
36984
39317
  "defaultValue": "Required",
36985
- "description": "Names the command being configured.",
39318
+ "description": "Provides current progress.",
39319
+ "name": "value",
39320
+ "values": "Finite numeric value"
39321
+ },
39322
+ {
39323
+ "defaultValue": "1",
39324
+ "description": "Defines the upper bound.",
39325
+ "name": "maximum",
39326
+ "values": "Positive numeric value"
39327
+ },
39328
+ {
39329
+ "defaultValue": "brand",
39330
+ "description": "Applies semantic progress color.",
39331
+ "name": "tone",
39332
+ "values": "brand · accent · success · warning · danger · neutral"
39333
+ },
39334
+ {
39335
+ "defaultValue": "4",
39336
+ "description": "Controls the clamped ring stroke.",
39337
+ "name": "lineWidth",
39338
+ "values": "Native display units"
39339
+ },
39340
+ {
39341
+ "defaultValue": "Required",
39342
+ "description": "Provides application-owned center content.",
39343
+ "name": "content",
39344
+ "values": "Native view slot"
39345
+ }
39346
+ ],
39347
+ "example": "LumenWatchProgressRing(value: elapsed, maximum: threshold) {\n TimerLabel()\n}",
39348
+ "exportName": "LumenWatchProgressRing",
39349
+ "importStatement": "import LumenUI",
39350
+ "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.",
39351
+ "language": "swift",
39352
+ "packageName": "LumenUI",
39353
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
39354
+ "sourceFile": "packages/swift/Sources/LumenUI/WatchComponents.swift",
39355
+ "symbol": "LumenWatchProgressRing"
39356
+ }
39357
+ },
39358
+ "name": "Wearable progress",
39359
+ "summary": "Surround wearable content with clamped semantic circular progress.",
39360
+ "tier": "shared"
39361
+ },
39362
+ {
39363
+ "accessibility": "Keeps status meaning in concise text instead of relying on semantic color alone.",
39364
+ "category": "Feedback",
39365
+ "contract": "Compact text-first wearable status using semantic tone",
39366
+ "guidance": "Use for a short, high-value wrist status. Do not use a badge as the only representation of an urgent notification.",
39367
+ "id": "wearable-status",
39368
+ "implementations": {
39369
+ "compose": {
39370
+ "api": [
39371
+ {
39372
+ "defaultValue": "Required",
39373
+ "description": "Provides concise status meaning.",
39374
+ "name": "text",
39375
+ "values": "Native localized text"
39376
+ },
39377
+ {
39378
+ "defaultValue": "neutral",
39379
+ "description": "Applies semantic emphasis.",
39380
+ "name": "tone",
39381
+ "values": "brand · accent · success · warning · danger · neutral"
39382
+ },
39383
+ {
39384
+ "defaultValue": "nil",
39385
+ "description": "Adds a supporting wearable glyph.",
39386
+ "name": "leading",
39387
+ "values": "Optional native visual"
39388
+ }
39389
+ ],
39390
+ "example": "LumenWearStatus(text = \"Phone unavailable\", tone = LumenWearTone.Warning)",
39391
+ "exportName": "LumenWearStatus",
39392
+ "importStatement": "import com.santi020k.lumen.LumenWearStatus",
39393
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":wear\")).",
39394
+ "language": "kotlin",
39395
+ "packageName": "com.santi020k:lumen-compose-wear",
39396
+ "setup": "Wrap wearable content in LumenWearTheme inside the application-owned Wear Material theme.",
39397
+ "sourceFile": "packages/compose/wear/src/main/kotlin/com/santi020k/lumen/wear/WearComponents.kt",
39398
+ "symbol": "LumenWearStatus"
39399
+ },
39400
+ "swiftui": {
39401
+ "api": [
39402
+ {
39403
+ "defaultValue": "Required",
39404
+ "description": "Provides concise status meaning.",
39405
+ "name": "title",
39406
+ "values": "Native localized text"
39407
+ },
39408
+ {
39409
+ "defaultValue": "neutral",
39410
+ "description": "Applies semantic emphasis.",
39411
+ "name": "tone",
39412
+ "values": "brand · accent · success · warning · danger · neutral"
39413
+ },
39414
+ {
39415
+ "defaultValue": "nil",
39416
+ "description": "Adds a supporting wearable glyph.",
39417
+ "name": "systemName",
39418
+ "values": "Optional native visual"
39419
+ }
39420
+ ],
39421
+ "example": "LumenWatchStatus(\"5-1-1\", systemName: \"cross.case.fill\", tone: .danger)",
39422
+ "exportName": "LumenWatchStatus",
39423
+ "importStatement": "import LumenUI",
39424
+ "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.",
39425
+ "language": "swift",
39426
+ "packageName": "LumenUI",
39427
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
39428
+ "sourceFile": "packages/swift/Sources/LumenUI/WatchComponents.swift",
39429
+ "symbol": "LumenWatchStatus"
39430
+ }
39431
+ },
39432
+ "name": "Wearable status",
39433
+ "summary": "Show a compact, text-first semantic status on the wrist.",
39434
+ "tier": "shared"
39435
+ },
39436
+ {
39437
+ "accessibility": "Combines label, value, and optional detail into one concise readable metric.",
39438
+ "category": "Data display",
39439
+ "contract": "At-a-glance wearable label, value, and optional detail",
39440
+ "guidance": "Use for one high-priority value with a short label. Avoid dashboard grids that overload small round screens.",
39441
+ "id": "wearable-metric",
39442
+ "implementations": {
39443
+ "compose": {
39444
+ "api": [
39445
+ {
39446
+ "defaultValue": "Required",
39447
+ "description": "Names the metric.",
36986
39448
  "name": "label",
36987
- "values": "LocalizedStringKey"
39449
+ "values": "Native localized text"
36988
39450
  },
36989
39451
  {
36990
39452
  "defaultValue": "Required",
36991
- "description": "Stores the current keyboard shortcut.",
36992
- "name": "shortcut",
36993
- "values": "Binding<LumenShortcut?>"
39453
+ "description": "Provides the formatted value.",
39454
+ "name": "value",
39455
+ "values": "String"
36994
39456
  },
36995
39457
  {
36996
39458
  "defaultValue": "nil",
36997
- "description": "Returns an application conflict message or nil.",
36998
- "name": "validation",
36999
- "values": "((LumenShortcut) -> String?)?"
39459
+ "description": "Adds brief supporting context.",
39460
+ "name": "detail",
39461
+ "values": "Optional native localized text"
37000
39462
  },
39463
+ {
39464
+ "defaultValue": "neutral",
39465
+ "description": "Applies semantic value color.",
39466
+ "name": "tone",
39467
+ "values": "brand · accent · success · warning · danger · neutral"
39468
+ }
39469
+ ],
39470
+ "example": "LumenWearMetric(label = \"Duration\", value = elapsed, tone = LumenWearTone.Brand)",
39471
+ "exportName": "LumenWearMetric",
39472
+ "importStatement": "import com.santi020k.lumen.LumenWearMetric",
39473
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":wear\")).",
39474
+ "language": "kotlin",
39475
+ "packageName": "com.santi020k:lumen-compose-wear",
39476
+ "setup": "Wrap wearable content in LumenWearTheme inside the application-owned Wear Material theme.",
39477
+ "sourceFile": "packages/compose/wear/src/main/kotlin/com/santi020k/lumen/wear/WearComponents.kt",
39478
+ "symbol": "LumenWearMetric"
39479
+ },
39480
+ "swiftui": {
39481
+ "api": [
37001
39482
  {
37002
39483
  "defaultValue": "Required",
37003
- "description": "Uses NSEvent keyboard capture and macOS modifier glyphs.",
37004
- "name": "platform",
37005
- "values": "macOS"
39484
+ "description": "Names the metric.",
39485
+ "name": "label",
39486
+ "values": "Native localized text"
39487
+ },
39488
+ {
39489
+ "defaultValue": "Required",
39490
+ "description": "Provides the formatted value.",
39491
+ "name": "value",
39492
+ "values": "String"
39493
+ },
39494
+ {
39495
+ "defaultValue": "nil",
39496
+ "description": "Adds brief supporting context.",
39497
+ "name": "detail",
39498
+ "values": "Optional native localized text"
39499
+ },
39500
+ {
39501
+ "defaultValue": "neutral",
39502
+ "description": "Applies semantic value color.",
39503
+ "name": "tone",
39504
+ "values": "brand · accent · success · warning · danger · neutral"
37006
39505
  }
37007
39506
  ],
37008
- "example": "LumenShortcutRecorder(\n \"Quick switch\",\n shortcut: $shortcut\n) { candidate in\n reserved.contains(candidate) ? \"Already in use.\" : nil\n}",
37009
- "exportName": "LumenShortcutRecorder",
39507
+ "example": "LumenWatchMetric(\"Duration\", value: elapsed, tone: .brand)",
39508
+ "exportName": "LumenWatchMetric",
37010
39509
  "importStatement": "import LumenUI",
37011
39510
  "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.",
37012
39511
  "language": "swift",
37013
39512
  "packageName": "LumenUI",
37014
39513
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
37015
- "sourceFile": "packages/swift/Sources/LumenUI/MacComponents.swift",
37016
- "symbol": "LumenShortcutRecorder"
39514
+ "sourceFile": "packages/swift/Sources/LumenUI/WatchComponents.swift",
39515
+ "symbol": "LumenWatchMetric"
37017
39516
  }
37018
39517
  },
37019
- "name": "Shortcut recorder",
37020
- "summary": "Capture, validate, change, and clear native macOS keyboard shortcuts.",
37021
- "supportedPlatforms": [
37022
- "macOS"
37023
- ],
37024
- "tier": "platform-specific"
39518
+ "name": "Wearable metric",
39519
+ "summary": "Present one at-a-glance wearable label, value, and optional detail.",
39520
+ "tier": "shared"
37025
39521
  },
37026
39522
  {
37027
- "accessibility": "Every symbol option has a readable label and selected state. Search and empty states use native controls and focus behavior.",
37028
- "category": "macOS utilities",
37029
- "contract": "Searchable and categorized SF Symbols selection",
37030
- "guidance": "Use only on macOS. Supply product-specific symbol options when the built-in common set is broader than the task requires.",
37031
- "id": "symbol-picker",
39523
+ "accessibility": "Preserves child actions and content in native traversal order without collapsing distinct controls.",
39524
+ "category": "Layout",
39525
+ "contract": "Compact wearable row with flexible leading, content, and trailing slots",
39526
+ "guidance": "Use inside native wearable scrolling containers. Keep list state, selection, navigation, and rotary input in the application.",
39527
+ "id": "wearable-list-row",
37032
39528
  "implementations": {
37033
- "swiftui": {
39529
+ "compose": {
37034
39530
  "api": [
37035
39531
  {
37036
- "defaultValue": "Choose a symbol",
37037
- "description": "Names the picker or popover trigger.",
37038
- "name": "title / label",
37039
- "values": "LocalizedStringKey"
39532
+ "defaultValue": "nil / EmptyView",
39533
+ "description": "Provides compact identity.",
39534
+ "name": "leading",
39535
+ "values": "Optional native view slot"
37040
39536
  },
37041
39537
  {
37042
39538
  "defaultValue": "Required",
37043
- "description": "Stores the selected SF Symbol name.",
37044
- "name": "selectedName",
37045
- "values": "Binding<String>"
39539
+ "description": "Provides primary row content.",
39540
+ "name": "content",
39541
+ "values": "Native view slot"
37046
39542
  },
37047
39543
  {
37048
- "defaultValue": ".common",
37049
- "description": "Provides labeled, categorized symbol choices.",
37050
- "name": "options",
37051
- "values": "[LumenSymbolOption]"
39544
+ "defaultValue": "nil / EmptyView",
39545
+ "description": "Provides short status or actions.",
39546
+ "name": "trailing",
39547
+ "values": "Optional native view slot"
39548
+ }
39549
+ ],
39550
+ "example": "LumenWearListRow { TimerHistoryLabel() }",
39551
+ "exportName": "LumenWearListRow",
39552
+ "importStatement": "import com.santi020k.lumen.LumenWearListRow",
39553
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":wear\")).",
39554
+ "language": "kotlin",
39555
+ "packageName": "com.santi020k:lumen-compose-wear",
39556
+ "setup": "Wrap wearable content in LumenWearTheme inside the application-owned Wear Material theme.",
39557
+ "sourceFile": "packages/compose/wear/src/main/kotlin/com/santi020k/lumen/wear/WearComponents.kt",
39558
+ "symbol": "LumenWearListRow"
39559
+ },
39560
+ "swiftui": {
39561
+ "api": [
39562
+ {
39563
+ "defaultValue": "nil / EmptyView",
39564
+ "description": "Provides compact identity.",
39565
+ "name": "leading",
39566
+ "values": "Optional native view slot"
37052
39567
  },
37053
39568
  {
37054
- "defaultValue": "Inline",
37055
- "description": "Selects the full picker or compact popover trigger.",
37056
- "name": "presentation",
37057
- "values": "Inline picker / popover button"
39569
+ "defaultValue": "Required",
39570
+ "description": "Provides primary row content.",
39571
+ "name": "content",
39572
+ "values": "Native view slot"
39573
+ },
39574
+ {
39575
+ "defaultValue": "nil / EmptyView",
39576
+ "description": "Provides short status or actions.",
39577
+ "name": "trailing",
39578
+ "values": "Optional native view slot"
37058
39579
  }
37059
39580
  ],
37060
- "example": "LumenSymbolPickerButton(\n \"Workspace symbol\",\n selectedName: $symbolName\n)",
37061
- "exportName": "LumenSymbolPicker / LumenSymbolPickerButton",
39581
+ "example": "LumenWatchListRow { TimerHistoryLabel() }",
39582
+ "exportName": "LumenWatchListRow",
37062
39583
  "importStatement": "import LumenUI",
37063
39584
  "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.",
37064
39585
  "language": "swift",
37065
39586
  "packageName": "LumenUI",
37066
39587
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
37067
- "sourceFile": "packages/swift/Sources/LumenUI/MacComponents.swift",
37068
- "symbol": "LumenSymbolPicker"
39588
+ "sourceFile": "packages/swift/Sources/LumenUI/WatchComponents.swift",
39589
+ "symbol": "LumenWatchListRow"
37069
39590
  }
37070
39591
  },
37071
- "name": "Symbol picker",
37072
- "summary": "Search and choose from labeled, categorized SF Symbols in a native macOS picker.",
37073
- "supportedPlatforms": [
37074
- "macOS"
37075
- ],
37076
- "tier": "platform-specific"
39592
+ "name": "Wearable list row",
39593
+ "summary": "Compose a compact wearable row with flexible content slots.",
39594
+ "tier": "shared"
37077
39595
  }
37078
39596
  ],
37079
39597
  "nativeSources": {
37080
39598
  "compose": {
37081
39599
  "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",
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",
39600
+ "packages/compose/src/main/kotlin/com/santi020k/lumen/ContentComponents.kt": "// cspell:ignore drawscope\npackage com.santi020k.lumen\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.BoxScope\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.geometry.Offset\nimport androidx.compose.ui.geometry.CornerRadius\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.RectangleShape\nimport androidx.compose.ui.graphics.StrokeCap\nimport androidx.compose.ui.graphics.StrokeJoin\nimport androidx.compose.ui.graphics.drawscope.Stroke\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\nimport kotlin.math.cos\nimport kotlin.math.hypot\nimport kotlin.math.sin\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\nenum class LumenGraphicSize(val dimension: Dp) {\n Sm(160.dp),\n Md(240.dp),\n Lg(320.dp)\n}\n\nenum class LumenGraphicTone {\n Accent,\n Brand,\n Neutral\n}\n\nenum class LumenGraphicVariant {\n Glow,\n Grid,\n Orbit\n}\n\n@Composable\nfun LumenGraphic(\n modifier: Modifier = Modifier,\n label: String? = null,\n size: LumenGraphicSize = LumenGraphicSize.Md,\n tone: LumenGraphicTone = LumenGraphicTone.Brand,\n variant: LumenGraphicVariant = LumenGraphicVariant.Orbit,\n content: @Composable BoxScope.() -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n val color = when (tone) {\n LumenGraphicTone.Accent -> colors.accent\n LumenGraphicTone.Brand -> colors.brand\n LumenGraphicTone.Neutral -> colors.inkMuted\n }\n val semanticsModifier = Modifier.clearAndSetSemantics {\n if (label != null) {\n contentDescription = label\n }\n }\n\n Box(\n modifier = modifier\n .size(size.dimension)\n .then(semanticsModifier),\n contentAlignment = Alignment.Center\n ) {\n Canvas(modifier = Modifier.matchParentSize()) {\n when (variant) {\n LumenGraphicVariant.Glow -> drawCircle(\n brush = Brush.radialGradient(\n colors = listOf(color.copy(alpha = 0.3f), color.copy(alpha = 0f)),\n center = center,\n radius = this.size.minDimension * 0.42f\n ),\n radius = this.size.minDimension * 0.42f\n )\n LumenGraphicVariant.Grid -> {\n for (index in 0..6) {\n val position = this.size.minDimension * (0.12f + (index * 0.76f / 6))\n drawLine(\n color = color.copy(alpha = 0.18f),\n start = Offset(position, this.size.height * 0.12f),\n end = Offset(position, this.size.height * 0.88f),\n strokeWidth = 1.dp.toPx()\n )\n drawLine(\n color = color.copy(alpha = 0.18f),\n start = Offset(this.size.width * 0.12f, position),\n end = Offset(this.size.width * 0.88f, position),\n strokeWidth = 1.dp.toPx()\n )\n }\n }\n LumenGraphicVariant.Orbit -> {\n for (ratio in listOf(0.9f, 0.62f, 0.34f)) {\n drawCircle(\n color = color.copy(alpha = 0.24f),\n radius = this.size.minDimension * ratio / 2,\n style = Stroke(width = 1.dp.toPx())\n )\n }\n drawLine(\n color = color.copy(alpha = 0.18f),\n start = Offset(this.size.width * 0.14f, center.y),\n end = Offset(this.size.width * 0.86f, center.y),\n strokeWidth = 1.dp.toPx()\n )\n drawLine(\n color = color.copy(alpha = 0.18f),\n start = Offset(center.x, this.size.height * 0.14f),\n end = Offset(center.x, this.size.height * 0.86f),\n strokeWidth = 1.dp.toPx()\n )\n }\n }\n }\n content()\n }\n}\n\nenum class LumenBackdropIntensity(val opacity: Float) {\n Subtle(0.4f),\n Medium(0.68f),\n Strong(1f)\n}\n\nenum class LumenBackdropTone {\n Accent,\n Brand,\n Neutral\n}\n\nenum class LumenBackdropVariant {\n Aurora,\n Dots,\n Grid,\n Rays\n}\n\n@Composable\nfun LumenBackdrop(\n modifier: Modifier = Modifier,\n intensity: LumenBackdropIntensity = LumenBackdropIntensity.Medium,\n tone: LumenBackdropTone = LumenBackdropTone.Brand,\n variant: LumenBackdropVariant = LumenBackdropVariant.Aurora,\n content: @Composable BoxScope.() -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n val color = when (tone) {\n LumenBackdropTone.Accent -> colors.accent\n LumenBackdropTone.Brand -> colors.brand\n LumenBackdropTone.Neutral -> colors.inkMuted\n }\n val secondaryColor = if (tone == LumenBackdropTone.Neutral) colors.surfaceStrong else colors.accent\n\n Box(modifier = modifier.heightIn(min = 160.dp).clip(RectangleShape)) {\n Canvas(modifier = Modifier.matchParentSize()) {\n when (variant) {\n LumenBackdropVariant.Aurora -> {\n drawCircle(\n color = color.copy(alpha = 0.22f * intensity.opacity),\n radius = size.width * 0.36f,\n center = Offset(size.width * 0.12f, size.height * 0.04f)\n )\n drawCircle(\n color = secondaryColor.copy(alpha = 0.18f * intensity.opacity),\n radius = size.width * 0.31f,\n center = Offset(size.width * 0.9f, size.height * 0.92f)\n )\n }\n LumenBackdropVariant.Dots -> {\n for (row in 0 until 6) {\n for (column in 0 until 8) {\n drawCircle(\n color = color.copy(alpha = 0.28f * intensity.opacity),\n radius = 1.5.dp.toPx(),\n center = Offset(\n (column + 0.5f) * size.width / 8,\n (row + 0.5f) * size.height / 6\n )\n )\n }\n }\n }\n LumenBackdropVariant.Grid -> {\n for (index in 0..6) {\n val x = index * size.width / 6\n val y = index * size.height / 6\n val lineColor = color.copy(alpha = 0.18f * intensity.opacity)\n drawLine(lineColor, Offset(x, 0f), Offset(x, size.height), 1.dp.toPx())\n drawLine(lineColor, Offset(0f, y), Offset(size.width, y), 1.dp.toPx())\n }\n }\n LumenBackdropVariant.Rays -> {\n val radius = hypot(size.width, size.height)\n for (index in 0 until 12) {\n val angle = index * Math.PI.toFloat() / 6\n drawLine(\n color = color.copy(alpha = 0.2f * intensity.opacity),\n start = center,\n end = Offset(\n center.x + cos(angle) * radius,\n center.y + sin(angle) * radius\n ),\n strokeWidth = 1.dp.toPx()\n )\n }\n }\n }\n }\n content()\n }\n}\n\nenum class LumenIllustrationSize(val dimension: Dp) {\n Sm(96.dp),\n Md(128.dp),\n Lg(176.dp)\n}\n\nenum class LumenIllustrationTone {\n Accent,\n Auto,\n Brand,\n Neutral\n}\n\nenum class LumenIllustrationVariant {\n Empty,\n Error,\n Offline,\n Success\n}\n\ninternal fun lumenIllustrationColor(\n colors: LumenColorPalette,\n tone: LumenIllustrationTone,\n variant: LumenIllustrationVariant\n): Color = when (tone) {\n LumenIllustrationTone.Accent -> colors.accent\n LumenIllustrationTone.Brand -> colors.brand\n LumenIllustrationTone.Neutral -> colors.inkMuted\n LumenIllustrationTone.Auto -> when (variant) {\n LumenIllustrationVariant.Empty -> colors.brand\n LumenIllustrationVariant.Error -> colors.danger\n LumenIllustrationVariant.Offline -> colors.inkMuted\n LumenIllustrationVariant.Success -> colors.success\n }\n}\n\n@Composable\nfun LumenIllustration(\n modifier: Modifier = Modifier,\n variant: LumenIllustrationVariant = LumenIllustrationVariant.Empty,\n tone: LumenIllustrationTone = LumenIllustrationTone.Auto,\n size: LumenIllustrationSize = LumenIllustrationSize.Md,\n label: String? = null\n) {\n val colors = LocalLumenTheme.current.colors\n val color = lumenIllustrationColor(colors, tone, variant)\n val semanticsModifier = if (label == null) {\n Modifier.clearAndSetSemantics {}\n } else {\n Modifier.semantics { contentDescription = label }\n }\n\n Canvas(modifier = modifier.size(size.dimension).then(semanticsModifier)) {\n val scale = this.size.minDimension / 120f\n val strokeWidth = maxOf(2.dp.toPx(), 2.5f * scale)\n val stroke = Stroke(width = strokeWidth, cap = StrokeCap.Round, join = StrokeJoin.Round)\n drawCircle(color.copy(alpha = 0.1f), radius = this.size.minDimension * 0.41f)\n\n when (variant) {\n LumenIllustrationVariant.Empty -> {\n drawRoundRect(\n color = color,\n topLeft = Offset(29f * scale, 43f * scale),\n size = Size(62f * scale, 40f * scale),\n cornerRadius = CornerRadius(8f * scale),\n style = stroke\n )\n drawLine(color, Offset(40f * scale, 60f * scale), Offset(80f * scale, 60f * scale), strokeWidth)\n }\n LumenIllustrationVariant.Error, LumenIllustrationVariant.Success -> {\n drawCircle(color, radius = 30f * scale, style = stroke)\n if (variant == LumenIllustrationVariant.Success) {\n drawLine(color, Offset(44f * scale, 61f * scale), Offset(55f * scale, 72f * scale), strokeWidth, StrokeCap.Round)\n drawLine(color, Offset(55f * scale, 72f * scale), Offset(77f * scale, 48f * scale), strokeWidth, StrokeCap.Round)\n } else {\n drawLine(color, Offset(47f * scale, 47f * scale), Offset(73f * scale, 73f * scale), strokeWidth, StrokeCap.Round)\n drawLine(color, Offset(73f * scale, 47f * scale), Offset(47f * scale, 73f * scale), strokeWidth, StrokeCap.Round)\n }\n }\n LumenIllustrationVariant.Offline -> {\n drawRoundRect(\n color = color,\n topLeft = Offset(24f * scale, 45f * scale),\n size = Size(72f * scale, 35f * scale),\n cornerRadius = CornerRadius(18f * scale),\n style = stroke\n )\n drawLine(color, Offset(30f * scale, 30f * scale), Offset(90f * scale, 90f * scale), strokeWidth, StrokeCap.Round)\n }\n }\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",
37083
39601
  "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",
37084
39602
  "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",
39603
+ "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt": "package com.santi020k.lumen\n\nimport android.app.Activity\nimport android.content.ActivityNotFoundException\nimport android.content.Intent\nimport android.net.Uri\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.padding\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.ModalBottomSheet\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.text.font.FontWeight\n\n/** A controlled native confirmation dialog with explicit cancel and confirm actions. */\n@Composable\nfun LumenAlertDialog(\n visible: Boolean,\n title: String,\n confirmLabel: String,\n onConfirm: () -> Unit,\n onDismiss: () -> Unit,\n modifier: Modifier = Modifier,\n description: String? = null,\n cancelLabel: String = \"Cancel\",\n destructive: Boolean = false,\n confirmEnabled: Boolean = true,\n confirmLoading: Boolean = false\n) {\n if (!visible) return\n\n AlertDialog(\n onDismissRequest = onDismiss,\n modifier = modifier,\n title = { Text(title, fontWeight = FontWeight.SemiBold) },\n text = description?.let { value -> { Text(value) } },\n confirmButton = {\n LumenButton(\n onClick = onConfirm,\n intent = if (destructive) LumenButtonIntent.Danger else LumenButtonIntent.Primary,\n loading = confirmLoading,\n enabled = confirmEnabled\n ) {\n Text(confirmLabel)\n }\n },\n dismissButton = {\n LumenButton(onClick = onDismiss, intent = LumenButtonIntent.Quiet) {\n Text(cancelLabel)\n }\n }\n )\n}\n\n/** A controlled Material bottom sheet with a shared heading, description, and action layout. */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun LumenSheet(\n visible: Boolean,\n onDismiss: () -> Unit,\n modifier: Modifier = Modifier,\n title: String? = null,\n description: String? = null,\n actions: @Composable () -> Unit = {},\n content: @Composable () -> Unit\n) {\n if (!visible) return\n\n ModalBottomSheet(onDismissRequest = onDismiss, modifier = modifier) {\n Column(\n modifier = Modifier\n .fillMaxWidth()\n .padding(horizontal = LumenSpacing.Xl)\n .padding(bottom = LumenSpacing.Xl),\n verticalArrangement = Arrangement.spacedBy(LumenSpacing.Lg)\n ) {\n if (title != null || description != null) {\n Column(verticalArrangement = Arrangement.spacedBy(LumenSpacing.Sm)) {\n title?.let { Text(it, fontWeight = FontWeight.SemiBold) }\n description?.let { Text(it, color = LocalLumenTheme.current.colors.inkSoft) }\n }\n }\n\n content()\n\n Row(\n modifier = Modifier.fillMaxWidth(),\n horizontalArrangement = Arrangement.End\n ) {\n actions()\n }\n }\n }\n}\n\nclass LumenMenuItem(\n val label: String,\n val enabled: Boolean = true,\n val destructive: Boolean = false,\n val leadingIcon: ImageVector? = null,\n val onClick: () -> Unit\n)\n\n/** A controlled native anchored menu with labeled disabled and destructive action states. */\n@Composable\nfun LumenMenu(\n expanded: Boolean,\n onDismissRequest: () -> Unit,\n items: List<LumenMenuItem>,\n modifier: Modifier = Modifier\n) {\n val colors = LocalLumenTheme.current.colors\n\n DropdownMenu(\n expanded = expanded,\n onDismissRequest = onDismissRequest,\n modifier = modifier\n ) {\n items.forEachIndexed { index, item ->\n DropdownMenuItem(\n text = {\n Text(\n item.label,\n color = if (item.destructive) colors.danger else colors.ink\n )\n },\n onClick = {\n onDismissRequest()\n item.onClick()\n },\n enabled = item.enabled,\n leadingIcon = item.leadingIcon?.let { icon ->\n {\n LumenIcon(\n imageVector = icon,\n tint = if (item.destructive) colors.danger else colors.inkSoft\n )\n }\n }\n )\n\n if (index < items.lastIndex) {\n HorizontalDivider(color = colors.line)\n }\n }\n }\n}\n\n@Immutable\ndata class LumenSharePayload(\n val text: String? = null,\n val uri: Uri? = null,\n val subject: String? = null,\n val mimeType: String = \"text/plain\"\n) {\n val hasContent: Boolean\n get() = !text.isNullOrBlank() || uri != null\n}\n\n/** A Lumen button that opens Android's native share sheet for text or a content URI. */\n@Composable\nfun LumenShareButton(\n payload: LumenSharePayload,\n chooserTitle: String,\n modifier: Modifier = Modifier,\n label: String = \"Share\",\n enabled: Boolean = true,\n onFailure: (ActivityNotFoundException) -> Unit = {}\n) {\n val context = LocalContext.current\n\n LumenButton(\n onClick = {\n val sendIntent = Intent(Intent.ACTION_SEND).apply {\n type = payload.mimeType\n payload.subject?.let { putExtra(Intent.EXTRA_SUBJECT, it) }\n payload.text?.let { putExtra(Intent.EXTRA_TEXT, it) }\n payload.uri?.let {\n putExtra(Intent.EXTRA_STREAM, it)\n addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n }\n }\n val chooser = Intent.createChooser(sendIntent, chooserTitle)\n\n if (context !is Activity) chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n\n try {\n context.startActivity(chooser)\n } catch (error: ActivityNotFoundException) {\n onFailure(error)\n }\n },\n modifier = modifier,\n intent = LumenButtonIntent.Secondary,\n enabled = enabled && payload.hasContent\n ) {\n Text(label)\n }\n}\n",
39604
+ "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt": "package com.santi020k.lumen\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.scaleIn\nimport androidx.compose.animation.scaleOut\nimport androidx.compose.animation.slideInVertically\nimport androidx.compose.animation.slideOutVertically\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.defaultMinSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.offset\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Badge\nimport androidx.compose.material3.BadgedBox\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.NavigationBar as MaterialNavigationBar\nimport androidx.compose.material3.NavigationBarItem\nimport androidx.compose.material3.NavigationBarItemDefaults\nimport androidx.compose.material3.SmallFloatingActionButton\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.runtime.Stable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.input.nestedscroll.NestedScrollConnection\nimport androidx.compose.ui.input.nestedscroll.NestedScrollSource\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.unit.IntOffset\nimport androidx.compose.ui.platform.LocalDensity\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\n\n@Immutable\ndata class LumenNavigationItem<Value>(\n val value: Value,\n val label: String,\n val icon: ImageVector,\n val selectedIcon: ImageVector = icon,\n val badge: LumenNavigationBadge? = null,\n val enabled: Boolean = true\n)\n\n@Immutable\ndata class LumenNavigationBadge(\n val text: String? = null,\n val accessibilityLabel: String = text ?: \"New activity\"\n) {\n companion object {\n fun dot(accessibilityLabel: String = \"New activity\"): LumenNavigationBadge =\n LumenNavigationBadge(accessibilityLabel = accessibilityLabel)\n\n fun count(value: Int): LumenNavigationBadge {\n val safeValue = value.coerceAtLeast(0)\n return LumenNavigationBadge(\n text = if (safeValue > 99) \"99+\" else safeValue.toString(),\n accessibilityLabel = \"$safeValue new items\"\n )\n }\n }\n}\n\ninternal fun <Value> isLumenNavigationItemSelected(itemValue: Value, selectedValue: Value): Boolean =\n itemValue == selectedValue\n\ninternal fun <Value> dispatchLumenNavigationSelection(\n itemValue: Value,\n selectedValue: Value,\n onValueChange: (Value) -> Unit,\n onReselect: ((Value) -> Unit)?\n) {\n if (itemValue == selectedValue && onReselect != null) onReselect(itemValue)\n else onValueChange(itemValue)\n}\n\n/**\n * Direction-aware visibility state for a bottom navigation bar.\n *\n * Attach [lumenNavigationBarScrollBehavior] to the parent of a vertically scrolling child, then\n * pass this state to [LumenNavigationBar]. Small direction changes are accumulated until the\n * threshold is reached so incidental finger movement does not make navigation flicker.\n */\n@Stable\nclass LumenNavigationBarScrollState internal constructor(\n initiallyVisible: Boolean,\n private val thresholdPx: Float\n) {\n var isVisible: Boolean by mutableStateOf(initiallyVisible)\n private set\n\n private var accumulatedDeltaY = 0f\n\n internal fun recordScrollDelta(deltaY: Float) {\n if (!deltaY.isFinite() || deltaY == 0f) return\n\n val changedDirection =\n (accumulatedDeltaY < 0f && deltaY > 0f) || (accumulatedDeltaY > 0f && deltaY < 0f)\n if (changedDirection) accumulatedDeltaY = 0f\n\n accumulatedDeltaY += deltaY\n when {\n accumulatedDeltaY <= -thresholdPx -> hide()\n accumulatedDeltaY >= thresholdPx -> show()\n }\n }\n\n internal val nestedScrollConnection = object : NestedScrollConnection {\n override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {\n recordScrollDelta(available.y)\n return Offset.Zero\n }\n }\n\n fun show() {\n isVisible = true\n accumulatedDeltaY = 0f\n }\n\n fun hide() {\n isVisible = false\n accumulatedDeltaY = 0f\n }\n}\n\n/** Remembers scroll-responsive navigation state using a density-aware movement threshold. */\n@Composable\nfun rememberLumenNavigationBarScrollState(\n initiallyVisible: Boolean = true,\n threshold: Dp = 16.dp\n): LumenNavigationBarScrollState {\n val thresholdPx = with(LocalDensity.current) {\n if (threshold.value.isFinite() && threshold > 0.dp) threshold.toPx() else 16.dp.toPx()\n }\n\n return remember(initiallyVisible, thresholdPx) {\n LumenNavigationBarScrollState(initiallyVisible, thresholdPx)\n }\n}\n\n/** Connects scroll deltas from a descendant scroll container to a Lumen navigation bar state. */\nfun Modifier.lumenNavigationBarScrollBehavior(\n state: LumenNavigationBarScrollState\n): Modifier = nestedScroll(state.nestedScrollConnection)\n\n/**\n * A Material-native controlled destination bar. Navigation stacks and screen content remain app-owned.\n */\n@Composable\nfun <Value> LumenNavigationBar(\n items: List<LumenNavigationItem<Value>>,\n selectedValue: Value,\n onValueChange: (Value) -> Unit,\n modifier: Modifier = Modifier,\n onReselect: ((Value) -> Unit)? = null,\n accessibilityLabel: String = \"Primary navigation\",\n scrollState: LumenNavigationBarScrollState? = null\n) {\n val colors = LocalLumenTheme.current.colors\n\n AnimatedVisibility(\n visible = scrollState?.isVisible ?: true,\n enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),\n exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()\n ) {\n MaterialNavigationBar(\n modifier = modifier.semantics { contentDescription = accessibilityLabel },\n containerColor = colors.surface,\n contentColor = colors.inkMuted\n ) {\n items.forEach { item ->\n val selected = isLumenNavigationItemSelected(item.value, selectedValue)\n\n NavigationBarItem(\n selected = selected,\n onClick = {\n dispatchLumenNavigationSelection(\n item.value,\n selectedValue,\n onValueChange,\n onReselect\n )\n },\n icon = {\n BadgedBox(\n badge = {\n item.badge?.let { badge ->\n LumenNavigationBadgeView(badge)\n }\n }\n ) {\n LumenIcon(\n imageVector = if (selected) item.selectedIcon else item.icon,\n contentDescription = null,\n size = LumenIconSize.Md,\n tint = (if (selected) colors.brand else colors.inkMuted).copy(\n alpha = if (item.enabled) 1f else 0.52f\n )\n )\n }\n },\n enabled = item.enabled,\n label = { Text(item.label, maxLines = 1) },\n colors = NavigationBarItemDefaults.colors(\n selectedIconColor = colors.brand,\n selectedTextColor = colors.brand,\n indicatorColor = colors.brandSoft,\n unselectedIconColor = colors.inkMuted,\n unselectedTextColor = colors.inkMuted,\n disabledIconColor = colors.inkMuted.copy(alpha = 0.52f),\n disabledTextColor = colors.inkMuted.copy(alpha = 0.52f)\n )\n )\n }\n }\n }\n}\n\n@Composable\nprivate fun LumenNavigationBadgeView(badge: LumenNavigationBadge) {\n val colors = LocalLumenTheme.current.colors\n\n Badge(\n modifier = Modifier.semantics { contentDescription = badge.accessibilityLabel },\n containerColor = colors.danger,\n contentColor = colors.onDanger\n ) {\n badge.text?.let { Text(it, maxLines = 1) }\n }\n}\n\n/**\n * A compact token-aware status or action surface intended directly above bottom navigation.\n */\n@Composable\nfun LumenNavigationBarAccessory(\n modifier: Modifier = Modifier,\n scrollState: LumenNavigationBarScrollState? = null,\n content: @Composable () -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n\n AnimatedVisibility(\n visible = scrollState?.isVisible ?: true,\n enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),\n exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()\n ) {\n Surface(\n modifier = modifier.fillMaxWidth(),\n color = colors.surface,\n contentColor = colors.ink\n ) {\n Box(\n modifier = Modifier\n .defaultMinSize(minHeight = 48.dp)\n .padding(horizontal = LumenSpacing.Lg, vertical = LumenSpacing.Sm)\n ) {\n content()\n }\n HorizontalDivider(color = colors.line)\n }\n }\n}\n\n/**\n * Material adaptive navigation that switches between a bottom bar and navigation rail as the\n * application window and device posture change. Destination content and routing remain app-owned.\n */\n@Composable\nfun <Value> LumenAdaptiveNavigationScaffold(\n items: List<LumenNavigationItem<Value>>,\n selectedValue: Value,\n onValueChange: (Value) -> Unit,\n modifier: Modifier = Modifier,\n onReselect: ((Value) -> Unit)? = null,\n content: @Composable () -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n\n NavigationSuiteScaffold(\n navigationSuiteItems = {\n items.forEach { navigationItem ->\n val selected = navigationItem.value == selectedValue\n\n item(\n selected = selected,\n onClick = {\n dispatchLumenNavigationSelection(\n navigationItem.value,\n selectedValue,\n onValueChange,\n onReselect\n )\n },\n icon = {\n LumenIcon(\n imageVector = if (selected) navigationItem.selectedIcon else navigationItem.icon,\n contentDescription = null,\n size = LumenIconSize.Md,\n tint = if (selected) colors.brand else colors.inkMuted\n )\n },\n enabled = navigationItem.enabled,\n label = { Text(navigationItem.label, maxLines = 1) },\n badge = navigationItem.badge?.let { badge ->\n { LumenNavigationBadgeView(badge) }\n }\n )\n }\n },\n modifier = modifier,\n containerColor = colors.canvas,\n contentColor = colors.ink,\n content = content\n )\n}\n\nenum class LumenFloatingActionButtonIntent {\n Accent,\n Brand,\n Danger\n}\n\nenum class LumenFloatingActionButtonSize {\n Regular,\n Small\n}\n\nenum class LumenFloatingActionButtonNavigationBehavior {\n AlwaysVisible,\n HideWithNavigation,\n FollowNavigation\n}\n\ninternal fun lumenFloatingActionButtonVisible(\n behavior: LumenFloatingActionButtonNavigationBehavior,\n navigationVisible: Boolean\n): Boolean = behavior != LumenFloatingActionButtonNavigationBehavior.HideWithNavigation || navigationVisible\n\ninternal fun lumenFloatingActionButtonOffset(\n behavior: LumenFloatingActionButtonNavigationBehavior,\n navigationVisible: Boolean\n): Dp = if (\n behavior == LumenFloatingActionButtonNavigationBehavior.FollowNavigation && !navigationVisible\n) {\n 16.dp\n} else {\n 0.dp\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 scrollState: LumenNavigationBarScrollState? = null,\n navigationBehavior: LumenFloatingActionButtonNavigationBehavior =\n LumenFloatingActionButtonNavigationBehavior.AlwaysVisible\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 val navigationVisible = scrollState?.isVisible ?: true\n val visible = lumenFloatingActionButtonVisible(navigationBehavior, navigationVisible)\n val offset by animateDpAsState(\n targetValue = lumenFloatingActionButtonOffset(navigationBehavior, navigationVisible),\n label = \"Lumen floating action navigation offset\"\n )\n\n AnimatedVisibility(\n visible = visible,\n enter = scaleIn() + fadeIn(),\n exit = scaleOut() + fadeOut()\n ) {\n when (size) {\n LumenFloatingActionButtonSize.Regular -> FloatingActionButton(\n onClick = onClick,\n modifier = modifier.offset { IntOffset(0, offset.roundToPx()) },\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.offset { IntOffset(0, offset.roundToPx()) },\n shape = RoundedCornerShape(LumenRadius.Md),\n containerColor = palette.background,\n contentColor = palette.foreground,\n content = content\n )\n }\n }\n}\n",
37086
39605
  "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",
37087
39606
  "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",
37088
39607
  "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",
37089
39608
  "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",
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"
39609
+ "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",
39610
+ "packages/compose/wear/src/main/kotlin/com/santi020k/lumen/wear/WearComponents.kt": "package com.santi020k.lumen\n\nimport androidx.compose.foundation.Canvas\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.BoxScope\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.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.BasicText\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.StrokeCap\nimport androidx.compose.ui.graphics.drawscope.Stroke\nimport androidx.compose.ui.semantics.ProgressBarRangeInfo\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.disabled\nimport androidx.compose.ui.semantics.progressBarRangeInfo\nimport androidx.compose.ui.semantics.role\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\n\nenum class LumenWearTone {\n Accent,\n Brand,\n Danger,\n Neutral,\n Success,\n Warning\n}\n\ndata class LumenWearProgressValue(\n val maximum: Float,\n val value: Float\n) {\n val fraction: Float\n get() = value / maximum\n\n companion object {\n fun resolve(value: Float, maximum: Float): LumenWearProgressValue {\n val safeMaximum = if (maximum.isFinite() && maximum > 0f) maximum else 1f\n val safeValue = if (value.isFinite()) value.coerceIn(0f, safeMaximum) else 0f\n return LumenWearProgressValue(maximum = safeMaximum, value = safeValue)\n }\n }\n}\n\ndata class LumenWearActionMetrics(\n val dimension: Dp,\n val ringWidth: Dp\n) {\n companion object {\n fun resolve(dimension: Dp, ringWidth: Dp): LumenWearActionMetrics {\n val safeDimension = if (dimension.value.isFinite()) {\n dimension.value.coerceIn(48f, 180f).dp\n } else {\n 120.dp\n }\n val safeRingWidth = if (ringWidth.value.isFinite()) {\n ringWidth.value.coerceIn(2f, 12f).dp\n } else {\n 4.dp\n }\n return LumenWearActionMetrics(safeDimension, safeRingWidth)\n }\n }\n}\n\nfun lumenWearColor(palette: LumenColorPalette, tone: LumenWearTone): Color = when (tone) {\n LumenWearTone.Accent -> palette.accent\n LumenWearTone.Brand -> palette.brandSolid\n LumenWearTone.Danger -> palette.danger\n LumenWearTone.Neutral -> palette.inkMuted\n LumenWearTone.Success -> palette.success\n LumenWearTone.Warning -> palette.warning\n}\n\n@Composable\nfun LumenWearTheme(\n darkTheme: Boolean = true,\n values: LumenThemeValues? = null,\n content: @Composable () -> Unit\n) {\n val resolvedValues = values ?: LumenThemeValues(\n colors = if (darkTheme) LumenColors.Dark else LumenColors.Light,\n isDark = darkTheme\n )\n CompositionLocalProvider(LocalLumenTheme provides resolvedValues, content = content)\n}\n\n@Composable\nfun LumenWearActionButton(\n accessibilityLabel: String,\n onClick: () -> Unit,\n modifier: Modifier = Modifier,\n tone: LumenWearTone = LumenWearTone.Brand,\n dimension: Dp = 120.dp,\n enabled: Boolean = true,\n content: @Composable BoxScope.() -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n val metrics = LumenWearActionMetrics.resolve(dimension, 4.dp)\n val color = lumenWearColor(colors, tone)\n\n Box(\n modifier = modifier\n .size(metrics.dimension)\n .clip(CircleShape)\n .background(color)\n .alpha(if (enabled) 1f else 0.48f)\n .clickable(enabled = enabled, role = Role.Button, onClick = onClick)\n .semantics {\n role = Role.Button\n contentDescription = accessibilityLabel\n if (!enabled) disabled()\n },\n contentAlignment = Alignment.Center,\n content = content\n )\n}\n\n@Composable\nfun LumenWearProgressRing(\n value: Float,\n modifier: Modifier = Modifier,\n maximum: Float = 1f,\n tone: LumenWearTone = LumenWearTone.Brand,\n lineWidth: Dp = 4.dp,\n content: @Composable BoxScope.() -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n val progress = LumenWearProgressValue.resolve(value, maximum)\n val metrics = LumenWearActionMetrics.resolve(120.dp, lineWidth)\n\n Box(\n modifier = modifier.semantics {\n progressBarRangeInfo = ProgressBarRangeInfo(progress.value, 0f..progress.maximum)\n },\n contentAlignment = Alignment.Center\n ) {\n Canvas(modifier = Modifier.matchParentSize()) {\n val strokeWidth = metrics.ringWidth.toPx()\n val radius = (size.minDimension - strokeWidth) / 2\n drawCircle(\n color = colors.line.copy(alpha = 0.5f),\n radius = radius,\n style = Stroke(width = strokeWidth)\n )\n drawArc(\n color = lumenWearColor(colors, tone),\n startAngle = -90f,\n sweepAngle = 360f * progress.fraction,\n useCenter = false,\n topLeft = Offset(center.x - radius, center.y - radius),\n size = androidx.compose.ui.geometry.Size(radius * 2, radius * 2),\n style = Stroke(width = strokeWidth, cap = StrokeCap.Round)\n )\n }\n content()\n }\n}\n\n@Composable\nfun LumenWearStatus(\n text: String,\n modifier: Modifier = Modifier,\n tone: LumenWearTone = LumenWearTone.Neutral,\n leading: (@Composable RowScope.() -> Unit)? = null\n) {\n val colors = LocalLumenTheme.current.colors\n val color = lumenWearColor(colors, tone)\n Row(\n modifier = modifier\n .clip(CircleShape)\n .background(color.copy(alpha = 0.24f))\n .padding(horizontal = LumenSpacing.Sm, vertical = LumenSpacing.Xs),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Xs),\n verticalAlignment = Alignment.CenterVertically\n ) {\n leading?.invoke(this)\n BasicText(\n text = text,\n style = TextStyle(\n color = if (tone == LumenWearTone.Neutral) colors.inkSoft else color,\n fontSize = 12.sp,\n fontWeight = FontWeight.SemiBold\n )\n )\n }\n}\n\n@Composable\nfun LumenWearMetric(\n label: String,\n value: String,\n modifier: Modifier = Modifier,\n detail: String? = null,\n tone: LumenWearTone = LumenWearTone.Neutral\n) {\n val colors = LocalLumenTheme.current.colors\n Column(\n modifier = modifier\n .clip(RoundedCornerShape(LumenRadius.Md))\n .background(colors.surfaceMuted.copy(alpha = 0.84f))\n .padding(LumenSpacing.Sm),\n verticalArrangement = Arrangement.spacedBy(2.dp)\n ) {\n BasicText(label, style = TextStyle(color = colors.inkMuted, fontSize = 12.sp))\n BasicText(\n value,\n style = TextStyle(\n color = lumenWearColor(colors, tone),\n fontSize = 18.sp,\n fontWeight = FontWeight.Bold\n )\n )\n if (detail != null) {\n BasicText(detail, style = TextStyle(color = colors.inkSoft, fontSize = 12.sp))\n }\n }\n}\n\n@Composable\nfun LumenWearListRow(\n modifier: Modifier = Modifier,\n leading: (@Composable RowScope.() -> Unit)? = null,\n trailing: (@Composable RowScope.() -> Unit)? = null,\n content: @Composable RowScope.() -> Unit\n) {\n val colors = LocalLumenTheme.current.colors\n Row(\n modifier = modifier\n .fillMaxWidth()\n .clip(RoundedCornerShape(LumenRadius.Md))\n .background(colors.surfaceMuted.copy(alpha = 0.84f))\n .padding(LumenSpacing.Sm),\n horizontalArrangement = Arrangement.spacedBy(LumenSpacing.Sm),\n verticalAlignment = Alignment.CenterVertically\n ) {\n leading?.invoke(this)\n content()\n trailing?.invoke(this)\n }\n}\n"
37091
39611
  },
37092
39612
  "react-native": {
37093
39613
  "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",
37094
- "packages/react-native/src/content-components.tsx": "import { type ReactElement, type ReactNode, type Ref } from 'react'\nimport {\n type DimensionValue,\n type HostInstance,\n Pressable,\n Text,\n type TextStyle,\n View,\n type ViewProps\n} from 'react-native'\n\nimport { resolveLumenDisclosureState, resolveLumenSkeletonHeight } from './content-recipes.js'\nimport { resolveLumenButtonOpacity } from './recipes.js'\nimport { useLumenTheme } from './theme-context.js'\n\nexport type LumenSkeletonShape = 'circle' | 'rectangle' | 'text'\n\nexport interface LumenSkeletonProps extends Omit<ViewProps, 'children'> {\n height?: number\n label?: string\n ref?: Ref<HostInstance>\n shape?: LumenSkeletonShape\n width?: DimensionValue\n}\n\nexport const LumenSkeleton = ({\n height = 16,\n label,\n ref,\n shape = 'text',\n style,\n width,\n ...props\n}: LumenSkeletonProps): ReactElement => {\n const theme = useLumenTheme()\n const safeHeight = resolveLumenSkeletonHeight(height)\n const resolvedWidth = width ?? (shape === 'circle' ? safeHeight : '100%')\n let borderRadius: number = theme.radii.md\n\n if (shape === 'circle') borderRadius = safeHeight / 2\n\n if (shape === 'text') borderRadius = theme.radii.sm\n\n return (\n <View\n ref={ref}\n {...props}\n accessibilityElementsHidden={!label}\n accessibilityLabel={label}\n accessibilityRole={label ? 'progressbar' : undefined}\n aria-busy={label ? true : undefined}\n importantForAccessibility={label ? 'yes' : 'no-hide-descendants'}\n style={[\n {\n backgroundColor: theme.colors.surfaceStrong,\n borderRadius,\n height: safeHeight,\n opacity: 0.72,\n width: resolvedWidth\n },\n style\n ]}\n />\n )\n}\n\nexport interface LumenDisclosureProps extends Omit<ViewProps, 'children'> {\n children: ReactNode\n description?: string\n disabled?: boolean\n expanded: boolean\n graphic?: ReactNode\n onExpandedChange: (expanded: boolean) => void\n ref?: Ref<HostInstance>\n title: string\n}\n\nexport const LumenDisclosure = ({\n children,\n description,\n disabled = false,\n expanded,\n graphic,\n onExpandedChange,\n ref,\n style,\n title,\n ...props\n}: LumenDisclosureProps): ReactElement => {\n const theme = useLumenTheme()\n const state = resolveLumenDisclosureState(expanded, disabled)\n\n return (\n <View\n ref={ref}\n {...props}\n style={[\n {\n backgroundColor: theme.colors.surface,\n borderColor: theme.colors.line,\n borderRadius: theme.radii.md,\n borderWidth: 1,\n overflow: 'hidden'\n },\n style\n ]}\n >\n <Pressable\n accessibilityRole=\"button\"\n accessibilityState={{ disabled, expanded }}\n aria-disabled={disabled}\n aria-expanded={expanded}\n disabled={disabled}\n onPress={() => {\n onExpandedChange(!expanded)\n }}\n style={({ pressed }) => ({\n alignItems: 'center',\n flexDirection: 'row',\n gap: theme.spacing.md,\n minHeight: 48,\n opacity: state.opacity * resolveLumenButtonOpacity(disabled, pressed),\n paddingHorizontal: theme.spacing.lg,\n paddingVertical: theme.spacing.md\n })}\n >\n {graphic ? <View>{graphic}</View> : null}\n <View style={{ flex: 1, 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 {title}\n </Text>\n {description ?\n <Text style={{ color: theme.colors.inkMuted, fontSize: theme.fontSizes.xs }}>{description}</Text> :\n null}\n </View>\n <Text\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n style={{ color: theme.colors.inkSoft, fontSize: theme.fontSizes.lg }}\n >\n {expanded ? '−' : '+'}\n </Text>\n </Pressable>\n {state.contentVisible ?\n (\n <View\n style={{\n borderTopColor: theme.colors.line,\n borderTopWidth: 1,\n padding: theme.spacing.lg\n }}\n >\n {children}\n </View>\n ) :\n null}\n </View>\n )\n}\n",
37095
- "packages/react-native/src/content-recipes.test.ts": "import { describe, expect, test } from 'vitest'\n\nimport {\n resolveLumenDisclosureState,\n resolveLumenSkeletonHeight\n} from './content-recipes.js'\n\ndescribe('content state recipes', () => {\n test('preserves controlled disclosure state', () => {\n expect(resolveLumenDisclosureState(true, false)).toEqual({\n contentVisible: true,\n disabled: false,\n opacity: 1\n })\n expect(resolveLumenDisclosureState(false, true)).toEqual({\n contentVisible: false,\n disabled: true,\n opacity: 0.52\n })\n })\n\n test('normalizes invalid skeleton heights', () => {\n expect(resolveLumenSkeletonHeight(24)).toBe(24)\n expect(resolveLumenSkeletonHeight(0)).toBe(16)\n expect(resolveLumenSkeletonHeight(Number.NaN)).toBe(16)\n })\n})\n",
37096
- "packages/react-native/src/content-recipes.ts": "export interface LumenDisclosureState {\n contentVisible: boolean\n disabled: boolean\n opacity: number\n}\n\nexport const resolveLumenDisclosureState = (\n expanded: boolean,\n disabled: boolean\n): LumenDisclosureState => ({\n contentVisible: expanded,\n disabled,\n opacity: disabled ? 0.52 : 1\n})\n\nexport const resolveLumenSkeletonHeight = (height: number): number => (\n Number.isFinite(height) && height > 0 ? height : 16\n)\n",
39614
+ "packages/react-native/src/content-components.tsx": "import { type ReactElement, type ReactNode, type Ref } from 'react'\nimport {\n type DimensionValue,\n type HostInstance,\n Pressable,\n Text,\n type TextStyle,\n View,\n type ViewProps\n} from 'react-native'\n\nimport {\n type LumenBackdropIntensity,\n type LumenGraphicSize,\n type LumenIllustrationSize,\n resolveLumenBackdropOpacity,\n resolveLumenDisclosureState,\n resolveLumenGraphicSize,\n resolveLumenIllustrationSize,\n resolveLumenSkeletonHeight } from './content-recipes.js'\nimport { resolveLumenButtonOpacity } from './recipes.js'\nimport { useLumenTheme } from './theme-context.js'\n\nexport type LumenSkeletonShape = 'circle' | 'rectangle' | 'text'\n\nexport interface LumenSkeletonProps extends Omit<ViewProps, 'children'> {\n height?: number\n label?: string\n ref?: Ref<HostInstance>\n shape?: LumenSkeletonShape\n width?: DimensionValue\n}\n\nexport const LumenSkeleton = ({\n height = 16,\n label,\n ref,\n shape = 'text',\n style,\n width,\n ...props\n}: LumenSkeletonProps): ReactElement => {\n const theme = useLumenTheme()\n const safeHeight = resolveLumenSkeletonHeight(height)\n const resolvedWidth = width ?? (shape === 'circle' ? safeHeight : '100%')\n let borderRadius: number = theme.radii.md\n\n if (shape === 'circle') borderRadius = safeHeight / 2\n\n if (shape === 'text') borderRadius = theme.radii.sm\n\n return (\n <View\n ref={ref}\n {...props}\n accessibilityElementsHidden={!label}\n accessibilityLabel={label}\n accessibilityRole={label ? 'progressbar' : undefined}\n aria-busy={label ? true : undefined}\n importantForAccessibility={label ? 'yes' : 'no-hide-descendants'}\n style={[\n {\n backgroundColor: theme.colors.surfaceStrong,\n borderRadius,\n height: safeHeight,\n opacity: 0.72,\n width: resolvedWidth\n },\n style\n ]}\n />\n )\n}\n\nexport type LumenGraphicTone = 'accent' | 'brand' | 'neutral'\nexport type LumenGraphicVariant = 'glow' | 'grid' | 'orbit'\n\nexport interface LumenGraphicProps extends ViewProps {\n children?: ReactNode\n label?: string\n ref?: Ref<HostInstance>\n size?: LumenGraphicSize\n tone?: LumenGraphicTone\n variant?: LumenGraphicVariant\n}\n\nconst graphicLines = Array.from({ length: 7 }, (_, index) => index)\nconst backdropDots = Array.from({ length: 48 }, (_, index) => index)\n\nexport const LumenGraphic = ({\n children,\n label,\n ref,\n size = 'md',\n style,\n tone = 'brand',\n variant = 'orbit',\n ...props\n}: LumenGraphicProps): ReactElement => {\n const theme = useLumenTheme()\n const dimension = resolveLumenGraphicSize(size)\n\n const color = {\n accent: theme.colors.accent,\n brand: theme.colors.brand,\n neutral: theme.colors.inkMuted\n }[tone]\n\n return (\n <View\n ref={ref}\n {...props}\n accessible={Boolean(label)}\n accessibilityElementsHidden={!label}\n accessibilityLabel={label}\n accessibilityRole={label ? 'image' : undefined}\n importantForAccessibility={label ? 'yes' : 'no-hide-descendants'}\n style={[\n {\n alignItems: 'center',\n height: dimension,\n justifyContent: 'center',\n overflow: 'hidden',\n position: 'relative',\n width: dimension\n },\n style\n ]}\n >\n {variant === 'glow' ?\n <View style={{ backgroundColor: color, borderRadius: dimension / 2, height: '72%', opacity: 0.18, width: '72%' }} /> :\n null}\n {variant === 'grid' ?\n (\n <View style={{ height: '76%', opacity: 0.18, position: 'absolute', width: '76%' }}>\n {graphicLines.map(index => (\n <View\n key={`horizontal-${index}`}\n style={{ backgroundColor: color, height: 1, left: 0, position: 'absolute', right: 0, top: `${index * 16.66}%` }}\n />\n ))}\n {graphicLines.map(index => (\n <View\n key={`vertical-${index}`}\n style={{ backgroundColor: color, bottom: 0, left: `${index * 16.66}%`, position: 'absolute', top: 0, width: 1 }}\n />\n ))}\n </View>\n ) :\n null}\n {variant === 'orbit' ?\n (\n <>\n {[0.9, 0.62, 0.34].map(ratio => (\n <View\n key={ratio}\n style={{\n borderColor: color,\n borderRadius: dimension,\n borderWidth: 1,\n height: dimension * ratio,\n opacity: 0.24,\n position: 'absolute',\n width: dimension * ratio\n }}\n />\n ))}\n <View style={{ backgroundColor: color, height: 1, opacity: 0.18, position: 'absolute', width: '72%' }} />\n <View style={{ backgroundColor: color, height: '72%', opacity: 0.18, position: 'absolute', width: 1 }} />\n </>\n ) :\n null}\n <View style={{ alignItems: 'center', justifyContent: 'center', position: 'absolute' }}>\n {children}\n </View>\n </View>\n )\n}\n\nexport type LumenBackdropTone = 'accent' | 'brand' | 'neutral'\nexport type LumenBackdropVariant = 'aurora' | 'dots' | 'grid' | 'rays'\n\nexport interface LumenBackdropProps extends ViewProps {\n children?: ReactNode\n intensity?: LumenBackdropIntensity\n ref?: Ref<HostInstance>\n tone?: LumenBackdropTone\n variant?: LumenBackdropVariant\n}\n\nexport const LumenBackdrop = ({\n children,\n intensity = 'medium',\n ref,\n style,\n tone = 'brand',\n variant = 'aurora',\n ...props\n}: LumenBackdropProps): ReactElement => {\n const theme = useLumenTheme()\n const color = { accent: theme.colors.accent, brand: theme.colors.brand, neutral: theme.colors.inkMuted }[tone]\n const secondaryColor = tone === 'neutral' ? theme.colors.surfaceStrong : theme.colors.accent\n const opacity = resolveLumenBackdropOpacity(intensity)\n\n return (\n <View ref={ref} {...props} style={[{ minHeight: 160, overflow: 'hidden', position: 'relative' }, style]}>\n <View\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n pointerEvents=\"none\"\n style={{ bottom: 0, left: 0, opacity, position: 'absolute', right: 0, top: 0 }}\n >\n {variant === 'aurora' ?\n (\n <>\n <View style={{ backgroundColor: color, borderRadius: 140, height: 280, left: -70, opacity: 0.22, position: 'absolute', top: -100, width: 280 }} />\n <View style={{ backgroundColor: secondaryColor, borderRadius: 120, bottom: -110, height: 240, opacity: 0.18, position: 'absolute', right: -50, width: 240 }} />\n </>\n ) :\n null}\n {variant === 'grid' ?\n (\n <>\n {graphicLines.map(index => (\n <View key={`backdrop-horizontal-${index}`} style={{ backgroundColor: color, height: 1, left: 0, opacity: 0.18, position: 'absolute', right: 0, top: `${index * 16.66}%` }} />\n ))}\n {graphicLines.map(index => (\n <View key={`backdrop-vertical-${index}`} style={{ backgroundColor: color, bottom: 0, left: `${index * 16.66}%`, opacity: 0.18, position: 'absolute', top: 0, width: 1 }} />\n ))}\n </>\n ) :\n null}\n {variant === 'dots' ?\n backdropDots.map(index => (\n <View\n key={`backdrop-dot-${index}`}\n style={{\n backgroundColor: color,\n borderRadius: 2,\n height: 3,\n left: `${(index % 8) * 14 + 1}%`,\n opacity: 0.28,\n position: 'absolute',\n top: `${Math.floor(index / 8) * 19 + 2}%`,\n width: 3\n }}\n />\n )) :\n null}\n {variant === 'rays' ?\n graphicLines.map(index => (\n <View\n key={`backdrop-ray-${index}`}\n style={{\n backgroundColor: color,\n height: 1,\n left: '50%',\n opacity: 0.2,\n position: 'absolute',\n top: '50%',\n transform: [{ rotate: `${index * 24 - 72}deg` }, { translateX: -240 }],\n width: 480\n }}\n />\n )) :\n null}\n </View>\n <View style={{ zIndex: 1 }}>{children}</View>\n </View>\n )\n}\n\nexport type LumenIllustrationTone = 'accent' | 'auto' | 'brand' | 'neutral'\nexport type LumenIllustrationVariant = 'empty' | 'error' | 'offline' | 'success'\n\nexport interface LumenIllustrationProps extends ViewProps {\n label?: string\n ref?: Ref<HostInstance>\n size?: LumenIllustrationSize\n tone?: LumenIllustrationTone\n variant?: LumenIllustrationVariant\n}\n\ninterface LumenIllustrationArtworkProps {\n color: string\n dimension: number\n strokeWidth: number\n variant: LumenIllustrationVariant\n}\n\nconst LumenIllustrationArtwork = ({\n color,\n dimension,\n strokeWidth,\n variant\n}: LumenIllustrationArtworkProps): ReactElement | null => {\n const line = (width: number, rotation: string, left: number, top: number): ReactElement => (\n <View\n style={{\n backgroundColor: color,\n borderRadius: strokeWidth,\n height: strokeWidth,\n left,\n position: 'absolute',\n top,\n transform: [{ rotate: rotation }],\n width\n }}\n />\n )\n\n if (variant === 'empty') {\n return (\n <View\n style={{\n borderColor: color,\n borderRadius: dimension * 0.08,\n borderWidth: strokeWidth,\n height: dimension * 0.34,\n marginTop: dimension * 0.14,\n width: dimension * 0.52\n }}\n >\n <View style={{ backgroundColor: color, height: strokeWidth, left: '18%', opacity: 0.7, position: 'absolute', top: '38%', width: '64%' }} />\n </View>\n )\n }\n\n if (variant === 'offline') {\n return (\n <View style={{ height: dimension * 0.52, position: 'relative', width: dimension * 0.64 }}>\n <View style={{ borderColor: color, borderRadius: dimension, borderWidth: strokeWidth, bottom: dimension * 0.08, height: dimension * 0.32, position: 'absolute', width: '100%' }} />\n {line(dimension * 0.72, '45deg', -dimension * 0.04, dimension * 0.24)}\n </View>\n )\n }\n\n return (\n <View style={{ borderColor: color, borderRadius: dimension, borderWidth: strokeWidth, height: dimension * 0.52, position: 'relative', width: dimension * 0.52 }}>\n {variant === 'success' ?\n (\n <>\n {line(dimension * 0.16, '45deg', dimension * 0.1, dimension * 0.25)}\n {line(dimension * 0.26, '-45deg', dimension * 0.19, dimension * 0.21)}\n </>\n ) :\n (\n <>\n {line(dimension * 0.28, '45deg', dimension * 0.11, dimension * 0.25)}\n {line(dimension * 0.28, '-45deg', dimension * 0.11, dimension * 0.25)}\n </>\n )}\n </View>\n )\n}\n\nexport const LumenIllustration = ({\n label,\n ref,\n size = 'md',\n style,\n tone = 'auto',\n variant = 'empty',\n ...props\n}: LumenIllustrationProps): ReactElement => {\n const theme = useLumenTheme()\n const dimension = resolveLumenIllustrationSize(size)\n\n const semanticColor = {\n empty: theme.colors.brand,\n error: theme.colors.danger,\n offline: theme.colors.inkMuted,\n success: theme.colors.success\n }[variant]\n\n const color = tone === 'auto' ?\n semanticColor :\n {\n accent: theme.colors.accent,\n brand: theme.colors.brand,\n neutral: theme.colors.inkMuted\n }[tone]\n\n const strokeWidth = Math.max(2, dimension / 48)\n\n return (\n <View\n ref={ref}\n {...props}\n accessible={Boolean(label)}\n accessibilityElementsHidden={!label}\n accessibilityLabel={label}\n accessibilityRole={label ? 'image' : undefined}\n importantForAccessibility={label ? 'yes' : 'no-hide-descendants'}\n style={[{ alignItems: 'center', height: dimension, justifyContent: 'center', position: 'relative', width: dimension }, style]}\n >\n <View style={{ backgroundColor: color, borderRadius: dimension, height: '82%', opacity: 0.1, position: 'absolute', width: '82%' }} />\n <LumenIllustrationArtwork color={color} dimension={dimension} strokeWidth={strokeWidth} variant={variant} />\n </View>\n )\n}\n\nexport interface LumenDisclosureProps extends Omit<ViewProps, 'children'> {\n children: ReactNode\n description?: string\n disabled?: boolean\n expanded: boolean\n graphic?: ReactNode\n onExpandedChange: (expanded: boolean) => void\n ref?: Ref<HostInstance>\n title: string\n}\n\nexport const LumenDisclosure = ({\n children,\n description,\n disabled = false,\n expanded,\n graphic,\n onExpandedChange,\n ref,\n style,\n title,\n ...props\n}: LumenDisclosureProps): ReactElement => {\n const theme = useLumenTheme()\n const state = resolveLumenDisclosureState(expanded, disabled)\n\n return (\n <View\n ref={ref}\n {...props}\n style={[\n {\n backgroundColor: theme.colors.surface,\n borderColor: theme.colors.line,\n borderRadius: theme.radii.md,\n borderWidth: 1,\n overflow: 'hidden'\n },\n style\n ]}\n >\n <Pressable\n accessibilityRole=\"button\"\n accessibilityState={{ disabled, expanded }}\n aria-disabled={disabled}\n aria-expanded={expanded}\n disabled={disabled}\n onPress={() => {\n onExpandedChange(!expanded)\n }}\n style={({ pressed }) => ({\n alignItems: 'center',\n flexDirection: 'row',\n gap: theme.spacing.md,\n minHeight: 48,\n opacity: state.opacity * resolveLumenButtonOpacity(disabled, pressed),\n paddingHorizontal: theme.spacing.lg,\n paddingVertical: theme.spacing.md\n })}\n >\n {graphic ? <View>{graphic}</View> : null}\n <View style={{ flex: 1, 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 {title}\n </Text>\n {description ?\n <Text style={{ color: theme.colors.inkMuted, fontSize: theme.fontSizes.xs }}>{description}</Text> :\n null}\n </View>\n <Text\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n style={{ color: theme.colors.inkSoft, fontSize: theme.fontSizes.lg }}\n >\n {expanded ? '−' : '+'}\n </Text>\n </Pressable>\n {state.contentVisible ?\n (\n <View\n style={{\n borderTopColor: theme.colors.line,\n borderTopWidth: 1,\n padding: theme.spacing.lg\n }}\n >\n {children}\n </View>\n ) :\n null}\n </View>\n )\n}\n",
39615
+ "packages/react-native/src/content-recipes.test.ts": "import { describe, expect, test } from 'vitest'\n\nimport {\n resolveLumenBackdropOpacity,\n resolveLumenDisclosureState,\n resolveLumenGraphicSize,\n resolveLumenIllustrationSize,\n resolveLumenSkeletonHeight\n} from './content-recipes.js'\n\ndescribe('content state recipes', () => {\n test('preserves controlled disclosure state', () => {\n expect(resolveLumenDisclosureState(true, false)).toEqual({\n contentVisible: true,\n disabled: false,\n opacity: 1\n })\n expect(resolveLumenDisclosureState(false, true)).toEqual({\n contentVisible: false,\n disabled: true,\n opacity: 0.52\n })\n })\n\n test('normalizes invalid skeleton heights', () => {\n expect(resolveLumenSkeletonHeight(24)).toBe(24)\n expect(resolveLumenSkeletonHeight(0)).toBe(16)\n expect(resolveLumenSkeletonHeight(Number.NaN)).toBe(16)\n })\n\n test('keeps graphic sizes aligned with the shared contract', () => {\n expect(resolveLumenGraphicSize('sm')).toBe(160)\n expect(resolveLumenGraphicSize('md')).toBe(240)\n expect(resolveLumenGraphicSize('lg')).toBe(320)\n })\n\n test('normalizes backdrop intensity and illustration sizes', () => {\n expect(resolveLumenBackdropOpacity('subtle')).toBe(0.4)\n expect(resolveLumenBackdropOpacity('medium')).toBe(0.68)\n expect(resolveLumenBackdropOpacity('strong')).toBe(1)\n expect(resolveLumenIllustrationSize('sm')).toBe(96)\n expect(resolveLumenIllustrationSize('md')).toBe(128)\n expect(resolveLumenIllustrationSize('lg')).toBe(176)\n })\n})\n",
39616
+ "packages/react-native/src/content-recipes.ts": "export interface LumenDisclosureState {\n contentVisible: boolean\n disabled: boolean\n opacity: number\n}\n\nexport const resolveLumenDisclosureState = (\n expanded: boolean,\n disabled: boolean\n): LumenDisclosureState => ({\n contentVisible: expanded,\n disabled,\n opacity: disabled ? 0.52 : 1\n})\n\nexport const resolveLumenSkeletonHeight = (height: number): number => (\n Number.isFinite(height) && height > 0 ? height : 16\n)\n\nexport type LumenGraphicSize = 'lg' | 'md' | 'sm'\n\nexport const resolveLumenGraphicSize = (size: LumenGraphicSize): number => ({\n lg: 320,\n md: 240,\n sm: 160\n})[size]\n\nexport type LumenBackdropIntensity = 'medium' | 'strong' | 'subtle'\n\nexport const resolveLumenBackdropOpacity = (\n intensity: LumenBackdropIntensity\n): number => ({ medium: 0.68, strong: 1, subtle: 0.4 })[intensity]\n\nexport type LumenIllustrationSize = 'lg' | 'md' | 'sm'\n\nexport const resolveLumenIllustrationSize = (\n size: LumenIllustrationSize\n): number => ({ lg: 176, md: 128, sm: 96 })[size]\n",
37097
39617
  "packages/react-native/src/form-components.tsx": "import {\n type ReactElement,\n type ReactNode,\n type Ref\n} from 'react'\nimport {\n type HostInstance,\n Pressable,\n Switch,\n type SwitchProps,\n Text,\n TextInput,\n type TextInputInstance,\n type TextInputProps,\n type TextStyle,\n View,\n type ViewProps\n} from 'react-native'\n\nimport { resolveLumenSearchFieldState } from './form-recipes.js'\nimport { resolveLumenButtonOpacity } from './recipes.js'\nimport { useLumenTheme } from './theme-context.js'\n\nconst resolveSearchValue = <Value,>(value: Value | undefined, fallback: Value): Value => value ?? fallback\n\nexport interface LumenToggleProps extends Omit<SwitchProps, 'onValueChange' | 'value'> {\n description?: string\n label: string\n onValueChange: (value: boolean) => void\n ref?: Ref<HostInstance>\n showLabel?: boolean\n value: boolean\n}\n\nexport const LumenToggle = ({\n description,\n disabled = false,\n label,\n onValueChange,\n ref,\n showLabel = true,\n style,\n value,\n ...props\n}: LumenToggleProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <View\n style={{\n alignItems: 'center',\n flexDirection: 'row',\n gap: theme.spacing.lg,\n minHeight: 44\n }}\n >\n {showLabel ?\n (\n <View style={{ flex: 1, 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 {description ?\n <Text style={{ color: theme.colors.inkMuted, fontSize: theme.fontSizes.xs }}>{description}</Text> :\n null}\n </View>\n ) :\n null}\n <Switch\n ref={ref}\n {...props}\n accessibilityLabel={label}\n disabled={disabled}\n ios_backgroundColor={theme.colors.surfaceStrong}\n onValueChange={onValueChange}\n style={[{ opacity: disabled ? 0.52 : 1 }, style]}\n thumbColor={value ? theme.colors.onBrand : theme.colors.surface}\n trackColor={{ false: theme.colors.surfaceStrong, true: theme.colors.brandSolid }}\n value={value}\n />\n </View>\n )\n}\n\nexport interface LumenSettingsRowProps extends ViewProps {\n control: ReactNode\n description?: string\n graphic?: ReactNode\n ref?: Ref<HostInstance>\n title: string\n}\n\nexport const LumenSettingsRow = ({\n control,\n description,\n graphic,\n ref,\n style,\n title,\n ...props\n}: LumenSettingsRowProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <View\n ref={ref}\n {...props}\n style={[\n {\n alignItems: 'center',\n flexDirection: 'row',\n gap: theme.spacing.lg,\n minHeight: 56,\n paddingVertical: theme.spacing.sm\n },\n style\n ]}\n >\n {graphic ? <View>{graphic}</View> : null}\n <View style={{ flex: 1, 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 {title}\n </Text>\n {description ?\n <Text style={{ color: theme.colors.inkMuted, fontSize: theme.fontSizes.xs }}>{description}</Text> :\n null}\n </View>\n <View>{control}</View>\n </View>\n )\n}\n\nexport interface LumenSearchFieldProps extends Omit<TextInputProps, 'onChangeText' | 'value'> {\n clearLabel?: string\n graphic?: ReactNode\n onChangeText: (value: string) => void\n prompt?: string\n ref?: Ref<TextInputInstance>\n value: string\n}\n\nexport const LumenSearchField = ({\n clearLabel = 'Clear search',\n editable = true,\n graphic,\n onChangeText,\n placeholder,\n placeholderTextColor,\n prompt = 'Search',\n ref,\n returnKeyType = 'search',\n style,\n value,\n ...props\n}: LumenSearchFieldProps): ReactElement => {\n const theme = useLumenTheme()\n const searchState = resolveLumenSearchFieldState(value, editable)\n\n return (\n <View\n style={{\n alignItems: 'center',\n backgroundColor: theme.colors.surface,\n borderColor: theme.colors.line,\n borderRadius: theme.radii.sm,\n borderWidth: 1,\n flexDirection: 'row',\n gap: theme.spacing.sm,\n minHeight: 44,\n opacity: searchState.opacity,\n paddingLeft: theme.spacing.md,\n paddingRight: theme.spacing.xs\n }}\n >\n {graphic}\n <TextInput\n ref={ref}\n {...props}\n accessibilityLabel={resolveSearchValue(props.accessibilityLabel, prompt)}\n editable={editable}\n onChangeText={onChangeText}\n placeholder={resolveSearchValue(placeholder, prompt)}\n placeholderTextColor={resolveSearchValue(placeholderTextColor, theme.colors.inkMuted)}\n returnKeyType={returnKeyType}\n style={[\n {\n color: theme.colors.ink,\n flex: 1,\n fontSize: theme.fontSizes.sm,\n minHeight: 42,\n paddingVertical: theme.spacing.sm\n },\n style\n ]}\n value={value}\n />\n {searchState.showClearAction ?\n (\n <Pressable\n accessibilityLabel={clearLabel}\n accessibilityRole=\"button\"\n hitSlop={4}\n onPress={() => {\n onChangeText('')\n }}\n style={({ pressed }) => ({\n opacity: resolveLumenButtonOpacity(false, pressed),\n padding: theme.spacing.sm\n })}\n >\n <Text\n style={{\n color: theme.colors.inkSoft,\n fontSize: theme.fontSizes.xs,\n fontWeight: String(theme.fontWeights.semibold) as TextStyle['fontWeight']\n }}\n >\n {clearLabel}\n </Text>\n </Pressable>\n ) :\n null}\n </View>\n )\n}\n",
37098
39618
  "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",
37099
39619
  "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",
37100
39620
  "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",
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",
39621
+ "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 LumenBackdrop,\n type LumenBackdropProps,\n type LumenBackdropTone,\n type LumenBackdropVariant,\n LumenDisclosure,\n type LumenDisclosureProps,\n LumenGraphic,\n type LumenGraphicProps,\n type LumenGraphicTone,\n type LumenGraphicVariant,\n LumenIllustration,\n type LumenIllustrationProps,\n type LumenIllustrationTone,\n type LumenIllustrationVariant,\n LumenSkeleton,\n type LumenSkeletonProps,\n type LumenSkeletonShape\n} from './content-components.js'\nexport {\n type LumenBackdropIntensity,\n type LumenGraphicSize,\n type LumenIllustrationSize\n} from './content-recipes.js'\nexport {\n LumenSearchField,\n type LumenSearchFieldProps,\n LumenSettingsRow,\n type LumenSettingsRowProps,\n LumenToggle,\n type LumenToggleProps\n} from './form-components.js'\nexport {\n LumenAlertDialog,\n type LumenAlertDialogProps,\n LumenMenu,\n type LumenMenuItem,\n type LumenMenuProps,\n LumenShareButton,\n type LumenShareButtonProps,\n LumenSheet,\n type LumenSheetProps\n} from './overlay-components.js'\nexport {\n type LumenMenuPosition,\n type LumenMenuPositionInput,\n resolveLumenMenuPosition\n} from './overlay-recipes.js'\nexport {\n LumenCollapsibleNavigationBar,\n type LumenCollapsibleNavigationBarProps,\n LumenNavigationAccessory,\n type LumenNavigationAccessoryProps,\n LumenNavigationBar,\n type LumenNavigationBarProps,\n type LumenNavigationItem,\n LumenRefreshControl,\n type LumenRefreshControlProps\n} from './platform-components.js'\nexport {\n type LumenNavigationBarVisibilityController,\n type LumenNavigationBarVisibilityOptions,\n useLumenNavigationBarVisibility\n} from './platform-hooks.js'\nexport {\n createLumenNavigationVisibilityState,\n type LumenNavigationBadge,\n type LumenNavigationVisibilityState,\n type LumenRefreshIndicatorTone,\n resolveLumenNavigationBadge,\n resolveLumenNavigationVisibility\n} 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 { LumenProvider, type LumenProviderProps } from './provider.js'\nexport {\n LumenCheckbox,\n type LumenCheckboxProps,\n LumenRadioGroup,\n type LumenRadioGroupProps,\n LumenSegmentedControl,\n type LumenSegmentedControlProps,\n type LumenSelectionOption\n} 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\n} from './tokens.generated.js'\n",
39622
+ "packages/react-native/src/overlay-components.tsx": "import {\n type ComponentRef,\n type ReactElement,\n type ReactNode,\n useRef,\n useState\n} from 'react'\nimport {\n Modal,\n Pressable,\n Share,\n type ShareContent,\n type ShareOptions,\n Text,\n type TextStyle,\n useWindowDimensions,\n View,\n type ViewStyle\n} from 'react-native'\n\nimport {\n type LumenMenuPosition,\n resolveLumenMenuPosition\n} from './overlay-recipes.js'\nimport { LumenButton } from './primitives.js'\nimport { useLumenTheme } from './theme-context.js'\n\nexport interface LumenAlertDialogProps {\n cancelLabel?: string\n confirmDisabled?: boolean\n confirmLabel: string\n confirmLoading?: boolean\n destructive?: boolean\n description?: string\n onConfirm: () => void\n onDismiss: () => void\n title: string\n visible: boolean\n}\n\n/** A controlled native confirmation dialog with explicit cancel and confirm actions. */\nexport const LumenAlertDialog = ({\n cancelLabel = 'Cancel',\n confirmDisabled = false,\n confirmLabel,\n confirmLoading = false,\n destructive = false,\n description,\n onConfirm,\n onDismiss,\n title,\n visible\n}: LumenAlertDialogProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <Modal\n animationType=\"fade\"\n onRequestClose={onDismiss}\n transparent\n visible={visible}\n >\n <View\n accessibilityViewIsModal\n style={{\n alignItems: 'center',\n backgroundColor: '#00000080',\n flex: 1,\n justifyContent: 'center',\n padding: theme.spacing.xl\n }}\n >\n <View\n accessibilityRole=\"alert\"\n style={{\n backgroundColor: theme.colors.surface,\n borderColor: theme.colors.line,\n borderRadius: theme.radii.lg,\n borderWidth: 1,\n gap: theme.spacing.lg,\n maxWidth: 440,\n padding: theme.spacing.xl,\n width: '100%'\n }}\n >\n <View style={{ gap: theme.spacing.sm }}>\n <Text\n style={{\n color: theme.colors.ink,\n fontSize: theme.fontSizes.lg,\n fontWeight: String(theme.fontWeights.semibold) as TextStyle['fontWeight']\n }}\n >\n {title}\n </Text>\n {description ?\n (\n <Text style={{ color: theme.colors.inkSoft, fontSize: theme.fontSizes.sm }}>\n {description}\n </Text>\n ) :\n null}\n </View>\n <View\n style={{\n flexDirection: 'row',\n flexWrap: 'wrap',\n gap: theme.spacing.sm,\n justifyContent: 'flex-end'\n }}\n >\n <LumenButton intent=\"quiet\" onPress={onDismiss}>\n {cancelLabel}\n </LumenButton>\n <LumenButton\n disabled={confirmDisabled}\n intent={destructive ? 'danger' : 'primary'}\n loading={confirmLoading}\n onPress={onConfirm}\n >\n {confirmLabel}\n </LumenButton>\n </View>\n </View>\n </View>\n </Modal>\n )\n}\n\nexport interface LumenSheetProps {\n actions?: ReactNode\n children: ReactNode\n description?: string\n onDismiss: () => void\n title?: string\n visible: boolean\n}\n\n/** A controlled bottom sheet that preserves native modal focus and dismissal behavior. */\nexport const LumenSheet = ({\n actions,\n children,\n description,\n onDismiss,\n title,\n visible\n}: LumenSheetProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <Modal\n animationType=\"slide\"\n onRequestClose={onDismiss}\n transparent\n visible={visible}\n >\n <View accessibilityViewIsModal style={{ flex: 1, justifyContent: 'flex-end' }}>\n <Pressable\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n onPress={onDismiss}\n style={{ backgroundColor: '#00000066', flex: 1 }}\n />\n <View\n style={{\n backgroundColor: theme.colors.surface,\n borderColor: theme.colors.line,\n borderTopLeftRadius: theme.radii.lg,\n borderTopRightRadius: theme.radii.lg,\n borderWidth: 1,\n gap: theme.spacing.lg,\n maxHeight: '90%',\n padding: theme.spacing.xl\n }}\n >\n {title || description ?\n (\n <View style={{ gap: theme.spacing.sm }}>\n {title ?\n (\n <Text\n accessibilityRole=\"header\"\n style={{\n color: theme.colors.ink,\n fontSize: theme.fontSizes.lg,\n fontWeight: String(theme.fontWeights.semibold) as TextStyle['fontWeight']\n }}\n >\n {title}\n </Text>\n ) :\n null}\n {description ?\n <Text style={{ color: theme.colors.inkSoft }}>{description}</Text> :\n null}\n </View>\n ) :\n null}\n {children}\n {actions ? <View style={{ alignItems: 'flex-end' }}>{actions}</View> : null}\n </View>\n </View>\n </Modal>\n )\n}\n\nexport interface LumenMenuItem {\n destructive?: boolean\n disabled?: boolean\n label: string\n onPress: () => void\n}\n\nexport interface LumenMenuProps {\n accessibilityLabel: string\n items: readonly LumenMenuItem[]\n trigger: ReactNode\n}\n\nconst menuWidth = 240\n\n/** An anchored action menu with native modal focus containment and labeled item states. */\nexport const LumenMenu = ({\n accessibilityLabel,\n items,\n trigger\n}: LumenMenuProps): ReactElement => {\n const theme = useLumenTheme()\n const triggerRef = useRef<ComponentRef<typeof Pressable>>(null)\n const { height: windowHeight, width: windowWidth } = useWindowDimensions()\n\n const [position, setPosition] = useState<LumenMenuPosition>({\n left: theme.spacing.lg,\n top: theme.spacing.lg\n })\n\n const [visible, setVisible] = useState(false)\n\n const open = (): void => {\n triggerRef.current?.measureInWindow((x, y, width, height) => {\n setPosition(resolveLumenMenuPosition({\n anchorHeight: height,\n anchorWidth: width,\n anchorX: x,\n anchorY: y,\n itemCount: items.length,\n margin: theme.spacing.lg,\n menuWidth,\n windowHeight,\n windowWidth\n }))\n\n setVisible(true)\n })\n }\n\n return (\n <>\n <Pressable\n ref={triggerRef}\n accessibilityLabel={accessibilityLabel}\n accessibilityRole=\"button\"\n accessibilityState={{ expanded: visible }}\n onPress={open}\n >\n {trigger}\n </Pressable>\n <Modal\n animationType=\"fade\"\n onRequestClose={() => {\n setVisible(false)\n }}\n transparent\n visible={visible}\n >\n <Pressable\n accessibilityViewIsModal\n onPress={() => {\n setVisible(false)\n }}\n style={{ flex: 1 }}\n >\n <View\n accessibilityLabel={accessibilityLabel}\n accessibilityRole=\"menu\"\n style={{\n backgroundColor: theme.colors.surface,\n borderColor: theme.colors.line,\n borderRadius: theme.radii.md,\n borderWidth: 1,\n left: position.left,\n padding: theme.spacing.sm,\n position: 'absolute',\n top: position.top,\n width: menuWidth\n }}\n >\n {items.map(item => (\n <Pressable\n key={item.label}\n accessibilityRole=\"menuitem\"\n accessibilityState={{ disabled: item.disabled }}\n disabled={item.disabled}\n onPress={() => {\n setVisible(false)\n\n item.onPress()\n }}\n style={({ pressed }): ViewStyle => ({\n backgroundColor: pressed ? theme.colors.surfaceMuted : 'transparent',\n borderRadius: theme.radii.sm,\n minHeight: 44,\n opacity: item.disabled ? 0.52 : 1,\n paddingHorizontal: theme.spacing.md,\n paddingVertical: theme.spacing.md\n })}\n >\n <Text\n style={{\n color: item.destructive ? theme.colors.danger : theme.colors.ink,\n fontSize: theme.fontSizes.sm\n }}\n >\n {item.label}\n </Text>\n </Pressable>\n ))}\n </View>\n </Pressable>\n </Modal>\n </>\n )\n}\n\nexport interface LumenShareButtonProps {\n content: ShareContent\n disabled?: boolean\n label?: string\n onError?: (error: unknown) => void\n onShared?: () => void\n options?: ShareOptions\n}\n\n/** A Lumen button that opens the operating system's share sheet. */\nexport const LumenShareButton = ({\n content,\n disabled = false,\n label = 'Share',\n onError,\n onShared,\n options\n}: LumenShareButtonProps): ReactElement => {\n const share = async (): Promise<void> => {\n try {\n const result = await Share.share(content, options)\n\n if (result.action === Share.sharedAction) onShared?.()\n } catch (error) {\n onError?.(error)\n }\n }\n\n return (\n <LumenButton\n disabled={disabled}\n intent=\"secondary\"\n onPress={() => {\n void share()\n }}\n >\n {label}\n </LumenButton>\n )\n}\n",
39623
+ "packages/react-native/src/overlay-recipes.test.ts": "import { describe, expect, test } from 'vitest'\n\nimport { resolveLumenMenuPosition } from './overlay-recipes.js'\n\ndescribe('React Native overlay recipes', () => {\n test('keeps anchored menus within the horizontal viewport', () => {\n expect(resolveLumenMenuPosition({\n anchorHeight: 44,\n anchorWidth: 44,\n anchorX: 350,\n anchorY: 40,\n itemCount: 2,\n margin: 16,\n menuWidth: 240,\n windowHeight: 800,\n windowWidth: 390\n })).toEqual({ left: 134, top: 88 })\n })\n\n test('places a menu above its trigger when the space below is insufficient', () => {\n expect(resolveLumenMenuPosition({\n anchorHeight: 44,\n anchorWidth: 44,\n anchorX: 24,\n anchorY: 700,\n itemCount: 3,\n margin: 16,\n menuWidth: 240,\n windowHeight: 800,\n windowWidth: 390\n })).toEqual({ left: 16, top: 524 })\n })\n})\n",
39624
+ "packages/react-native/src/overlay-recipes.ts": "export interface LumenMenuPositionInput {\n anchorHeight: number\n anchorWidth: number\n anchorX: number\n anchorY: number\n itemCount: number\n margin: number\n menuWidth: number\n windowHeight: number\n windowWidth: number\n}\n\nexport interface LumenMenuPosition {\n left: number\n top: number\n}\n\nexport const resolveLumenMenuPosition = ({\n anchorHeight,\n anchorWidth,\n anchorX,\n anchorY,\n itemCount,\n margin,\n menuWidth,\n windowHeight,\n windowWidth\n}: LumenMenuPositionInput): LumenMenuPosition => {\n const left = Math.max(\n margin,\n Math.min(anchorX + anchorWidth - menuWidth, windowWidth - menuWidth - margin)\n )\n\n const estimatedHeight = Math.max(1, itemCount) * 48 + margin * 2\n const below = anchorY + anchorHeight + 4\n const top = below + estimatedHeight <= windowHeight ? below : Math.max(margin, anchorY - estimatedHeight)\n\n return { left, top }\n}\n",
39625
+ "packages/react-native/src/platform-components.tsx": "import {\n type ComponentRef,\n type ReactElement,\n type ReactNode,\n type Ref,\n useEffect,\n useState\n} from 'react'\nimport {\n Animated,\n Easing,\n type HostInstance,\n Pressable,\n RefreshControl,\n type RefreshControlProps,\n type StyleProp,\n Text,\n type TextStyle,\n View,\n type ViewProps,\n type ViewStyle\n} from 'react-native'\n\nimport {\n type LumenNavigationBadge,\n type LumenRefreshIndicatorTone,\n type LumenResolvedNavigationBadge,\n resolveLumenNavigationAction,\n resolveLumenNavigationBadge,\n resolveLumenNavigationItemState,\n resolveLumenRefreshColors\n} from './platform-recipes.js'\nimport { LumenIcon, type LumenIconGraphic } from './primitives.js'\nimport { useLumenTheme } from './theme-context.js'\n\nexport interface LumenNavigationItem {\n badge?: LumenNavigationBadge\n disabled?: boolean\n icon?: LumenIconGraphic\n label: string\n selectedIcon?: LumenIconGraphic\n value: string\n}\n\nexport interface LumenNavigationBarProps extends Omit<ViewProps, 'children'> {\n accessibilityLabel?: string\n items: readonly LumenNavigationItem[]\n onReselect?: (value: string) => void\n onValueChange: (value: string) => void\n ref?: Ref<HostInstance>\n value: string\n}\n\ninterface LumenNavigationItemIconProps {\n badge: LumenResolvedNavigationBadge | undefined\n color: string\n graphic: LumenIconGraphic | undefined\n}\n\nconst LumenNavigationItemIcon = ({\n badge,\n color,\n graphic: Graphic\n}: LumenNavigationItemIconProps): ReactElement | null => {\n const theme = useLumenTheme()\n\n if (!Graphic && !badge) return null\n\n return (\n <View style={{ minHeight: 24, minWidth: 24 }}>\n {Graphic ?\n (\n <LumenIcon color={color} decorative icon={Graphic} size=\"md\" />\n ) :\n null}\n {badge ?\n (\n <View\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n style={{\n alignItems: 'center',\n backgroundColor: theme.colors.danger,\n borderRadius: 999,\n justifyContent: 'center',\n minHeight: badge.displayValue ? 16 : 8,\n minWidth: badge.displayValue ? 16 : 8,\n maxWidth: badge.displayValue ? 40 : 8,\n paddingHorizontal: badge.displayValue ? 4 : 0,\n position: 'absolute',\n right: -8,\n top: -5\n }}\n >\n {badge.displayValue ?\n (\n <Text\n numberOfLines={1}\n style={{\n color: theme.colors.onDanger,\n fontSize: 10,\n fontWeight: '700'\n }}\n >\n {badge.displayValue}\n </Text>\n ) :\n null}\n </View>\n ) :\n null}\n </View>\n )\n}\n\ninterface LumenNavigationDestinationProps {\n item: LumenNavigationItem\n onReselect: ((value: string) => void) | undefined\n onValueChange: (value: string) => void\n value: string\n}\n\nconst LumenNavigationDestination = ({\n item,\n onReselect,\n onValueChange,\n value\n}: LumenNavigationDestinationProps): ReactElement => {\n const theme = useLumenTheme()\n\n const itemState = resolveLumenNavigationItemState(\n theme.colors,\n item.value,\n value,\n Boolean(item.disabled),\n false\n )\n\n const selected = itemState.selected\n const badge = resolveLumenNavigationBadge(item.badge)\n const graphic = selected && item.selectedIcon ? item.selectedIcon : item.icon\n\n return (\n <Pressable\n accessibilityLabel={item.label}\n accessibilityRole=\"tab\"\n accessibilityState={{ disabled: item.disabled, selected }}\n accessibilityValue={badge ? { text: badge.accessibilityLabel } : undefined}\n disabled={item.disabled}\n onPress={() => {\n if (\n resolveLumenNavigationAction(\n item.value,\n value,\n Boolean(onReselect)\n ) === 'reselect'\n )\n onReselect?.(item.value)\n else onValueChange(item.value)\n }}\n style={({ pressed }) => ({\n alignItems: 'center',\n flex: 1,\n gap: theme.spacing.xs,\n justifyContent: 'center',\n minHeight: 64,\n opacity: resolveLumenNavigationItemState(\n theme.colors,\n item.value,\n value,\n Boolean(item.disabled),\n pressed\n ).opacity,\n paddingHorizontal: theme.spacing.xs,\n paddingVertical: theme.spacing.sm\n })}\n >\n <LumenNavigationItemIcon\n badge={badge}\n color={itemState.color}\n graphic={graphic}\n />\n <Text\n numberOfLines={1}\n style={{\n color: itemState.color,\n fontSize: theme.fontSizes.xs,\n fontWeight: String(\n selected ? theme.fontWeights.semibold : theme.fontWeights.medium\n ) as TextStyle['fontWeight']\n }}\n >\n {item.label}\n </Text>\n </Pressable>\n )\n}\n\n/**\n * A controlled, token-aware destination bar for app-level navigation.\n * Screen routing and navigation history remain application-owned.\n */\nexport const LumenNavigationBar = ({\n accessibilityLabel = 'Primary navigation',\n items,\n onReselect,\n onValueChange,\n ref,\n style,\n value,\n ...props\n}: LumenNavigationBarProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <View\n ref={ref}\n {...props}\n accessibilityLabel={accessibilityLabel}\n accessibilityRole=\"tablist\"\n style={[\n {\n backgroundColor: theme.colors.surface,\n borderColor: theme.colors.line,\n borderTopWidth: 1,\n flexDirection: 'row',\n minHeight: 64,\n paddingHorizontal: theme.spacing.sm\n },\n style\n ]}\n >\n {items.map(item => (\n <LumenNavigationDestination\n item={item}\n key={item.value}\n onReselect={onReselect}\n onValueChange={onValueChange}\n value={value}\n />\n ))}\n </View>\n )\n}\n\nexport interface LumenNavigationAccessoryProps extends Omit<ViewProps, 'children'> {\n children: ReactNode\n}\n\n/** A compact token-aware status or action surface placed above bottom navigation. */\nexport const LumenNavigationAccessory = ({\n children,\n style,\n ...props\n}: LumenNavigationAccessoryProps): ReactElement => {\n const theme = useLumenTheme()\n\n return (\n <View\n {...props}\n style={[\n {\n alignItems: 'center',\n backgroundColor: theme.colors.surface,\n borderColor: theme.colors.line,\n borderTopWidth: 1,\n flexDirection: 'row',\n height: 48,\n paddingHorizontal: theme.spacing.lg,\n paddingVertical: theme.spacing.sm\n },\n style\n ]}\n >\n {children}\n </View>\n )\n}\n\nexport interface LumenCollapsibleNavigationBarProps extends LumenNavigationBarProps {\n accessory?: ReactNode\n containerStyle?: StyleProp<ViewStyle>\n visible: boolean\n}\n\n/** An animated, layout-collapsing wrapper around LumenNavigationBar. */\nexport const LumenCollapsibleNavigationBar = ({\n accessory,\n containerStyle,\n visible,\n ...props\n}: LumenCollapsibleNavigationBarProps): ReactElement => {\n const theme = useLumenTheme()\n const [progress] = useState(() => new Animated.Value(visible ? 1 : 0))\n\n useEffect(() => {\n const animation = Animated.timing(progress, {\n duration: theme.durations.standard,\n easing: Easing.bezier(0.32, 0.72, 0, 1),\n toValue: visible ? 1 : 0,\n useNativeDriver: false\n })\n\n animation.start()\n\n return () => {\n animation.stop()\n }\n }, [progress, theme.durations.standard, visible])\n\n return (\n <Animated.View\n accessibilityElementsHidden={!visible}\n importantForAccessibility={visible ? 'auto' : 'no-hide-descendants'}\n pointerEvents={visible ? 'auto' : 'none'}\n style={[\n containerStyle,\n {\n height: progress.interpolate({\n inputRange: [0, 1],\n outputRange: [0, accessory ? 112 : 64]\n }),\n opacity: progress,\n overflow: 'hidden'\n }\n ]}\n >\n {accessory ?\n <LumenNavigationAccessory>{accessory}</LumenNavigationAccessory> :\n null}\n <LumenNavigationBar {...props} />\n </Animated.View>\n )\n}\n\nexport interface LumenRefreshControlProps extends Omit<\n RefreshControlProps,\n | 'accessibilityLabel' |\n 'colors' |\n 'onRefresh' |\n 'progressBackgroundColor' |\n 'tintColor' |\n '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",
39626
+ "packages/react-native/src/platform-hooks.ts": "import { useCallback, useRef, useState } from 'react'\nimport type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native'\n\nimport {\n createLumenNavigationVisibilityState,\n resolveLumenNavigationVisibility\n} from './platform-recipes.js'\n\nexport interface LumenNavigationBarVisibilityOptions {\n initiallyVisible?: boolean\n threshold?: number\n}\n\nexport interface LumenNavigationBarVisibilityController {\n hide: () => void\n onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void\n show: () => void\n visible: boolean\n}\n\n/**\n * Produces scroll-driven visibility for bottom navigation without owning the application's list.\n * Pass `onScroll` to a native scroll container and `visible` to LumenCollapsibleNavigationBar.\n */\nexport const useLumenNavigationBarVisibility = ({\n initiallyVisible = true,\n threshold = 16\n}: LumenNavigationBarVisibilityOptions = {}): LumenNavigationBarVisibilityController => {\n const navigationStateRef = useRef(\n createLumenNavigationVisibilityState(initiallyVisible)\n )\n\n const [visible, setVisible] = useState(initiallyVisible)\n\n const updateVisibility = useCallback((nextVisible: boolean) => {\n navigationStateRef.current = {\n ...navigationStateRef.current,\n accumulatedDelta: 0,\n visible: nextVisible\n }\n\n setVisible(nextVisible)\n }, [])\n\n const onScroll = useCallback(\n (event: NativeSyntheticEvent<NativeScrollEvent>) => {\n const nextState = resolveLumenNavigationVisibility(\n navigationStateRef.current,\n event.nativeEvent.contentOffset.y,\n threshold\n )\n\n navigationStateRef.current = nextState\n\n setVisible(current => current === nextState.visible ? current : nextState.visible)\n },\n [threshold]\n )\n\n const hide = useCallback(() => {\n updateVisibility(false)\n }, [updateVisibility])\n\n const show = useCallback(() => {\n updateVisibility(true)\n }, [updateVisibility])\n\n return { hide, onScroll, show, visible }\n}\n",
39627
+ "packages/react-native/src/platform-recipes.test.ts": "import { describe, expect, test } from 'vitest'\n\nimport {\n createLumenNavigationVisibilityState,\n resolveLumenNavigationAction,\n resolveLumenNavigationBadge,\n resolveLumenNavigationItemState,\n resolveLumenNavigationVisibility,\n resolveLumenRefreshColors\n} 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(\n resolveLumenRefreshColors(lumenDarkTheme.colors, 'accent').indicator\n ).toBe(lumenDarkTheme.colors.accent)\n expect(\n resolveLumenRefreshColors(lumenDarkTheme.colors, 'neutral').indicator\n ).toBe(lumenDarkTheme.colors.inkSoft)\n })\n\n test('resolves selected and disabled destination states', () => {\n expect(\n resolveLumenNavigationItemState(\n lumenLightTheme.colors,\n 'home',\n 'home',\n false,\n false\n )\n ).toEqual({\n color: lumenLightTheme.colors.brand,\n opacity: 1,\n selected: true\n })\n expect(\n resolveLumenNavigationItemState(\n lumenDarkTheme.colors,\n 'search',\n 'home',\n true,\n true\n )\n ).toEqual({\n color: lumenDarkTheme.colors.inkMuted,\n opacity: 0.52,\n selected: false\n })\n })\n\n test('resolves dot, text, and capped count navigation badges', () => {\n expect(resolveLumenNavigationBadge(true)).toEqual({\n accessibilityLabel: 'New activity'\n })\n expect(resolveLumenNavigationBadge('Live')).toEqual({\n accessibilityLabel: 'Live',\n displayValue: 'Live'\n })\n expect(resolveLumenNavigationBadge(128)).toEqual({\n accessibilityLabel: '128 new items',\n displayValue: '99+'\n })\n expect(resolveLumenNavigationBadge(Number.NaN)).toBeUndefined()\n })\n\n test('distinguishes destination selection from re-selection', () => {\n expect(resolveLumenNavigationAction('home', 'search', true)).toBe('select')\n expect(resolveLumenNavigationAction('home', 'home', false)).toBe('select')\n expect(resolveLumenNavigationAction('home', 'home', true)).toBe('reselect')\n })\n\n test('hides and reveals navigation after deliberate directional travel', () => {\n let state = createLumenNavigationVisibilityState()\n\n state = resolveLumenNavigationVisibility(state, 8)\n expect(state.visible).toBe(true)\n state = resolveLumenNavigationVisibility(state, 16)\n expect(state.visible).toBe(false)\n\n state = resolveLumenNavigationVisibility(state, 12)\n state = resolveLumenNavigationVisibility(state, 17)\n state = resolveLumenNavigationVisibility(state, 1)\n expect(state.visible).toBe(true)\n })\n\n test('reveals navigation at the top and ignores invalid offsets', () => {\n const hidden = createLumenNavigationVisibilityState(false)\n const invalid = resolveLumenNavigationVisibility(hidden, Number.NaN)\n\n expect(invalid).toBe(hidden)\n expect(resolveLumenNavigationVisibility(hidden, -12)).toEqual({\n accumulatedDelta: 0,\n offset: 0,\n visible: true\n })\n })\n})\n",
39628
+ "packages/react-native/src/platform-recipes.ts": "import type { LumenTheme } from './theme.js'\n\nexport type LumenRefreshIndicatorTone = 'accent' | 'brand' | 'neutral'\nexport type LumenNavigationBadge = boolean | number | string\n\nexport interface LumenResolvedNavigationBadge {\n accessibilityLabel: string\n displayValue?: string\n}\n\nexport type LumenNavigationAction = 'reselect' | 'select'\n\nexport const resolveLumenNavigationAction = (\n itemValue: string,\n selectedValue: string,\n hasReselectHandler: boolean\n): LumenNavigationAction => itemValue === selectedValue && hasReselectHandler ? 'reselect' : 'select'\n\nexport const resolveLumenNavigationBadge = (\n badge: LumenNavigationBadge | undefined\n): LumenResolvedNavigationBadge | undefined => {\n if (badge === undefined || badge === false) return undefined\n\n if (badge === true) return { accessibilityLabel: 'New activity' }\n\n if (typeof badge === 'number') {\n if (!Number.isFinite(badge)) return undefined\n\n const count = Math.max(0, Math.trunc(badge))\n\n return {\n accessibilityLabel: `${count} new items`,\n displayValue: count > 99 ? '99+' : String(count)\n }\n }\n\n const displayValue = badge.trim()\n\n if (displayValue.length === 0) return undefined\n\n return { accessibilityLabel: displayValue, displayValue }\n}\n\nexport interface LumenNavigationItemState {\n color: string\n opacity: number\n selected: boolean\n}\n\nexport interface LumenNavigationVisibilityState {\n accumulatedDelta: number\n offset: number\n visible: boolean\n}\n\nexport const createLumenNavigationVisibilityState = (\n visible = true\n): LumenNavigationVisibilityState => ({\n accumulatedDelta: 0,\n offset: 0,\n visible\n})\n\nconst resolveScrollThreshold = (threshold: number): number => {\n if (!Number.isFinite(threshold) || threshold <= 0) return 16\n\n return threshold\n}\n\nconst accumulateScrollDelta = (\n accumulatedDelta: number,\n delta: number\n): number => {\n const continuingDirection = Math.sign(accumulatedDelta) === Math.sign(delta)\n\n return (continuingDirection ? accumulatedDelta : 0) + delta\n}\n\n/**\n * Resolves navigation visibility from a native vertical scroll offset.\n * Positive travel hides the bar, reverse travel reveals it, and reaching the top always reveals it.\n */\nexport const resolveLumenNavigationVisibility = (\n state: LumenNavigationVisibilityState,\n nextOffset: number,\n threshold = 16\n): LumenNavigationVisibilityState => {\n if (!Number.isFinite(nextOffset)) return state\n\n const safeOffset = Math.max(0, nextOffset)\n\n if (safeOffset === 0) return createLumenNavigationVisibilityState(true)\n\n const safeThreshold = resolveScrollThreshold(threshold)\n const delta = safeOffset - state.offset\n\n if (delta === 0) return { ...state, offset: safeOffset }\n\n const accumulatedDelta = accumulateScrollDelta(state.accumulatedDelta, delta)\n\n if (accumulatedDelta >= safeThreshold) {\n return { accumulatedDelta: 0, offset: safeOffset, visible: false }\n }\n\n if (accumulatedDelta <= -safeThreshold) {\n return { accumulatedDelta: 0, offset: safeOffset, visible: true }\n }\n\n return { accumulatedDelta, offset: safeOffset, visible: state.visible }\n}\n\nexport const resolveLumenNavigationItemState = (\n colors: LumenTheme['colors'],\n itemValue: string,\n selectedValue: string,\n disabled: boolean,\n pressed: boolean\n): LumenNavigationItemState => {\n const selected = itemValue === selectedValue\n let opacity = 1\n\n if (disabled) opacity = 0.52\n else if (pressed) opacity = 0.72\n\n return {\n color: selected ? colors.brand : colors.inkMuted,\n opacity,\n selected\n }\n}\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",
37105
39629
  "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",
37106
39630
  "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",
37107
39631
  "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",
@@ -37120,18 +39644,21 @@
37120
39644
  "packages/react-native/src/tokens.generated.ts": "/* This file is generated by scripts/generate-platform-tokens.mjs. */\n\nexport const lumenColorTokens = {\n light: {\n canvas: \"#FFFFFF\",\n surface: \"#FFFFFF\",\n surfaceMuted: \"#F3F4F6\",\n surfaceStrong: \"#E5E7EB\",\n line: \"#D7DAE0\",\n ink: \"#0F1729\",\n inkSoft: \"#454F5F\",\n inkMuted: \"#6C7889\",\n brand: \"#2463EB\",\n brandSolid: \"#1452D7\",\n brandSoft: \"#F0F5FF\",\n onBrand: \"#FFFFFF\",\n accent: \"#16A286\",\n success: \"#1B9D4A\",\n warning: \"#F59F0A\",\n danger: \"#EF4343\",\n onDanger: \"#000000\"\n },\n dark: {\n canvas: \"#101219\",\n surface: \"#171A21\",\n surfaceMuted: \"#20242C\",\n surfaceStrong: \"#303540\",\n line: \"#393F4C\",\n ink: \"#F3F5F7\",\n inkSoft: \"#BCC3CD\",\n inkMuted: \"#949CA8\",\n brand: \"#29B4F5\",\n brandSolid: \"#087AAF\",\n brandSoft: \"#07354B\",\n onBrand: \"#FFFFFF\",\n accent: \"#1AC19A\",\n success: \"#24C25E\",\n warning: \"#F6A823\",\n danger: \"#F15B5B\",\n onDanger: \"#101219\"\n }\n} as const\n\nexport const lumenCssColorTokens = {\n light: {\n canvas: \"0 0% 100%\",\n surface: \"0 0% 100%\",\n surfaceMuted: \"220 14% 96%\",\n surfaceStrong: \"220 13% 91%\",\n line: \"220 13% 86%\",\n ink: \"222 47% 11%\",\n inkSoft: \"215 16% 32%\",\n inkMuted: \"215 12% 48%\",\n brand: \"221 83% 53%\",\n brandSolid: \"221 83% 46%\",\n brandSoft: \"218 100% 97%\",\n onBrand: \"0 0% 100%\",\n accent: \"168 76% 36%\",\n success: \"142 71% 36%\",\n warning: \"38 92% 50%\",\n danger: \"0 84% 60%\",\n onDanger: \"0 0% 0%\"\n },\n dark: {\n canvas: \"224 24% 8%\",\n surface: \"224 18% 11%\",\n surfaceMuted: \"223 16% 15%\",\n surfaceStrong: \"222 14% 22%\",\n line: \"222 14% 26%\",\n ink: \"210 20% 96%\",\n inkSoft: \"214 14% 77%\",\n inkMuted: \"216 10% 62%\",\n brand: \"199 91% 56%\",\n brandSolid: \"199 91% 36%\",\n brandSoft: \"199 84% 16%\",\n onBrand: \"0 0% 100%\",\n accent: \"166 76% 43%\",\n success: \"142 69% 45%\",\n warning: \"38 92% 55%\",\n danger: \"0 84% 65%\",\n onDanger: \"224 24% 8%\"\n }\n} as const\n\nexport const lumenSpacing = {\n zero: 0,\n xs: 4,\n sm: 8,\n md: 12,\n lg: 16,\n xl: 24,\n \"2xl\": 32,\n \"3xl\": 48\n} as const\n\nexport const lumenRadii = {\n sm: 6,\n md: 10,\n lg: 12,\n full: 9999\n} as const\n\nexport const lumenFontFamilies = {\n sans: [\"Montserrat\",\"Avenir Next\",\"Segoe UI\",\"sans-serif\"]\n} as const\n\nexport const lumenFontSizes = {\n xs: 12,\n sm: 14,\n md: 16,\n lg: 18,\n xl: 20,\n \"2xl\": 24\n} as const\n\nexport const lumenFontWeights = {\n regular: 400,\n medium: 500,\n semibold: 600,\n bold: 700\n} as const\n\nexport const lumenDurations = {\n fast: 120,\n standard: 160,\n slow: 300\n} as const\n\nexport const lumenEasings = {\n standard: [0.32,0.72,0,1],\n emphasized: [0.22,1,0.36,1]\n} as const\n\nexport const lumenElevation = {\n resting: 1,\n raised: 3,\n overlay: 6\n} as const\n\nexport type LumenColorScheme = keyof typeof lumenColorTokens\nexport type LumenSemanticColor = keyof typeof lumenColorTokens.light\n"
37121
39645
  },
37122
39646
  "swiftui": {
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",
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",
39647
+ "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\n#if !os(watchOS)\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#endif\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",
39648
+ "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\n/// A token-aware URL action that preserves SwiftUI's native link-opening behavior.\npublic struct LumenLink<Label: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let destination: URL\n private let label: Label\n private let showsExternalIndicator: Bool\n\n public init(\n destination: URL,\n showsExternalIndicator: Bool = false,\n @ViewBuilder label: () -> Label\n ) {\n self.destination = destination\n self.label = label()\n self.showsExternalIndicator = showsExternalIndicator\n }\n\n public var body: some View {\n Link(destination: destination) {\n HStack(spacing: LumenSpacing.xs) {\n label\n\n if showsExternalIndicator {\n Image(systemName: \"arrow.up.right\")\n .font(.caption2.weight(.semibold))\n .accessibilityHidden(true)\n }\n }\n }\n .foregroundStyle(theme.colors.brandSolid)\n .underline()\n }\n}\n\npublic extension LumenLink where Label == Text {\n init(\n _ title: LocalizedStringKey,\n destination: URL,\n showsExternalIndicator: Bool = false\n ) {\n self.init(destination: destination, showsExternalIndicator: showsExternalIndicator) {\n Text(title)\n }\n }\n}\n#endif\n",
39649
+ "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 enum LumenGraphicSize: Sendable {\n case lg\n case md\n case sm\n\n var dimension: CGFloat {\n switch self {\n case .lg: 320\n case .md: 240\n case .sm: 160\n }\n }\n}\n\npublic enum LumenGraphicTone: Sendable {\n case accent\n case brand\n case neutral\n}\n\npublic enum LumenGraphicVariant: Sendable {\n case glow\n case grid\n case orbit\n}\n\npublic struct LumenGraphic<Content: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let content: Content\n private let label: String?\n private let size: LumenGraphicSize\n private let tone: LumenGraphicTone\n private let variant: LumenGraphicVariant\n\n public init(\n label: String? = nil,\n size: LumenGraphicSize = .md,\n tone: LumenGraphicTone = .brand,\n variant: LumenGraphicVariant = .orbit,\n @ViewBuilder content: () -> Content\n ) {\n self.label = label\n self.size = size\n self.tone = tone\n self.variant = variant\n self.content = content()\n }\n\n public var body: some View {\n ZStack {\n graphicLayer\n content\n }\n .frame(width: size.dimension, height: size.dimension)\n .clipped()\n .accessibilityElement(children: label == nil ? .ignore : .combine)\n .accessibilityHidden(label == nil)\n .accessibilityLabel(Text(label ?? \"\"))\n }\n\n private var color: Color {\n switch tone {\n case .accent: theme.colors.accent\n case .brand: theme.colors.brand\n case .neutral: theme.colors.inkMuted\n }\n }\n\n @ViewBuilder\n private var graphicLayer: some View {\n switch variant {\n case .glow:\n Circle()\n .fill(\n RadialGradient(\n colors: [color.opacity(0.3), color.opacity(0)],\n center: .center,\n startRadius: 0,\n endRadius: size.dimension * 0.42\n )\n )\n .padding(size.dimension * 0.08)\n case .grid:\n Canvas { context, canvasSize in\n var path = Path()\n for index in 0...6 {\n let position = CGFloat(index) * canvasSize.width / 6\n path.move(to: CGPoint(x: position, y: 0))\n path.addLine(to: CGPoint(x: position, y: canvasSize.height))\n path.move(to: CGPoint(x: 0, y: position))\n path.addLine(to: CGPoint(x: canvasSize.width, y: position))\n }\n context.stroke(path, with: .color(color.opacity(0.18)), lineWidth: 1)\n }\n .padding(size.dimension * 0.12)\n case .orbit:\n Canvas { context, canvasSize in\n let bounds = CGRect(origin: .zero, size: canvasSize)\n for ratio in [0.9, 0.62, 0.34] {\n let inset = canvasSize.width * (1 - ratio) / 2\n context.stroke(\n Path(ellipseIn: bounds.insetBy(dx: inset, dy: inset)),\n with: .color(color.opacity(0.24)),\n lineWidth: 1\n )\n }\n var axes = Path()\n axes.move(to: CGPoint(x: canvasSize.width * 0.14, y: canvasSize.height / 2))\n axes.addLine(to: CGPoint(x: canvasSize.width * 0.86, y: canvasSize.height / 2))\n axes.move(to: CGPoint(x: canvasSize.width / 2, y: canvasSize.height * 0.14))\n axes.addLine(to: CGPoint(x: canvasSize.width / 2, y: canvasSize.height * 0.86))\n context.stroke(axes, with: .color(color.opacity(0.18)), lineWidth: 1)\n }\n }\n }\n}\n\npublic enum LumenBackdropIntensity: Sendable {\n case medium\n case strong\n case subtle\n\n var opacity: Double {\n switch self {\n case .medium: 0.68\n case .strong: 1\n case .subtle: 0.4\n }\n }\n}\n\npublic enum LumenBackdropTone: Sendable {\n case accent\n case brand\n case neutral\n}\n\npublic enum LumenBackdropVariant: Sendable {\n case aurora\n case dots\n case grid\n case rays\n}\n\npublic struct LumenBackdrop<Content: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let content: Content\n private let intensity: LumenBackdropIntensity\n private let tone: LumenBackdropTone\n private let variant: LumenBackdropVariant\n\n public init(\n intensity: LumenBackdropIntensity = .medium,\n tone: LumenBackdropTone = .brand,\n variant: LumenBackdropVariant = .aurora,\n @ViewBuilder content: () -> Content\n ) {\n self.intensity = intensity\n self.tone = tone\n self.variant = variant\n self.content = content()\n }\n\n public var body: some View {\n ZStack {\n backdropLayer\n .opacity(intensity.opacity)\n .accessibilityHidden(true)\n content\n }\n .clipped()\n }\n\n private var color: Color {\n switch tone {\n case .accent: theme.colors.accent\n case .brand: theme.colors.brand\n case .neutral: theme.colors.inkMuted\n }\n }\n\n private var secondaryColor: Color {\n tone == .neutral ? theme.colors.surfaceStrong : theme.colors.accent\n }\n\n @ViewBuilder\n private var backdropLayer: some View {\n switch variant {\n case .aurora:\n GeometryReader { proxy in\n Circle()\n .fill(color.opacity(0.22))\n .frame(width: proxy.size.width * 0.72)\n .offset(x: -proxy.size.width * 0.2, y: -proxy.size.height * 0.46)\n Circle()\n .fill(secondaryColor.opacity(0.18))\n .frame(width: proxy.size.width * 0.62)\n .offset(x: proxy.size.width * 0.58, y: proxy.size.height * 0.44)\n }\n case .dots:\n Canvas { context, canvasSize in\n for row in 0..<6 {\n for column in 0..<8 {\n let point = CGPoint(\n x: (CGFloat(column) + 0.5) * canvasSize.width / 8,\n y: (CGFloat(row) + 0.5) * canvasSize.height / 6\n )\n context.fill(\n Path(ellipseIn: CGRect(x: point.x - 1.5, y: point.y - 1.5, width: 3, height: 3)),\n with: .color(color.opacity(0.28))\n )\n }\n }\n }\n case .grid:\n Canvas { context, canvasSize in\n var path = Path()\n for index in 0...6 {\n let x = CGFloat(index) * canvasSize.width / 6\n let y = CGFloat(index) * canvasSize.height / 6\n path.move(to: CGPoint(x: x, y: 0))\n path.addLine(to: CGPoint(x: x, y: canvasSize.height))\n path.move(to: CGPoint(x: 0, y: y))\n path.addLine(to: CGPoint(x: canvasSize.width, y: y))\n }\n context.stroke(path, with: .color(color.opacity(0.18)), lineWidth: 1)\n }\n case .rays:\n Canvas { context, canvasSize in\n var path = Path()\n let center = CGPoint(x: canvasSize.width / 2, y: canvasSize.height / 2)\n let radius = sqrt(\n canvasSize.width * canvasSize.width + canvasSize.height * canvasSize.height\n )\n for index in 0..<12 {\n let angle = CGFloat(index) * .pi / 6\n path.move(to: center)\n path.addLine(to: CGPoint(\n x: center.x + cos(angle) * radius,\n y: center.y + sin(angle) * radius\n ))\n }\n context.stroke(path, with: .color(color.opacity(0.2)), lineWidth: 1)\n }\n }\n }\n}\n\npublic enum LumenIllustrationSize: Sendable {\n case lg\n case md\n case sm\n\n var dimension: CGFloat {\n switch self {\n case .lg: 176\n case .md: 128\n case .sm: 96\n }\n }\n}\n\npublic enum LumenIllustrationTone: Sendable {\n case accent\n case auto\n case brand\n case neutral\n}\n\npublic enum LumenIllustrationVariant: Sendable {\n case empty\n case error\n case offline\n case success\n}\n\npublic struct LumenIllustration: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let label: String?\n private let size: LumenIllustrationSize\n private let tone: LumenIllustrationTone\n private let variant: LumenIllustrationVariant\n\n public init(\n variant: LumenIllustrationVariant = .empty,\n tone: LumenIllustrationTone = .auto,\n size: LumenIllustrationSize = .md,\n label: String? = nil\n ) {\n self.variant = variant\n self.tone = tone\n self.size = size\n self.label = label\n }\n\n public var body: some View {\n Canvas { context, canvasSize in\n let scale = min(canvasSize.width, canvasSize.height) / 120\n let lineWidth = max(2, 2.5 * scale)\n let bounds = CGRect(origin: .zero, size: canvasSize)\n context.fill(\n Path(ellipseIn: bounds.insetBy(dx: 11 * scale, dy: 11 * scale)),\n with: .color(color.opacity(0.1))\n )\n drawArtwork(context: &context, scale: scale, lineWidth: lineWidth)\n }\n .frame(width: size.dimension, height: size.dimension)\n .accessibilityElement(children: .ignore)\n .accessibilityHidden(label == nil)\n .accessibilityLabel(Text(label ?? \"\"))\n }\n\n private var color: Color {\n switch tone {\n case .accent: theme.colors.accent\n case .brand: theme.colors.brand\n case .neutral: theme.colors.inkMuted\n case .auto:\n switch variant {\n case .empty: theme.colors.brand\n case .error: theme.colors.danger\n case .offline: theme.colors.inkMuted\n case .success: theme.colors.success\n }\n }\n }\n\n private func drawArtwork(context: inout GraphicsContext, scale: CGFloat, lineWidth: CGFloat) {\n switch variant {\n case .empty:\n var tray = Path(roundedRect: CGRect(x: 29, y: 43, width: 62, height: 40).scaled(by: scale), cornerRadius: 8 * scale)\n tray.move(to: CGPoint(x: 40 * scale, y: 60 * scale))\n tray.addLine(to: CGPoint(x: 80 * scale, y: 60 * scale))\n context.stroke(tray, with: .color(color), lineWidth: lineWidth)\n case .error, .success:\n context.stroke(\n Path(ellipseIn: CGRect(x: 30, y: 30, width: 60, height: 60).scaled(by: scale)),\n with: .color(color),\n lineWidth: lineWidth\n )\n var symbol = Path()\n if variant == .success {\n symbol.move(to: CGPoint(x: 44 * scale, y: 61 * scale))\n symbol.addLine(to: CGPoint(x: 55 * scale, y: 72 * scale))\n symbol.addLine(to: CGPoint(x: 77 * scale, y: 48 * scale))\n } else {\n symbol.move(to: CGPoint(x: 47 * scale, y: 47 * scale))\n symbol.addLine(to: CGPoint(x: 73 * scale, y: 73 * scale))\n symbol.move(to: CGPoint(x: 73 * scale, y: 47 * scale))\n symbol.addLine(to: CGPoint(x: 47 * scale, y: 73 * scale))\n }\n context.stroke(symbol, with: .color(color), style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))\n case .offline:\n var cloud = Path(roundedRect: CGRect(x: 24, y: 45, width: 72, height: 35).scaled(by: scale), cornerRadius: 18 * scale)\n cloud.move(to: CGPoint(x: 30 * scale, y: 30 * scale))\n cloud.addLine(to: CGPoint(x: 90 * scale, y: 90 * scale))\n context.stroke(cloud, with: .color(color), style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))\n }\n }\n}\n\nprivate extension CGRect {\n func scaled(by scale: CGFloat) -> CGRect {\n CGRect(x: origin.x * scale, y: origin.y * scale, width: width * scale, height: height * scale)\n }\n}\n\npublic struct LumenDisclosure<Content: View>: View {\n @Environment(\\.lumenTheme) private var theme\n @State private var internalIsExpanded: Bool\n\n private let content: Content\n private let customLabel: AnyView?\n private let description: String?\n private let externalIsExpanded: Binding<Bool>?\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.content = content()\n self.customLabel = nil\n self.description = description\n self.externalIsExpanded = isExpanded\n _internalIsExpanded = State(initialValue: isExpanded.wrappedValue)\n self.title = title\n }\n\n public init(\n _ title: String,\n initiallyExpanded: Bool = false,\n description: String? = nil,\n @ViewBuilder content: () -> Content\n ) {\n self.content = content()\n self.customLabel = nil\n self.description = description\n self.externalIsExpanded = nil\n _internalIsExpanded = State(initialValue: initiallyExpanded)\n self.title = title\n }\n\n public init<Label: View>(\n isExpanded: Binding<Bool>,\n @ViewBuilder label: () -> Label,\n @ViewBuilder content: () -> Content\n ) {\n self.content = content()\n self.customLabel = AnyView(label())\n self.description = nil\n self.externalIsExpanded = isExpanded\n _internalIsExpanded = State(initialValue: isExpanded.wrappedValue)\n self.title = nil\n }\n\n public init<Label: View>(\n initiallyExpanded: Bool = false,\n @ViewBuilder label: () -> Label,\n @ViewBuilder content: () -> Content\n ) {\n self.content = content()\n self.customLabel = AnyView(label())\n self.description = nil\n self.externalIsExpanded = nil\n _internalIsExpanded = State(initialValue: initiallyExpanded)\n self.title = nil\n }\n\n public var body: some View {\n DisclosureGroup(isExpanded: expansionBinding) {\n content\n .frame(maxWidth: .infinity, alignment: .leading)\n .padding(.top, LumenSpacing.md)\n } label: {\n disclosureLabel\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 private var expansionBinding: Binding<Bool> {\n externalIsExpanded ?? Binding(\n get: { internalIsExpanded },\n set: { internalIsExpanded = $0 }\n )\n }\n\n @ViewBuilder\n private var disclosureLabel: some View {\n if let customLabel {\n customLabel\n } else if let title {\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 }\n}\n#endif\n",
37126
39650
  "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",
37127
39651
  "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",
37128
39652
  "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",
39653
+ "packages/swift/Sources/LumenUI/NavigationComponents.swift": "import SwiftUI\n\n/// Cross-version policy for the native SwiftUI tab bar on iPhone.\npublic enum LumenTabBarMinimizeBehavior: Sendable {\n /// Let SwiftUI choose the platform-appropriate behavior.\n case automatic\n /// Keep the tab bar at its regular size.\n case never\n /// Minimize while scrolling toward later content and expand in the opposite direction.\n case onScrollDown\n /// Minimize while scrolling toward earlier content and expand in the opposite direction.\n case onScrollUp\n}\n\n/// Content that adapts to the expanded or inline placement of an iOS tab-view bottom accessory.\npublic struct LumenTabAccessory<Expanded: View, Compact: View>: View {\n private let compact: Compact\n private let expanded: Expanded\n\n public init(\n @ViewBuilder expanded: () -> Expanded,\n @ViewBuilder compact: () -> Compact\n ) {\n self.expanded = expanded()\n self.compact = compact()\n }\n\n public var body: some View {\n #if os(iOS) && compiler(>=6.2)\n if #available(iOS 26.0, *) {\n LumenAdaptiveTabAccessory(expanded: expanded, compact: compact)\n } else {\n expanded\n }\n #else\n expanded\n #endif\n }\n}\n\npublic extension View {\n /// Applies SwiftUI's native tab-bar minimization on iOS 26 and later.\n ///\n /// Earlier iOS versions and other Apple platforms retain their system-default tab behavior.\n @ViewBuilder\n func lumenTabBarMinimizeBehavior(\n _ behavior: LumenTabBarMinimizeBehavior\n ) -> some View {\n #if os(iOS) && compiler(>=6.2)\n if #available(iOS 26.0, *) {\n tabBarMinimizeBehavior(behavior.swiftUIValue)\n } else {\n self\n }\n #else\n self\n #endif\n }\n\n /// Adds an adaptive bottom accessory to a native `TabView`.\n ///\n /// iOS 26 uses the native tab accessory placement. Earlier versions present the same content\n /// in a token-aware safe-area inset above the system tab bar.\n @ViewBuilder\n func lumenTabViewBottomAccessory<Accessory: View>(\n isEnabled: Bool = true,\n @ViewBuilder content: () -> Accessory\n ) -> some View {\n #if os(iOS)\n #if compiler(>=6.2)\n if #available(iOS 26.0, *) {\n if isEnabled {\n tabViewBottomAccessory {\n content()\n }\n } else {\n self\n }\n } else {\n safeAreaInset(edge: .bottom, spacing: 0) {\n if isEnabled {\n LumenLegacyTabAccessorySurface {\n content()\n }\n }\n }\n }\n #else\n safeAreaInset(edge: .bottom, spacing: 0) {\n if isEnabled {\n LumenLegacyTabAccessorySurface {\n content()\n }\n }\n }\n #endif\n #else\n self\n #endif\n }\n}\n\n#if os(iOS) && compiler(>=6.2)\n@available(iOS 26.0, *)\nprivate extension LumenTabBarMinimizeBehavior {\n var swiftUIValue: TabBarMinimizeBehavior {\n switch self {\n case .automatic: .automatic\n case .never: .never\n case .onScrollDown: .onScrollDown\n case .onScrollUp: .onScrollUp\n }\n }\n}\n\n@available(iOS 26.0, *)\nprivate struct LumenAdaptiveTabAccessory<Expanded: View, Compact: View>: View {\n @Environment(\\.tabViewBottomAccessoryPlacement) private var placement\n\n let expanded: Expanded\n let compact: Compact\n\n var body: some View {\n if placement == .inline {\n compact\n } else {\n expanded\n }\n }\n}\n#endif\n\n#if os(iOS)\nprivate struct LumenLegacyTabAccessorySurface<Content: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\n let content: Content\n\n init(@ViewBuilder content: () -> Content) {\n self.content = content()\n }\n\n var body: some View {\n content\n .frame(maxWidth: .infinity)\n .padding(.horizontal, LumenSpacing.lg)\n .padding(.vertical, LumenSpacing.sm)\n .background(theme.colors.surface)\n .overlay(alignment: .top) {\n Rectangle()\n .fill(theme.colors.line)\n .frame(height: 1)\n .accessibilityHidden(true)\n }\n }\n}\n#endif\n\npublic struct LumenNavigationItem<Value: Hashable>: Identifiable {\n public let badge: LumenNavigationBadge?\n public let disabled: Bool\n public let label: LocalizedStringKey\n public let selectedSystemName: String?\n public let systemName: String?\n public let value: Value\n\n public var id: Value { value }\n\n public init(\n _ label: LocalizedStringKey,\n value: Value,\n systemName: String? = nil,\n selectedSystemName: String? = nil,\n badge: LumenNavigationBadge? = nil,\n disabled: Bool = false\n ) {\n self.label = label\n self.value = value\n self.systemName = systemName\n self.selectedSystemName = selectedSystemName\n self.badge = badge\n self.disabled = disabled\n }\n}\n\n/// Compact destination status presented as a dot or short text value.\npublic enum LumenNavigationBadge: Equatable, Sendable {\n case dot(accessibilityLabel: String = \"New activity\")\n case text(String, accessibilityLabel: String? = nil)\n\n /// Produces a compact count capped at `99+` while retaining the full count for accessibility.\n public static func count(_ value: Int) -> LumenNavigationBadge {\n let safeValue = max(0, value)\n return .text(\n safeValue > 99 ? \"99+\" : String(safeValue),\n accessibilityLabel: \"\\(safeValue) new items\"\n )\n }\n\n internal var displayText: String? {\n switch self {\n case .dot: nil\n case let .text(value, _): value\n }\n }\n\n internal var spokenLabel: String {\n switch self {\n case let .dot(accessibilityLabel): accessibilityLabel\n case let .text(value, accessibilityLabel): accessibilityLabel ?? value\n }\n }\n}\n\ninternal func dispatchLumenNavigationSelection<Value: Equatable>(\n itemValue: Value,\n selectedValue: Value,\n onValueChange: (Value) -> Void,\n onReselect: ((Value) -> Void)?\n) {\n if itemValue == selectedValue, let onReselect {\n onReselect(itemValue)\n } else {\n onValueChange(itemValue)\n }\n}\n\n/// A controlled destination bar that leaves navigation paths and presented content app-owned.\npublic struct LumenNavigationBar<Selection: Hashable>: View {\n @Binding private var selection: Selection\n @Environment(\\.lumenTheme) private var theme\n\n private let items: [LumenNavigationItem<Selection>]\n private let label: LocalizedStringKey\n private let onReselect: ((Selection) -> Void)?\n\n public init(\n _ label: LocalizedStringKey = \"Primary navigation\",\n selection: Binding<Selection>,\n items: [LumenNavigationItem<Selection>],\n onReselect: ((Selection) -> Void)? = nil\n ) {\n self.label = label\n _selection = selection\n self.items = items\n self.onReselect = onReselect\n }\n\n public var body: some View {\n HStack(spacing: LumenSpacing.xs) {\n ForEach(items) { item in\n let selected = selection == item.value\n\n Button {\n dispatchLumenNavigationSelection(\n itemValue: item.value,\n selectedValue: selection,\n onValueChange: { selection = $0 },\n onReselect: onReselect\n )\n } label: {\n VStack(spacing: LumenSpacing.xs) {\n if let systemName = selected ? item.selectedSystemName ?? item.systemName : item.systemName {\n ZStack(alignment: .topTrailing) {\n LumenIcon(\n systemName: systemName,\n size: .md,\n color: selected ? theme.colors.brand : theme.colors.inkMuted\n )\n\n if let badge = item.badge {\n LumenNavigationBadgeView(badge: badge)\n .offset(x: 8, y: -6)\n }\n }\n }\n\n Text(item.label)\n .font(.caption.weight(selected ? .semibold : .medium))\n .foregroundStyle(selected ? theme.colors.brand : theme.colors.inkMuted)\n .lineLimit(1)\n }\n .frame(maxWidth: .infinity, minHeight: 48)\n .contentShape(Rectangle())\n }\n .buttonStyle(.plain)\n .disabled(item.disabled)\n .opacity(item.disabled ? 0.52 : 1)\n .accessibilityAddTraits(selected ? .isSelected : [])\n .accessibilityValue(item.badge.map { Text($0.spokenLabel) } ?? Text(\"\"))\n }\n }\n .padding(.horizontal, LumenSpacing.sm)\n .padding(.vertical, LumenSpacing.sm)\n .background(theme.colors.surface)\n .overlay(alignment: .top) {\n Rectangle()\n .fill(theme.colors.line)\n .frame(height: 1)\n .accessibilityHidden(true)\n }\n .accessibilityElement(children: .contain)\n .accessibilityLabel(label)\n }\n}\n\nprivate struct LumenNavigationBadgeView: View {\n @Environment(\\.lumenTheme) private var theme\n\n let badge: LumenNavigationBadge\n\n var body: some View {\n Group {\n if let displayText = badge.displayText {\n Text(displayText)\n .font(.caption2.weight(.bold))\n .foregroundStyle(theme.colors.onDanger)\n .padding(.horizontal, 5)\n .frame(minWidth: 16, minHeight: 16)\n .lineLimit(1)\n } else {\n Circle()\n .fill(theme.colors.danger)\n .frame(width: 8, height: 8)\n }\n }\n .background {\n if badge.displayText != nil {\n Capsule().fill(theme.colors.danger)\n }\n }\n .accessibilityHidden(true)\n }\n}\n",
39654
+ "packages/swift/Sources/LumenUI/OverlayComponents.swift": "import CoreTransferable\nimport SwiftUI\n\nprivate struct LumenAlertDialogModifier: ViewModifier {\n @Binding var isPresented: Bool\n\n let cancelLabel: LocalizedStringKey\n let confirmDisabled: Bool\n let confirmLabel: LocalizedStringKey\n let confirmLoading: Bool\n let confirmRole: ButtonRole?\n let description: LocalizedStringKey?\n let onConfirm: () -> Void\n let title: LocalizedStringKey\n\n func body(content: Content) -> some View {\n content.alert(title, isPresented: $isPresented) {\n Button(cancelLabel, role: .cancel) {}\n Button(role: confirmRole, action: onConfirm) {\n HStack(spacing: LumenSpacing.sm) {\n if confirmLoading {\n ProgressView()\n .controlSize(.small)\n }\n Text(confirmLabel)\n }\n }\n .disabled(confirmDisabled || confirmLoading)\n .accessibilityValue(confirmLoading ? Text(\"Loading\") : Text(\"\"))\n } message: {\n if let description {\n Text(description)\n }\n }\n }\n}\n\npublic extension View {\n /// Presents a controlled native confirmation alert with explicit cancel and confirm actions.\n func lumenAlertDialog(\n isPresented: Binding<Bool>,\n title: LocalizedStringKey,\n description: LocalizedStringKey? = nil,\n confirmLabel: LocalizedStringKey,\n cancelLabel: LocalizedStringKey = \"Cancel\",\n confirmRole: ButtonRole? = nil,\n confirmDisabled: Bool = false,\n confirmLoading: Bool = false,\n onConfirm: @escaping () -> Void\n ) -> some View {\n modifier(\n LumenAlertDialogModifier(\n isPresented: isPresented,\n cancelLabel: cancelLabel,\n confirmDisabled: confirmDisabled,\n confirmLabel: confirmLabel,\n confirmLoading: confirmLoading,\n confirmRole: confirmRole,\n description: description,\n onConfirm: onConfirm,\n title: title\n )\n )\n }\n}\n\nprivate struct LumenSheetModifier<SheetContent: View, Actions: View>: ViewModifier {\n @Binding var isPresented: Bool\n\n let actions: Actions\n let description: LocalizedStringKey?\n let onDismiss: () -> Void\n let sheetContent: SheetContent\n let title: LocalizedStringKey?\n\n func body(content: Content) -> some View {\n content.sheet(isPresented: $isPresented, onDismiss: onDismiss) {\n VStack(alignment: .leading, spacing: LumenSpacing.lg) {\n if title != nil || description != nil {\n VStack(alignment: .leading, spacing: LumenSpacing.sm) {\n if let title {\n Text(title)\n .font(.title2.weight(.semibold))\n .accessibilityAddTraits(.isHeader)\n }\n if let description {\n Text(description)\n .font(.callout)\n .foregroundStyle(.secondary)\n }\n }\n }\n\n sheetContent\n\n actions\n .frame(maxWidth: .infinity, alignment: .trailing)\n }\n .padding(LumenSpacing.xl)\n }\n }\n}\n\npublic extension View {\n /// Presents application-owned content in a native sheet with an optional heading and actions.\n func lumenSheet<SheetContent: View, Actions: View>(\n isPresented: Binding<Bool>,\n title: LocalizedStringKey? = nil,\n description: LocalizedStringKey? = nil,\n onDismiss: @escaping () -> Void = {},\n @ViewBuilder actions: () -> Actions,\n @ViewBuilder content: () -> SheetContent\n ) -> some View {\n modifier(\n LumenSheetModifier(\n isPresented: isPresented,\n actions: actions(),\n description: description,\n onDismiss: onDismiss,\n sheetContent: content(),\n title: title\n )\n )\n }\n\n func lumenSheet<SheetContent: View>(\n isPresented: Binding<Bool>,\n title: LocalizedStringKey? = nil,\n description: LocalizedStringKey? = nil,\n onDismiss: @escaping () -> Void = {},\n @ViewBuilder content: () -> SheetContent\n ) -> some View {\n lumenSheet(\n isPresented: isPresented,\n title: title,\n description: description,\n onDismiss: onDismiss,\n actions: { EmptyView() },\n content: content\n )\n }\n}\n\npublic struct LumenMenuItem {\n fileprivate let action: () -> Void\n fileprivate let disabled: Bool\n fileprivate let role: ButtonRole?\n fileprivate let systemName: String?\n fileprivate let title: LocalizedStringKey\n\n public init(\n _ title: LocalizedStringKey,\n systemName: String? = nil,\n role: ButtonRole? = nil,\n disabled: Bool = false,\n action: @escaping () -> Void\n ) {\n self.title = title\n self.systemName = systemName\n self.role = role\n self.disabled = disabled\n self.action = action\n }\n}\n\n/// A native anchored menu that preserves disabled and destructive action semantics.\n#if !os(watchOS)\npublic struct LumenMenu<MenuLabel: View>: View {\n private let items: [LumenMenuItem]\n private let label: MenuLabel\n\n public init(\n items: [LumenMenuItem],\n @ViewBuilder label: () -> MenuLabel\n ) {\n self.items = items\n self.label = label()\n }\n\n public var body: some View {\n Menu {\n ForEach(Array(items.enumerated()), id: \\.offset) { _, item in\n Button(role: item.role, action: item.action) {\n if let systemName = item.systemName {\n SwiftUI.Label(item.title, systemImage: systemName)\n } else {\n Text(item.title)\n }\n }\n .disabled(item.disabled)\n }\n } label: {\n label\n }\n }\n}\n#endif\n\n/// A Lumen-styled button backed by SwiftUI's native ShareLink presentation.\npublic struct LumenShareButton: View {\n private let shareLink: AnyView\n\n public init<Label: View>(\n item: String,\n @ViewBuilder label: () -> Label\n ) {\n shareLink = AnyView(\n ShareLink(item: item, label: label)\n .buttonStyle(LumenButtonStyle(intent: .secondary))\n )\n }\n\n public init<Label: View>(\n item: URL,\n @ViewBuilder label: () -> Label\n ) {\n shareLink = AnyView(\n ShareLink(item: item, label: label)\n .buttonStyle(LumenButtonStyle(intent: .secondary))\n )\n }\n\n @available(tvOS, unavailable)\n public init<\n Item: Transferable,\n PreviewImage: Transferable,\n PreviewIcon: Transferable,\n Label: View\n >(\n item: Item,\n preview: SharePreview<PreviewImage, PreviewIcon>,\n @ViewBuilder label: () -> Label\n ) {\n shareLink = AnyView(\n ShareLink(item: item, preview: preview, label: label)\n .buttonStyle(LumenButtonStyle(intent: .secondary))\n )\n }\n\n public init(_ label: LocalizedStringKey = \"Share\", item: String) {\n self.init(item: item) {\n Text(label)\n }\n }\n\n public init(_ label: LocalizedStringKey = \"Share\", item: URL) {\n self.init(item: item) {\n Text(label)\n }\n }\n\n public var body: some View {\n shareLink\n }\n}\n",
37129
39655
  "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",
37130
39656
  "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",
37131
39657
  "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",
37132
39658
  "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",
37133
39659
  "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",
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"
39660
+ "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",
39661
+ "packages/swift/Sources/LumenUI/WatchComponents.swift": "import SwiftUI\n\npublic enum LumenWatchTone: Sendable {\n case accent\n case brand\n case danger\n case neutral\n case success\n case warning\n}\n\npublic struct LumenWatchProgressValue: Equatable, Sendable {\n public let maximum: Double\n public let value: Double\n\n public static func resolve(_ value: Double, maximum: Double) -> LumenWatchProgressValue {\n let safeMaximum = maximum.isFinite && maximum > 0 ? maximum : 1\n let safeValue = value.isFinite ? min(max(value, 0), safeMaximum) : 0\n return LumenWatchProgressValue(maximum: safeMaximum, value: safeValue)\n }\n\n public var fraction: Double {\n value / maximum\n }\n\n var accessibilityPercentage: Int {\n Int(fraction * 100)\n }\n}\n\npublic struct LumenWatchActionMetrics: Equatable, Sendable {\n public let dimension: CGFloat\n public let ringWidth: CGFloat\n\n public static func resolve(dimension: CGFloat, ringWidth: CGFloat) -> LumenWatchActionMetrics {\n let safeDimension = dimension.isFinite ? min(max(dimension, 44), 180) : 120\n let safeRingWidth = ringWidth.isFinite ? min(max(ringWidth, 2), 12) : 4\n return LumenWatchActionMetrics(dimension: safeDimension, ringWidth: safeRingWidth)\n }\n}\n\nfunc lumenWatchColor(_ palette: LumenColorPalette, tone: LumenWatchTone) -> Color {\n switch tone {\n case .accent:\n palette.accent\n case .brand:\n palette.brandSolid\n case .danger:\n palette.danger\n case .neutral:\n palette.inkMuted\n case .success:\n palette.success\n case .warning:\n palette.warning\n }\n}\n\n#if os(watchOS)\npublic struct LumenWatchActionButton<Label: View>: View {\n @Environment(\\.isLuminanceReduced) private var isLuminanceReduced\n @Environment(\\.lumenTheme) private var theme\n\n private let accessibilityLabel: LocalizedStringKey\n private let action: () -> Void\n private let dimension: CGFloat\n private let enabled: Bool\n private let label: Label\n private let tone: LumenWatchTone\n\n public init(\n _ accessibilityLabel: LocalizedStringKey,\n tone: LumenWatchTone = .brand,\n dimension: CGFloat = 120,\n enabled: Bool = true,\n action: @escaping () -> Void,\n @ViewBuilder label: () -> Label\n ) {\n self.accessibilityLabel = accessibilityLabel\n self.tone = tone\n self.dimension = dimension\n self.enabled = enabled\n self.action = action\n self.label = label()\n }\n\n public var body: some View {\n let metrics = LumenWatchActionMetrics.resolve(dimension: dimension, ringWidth: 4)\n let color = lumenWatchColor(theme.colors, tone: tone)\n\n Button(action: action) {\n label\n .foregroundStyle(tone == .danger ? theme.colors.onDanger : theme.colors.onBrand)\n .frame(width: metrics.dimension, height: metrics.dimension)\n .background(color.opacity(enabled ? 1 : 0.48), in: Circle())\n .shadow(color: isLuminanceReduced ? .clear : color.opacity(0.38), radius: 7)\n }\n .buttonStyle(.plain)\n .disabled(!enabled)\n .accessibilityLabel(Text(accessibilityLabel))\n }\n}\n\npublic struct LumenWatchProgressRing<Content: View>: View {\n @Environment(\\.isLuminanceReduced) private var isLuminanceReduced\n @Environment(\\.lumenTheme) private var theme\n\n private let content: Content\n private let lineWidth: CGFloat\n private let maximum: Double\n private let tone: LumenWatchTone\n private let value: Double\n\n public init(\n value: Double,\n maximum: Double = 1,\n tone: LumenWatchTone = .brand,\n lineWidth: CGFloat = 4,\n @ViewBuilder content: () -> Content\n ) {\n self.value = value\n self.maximum = maximum\n self.tone = tone\n self.lineWidth = lineWidth\n self.content = content()\n }\n\n public var body: some View {\n let progress = LumenWatchProgressValue.resolve(value, maximum: maximum)\n let metrics = LumenWatchActionMetrics.resolve(dimension: 120, ringWidth: lineWidth)\n\n ZStack {\n if !isLuminanceReduced {\n Circle()\n .stroke(theme.colors.line.opacity(0.5), lineWidth: metrics.ringWidth)\n Circle()\n .trim(from: 0, to: progress.fraction)\n .stroke(\n lumenWatchColor(theme.colors, tone: tone),\n style: StrokeStyle(lineWidth: metrics.ringWidth, lineCap: .round)\n )\n .rotationEffect(.degrees(-90))\n .accessibilityHidden(true)\n }\n content\n }\n .accessibilityElement(children: .contain)\n .accessibilityValue(Text(\"\\(progress.accessibilityPercentage) percent\"))\n }\n}\n\npublic struct LumenWatchStatus: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let systemName: String?\n private let title: LocalizedStringKey\n private let tone: LumenWatchTone\n\n public init(\n _ title: LocalizedStringKey,\n systemName: String? = nil,\n tone: LumenWatchTone = .neutral\n ) {\n self.title = title\n self.systemName = systemName\n self.tone = tone\n }\n\n public var body: some View {\n Group {\n if let systemName {\n Label(title, systemImage: systemName)\n } else {\n Text(title)\n }\n }\n .font(.caption2.weight(.semibold))\n .foregroundStyle(foregroundColor)\n .padding(.horizontal, LumenSpacing.sm)\n .padding(.vertical, LumenSpacing.xs)\n .background(color.opacity(0.24), in: Capsule())\n }\n\n private var color: Color {\n lumenWatchColor(theme.colors, tone: tone)\n }\n\n private var foregroundColor: Color {\n tone == .neutral ? theme.colors.inkSoft : color\n }\n}\n\npublic struct LumenWatchMetric: View {\n @Environment(\\.lumenTheme) private var theme\n\n private let detail: LocalizedStringKey?\n private let label: LocalizedStringKey\n private let tone: LumenWatchTone\n private let value: String\n\n public init(\n _ label: LocalizedStringKey,\n value: String,\n detail: LocalizedStringKey? = nil,\n tone: LumenWatchTone = .neutral\n ) {\n self.label = label\n self.value = value\n self.detail = detail\n self.tone = tone\n }\n\n public var body: some View {\n VStack(alignment: .leading, spacing: 2) {\n Text(label)\n .font(.caption2)\n .foregroundStyle(theme.colors.inkMuted)\n Text(value)\n .font(.system(.body, design: .rounded, weight: .bold))\n .foregroundStyle(lumenWatchColor(theme.colors, tone: tone))\n if let detail {\n Text(detail)\n .font(.caption2)\n .foregroundStyle(theme.colors.inkSoft)\n }\n }\n .padding(LumenSpacing.sm)\n .background(theme.colors.surfaceMuted.opacity(0.84), in: RoundedRectangle(cornerRadius: LumenRadius.md))\n .accessibilityElement(children: .combine)\n }\n}\n\npublic struct LumenWatchListRow<Content: View, Leading: View, Trailing: View>: View {\n @Environment(\\.lumenTheme) private var theme\n\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(spacing: LumenSpacing.sm) {\n leading\n content\n .frame(maxWidth: .infinity, alignment: .leading)\n trailing\n }\n .padding(LumenSpacing.sm)\n .background(theme.colors.surfaceMuted.opacity(0.84), in: RoundedRectangle(cornerRadius: LumenRadius.md))\n }\n}\n\npublic extension LumenWatchListRow where Leading == EmptyView, Trailing == EmptyView {\n init(@ViewBuilder content: () -> Content) {\n self.init(leading: { EmptyView() }, content: content, trailing: { EmptyView() })\n }\n}\n#endif\n"
37135
39662
  }
37136
39663
  },
37137
39664
  "recipes": [
@@ -37535,6 +40062,94 @@
37535
40062
  }
37536
40063
  }
37537
40064
  ],
40065
+ "releaseManifest": {
40066
+ "schemaVersion": 1,
40067
+ "release": {
40068
+ "compose": {
40069
+ "artifacts": [
40070
+ "com.santi020k:lumen-compose",
40071
+ "com.santi020k:lumen-compose-wear"
40072
+ ],
40073
+ "version": "0.4.0"
40074
+ },
40075
+ "npm": {
40076
+ "packages": {
40077
+ "@santi020k/lumen-astro": {
40078
+ "peerDependencies": {
40079
+ "astro": ">=5"
40080
+ },
40081
+ "version": "1.4.0"
40082
+ },
40083
+ "@santi020k/lumen-core": {
40084
+ "peerDependencies": {},
40085
+ "version": "1.4.0"
40086
+ },
40087
+ "@santi020k/lumen-elements": {
40088
+ "peerDependencies": {},
40089
+ "version": "1.4.0"
40090
+ },
40091
+ "@santi020k/lumen-icons-brand": {
40092
+ "peerDependencies": {},
40093
+ "version": "1.0.1"
40094
+ },
40095
+ "@santi020k/lumen": {
40096
+ "peerDependencies": {},
40097
+ "version": "1.5.0"
40098
+ },
40099
+ "@santi020k/lumen-mcp": {
40100
+ "peerDependencies": {},
40101
+ "version": "1.5.0"
40102
+ },
40103
+ "@santi020k/lumen-react": {
40104
+ "peerDependencies": {
40105
+ "react": ">=19"
40106
+ },
40107
+ "version": "1.4.0"
40108
+ },
40109
+ "@santi020k/lumen-react-hook-form": {
40110
+ "peerDependencies": {
40111
+ "@santi020k/lumen-react": "^1.0.0",
40112
+ "react": ">=19",
40113
+ "react-hook-form": ">=7.76.0 <8"
40114
+ },
40115
+ "version": "1.0.1"
40116
+ },
40117
+ "@santi020k/lumen-react-native": {
40118
+ "peerDependencies": {
40119
+ "react": ">=19.2",
40120
+ "react-native": ">=0.86.2"
40121
+ },
40122
+ "version": "0.4.0"
40123
+ },
40124
+ "@santi020k/lumen-tokens": {
40125
+ "peerDependencies": {},
40126
+ "version": "0.1.0"
40127
+ }
40128
+ }
40129
+ },
40130
+ "swift": {
40131
+ "package": "https://github.com/santi020k/lumen.git",
40132
+ "revision": null,
40133
+ "tag": "v1.5.0"
40134
+ },
40135
+ "version": "1.5.0"
40136
+ },
40137
+ "migration": {
40138
+ "codemod": null,
40139
+ "css": {
40140
+ "astro": "@santi020k/lumen-astro/styles.css",
40141
+ "elements": "@santi020k/lumen-elements/styles.css",
40142
+ "react": "@santi020k/lumen-react/styles.css"
40143
+ },
40144
+ "deprecatedExports": [],
40145
+ "removedExports": [],
40146
+ "runtime": {
40147
+ "astro": "Mount UIPrimitives once when an imported component declares the ui-primitives runtime.",
40148
+ "elements": "Call defineLumenElements once before rendering lumen-* elements.",
40149
+ "react": "Use the public React behavior hooks for behavior-heavy primitives."
40150
+ }
40151
+ }
40152
+ },
37538
40153
  "rules": "# Lumen UI\n\nLumen is a multi-framework primitive UI system with Astro as the reference implementation.\n\nUse these files first when generating, reviewing, or migrating apps that use Lumen:\n\n- README.md: public overview and package strategy.\n- docs/ai-usage.md: AI-focused setup, package selection, and component selection.\n- skills/lumen-ui/SKILL.md: portable Agent Skill for building, reviewing, and theming Lumen interfaces.\n- docs/feature-roadmap.md: competitor-informed gaps and implementation priorities.\n- registry/lumen.registry.json: installable component, recipe, and documentation manifest.\n- packages/core/src/components.ts: authoritative shared component catalog.\n- packages/astro/components: reference component markup and prop shapes.\n- packages/astro/styles/lumen.css: standalone CSS contract for all frameworks.\n- @santi020k/lumen-mcp: Model Context Protocol server exposing snapshot provenance, structured component discovery, framework-aware usage/source, recipes, tolerant search, tokens, rules, and lumen:// resources for AI agents.\n\nInstall the portable Lumen Agent Skill:\n\n```bash\nnpx skills add santi020k/lumen --skill lumen-ui\n```\n\nDefault usage:\n\n```astro\n---\nimport { Button, Card, Input } from '@santi020k/lumen-astro'\nimport { UIPrimitives } from '@santi020k/lumen-astro'\nimport '@santi020k/lumen-astro/styles.css'\n---\n\n<UIPrimitives />\n<Card>\n <label for=\"email\">Email</label>\n <Input id=\"email\" type=\"email\" />\n <Button>Subscribe</Button>\n</Card>\n```\n\nRules for agents:\n\n- When Lumen MCP is connected, read `lumen://meta`, `lumen://diagnostics`, and `lumen://rules`, pass the target framework to `lumen_search`, and follow the framework behavior returned by `lumen_get_component`. Retain `lumen://catalog-manifest` and use `lumen_diff_catalog` after upgrades when the client supports cached context.\n- Prefer Astro unless the user explicitly targets React or Web Components.\n- In React, use the visual components with behavior hooks for Dialog, Popover, DropdownMenu, Tabs, Select, Toast, and Tooltip instead of mounting the Astro runtime.\n- In Web Components, registered `lumen-*` elements include behavior parity for Dialog, Popover, DropdownMenu, Tabs, Select, Toast, and Tooltip, including ARIA state, keyboard paths, Escape/outside dismissal, focus handling, native select form participation, and the shared toast controller.\n- Do not require Tailwind configuration for Lumen components.\n- Use public tokens: canvas, surface, surface-muted, surface-strong, line, ink, ink-soft, ink-muted, brand, brand-solid, brand-soft, accent, success, warning, danger.\n- Mount `UIPrimitives` once for interactive Astro primitives.\n- Use `glass` for glass surfaces. Overlay `surface=\"glass\"` is a deprecated compatibility alias that maps to `glass`.\n- Treat schedule, data collection, rich text, theme builder, and advanced fields as supported surfaces with progressively enhanced behavior.\n- Use `lumen list`, `lumen show <component>`, and `lumen add <component>` for individually addressable registry components; component names also resolve from kebab-case aliases such as `data-table`.\n- Runtime CustomEvents used by `UIPrimitives`: `ui:data-table-selection-change` from `[data-ui-datatable]` with `{ values }` (`ui:datatable-selection-change` remains a deprecated alias until 1.0); `ui:schedule-change` from `[data-ui-schedule]` with `{ eventId, slot }`; `ui:virtual-list-range` from `[data-ui-virtual-list]` with `{ startIndex, endIndex }`; `ui:tag-remove` from tag groups with `{ value }`; `ui:editor-command` from rich text editors with `{ command }`; `ui:theme-change` from theme builders with `{ hue, tokens }`; `ui:theme-export` from theme builders with `{ css }`; `ui:validate`, `ui:invalid`, and `ui:valid` from `[data-ui-form]` for native Constraint Validation API enhancement; document-level `ui:toast`, `ui:toast-update`, and `ui:toast-dismiss` for runtime toasts; `ui:toast-action` from `[data-ui-toast]` when an action button is pressed.\n- Add `data-ui-form` to forms that should reflect `required`, `pattern`, `min`, `max`, `type`, and custom `setCustomValidity()` state into Field error slots on blur and submit. Use `data-error-required`, `data-error-pattern`, `data-error-min`, `data-error-max`, or `data-error-custom` to override browser validation messages.\n- Toasts use `Sonner` viewports with `placement` and `maxCount`. With `UIPrimitives` or registered Web Components, use `window.LumenToast.create(detail)`, `.update(id, detail)`, and `.dismiss(id?)`, import `LumenToast` from `@santi020k/lumen-elements`, or dispatch the matching document events. Runtime toast `detail` supports `title`, `description`, `variant`, `duration`, `placement`, `max`, and `action`.\n",
37539
40154
  "tokens": {
37540
40155
  "chart": {