@santi020k/lumen-mcp 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/data/lumen-data.json +1718 -51
  2. package/package.json +1 -1
@@ -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,22 +159,22 @@
156
159
  "VisuallyHidden": "17808de2f795be62dbeb1f8c649fedd7dda5acbd8af3bbc1d1bbec20e788e3b3"
157
160
  },
158
161
  "nativeComponents": {
159
- "Theme": "ffb8e66eaa1fa80ecc138bb047ec2ee0d2aaddb14e75103f9acd330446891609",
162
+ "Theme": "527f0afa78e08580631be6b45fb5980ef2b827472dce29190510eea7f7b73e35",
160
163
  "Text": "da836769d7734d25de5c5819b3d8eb7cdb0a7347baa4a757ecfcbde2388391ba",
161
- "Surface": "96dd2928844a043dd042f3d3bed947be32d2647a369889cdf1e2ba30183f7f33",
162
- "Icon": "efa0d01b9a7313a5f5b39e9a89891fe3839ae62e42dc38b8cff5f36611488cfb",
163
- "Icon button": "4ef0f3d4823ee71a3ebb66948cedb2af41f5bf79aa5c47feb66ba29ba01e26a0",
164
+ "Surface": "b0605587e07f7e0d15b6f5adaf3c2f067b388306e279ca32029e8a44201e1135",
165
+ "Icon": "79ed08ed3eb03bdbe69f895932ef1fdf842320015e74428cae2cf7f70c9e97bf",
166
+ "Icon button": "6088ce23834d92200ea6779fc8a435b2beace575f68247d474069c4df539e44d",
164
167
  "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",
168
+ "Text field": "5609ef9607cec03a5bf48ab2f706f19cbb0750db2697626390e2ecb7cc665afc",
169
+ "Badge": "96c9707c9ea84f47fde14739fbd9a3789d99aa501d7271ca55f0d5fdb330379e",
170
+ "Divider": "6848eae113b3ccaf01aa5d05b3463ab6c208374165423cc94334eb3448cbc597",
171
+ "Spinner": "cb3f28a5162edb83906a181fa64b0557397cf4b921bd12f41a48165c075a8155",
172
+ "Card": "5481383b9baa0e758b4ecb7cedaeff485998c2cddf6a99835ef5f48be25d4648",
173
+ "Alert": "71c9658e4b5426c0da101adc44c60ffc56a82567a59be1f2776da6ab603c86c3",
174
+ "Progress": "b38f105092118eb2a67feeadc6642cb884245321b3c03e6aa8f96e41c3252bfc",
175
+ "Skeleton": "328820880ed1b1fedd86701053fca980e75ef6b0159628a77de60aa3fb9cfafa",
176
+ "Disclosure": "6e940a230839fc34905dd3e629c961179d40bed0c8ed834333e9e71c20c2c56f",
177
+ "Avatar": "35544a2d015338f7b240234619b2eebe5ca97710d3b39af32931c0cb5f544813",
175
178
  "Textarea": "221a265a3ed0eca986f1b6e377f92217ab343359a64081a021d0cf079eaa2c6e",
176
179
  "Field group": "1d736820d5eabd202090e35555be772a06c26a1c47745417d5a23faa07c1e3fa",
177
180
  "Chip": "fcbce7af9bb38a1bce7f5c337a0cdb8eb8721aa0a0f36c510ff764acdef986f5",
@@ -182,21 +185,29 @@
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": "badc66c91eddd344b30058ec62f49e29ca8e3cb4c2fbe7a329f9d87b1dcd4516",
189
+ "Radio group": "16af92535d9ce1d7b899820fcac65cb068e4e14e4f70e72b0eaedc83f3e0b66d",
190
+ "Segmented control": "001c132f08dbaaebd00aee9bc0c68cdb5d198506314b07a6a1e473740abfc868",
191
+ "Navigation bar": "6a196a6f9924057040e7cbf99fec903f864d177aca462199cdfe8d076a782539",
192
+ "Empty state": "40301270a9d34234d0a3500e7d8d29f70f5886d4cb07647c3439c695619ec97a",
193
+ "List row": "ea220c485ae90e6d0670e8f526f7925be568377c467bffb87c68d9ec79392441",
194
+ "Banner": "95f00d14a494253cb4aed2694a883c3b4bffbf2316e7536594a02117b8991808",
195
+ "Stat": "3b357dde8e5c31b1a91df44ebc12c01fdbf2d0e74dfb721a8b47a88722542145",
192
196
  "Gauge": "d8bb93ae076c2fe70e2dbc149cf6d99bdb2199c176dff0f6ccd0016eb1b62b49",
193
- "Section header": "dfdadc03ca5d89b0e19d197edce1e2d8e83e2d39381aa7dcbc328b7d53d52b7c",
194
- "Status bar": "a1b36ae346299e01bcac31b852aa1cf84d46aef3fda333b321b2610e7792a289",
195
- "Refresh control": "37a3e0a9d085780b9d3dead61fee2849108ae4f33e6d0814ddf680c4f5829b7c",
197
+ "Section header": "4e1dd5fae63e25270c62b74fc347a18cff71050bb8498b003725cdb532edb734",
198
+ "Status bar": "d67f1a895b5ae0541b4a4e5320858e27a3ec1dda50ef655e122da1373ff1bd90",
199
+ "Refresh control": "2626b04e4dab27f7fecd9210ad6d3a150548c5e8053f94fbb7f3ce05342bceb4",
196
200
  "Date field": "2397d6907ac230497077d3b388b330fc43680f6e7cadd2faef3480a71d719ec3",
197
- "Floating action button": "614a972743528ef2df98f9a64281eef4cbed1725c42b8c86da534645be15e8f6",
201
+ "Floating action button": "ba8237274309f2fbd7b78d42ea6862dc018397209111d88d4abd9ce9b877da84",
198
202
  "Shortcut recorder": "a5a2fa237d2efe604431bc35d46f063328fdc0fd8c0485d53d1692b1f0a76926",
199
- "Symbol picker": "57b3383a74549aa62dd97dfa627464818e4b65d5b8c130ecb3697059668fbb27"
203
+ "Symbol picker": "57b3383a74549aa62dd97dfa627464818e4b65d5b8c130ecb3697059668fbb27",
204
+ "Graphic": "54a853609b5abdc397d2e209685d816338db10b14b2d112369ecb6e532c2df3a",
205
+ "Backdrop": "89d9f325e636ad8e30f0afc333a1a3c31b761075ca6b83c454abf3f95c427dcc",
206
+ "Illustration": "65c87de0c17a8161645dfa7787c43b8b144f433fbb4a72fe1691afe319750eda",
207
+ "Alert dialog": "8ee22136b2c66aec77df2bd6234f470cd0a8e97f52abeedd10c12ae9d9ec7bd7",
208
+ "Sheet": "1873aeda95cd73a25ae89cb846c0bea94f287fa2559bc6bbe33809a25585f9ca",
209
+ "Menu": "0542e0c7006431c82248b3d2456418504236c23e6cd2092ae65bdc4be94eaab7",
210
+ "Share button": "7dcd8523d024d98679e3a78d05b237e80d26ce0222669fe1a6df588a6006effb"
200
211
  },
201
212
  "recipes": {
202
213
  "all-components": "517dfb03b8ca5483f5412a00ca2a151763d8d4429a44e398d4d74733793f0fd7",
@@ -1764,6 +1775,204 @@
1764
1775
  ],
1765
1776
  "runtimeEvents": []
1766
1777
  },
1778
+ {
1779
+ "apiReference": [
1780
+ {
1781
+ "attribute": "variant",
1782
+ "defaultValue": "\"aurora\"",
1783
+ "description": "Selects the ambient background pattern.",
1784
+ "values": "\"aurora\" | \"dots\" | \"grid\" | \"rays\""
1785
+ },
1786
+ {
1787
+ "attribute": "tone",
1788
+ "defaultValue": "\"brand\"",
1789
+ "description": "Uses a semantic Lumen color for the pattern.",
1790
+ "values": "\"brand\" | \"accent\" | \"neutral\""
1791
+ },
1792
+ {
1793
+ "attribute": "intensity",
1794
+ "defaultValue": "\"medium\"",
1795
+ "description": "Controls decoration opacity without fading child content.",
1796
+ "values": "\"subtle\" | \"medium\" | \"strong\""
1797
+ },
1798
+ {
1799
+ "attribute": "children",
1800
+ "defaultValue": "required",
1801
+ "description": "Keeps meaningful content above the decorative layer.",
1802
+ "values": "rendered content"
1803
+ },
1804
+ {
1805
+ "attribute": "class, className",
1806
+ "defaultValue": "\"\"",
1807
+ "description": "Merges custom classes with the generated ui-* root classes.",
1808
+ "values": "string"
1809
+ },
1810
+ {
1811
+ "attribute": "...native attributes",
1812
+ "defaultValue": "-",
1813
+ "description": "Forwards standard attributes to the root element unless the component consumes them.",
1814
+ "values": "HTML attributes"
1815
+ }
1816
+ ],
1817
+ "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",
1818
+ "category": "Brand",
1819
+ "collections": [],
1820
+ "dependencies": [
1821
+ "styles"
1822
+ ],
1823
+ "description": "Adds a portable ambient pattern behind meaningful content.",
1824
+ "behavior": {
1825
+ "astro": "none",
1826
+ "elements": "registered-element",
1827
+ "react": "component"
1828
+ },
1829
+ "files": [
1830
+ "packages/astro/components/Backdrop.astro",
1831
+ "packages/astro/styles/lumen.css",
1832
+ "packages/react/src/components.tsx"
1833
+ ],
1834
+ "frameworkDetails": {
1835
+ "astro": {
1836
+ "available": true,
1837
+ "example": "<Backdrop intensity=\"medium\" tone=\"brand\" variant=\"aurora\"><Card variant=\"glass\">Project overview</Card></Backdrop>",
1838
+ "importStatement": "import { Backdrop, Card } from '@santi020k/lumen-astro'",
1839
+ "language": "astro",
1840
+ "packageName": "@santi020k/lumen-astro",
1841
+ "props": [
1842
+ {
1843
+ "name": "class",
1844
+ "optional": true,
1845
+ "type": "string | null"
1846
+ },
1847
+ {
1848
+ "name": "className",
1849
+ "optional": true,
1850
+ "type": "string"
1851
+ },
1852
+ {
1853
+ "name": "intensity",
1854
+ "optional": true,
1855
+ "type": "'medium' | 'strong' | 'subtle'"
1856
+ },
1857
+ {
1858
+ "name": "tone",
1859
+ "optional": true,
1860
+ "type": "'accent' | 'brand' | 'neutral'"
1861
+ },
1862
+ {
1863
+ "name": "variant",
1864
+ "optional": true,
1865
+ "type": "'aurora' | 'dots' | 'grid' | 'rays'"
1866
+ }
1867
+ ],
1868
+ "propsExtends": "HTMLAttributes<'div'>",
1869
+ "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",
1870
+ "styleImport": "import '@santi020k/lumen-astro/styles.css'"
1871
+ },
1872
+ "elements": {
1873
+ "attributes": [
1874
+ "intensity",
1875
+ "tone",
1876
+ "variant",
1877
+ "children",
1878
+ "class, className",
1879
+ "...native attributes"
1880
+ ],
1881
+ "available": true,
1882
+ "behavior": {
1883
+ "mode": "registration",
1884
+ "setup": "Call defineLumenElements() once before rendering Lumen custom elements."
1885
+ },
1886
+ "example": "<lumen-backdrop intensity=\"medium\" tone=\"brand\" variant=\"aurora\"><lumen-card variant=\"glass\">Project overview</lumen-card></lumen-backdrop>",
1887
+ "importStatement": "import { defineLumenElements } from '@santi020k/lumen-elements/define'",
1888
+ "language": "html",
1889
+ "packageName": "@santi020k/lumen-elements",
1890
+ "props": [],
1891
+ "propsExtends": "HTMLElement attributes",
1892
+ "registration": "defineLumenElements()",
1893
+ "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 }",
1894
+ "styleImport": "import '@santi020k/lumen-elements/styles.css'",
1895
+ "tagName": "lumen-backdrop"
1896
+ },
1897
+ "react": {
1898
+ "available": true,
1899
+ "example": "<Backdrop intensity=\"medium\" tone=\"brand\" variant=\"aurora\"><Card variant=\"glass\">Project overview</Card></Backdrop>",
1900
+ "importStatement": "import { Backdrop, Card } from '@santi020k/lumen-react'",
1901
+ "language": "tsx",
1902
+ "packageName": "@santi020k/lumen-react",
1903
+ "props": [
1904
+ {
1905
+ "name": "intensity",
1906
+ "optional": true,
1907
+ "type": "'medium' | 'strong' | 'subtle'"
1908
+ },
1909
+ {
1910
+ "name": "tone",
1911
+ "optional": true,
1912
+ "type": "'accent' | 'brand' | 'neutral'"
1913
+ },
1914
+ {
1915
+ "name": "variant",
1916
+ "optional": true,
1917
+ "type": "'aurora' | 'dots' | 'grid' | 'rays'"
1918
+ }
1919
+ ],
1920
+ "propsExtends": "ComponentPropsWithoutRef<'div'>",
1921
+ "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)",
1922
+ "styleImport": "import '@santi020k/lumen-react/styles.css'"
1923
+ }
1924
+ },
1925
+ "frameworks": {
1926
+ "astro": true,
1927
+ "elements": true,
1928
+ "react": true
1929
+ },
1930
+ "guidance": null,
1931
+ "kebab": "backdrop",
1932
+ "keyboardInteractions": [],
1933
+ "keywords": [
1934
+ "adds",
1935
+ "ambient",
1936
+ "backdrop",
1937
+ "behind",
1938
+ "brand",
1939
+ "content",
1940
+ "meaningful",
1941
+ "pattern",
1942
+ "portable"
1943
+ ],
1944
+ "name": "Backdrop",
1945
+ "props": [
1946
+ {
1947
+ "name": "class",
1948
+ "optional": true,
1949
+ "type": "string | null"
1950
+ },
1951
+ {
1952
+ "name": "className",
1953
+ "optional": true,
1954
+ "type": "string"
1955
+ },
1956
+ {
1957
+ "name": "intensity",
1958
+ "optional": true,
1959
+ "type": "'medium' | 'strong' | 'subtle'"
1960
+ },
1961
+ {
1962
+ "name": "tone",
1963
+ "optional": true,
1964
+ "type": "'accent' | 'brand' | 'neutral'"
1965
+ },
1966
+ {
1967
+ "name": "variant",
1968
+ "optional": true,
1969
+ "type": "'aurora' | 'dots' | 'grid' | 'rays'"
1970
+ }
1971
+ ],
1972
+ "propsExtends": "HTMLAttributes<'div'>",
1973
+ "recipes": [],
1974
+ "runtimeEvents": []
1975
+ },
1767
1976
  {
1768
1977
  "apiReference": [
1769
1978
  {
@@ -11088,6 +11297,222 @@
11088
11297
  "recipes": [],
11089
11298
  "runtimeEvents": []
11090
11299
  },
11300
+ {
11301
+ "apiReference": [
11302
+ {
11303
+ "attribute": "variant",
11304
+ "defaultValue": "\"orbit\"",
11305
+ "description": "Selects a portable decorative pattern around application-provided artwork.",
11306
+ "values": "\"glow\" | \"grid\" | \"orbit\""
11307
+ },
11308
+ {
11309
+ "attribute": "tone",
11310
+ "defaultValue": "\"brand\"",
11311
+ "description": "Uses the matching semantic Lumen color without introducing a product palette.",
11312
+ "values": "\"brand\" | \"accent\" | \"neutral\""
11313
+ },
11314
+ {
11315
+ "attribute": "size",
11316
+ "defaultValue": "\"md\"",
11317
+ "description": "Uses the shared 160, 240, or 320 unit composition size.",
11318
+ "values": "\"sm\" | \"md\" | \"lg\""
11319
+ },
11320
+ {
11321
+ "attribute": "label",
11322
+ "defaultValue": "-",
11323
+ "description": "Exposes the complete composition as one named image; without it the graphic is decorative.",
11324
+ "values": "string"
11325
+ },
11326
+ {
11327
+ "attribute": "class, className",
11328
+ "defaultValue": "\"\"",
11329
+ "description": "Merges custom classes with the generated ui-* root classes.",
11330
+ "values": "string"
11331
+ },
11332
+ {
11333
+ "attribute": "...native attributes",
11334
+ "defaultValue": "-",
11335
+ "description": "Forwards standard attributes to the root element unless the component consumes them.",
11336
+ "values": "HTML attributes"
11337
+ }
11338
+ ],
11339
+ "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",
11340
+ "category": "Brand",
11341
+ "collections": [],
11342
+ "dependencies": [
11343
+ "styles"
11344
+ ],
11345
+ "description": "Frames product-owned artwork with portable glow, grid, or orbit treatments.",
11346
+ "behavior": {
11347
+ "astro": "none",
11348
+ "elements": "registered-element",
11349
+ "react": "component"
11350
+ },
11351
+ "files": [
11352
+ "packages/astro/components/Graphic.astro",
11353
+ "packages/astro/styles/lumen.css",
11354
+ "packages/react/src/components.tsx"
11355
+ ],
11356
+ "frameworkDetails": {
11357
+ "astro": {
11358
+ "available": true,
11359
+ "example": "<Graphic size=\"lg\" tone=\"accent\" variant=\"orbit\"><Icon name=\"sparkles\" decorative size=\"xl\" /></Graphic>",
11360
+ "importStatement": "import { Graphic, Icon } from '@santi020k/lumen-astro'",
11361
+ "language": "astro",
11362
+ "packageName": "@santi020k/lumen-astro",
11363
+ "props": [
11364
+ {
11365
+ "name": "class",
11366
+ "optional": true,
11367
+ "type": "string | null"
11368
+ },
11369
+ {
11370
+ "name": "className",
11371
+ "optional": true,
11372
+ "type": "string"
11373
+ },
11374
+ {
11375
+ "name": "label",
11376
+ "optional": true,
11377
+ "type": "string"
11378
+ },
11379
+ {
11380
+ "name": "size",
11381
+ "optional": true,
11382
+ "type": "'lg' | 'md' | 'sm'"
11383
+ },
11384
+ {
11385
+ "name": "tone",
11386
+ "optional": true,
11387
+ "type": "'accent' | 'brand' | 'neutral'"
11388
+ },
11389
+ {
11390
+ "name": "variant",
11391
+ "optional": true,
11392
+ "type": "'glow' | 'grid' | 'orbit'"
11393
+ }
11394
+ ],
11395
+ "propsExtends": "HTMLAttributes<'div'>",
11396
+ "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",
11397
+ "styleImport": "import '@santi020k/lumen-astro/styles.css'"
11398
+ },
11399
+ "elements": {
11400
+ "attributes": [
11401
+ "size",
11402
+ "tone",
11403
+ "variant",
11404
+ "label",
11405
+ "class, className",
11406
+ "...native attributes"
11407
+ ],
11408
+ "available": true,
11409
+ "behavior": {
11410
+ "mode": "registration",
11411
+ "setup": "Call defineLumenElements() once before rendering Lumen custom elements."
11412
+ },
11413
+ "example": "<lumen-graphic size=\"lg\" tone=\"accent\" variant=\"orbit\"><lumen-icon name=\"sparkles\" decorative size=\"xl\"></lumen-icon></lumen-graphic>",
11414
+ "importStatement": "import { defineLumenElements } from '@santi020k/lumen-elements/define'",
11415
+ "language": "html",
11416
+ "packageName": "@santi020k/lumen-elements",
11417
+ "props": [],
11418
+ "propsExtends": "HTMLElement attributes",
11419
+ "registration": "defineLumenElements()",
11420
+ "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 }",
11421
+ "styleImport": "import '@santi020k/lumen-elements/styles.css'",
11422
+ "tagName": "lumen-graphic"
11423
+ },
11424
+ "react": {
11425
+ "available": true,
11426
+ "example": "<Graphic size=\"lg\" tone=\"accent\" variant=\"orbit\"><Icon name=\"sparkles\" decorative size=\"xl\" /></Graphic>",
11427
+ "importStatement": "import { Graphic, Icon } from '@santi020k/lumen-react'",
11428
+ "language": "tsx",
11429
+ "packageName": "@santi020k/lumen-react",
11430
+ "props": [
11431
+ {
11432
+ "name": "label",
11433
+ "optional": true,
11434
+ "type": "string"
11435
+ },
11436
+ {
11437
+ "name": "size",
11438
+ "optional": true,
11439
+ "type": "'lg' | 'md' | 'sm'"
11440
+ },
11441
+ {
11442
+ "name": "tone",
11443
+ "optional": true,
11444
+ "type": "'accent' | 'brand' | 'neutral'"
11445
+ },
11446
+ {
11447
+ "name": "variant",
11448
+ "optional": true,
11449
+ "type": "'glow' | 'grid' | 'orbit'"
11450
+ }
11451
+ ],
11452
+ "propsExtends": "ComponentPropsWithoutRef<'div'>",
11453
+ "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)",
11454
+ "styleImport": "import '@santi020k/lumen-react/styles.css'"
11455
+ }
11456
+ },
11457
+ "frameworks": {
11458
+ "astro": true,
11459
+ "elements": true,
11460
+ "react": true
11461
+ },
11462
+ "guidance": null,
11463
+ "kebab": "graphic",
11464
+ "keyboardInteractions": [],
11465
+ "keywords": [
11466
+ "artwork",
11467
+ "brand",
11468
+ "frames",
11469
+ "glow",
11470
+ "graphic",
11471
+ "grid",
11472
+ "orbit",
11473
+ "owned",
11474
+ "portable",
11475
+ "product",
11476
+ "treatments",
11477
+ "with"
11478
+ ],
11479
+ "name": "Graphic",
11480
+ "props": [
11481
+ {
11482
+ "name": "class",
11483
+ "optional": true,
11484
+ "type": "string | null"
11485
+ },
11486
+ {
11487
+ "name": "className",
11488
+ "optional": true,
11489
+ "type": "string"
11490
+ },
11491
+ {
11492
+ "name": "label",
11493
+ "optional": true,
11494
+ "type": "string"
11495
+ },
11496
+ {
11497
+ "name": "size",
11498
+ "optional": true,
11499
+ "type": "'lg' | 'md' | 'sm'"
11500
+ },
11501
+ {
11502
+ "name": "tone",
11503
+ "optional": true,
11504
+ "type": "'accent' | 'brand' | 'neutral'"
11505
+ },
11506
+ {
11507
+ "name": "variant",
11508
+ "optional": true,
11509
+ "type": "'glow' | 'grid' | 'orbit'"
11510
+ }
11511
+ ],
11512
+ "propsExtends": "HTMLAttributes<'div'>",
11513
+ "recipes": [],
11514
+ "runtimeEvents": []
11515
+ },
11091
11516
  {
11092
11517
  "apiReference": [
11093
11518
  {
@@ -11736,6 +12161,222 @@
11736
12161
  "recipes": [],
11737
12162
  "runtimeEvents": []
11738
12163
  },
12164
+ {
12165
+ "apiReference": [
12166
+ {
12167
+ "attribute": "variant",
12168
+ "defaultValue": "\"empty\"",
12169
+ "description": "Selects a built-in semantic scene.",
12170
+ "values": "\"empty\" | \"success\" | \"error\" | \"offline\""
12171
+ },
12172
+ {
12173
+ "attribute": "tone",
12174
+ "defaultValue": "\"auto\"",
12175
+ "description": "Uses the variant semantic color automatically or an explicit portable tone.",
12176
+ "values": "\"auto\" | \"brand\" | \"accent\" | \"neutral\""
12177
+ },
12178
+ {
12179
+ "attribute": "size",
12180
+ "defaultValue": "\"md\"",
12181
+ "description": "Uses the shared 96, 128, or 176 unit illustration size.",
12182
+ "values": "\"sm\" | \"md\" | \"lg\""
12183
+ },
12184
+ {
12185
+ "attribute": "label",
12186
+ "defaultValue": "-",
12187
+ "description": "Exposes the illustration as one named image; without it the artwork is decorative.",
12188
+ "values": "string"
12189
+ },
12190
+ {
12191
+ "attribute": "class, className",
12192
+ "defaultValue": "\"\"",
12193
+ "description": "Merges custom classes with the generated ui-* root classes.",
12194
+ "values": "string"
12195
+ },
12196
+ {
12197
+ "attribute": "...native attributes",
12198
+ "defaultValue": "-",
12199
+ "description": "Forwards standard attributes to the root element unless the component consumes them.",
12200
+ "values": "HTML attributes"
12201
+ }
12202
+ ],
12203
+ "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",
12204
+ "category": "Feedback",
12205
+ "collections": [],
12206
+ "dependencies": [
12207
+ "styles"
12208
+ ],
12209
+ "description": "Shows a built-in semantic scene for empty, success, error, or offline states.",
12210
+ "behavior": {
12211
+ "astro": "none",
12212
+ "elements": "registered-element",
12213
+ "react": "component"
12214
+ },
12215
+ "files": [
12216
+ "packages/astro/components/Illustration.astro",
12217
+ "packages/astro/styles/lumen.css",
12218
+ "packages/react/src/components.tsx"
12219
+ ],
12220
+ "frameworkDetails": {
12221
+ "astro": {
12222
+ "available": true,
12223
+ "example": "<Illustration label=\"No projects yet\" size=\"md\" variant=\"empty\" />",
12224
+ "importStatement": "import { Illustration } from '@santi020k/lumen-astro'",
12225
+ "language": "astro",
12226
+ "packageName": "@santi020k/lumen-astro",
12227
+ "props": [
12228
+ {
12229
+ "name": "class",
12230
+ "optional": true,
12231
+ "type": "string | null"
12232
+ },
12233
+ {
12234
+ "name": "className",
12235
+ "optional": true,
12236
+ "type": "string"
12237
+ },
12238
+ {
12239
+ "name": "label",
12240
+ "optional": true,
12241
+ "type": "string"
12242
+ },
12243
+ {
12244
+ "name": "size",
12245
+ "optional": true,
12246
+ "type": "'lg' | 'md' | 'sm'"
12247
+ },
12248
+ {
12249
+ "name": "tone",
12250
+ "optional": true,
12251
+ "type": "'accent' | 'auto' | 'brand' | 'neutral'"
12252
+ },
12253
+ {
12254
+ "name": "variant",
12255
+ "optional": true,
12256
+ "type": "'empty' | 'error' | 'offline' | 'success'"
12257
+ }
12258
+ ],
12259
+ "propsExtends": "HTMLAttributes<'span'>",
12260
+ "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",
12261
+ "styleImport": "import '@santi020k/lumen-astro/styles.css'"
12262
+ },
12263
+ "elements": {
12264
+ "attributes": [
12265
+ "size",
12266
+ "tone",
12267
+ "variant",
12268
+ "label",
12269
+ "class, className",
12270
+ "...native attributes"
12271
+ ],
12272
+ "available": true,
12273
+ "behavior": {
12274
+ "mode": "registration",
12275
+ "setup": "Call defineLumenElements() once before rendering Lumen custom elements."
12276
+ },
12277
+ "example": "<lumen-illustration label=\"No projects yet\" size=\"md\" variant=\"empty\"></lumen-illustration>",
12278
+ "importStatement": "import { defineLumenElements } from '@santi020k/lumen-elements/define'",
12279
+ "language": "html",
12280
+ "packageName": "@santi020k/lumen-elements",
12281
+ "props": [],
12282
+ "propsExtends": "HTMLElement attributes",
12283
+ "registration": "defineLumenElements()",
12284
+ "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 }",
12285
+ "styleImport": "import '@santi020k/lumen-elements/styles.css'",
12286
+ "tagName": "lumen-illustration"
12287
+ },
12288
+ "react": {
12289
+ "available": true,
12290
+ "example": "<Illustration label=\"No projects yet\" size=\"md\" variant=\"empty\" />",
12291
+ "importStatement": "import { Illustration } from '@santi020k/lumen-react'",
12292
+ "language": "tsx",
12293
+ "packageName": "@santi020k/lumen-react",
12294
+ "props": [
12295
+ {
12296
+ "name": "label",
12297
+ "optional": true,
12298
+ "type": "string"
12299
+ },
12300
+ {
12301
+ "name": "size",
12302
+ "optional": true,
12303
+ "type": "'lg' | 'md' | 'sm'"
12304
+ },
12305
+ {
12306
+ "name": "tone",
12307
+ "optional": true,
12308
+ "type": "'accent' | 'auto' | 'brand' | 'neutral'"
12309
+ },
12310
+ {
12311
+ "name": "variant",
12312
+ "optional": true,
12313
+ "type": "'empty' | 'error' | 'offline' | 'success'"
12314
+ }
12315
+ ],
12316
+ "propsExtends": "ComponentPropsWithoutRef<'span'>",
12317
+ "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)",
12318
+ "styleImport": "import '@santi020k/lumen-react/styles.css'"
12319
+ }
12320
+ },
12321
+ "frameworks": {
12322
+ "astro": true,
12323
+ "elements": true,
12324
+ "react": true
12325
+ },
12326
+ "guidance": null,
12327
+ "kebab": "illustration",
12328
+ "keyboardInteractions": [],
12329
+ "keywords": [
12330
+ "built",
12331
+ "empty",
12332
+ "error",
12333
+ "feedback",
12334
+ "for",
12335
+ "illustration",
12336
+ "offline",
12337
+ "scene",
12338
+ "semantic",
12339
+ "shows",
12340
+ "states",
12341
+ "success"
12342
+ ],
12343
+ "name": "Illustration",
12344
+ "props": [
12345
+ {
12346
+ "name": "class",
12347
+ "optional": true,
12348
+ "type": "string | null"
12349
+ },
12350
+ {
12351
+ "name": "className",
12352
+ "optional": true,
12353
+ "type": "string"
12354
+ },
12355
+ {
12356
+ "name": "label",
12357
+ "optional": true,
12358
+ "type": "string"
12359
+ },
12360
+ {
12361
+ "name": "size",
12362
+ "optional": true,
12363
+ "type": "'lg' | 'md' | 'sm'"
12364
+ },
12365
+ {
12366
+ "name": "tone",
12367
+ "optional": true,
12368
+ "type": "'accent' | 'auto' | 'brand' | 'neutral'"
12369
+ },
12370
+ {
12371
+ "name": "variant",
12372
+ "optional": true,
12373
+ "type": "'empty' | 'error' | 'offline' | 'success'"
12374
+ }
12375
+ ],
12376
+ "propsExtends": "HTMLAttributes<'span'>",
12377
+ "recipes": [],
12378
+ "runtimeEvents": []
12379
+ },
11739
12380
  {
11740
12381
  "apiReference": [
11741
12382
  {
@@ -32294,13 +32935,13 @@
32294
32935
  }
32295
32936
  ],
32296
32937
  "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"
32938
+ "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.3.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.3.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`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 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",
32939
+ "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`, 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/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\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
32940
  },
32300
32941
  "meta": {
32301
- "catalogHash": "02fdac6cca80508301905223659ae7164ddc65adc9a9d61327e51d9fa45ce68f",
32302
- "componentCount": 153,
32303
- "nativeComponentCount": 41,
32942
+ "catalogHash": "da62e47060d9710896cd6ca5bfd676b97ae72cea966071535e76f895480cf745",
32943
+ "componentCount": 156,
32944
+ "nativeComponentCount": 49,
32304
32945
  "packages": [
32305
32946
  "@santi020k/lumen-astro",
32306
32947
  "@santi020k/lumen-react",
@@ -32312,12 +32953,12 @@
32312
32953
  "com.santi020k:lumen-compose"
32313
32954
  ],
32314
32955
  "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",
32956
+ "@santi020k/lumen-astro": "1.4.0",
32957
+ "@santi020k/lumen-core": "1.4.0",
32958
+ "@santi020k/lumen-elements": "1.4.0",
32959
+ "@santi020k/lumen-mcp": "1.4.0",
32960
+ "@santi020k/lumen-react": "1.4.0",
32961
+ "@santi020k/lumen-react-native": "0.3.0",
32321
32962
  "@santi020k/lumen-tokens": "0.1.0",
32322
32963
  "LumenUI": "workspace",
32323
32964
  "com.santi020k:lumen-compose": "workspace"
@@ -32325,7 +32966,7 @@
32325
32966
  "registryName": "lumen",
32326
32967
  "registryVersion": 1,
32327
32968
  "schemaVersion": 4,
32328
- "serverVersion": "1.3.0"
32969
+ "serverVersion": "1.4.0"
32329
32970
  },
32330
32971
  "nativeComponents": [
32331
32972
  {
@@ -32702,7 +33343,7 @@
32702
33343
  "language": "kotlin",
32703
33344
  "packageName": "com.santi020k:lumen-compose",
32704
33345
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
32705
- "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt",
33346
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
32706
33347
  "symbol": "LumenIcon"
32707
33348
  },
32708
33349
  "react-native": {
@@ -33722,7 +34363,7 @@
33722
34363
  "language": "kotlin",
33723
34364
  "packageName": "com.santi020k:lumen-compose",
33724
34365
  "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
33725
- "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/SharedComponents.kt",
34366
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
33726
34367
  "symbol": "LumenAlert"
33727
34368
  },
33728
34369
  "react-native": {
@@ -33796,7 +34437,7 @@
33796
34437
  "language": "swift",
33797
34438
  "packageName": "LumenUI",
33798
34439
  "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
33799
- "sourceFile": "packages/swift/Sources/LumenUI/SharedComponents.swift",
34440
+ "sourceFile": "packages/swift/Sources/LumenUI/OverlayComponents.swift",
33800
34441
  "symbol": "LumenAlert"
33801
34442
  }
33802
34443
  },
@@ -35965,6 +36606,129 @@
35965
36606
  "summary": "Choose one value from a compact row of short peer options.",
35966
36607
  "tier": "shared"
35967
36608
  },
36609
+ {
36610
+ "accessibility": "The group has a readable navigation label and every destination exposes selected and disabled state through native tab semantics.",
36611
+ "category": "Navigation",
36612
+ "contract": "Controlled selection from a small set of peer app destinations",
36613
+ "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.",
36614
+ "id": "navigation-bar",
36615
+ "implementations": {
36616
+ "compose": {
36617
+ "api": [
36618
+ {
36619
+ "defaultValue": "Required",
36620
+ "description": "Provides destination values, short labels, native icons, and disabled state.",
36621
+ "name": "items",
36622
+ "values": "List<LumenNavigationItem<Value>>"
36623
+ },
36624
+ {
36625
+ "defaultValue": "Required",
36626
+ "description": "Stores the active destination.",
36627
+ "name": "selectedValue",
36628
+ "values": "Value"
36629
+ },
36630
+ {
36631
+ "defaultValue": "Required",
36632
+ "description": "Requests a destination change without owning navigation history.",
36633
+ "name": "onValueChange",
36634
+ "values": "(Value) -> Unit"
36635
+ },
36636
+ {
36637
+ "defaultValue": "Primary navigation",
36638
+ "description": "Names the destination group for assistive technology.",
36639
+ "name": "accessibilityLabel",
36640
+ "values": "String"
36641
+ }
36642
+ ],
36643
+ "example": "LumenNavigationBar(\n items = destinations,\n selectedValue = destination,\n onValueChange = ::setDestination\n)",
36644
+ "exportName": "LumenNavigationBar",
36645
+ "importStatement": "import com.santi020k.lumen.LumenNavigationBar",
36646
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
36647
+ "language": "kotlin",
36648
+ "packageName": "com.santi020k:lumen-compose",
36649
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
36650
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/PlatformComponents.kt",
36651
+ "symbol": "LumenNavigationBar"
36652
+ },
36653
+ "react-native": {
36654
+ "api": [
36655
+ {
36656
+ "defaultValue": "Required",
36657
+ "description": "Provides destination values, short labels, native icons, and disabled state.",
36658
+ "name": "items",
36659
+ "values": "readonly LumenNavigationItem[]"
36660
+ },
36661
+ {
36662
+ "defaultValue": "Required",
36663
+ "description": "Stores the active destination.",
36664
+ "name": "value",
36665
+ "values": "string"
36666
+ },
36667
+ {
36668
+ "defaultValue": "Required",
36669
+ "description": "Requests a destination change without owning navigation history.",
36670
+ "name": "onValueChange",
36671
+ "values": "(string) => void"
36672
+ },
36673
+ {
36674
+ "defaultValue": "Primary navigation",
36675
+ "description": "Names the destination group for assistive technology.",
36676
+ "name": "accessibilityLabel",
36677
+ "values": "String"
36678
+ }
36679
+ ],
36680
+ "example": "<LumenNavigationBar\n items={destinations}\n value={destination}\n onValueChange={setDestination}\n/>",
36681
+ "exportName": "LumenNavigationBar",
36682
+ "importStatement": "import { LumenNavigationBar } from '@santi020k/lumen-react-native'",
36683
+ "install": "pnpm add @santi020k/lumen-react-native",
36684
+ "language": "tsx",
36685
+ "packageName": "@santi020k/lumen-react-native",
36686
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
36687
+ "sourceFile": "packages/react-native/src/index.ts",
36688
+ "symbol": "LumenNavigationBar"
36689
+ },
36690
+ "swiftui": {
36691
+ "api": [
36692
+ {
36693
+ "defaultValue": "Required",
36694
+ "description": "Provides destination values, short labels, native icons, and disabled state.",
36695
+ "name": "items",
36696
+ "values": "[LumenNavigationItem<Selection>]"
36697
+ },
36698
+ {
36699
+ "defaultValue": "Required",
36700
+ "description": "Stores the active destination.",
36701
+ "name": "selection",
36702
+ "values": "Binding<Selection>"
36703
+ },
36704
+ {
36705
+ "defaultValue": "Required",
36706
+ "description": "Requests a destination change without owning navigation history.",
36707
+ "name": "onValueChange",
36708
+ "values": "Binding setter"
36709
+ },
36710
+ {
36711
+ "defaultValue": "Primary navigation",
36712
+ "description": "Names the destination group for assistive technology.",
36713
+ "name": "label",
36714
+ "values": "String"
36715
+ }
36716
+ ],
36717
+ "example": "LumenNavigationBar(\n selection: $destination,\n items: destinations\n)",
36718
+ "exportName": "LumenNavigationBar",
36719
+ "importStatement": "import LumenUI",
36720
+ "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.",
36721
+ "language": "swift",
36722
+ "packageName": "LumenUI",
36723
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
36724
+ "sourceFile": "packages/swift/Sources/LumenUI/NavigationComponents.swift",
36725
+ "symbol": "LumenNavigationBar"
36726
+ }
36727
+ },
36728
+ "name": "Navigation bar",
36729
+ "summary": "Select among peer application destinations with native visuals and controlled state.",
36730
+ "tier": "shared"
36731
+ },
35968
36732
  {
35969
36733
  "accessibility": "The title, description, optional graphic, and recovery actions remain in a readable native order.",
35970
36734
  "category": "Feedback",
@@ -37074,15 +37838,913 @@
37074
37838
  "macOS"
37075
37839
  ],
37076
37840
  "tier": "platform-specific"
37841
+ },
37842
+ {
37843
+ "accessibility": "Decorative graphics stay hidden from assistive technology; an optional label exposes the complete composition as one image.",
37844
+ "category": "Data display",
37845
+ "contract": "Token-aware glow, grid, and orbit presets around application-provided artwork",
37846
+ "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.",
37847
+ "id": "graphic",
37848
+ "implementations": {
37849
+ "compose": {
37850
+ "api": [
37851
+ {
37852
+ "defaultValue": "orbit",
37853
+ "description": "Selects the decorative composition.",
37854
+ "name": "variant",
37855
+ "values": "glow · grid · orbit"
37856
+ },
37857
+ {
37858
+ "defaultValue": "md",
37859
+ "description": "Selects a shared native dimension.",
37860
+ "name": "size",
37861
+ "values": "sm · md · lg"
37862
+ },
37863
+ {
37864
+ "defaultValue": "brand",
37865
+ "description": "Selects the semantic decoration color.",
37866
+ "name": "tone",
37867
+ "values": "brand · accent · neutral"
37868
+ },
37869
+ {
37870
+ "defaultValue": "null",
37871
+ "description": "Optionally exposes the composition as one labeled image.",
37872
+ "name": "label",
37873
+ "values": "String?"
37874
+ }
37875
+ ],
37876
+ "example": "LumenGraphic(\n variant = LumenGraphicVariant.Orbit,\n label = \"Project overview\"\n) {\n ProjectArtwork()\n}",
37877
+ "exportName": "LumenGraphic",
37878
+ "importStatement": "import com.santi020k.lumen.LumenGraphic",
37879
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
37880
+ "language": "kotlin",
37881
+ "packageName": "com.santi020k:lumen-compose",
37882
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
37883
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/ContentComponents.kt",
37884
+ "symbol": "LumenGraphic"
37885
+ },
37886
+ "react-native": {
37887
+ "api": [
37888
+ {
37889
+ "defaultValue": "orbit",
37890
+ "description": "Selects the decorative composition.",
37891
+ "name": "variant",
37892
+ "values": "glow · grid · orbit"
37893
+ },
37894
+ {
37895
+ "defaultValue": "md",
37896
+ "description": "Selects a shared native dimension.",
37897
+ "name": "size",
37898
+ "values": "sm · md · lg"
37899
+ },
37900
+ {
37901
+ "defaultValue": "brand",
37902
+ "description": "Selects the semantic decoration color.",
37903
+ "name": "tone",
37904
+ "values": "brand · accent · neutral"
37905
+ },
37906
+ {
37907
+ "defaultValue": "undefined",
37908
+ "description": "Optionally exposes the composition as one labeled image.",
37909
+ "name": "label",
37910
+ "values": "string"
37911
+ }
37912
+ ],
37913
+ "example": "<LumenGraphic variant=\"orbit\" label=\"Project overview\">\n <ProjectArtwork />\n</LumenGraphic>",
37914
+ "exportName": "LumenGraphic",
37915
+ "importStatement": "import { LumenGraphic } from '@santi020k/lumen-react-native'",
37916
+ "install": "pnpm add @santi020k/lumen-react-native",
37917
+ "language": "tsx",
37918
+ "packageName": "@santi020k/lumen-react-native",
37919
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
37920
+ "sourceFile": "packages/react-native/src/content-components.tsx",
37921
+ "symbol": "LumenGraphic"
37922
+ },
37923
+ "swiftui": {
37924
+ "api": [
37925
+ {
37926
+ "defaultValue": "orbit",
37927
+ "description": "Selects the decorative composition.",
37928
+ "name": "variant",
37929
+ "values": "glow · grid · orbit"
37930
+ },
37931
+ {
37932
+ "defaultValue": "md",
37933
+ "description": "Selects a shared native dimension.",
37934
+ "name": "size",
37935
+ "values": "sm · md · lg"
37936
+ },
37937
+ {
37938
+ "defaultValue": "brand",
37939
+ "description": "Selects the semantic decoration color.",
37940
+ "name": "tone",
37941
+ "values": "brand · accent · neutral"
37942
+ },
37943
+ {
37944
+ "defaultValue": "nil",
37945
+ "description": "Optionally exposes the composition as one labeled image.",
37946
+ "name": "label",
37947
+ "values": "LocalizedStringKey?"
37948
+ }
37949
+ ],
37950
+ "example": "LumenGraphic(\n variant: .orbit,\n label: \"Project overview\"\n) {\n ProjectArtwork()\n}",
37951
+ "exportName": "LumenGraphic",
37952
+ "importStatement": "import LumenUI",
37953
+ "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.",
37954
+ "language": "swift",
37955
+ "packageName": "LumenUI",
37956
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
37957
+ "sourceFile": "packages/swift/Sources/LumenUI/ContentComponents.swift",
37958
+ "symbol": "LumenGraphic"
37959
+ }
37960
+ },
37961
+ "name": "Graphic",
37962
+ "summary": "Frame application-provided artwork with token-aware glow, grid, or orbit decoration.",
37963
+ "tier": "shared"
37964
+ },
37965
+ {
37966
+ "accessibility": "The ambient layer has no accessibility semantics; child content keeps its native reading order, names, and controls.",
37967
+ "category": "Data display",
37968
+ "contract": "Ambient aurora, dots, grid, and rays behind application content with semantic tones and intensity",
37969
+ "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.",
37970
+ "id": "backdrop",
37971
+ "implementations": {
37972
+ "compose": {
37973
+ "api": [
37974
+ {
37975
+ "defaultValue": "aurora",
37976
+ "description": "Selects the ambient pattern.",
37977
+ "name": "variant",
37978
+ "values": "aurora · dots · grid · rays"
37979
+ },
37980
+ {
37981
+ "defaultValue": "brand",
37982
+ "description": "Selects the semantic decoration color.",
37983
+ "name": "tone",
37984
+ "values": "brand · accent · neutral"
37985
+ },
37986
+ {
37987
+ "defaultValue": "medium",
37988
+ "description": "Controls only the decoration opacity.",
37989
+ "name": "intensity",
37990
+ "values": "subtle · medium · strong"
37991
+ },
37992
+ {
37993
+ "defaultValue": "Required",
37994
+ "description": "Renders meaningful content above the decorative layer.",
37995
+ "name": "content",
37996
+ "values": "Native view content"
37997
+ }
37998
+ ],
37999
+ "example": "LumenBackdrop(\n intensity = LumenBackdropIntensity.Medium,\n variant = LumenBackdropVariant.Aurora\n) {\n ProjectOverview()\n}",
38000
+ "exportName": "LumenBackdrop",
38001
+ "importStatement": "import com.santi020k.lumen.LumenBackdrop",
38002
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38003
+ "language": "kotlin",
38004
+ "packageName": "com.santi020k:lumen-compose",
38005
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38006
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/ContentComponents.kt",
38007
+ "symbol": "LumenBackdrop"
38008
+ },
38009
+ "react-native": {
38010
+ "api": [
38011
+ {
38012
+ "defaultValue": "aurora",
38013
+ "description": "Selects the ambient pattern.",
38014
+ "name": "variant",
38015
+ "values": "aurora · dots · grid · rays"
38016
+ },
38017
+ {
38018
+ "defaultValue": "brand",
38019
+ "description": "Selects the semantic decoration color.",
38020
+ "name": "tone",
38021
+ "values": "brand · accent · neutral"
38022
+ },
38023
+ {
38024
+ "defaultValue": "medium",
38025
+ "description": "Controls only the decoration opacity.",
38026
+ "name": "intensity",
38027
+ "values": "subtle · medium · strong"
38028
+ },
38029
+ {
38030
+ "defaultValue": "Required",
38031
+ "description": "Renders meaningful content above the decorative layer.",
38032
+ "name": "content",
38033
+ "values": "Native view content"
38034
+ }
38035
+ ],
38036
+ "example": "<LumenBackdrop intensity=\"medium\" variant=\"aurora\">\n <ProjectOverview />\n</LumenBackdrop>",
38037
+ "exportName": "LumenBackdrop",
38038
+ "importStatement": "import { LumenBackdrop } from '@santi020k/lumen-react-native'",
38039
+ "install": "pnpm add @santi020k/lumen-react-native",
38040
+ "language": "tsx",
38041
+ "packageName": "@santi020k/lumen-react-native",
38042
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38043
+ "sourceFile": "packages/react-native/src/content-components.tsx",
38044
+ "symbol": "LumenBackdrop"
38045
+ },
38046
+ "swiftui": {
38047
+ "api": [
38048
+ {
38049
+ "defaultValue": "aurora",
38050
+ "description": "Selects the ambient pattern.",
38051
+ "name": "variant",
38052
+ "values": "aurora · dots · grid · rays"
38053
+ },
38054
+ {
38055
+ "defaultValue": "brand",
38056
+ "description": "Selects the semantic decoration color.",
38057
+ "name": "tone",
38058
+ "values": "brand · accent · neutral"
38059
+ },
38060
+ {
38061
+ "defaultValue": "medium",
38062
+ "description": "Controls only the decoration opacity.",
38063
+ "name": "intensity",
38064
+ "values": "subtle · medium · strong"
38065
+ },
38066
+ {
38067
+ "defaultValue": "Required",
38068
+ "description": "Renders meaningful content above the decorative layer.",
38069
+ "name": "content",
38070
+ "values": "Native view content"
38071
+ }
38072
+ ],
38073
+ "example": "LumenBackdrop(\n intensity: .medium,\n variant: .aurora\n) {\n ProjectOverview()\n}",
38074
+ "exportName": "LumenBackdrop",
38075
+ "importStatement": "import LumenUI",
38076
+ "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.",
38077
+ "language": "swift",
38078
+ "packageName": "LumenUI",
38079
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38080
+ "sourceFile": "packages/swift/Sources/LumenUI/ContentComponents.swift",
38081
+ "symbol": "LumenBackdrop"
38082
+ }
38083
+ },
38084
+ "name": "Backdrop",
38085
+ "summary": "Place a token-aware ambient pattern behind native application content.",
38086
+ "tier": "shared"
38087
+ },
38088
+ {
38089
+ "accessibility": "Illustrations are decorative by default; an optional label exposes the complete scene as one image.",
38090
+ "category": "Feedback",
38091
+ "contract": "Built-in empty, success, error, and offline scenes with shared semantic colors and sizes",
38092
+ "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.",
38093
+ "id": "illustration",
38094
+ "implementations": {
38095
+ "compose": {
38096
+ "api": [
38097
+ {
38098
+ "defaultValue": "empty",
38099
+ "description": "Selects the semantic scene.",
38100
+ "name": "variant",
38101
+ "values": "empty · success · error · offline"
38102
+ },
38103
+ {
38104
+ "defaultValue": "auto",
38105
+ "description": "Uses the state color automatically or an explicit portable tone.",
38106
+ "name": "tone",
38107
+ "values": "auto · brand · accent · neutral"
38108
+ },
38109
+ {
38110
+ "defaultValue": "md",
38111
+ "description": "Selects the shared 96, 128, or 176 unit dimension.",
38112
+ "name": "size",
38113
+ "values": "sm · md · lg"
38114
+ },
38115
+ {
38116
+ "defaultValue": "null",
38117
+ "description": "Optionally exposes the illustration as one labeled image.",
38118
+ "name": "label",
38119
+ "values": "String?"
38120
+ }
38121
+ ],
38122
+ "example": "LumenIllustration(\n variant = LumenIllustrationVariant.Success,\n label = \"Saved successfully\"\n)",
38123
+ "exportName": "LumenIllustration",
38124
+ "importStatement": "import com.santi020k.lumen.LumenIllustration",
38125
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38126
+ "language": "kotlin",
38127
+ "packageName": "com.santi020k:lumen-compose",
38128
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38129
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/ContentComponents.kt",
38130
+ "symbol": "LumenIllustration"
38131
+ },
38132
+ "react-native": {
38133
+ "api": [
38134
+ {
38135
+ "defaultValue": "empty",
38136
+ "description": "Selects the semantic scene.",
38137
+ "name": "variant",
38138
+ "values": "empty · success · error · offline"
38139
+ },
38140
+ {
38141
+ "defaultValue": "auto",
38142
+ "description": "Uses the state color automatically or an explicit portable tone.",
38143
+ "name": "tone",
38144
+ "values": "auto · brand · accent · neutral"
38145
+ },
38146
+ {
38147
+ "defaultValue": "md",
38148
+ "description": "Selects the shared 96, 128, or 176 unit dimension.",
38149
+ "name": "size",
38150
+ "values": "sm · md · lg"
38151
+ },
38152
+ {
38153
+ "defaultValue": "undefined",
38154
+ "description": "Optionally exposes the illustration as one labeled image.",
38155
+ "name": "label",
38156
+ "values": "string"
38157
+ }
38158
+ ],
38159
+ "example": "<LumenIllustration\n label=\"Saved successfully\"\n variant=\"success\"\n/>",
38160
+ "exportName": "LumenIllustration",
38161
+ "importStatement": "import { LumenIllustration } from '@santi020k/lumen-react-native'",
38162
+ "install": "pnpm add @santi020k/lumen-react-native",
38163
+ "language": "tsx",
38164
+ "packageName": "@santi020k/lumen-react-native",
38165
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38166
+ "sourceFile": "packages/react-native/src/content-components.tsx",
38167
+ "symbol": "LumenIllustration"
38168
+ },
38169
+ "swiftui": {
38170
+ "api": [
38171
+ {
38172
+ "defaultValue": "empty",
38173
+ "description": "Selects the semantic scene.",
38174
+ "name": "variant",
38175
+ "values": "empty · success · error · offline"
38176
+ },
38177
+ {
38178
+ "defaultValue": "auto",
38179
+ "description": "Uses the state color automatically or an explicit portable tone.",
38180
+ "name": "tone",
38181
+ "values": "auto · brand · accent · neutral"
38182
+ },
38183
+ {
38184
+ "defaultValue": "md",
38185
+ "description": "Selects the shared 96, 128, or 176 unit dimension.",
38186
+ "name": "size",
38187
+ "values": "sm · md · lg"
38188
+ },
38189
+ {
38190
+ "defaultValue": "nil",
38191
+ "description": "Optionally exposes the illustration as one labeled image.",
38192
+ "name": "label",
38193
+ "values": "String?"
38194
+ }
38195
+ ],
38196
+ "example": "LumenIllustration(\n variant: .success,\n label: \"Saved successfully\"\n)",
38197
+ "exportName": "LumenIllustration",
38198
+ "importStatement": "import LumenUI",
38199
+ "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.",
38200
+ "language": "swift",
38201
+ "packageName": "LumenUI",
38202
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38203
+ "sourceFile": "packages/swift/Sources/LumenUI/ContentComponents.swift",
38204
+ "symbol": "LumenIllustration"
38205
+ }
38206
+ },
38207
+ "name": "Illustration",
38208
+ "summary": "Render a built-in empty, success, error, or offline semantic scene.",
38209
+ "tier": "shared"
38210
+ },
38211
+ {
38212
+ "accessibility": "Native modal focus stays inside the confirmation surface and cancel and confirm remain independently named actions.",
38213
+ "category": "Feedback",
38214
+ "contract": "Controlled confirmation with cancel, confirm, destructive, disabled, and loading states",
38215
+ "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.",
38216
+ "id": "alert-dialog",
38217
+ "implementations": {
38218
+ "compose": {
38219
+ "api": [
38220
+ {
38221
+ "defaultValue": "Required",
38222
+ "description": "Controls native modal presentation.",
38223
+ "name": "visible",
38224
+ "values": "Boolean"
38225
+ },
38226
+ {
38227
+ "defaultValue": "Description optional",
38228
+ "description": "Explains the decision.",
38229
+ "name": "title / description",
38230
+ "values": "Native localized text"
38231
+ },
38232
+ {
38233
+ "defaultValue": "Cancel label: Cancel",
38234
+ "description": "Names both actions.",
38235
+ "name": "confirmLabel / cancelLabel",
38236
+ "values": "Native localized text"
38237
+ },
38238
+ {
38239
+ "defaultValue": "false",
38240
+ "description": "Communicates whether the confirm action is destructive.",
38241
+ "name": "destructive",
38242
+ "values": "Boolean"
38243
+ },
38244
+ {
38245
+ "defaultValue": "true",
38246
+ "description": "Controls whether the confirm action can be activated.",
38247
+ "name": "confirmEnabled",
38248
+ "values": "Boolean"
38249
+ },
38250
+ {
38251
+ "defaultValue": "false",
38252
+ "description": "Communicates asynchronous confirmation progress.",
38253
+ "name": "confirmLoading",
38254
+ "values": "Boolean"
38255
+ }
38256
+ ],
38257
+ "example": "LumenAlertDialog(\n visible = showConfirmation,\n title = \"Delete report?\",\n confirmLabel = \"Delete\",\n destructive = true,\n onConfirm = ::deleteReport,\n onDismiss = { showConfirmation = false }\n)",
38258
+ "exportName": "LumenAlertDialog",
38259
+ "importStatement": "import com.santi020k.lumen.LumenAlertDialog",
38260
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38261
+ "language": "kotlin",
38262
+ "packageName": "com.santi020k:lumen-compose",
38263
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38264
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
38265
+ "symbol": "LumenAlertDialog"
38266
+ },
38267
+ "react-native": {
38268
+ "api": [
38269
+ {
38270
+ "defaultValue": "Required",
38271
+ "description": "Controls native modal presentation.",
38272
+ "name": "visible",
38273
+ "values": "boolean"
38274
+ },
38275
+ {
38276
+ "defaultValue": "Description optional",
38277
+ "description": "Explains the decision.",
38278
+ "name": "title / description",
38279
+ "values": "Native localized text"
38280
+ },
38281
+ {
38282
+ "defaultValue": "Cancel label: Cancel",
38283
+ "description": "Names both actions.",
38284
+ "name": "confirmLabel / cancelLabel",
38285
+ "values": "Native localized text"
38286
+ },
38287
+ {
38288
+ "defaultValue": "false",
38289
+ "description": "Communicates whether the confirm action is destructive.",
38290
+ "name": "destructive",
38291
+ "values": "boolean"
38292
+ },
38293
+ {
38294
+ "defaultValue": "false",
38295
+ "description": "Controls whether the confirm action can be activated.",
38296
+ "name": "confirmDisabled",
38297
+ "values": "boolean"
38298
+ },
38299
+ {
38300
+ "defaultValue": "false",
38301
+ "description": "Communicates asynchronous confirmation progress.",
38302
+ "name": "confirmLoading",
38303
+ "values": "boolean"
38304
+ }
38305
+ ],
38306
+ "example": "<LumenAlertDialog\n visible={showConfirmation}\n title=\"Delete report?\"\n confirmLabel=\"Delete\"\n destructive\n onConfirm={deleteReport}\n onDismiss={() => setShowConfirmation(false)}\n/>",
38307
+ "exportName": "LumenAlertDialog",
38308
+ "importStatement": "import { LumenAlertDialog } from '@santi020k/lumen-react-native'",
38309
+ "install": "pnpm add @santi020k/lumen-react-native",
38310
+ "language": "tsx",
38311
+ "packageName": "@santi020k/lumen-react-native",
38312
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38313
+ "sourceFile": "packages/react-native/src/index.ts",
38314
+ "symbol": "LumenAlertDialog"
38315
+ },
38316
+ "swiftui": {
38317
+ "api": [
38318
+ {
38319
+ "defaultValue": "Required",
38320
+ "description": "Controls native modal presentation.",
38321
+ "name": "isPresented",
38322
+ "values": "Binding<Bool>"
38323
+ },
38324
+ {
38325
+ "defaultValue": "Description optional",
38326
+ "description": "Explains the decision.",
38327
+ "name": "title / description",
38328
+ "values": "Native localized text"
38329
+ },
38330
+ {
38331
+ "defaultValue": "Cancel label: Cancel",
38332
+ "description": "Names both actions.",
38333
+ "name": "confirmLabel / cancelLabel",
38334
+ "values": "Native localized text"
38335
+ },
38336
+ {
38337
+ "defaultValue": "nil",
38338
+ "description": "Communicates whether the confirm action is destructive.",
38339
+ "name": "confirmRole",
38340
+ "values": "ButtonRole?"
38341
+ },
38342
+ {
38343
+ "defaultValue": "false",
38344
+ "description": "Controls whether the confirm action can be activated.",
38345
+ "name": "confirmDisabled",
38346
+ "values": "Bool"
38347
+ },
38348
+ {
38349
+ "defaultValue": "false",
38350
+ "description": "Communicates asynchronous confirmation progress.",
38351
+ "name": "confirmLoading",
38352
+ "values": "Bool"
38353
+ }
38354
+ ],
38355
+ "example": "content.lumenAlertDialog(\n isPresented: $showConfirmation,\n title: \"Delete report?\",\n confirmLabel: \"Delete\",\n confirmRole: .destructive,\n onConfirm: deleteReport\n)",
38356
+ "exportName": "lumenAlertDialog",
38357
+ "importStatement": "import LumenUI",
38358
+ "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.",
38359
+ "language": "swift",
38360
+ "packageName": "LumenUI",
38361
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38362
+ "sourceFile": "packages/swift/Sources/LumenUI/OverlayComponents.swift",
38363
+ "symbol": "lumenAlertDialog"
38364
+ }
38365
+ },
38366
+ "name": "Alert dialog",
38367
+ "summary": "Request a controlled native confirmation with explicit cancel and confirm states.",
38368
+ "tier": "shared"
38369
+ },
38370
+ {
38371
+ "accessibility": "The native modal presentation contains focus while preserving the semantics and reading order of application-owned content.",
38372
+ "category": "Layout",
38373
+ "contract": "Controlled supplemental surface with optional heading, description, actions, and native dismissal",
38374
+ "guidance": "Use for supplemental editing or detail that should not replace the current screen. Keep form state and dismissal decisions application-owned.",
38375
+ "id": "sheet",
38376
+ "implementations": {
38377
+ "compose": {
38378
+ "api": [
38379
+ {
38380
+ "defaultValue": "Required",
38381
+ "description": "Controls native sheet presentation.",
38382
+ "name": "visible",
38383
+ "values": "Boolean"
38384
+ },
38385
+ {
38386
+ "defaultValue": "nil / undefined",
38387
+ "description": "Provides optional heading context.",
38388
+ "name": "title / description",
38389
+ "values": "Native localized text"
38390
+ },
38391
+ {
38392
+ "defaultValue": "Required",
38393
+ "description": "Returns presentation ownership to the application.",
38394
+ "name": "onDismiss",
38395
+ "values": "() -> Void"
38396
+ },
38397
+ {
38398
+ "defaultValue": "Actions optional",
38399
+ "description": "Composes application-owned content and actions.",
38400
+ "name": "content / actions",
38401
+ "values": "Native view slots"
38402
+ }
38403
+ ],
38404
+ "example": "LumenSheet(\n visible = showEditor,\n onDismiss = { showEditor = false },\n title = \"Edit report\"\n) {\n ReportEditor()\n}",
38405
+ "exportName": "LumenSheet",
38406
+ "importStatement": "import com.santi020k.lumen.LumenSheet",
38407
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38408
+ "language": "kotlin",
38409
+ "packageName": "com.santi020k:lumen-compose",
38410
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38411
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
38412
+ "symbol": "LumenSheet"
38413
+ },
38414
+ "react-native": {
38415
+ "api": [
38416
+ {
38417
+ "defaultValue": "Required",
38418
+ "description": "Controls native sheet presentation.",
38419
+ "name": "visible",
38420
+ "values": "boolean"
38421
+ },
38422
+ {
38423
+ "defaultValue": "nil / undefined",
38424
+ "description": "Provides optional heading context.",
38425
+ "name": "title / description",
38426
+ "values": "Native localized text"
38427
+ },
38428
+ {
38429
+ "defaultValue": "Required",
38430
+ "description": "Returns presentation ownership to the application.",
38431
+ "name": "onDismiss",
38432
+ "values": "() -> Void"
38433
+ },
38434
+ {
38435
+ "defaultValue": "Actions optional",
38436
+ "description": "Composes application-owned content and actions.",
38437
+ "name": "content / actions",
38438
+ "values": "Native view slots"
38439
+ }
38440
+ ],
38441
+ "example": "<LumenSheet\n visible={showEditor}\n title=\"Edit report\"\n onDismiss={() => setShowEditor(false)}\n>\n <ReportEditor />\n</LumenSheet>",
38442
+ "exportName": "LumenSheet",
38443
+ "importStatement": "import { LumenSheet } from '@santi020k/lumen-react-native'",
38444
+ "install": "pnpm add @santi020k/lumen-react-native",
38445
+ "language": "tsx",
38446
+ "packageName": "@santi020k/lumen-react-native",
38447
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38448
+ "sourceFile": "packages/react-native/src/index.ts",
38449
+ "symbol": "LumenSheet"
38450
+ },
38451
+ "swiftui": {
38452
+ "api": [
38453
+ {
38454
+ "defaultValue": "Required",
38455
+ "description": "Controls native sheet presentation.",
38456
+ "name": "isPresented",
38457
+ "values": "Binding<Bool>"
38458
+ },
38459
+ {
38460
+ "defaultValue": "nil / undefined",
38461
+ "description": "Provides optional heading context.",
38462
+ "name": "title / description",
38463
+ "values": "Native localized text"
38464
+ },
38465
+ {
38466
+ "defaultValue": "Required",
38467
+ "description": "Returns presentation ownership to the application.",
38468
+ "name": "onDismiss",
38469
+ "values": "() -> Void"
38470
+ },
38471
+ {
38472
+ "defaultValue": "Actions optional",
38473
+ "description": "Composes application-owned content and actions.",
38474
+ "name": "content / actions",
38475
+ "values": "Native view slots"
38476
+ }
38477
+ ],
38478
+ "example": "content.lumenSheet(\n isPresented: $showEditor,\n title: \"Edit report\"\n) {\n ReportEditor()\n}",
38479
+ "exportName": "lumenSheet",
38480
+ "importStatement": "import LumenUI",
38481
+ "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.",
38482
+ "language": "swift",
38483
+ "packageName": "LumenUI",
38484
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38485
+ "sourceFile": "packages/swift/Sources/LumenUI/OverlayComponents.swift",
38486
+ "symbol": "lumenSheet"
38487
+ }
38488
+ },
38489
+ "name": "Sheet",
38490
+ "summary": "Present supplemental application content in a controlled native sheet.",
38491
+ "tier": "shared"
38492
+ },
38493
+ {
38494
+ "accessibility": "The trigger exposes expanded state and each native menu action reports its label, disabled state, and destructive role.",
38495
+ "category": "Actions",
38496
+ "contract": "Anchored action menu with labeled disabled and destructive item states",
38497
+ "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.",
38498
+ "id": "menu",
38499
+ "implementations": {
38500
+ "compose": {
38501
+ "api": [
38502
+ {
38503
+ "defaultValue": "Required",
38504
+ "description": "Provides labeled actions and states.",
38505
+ "name": "items",
38506
+ "values": "Native Lumen menu item collection"
38507
+ },
38508
+ {
38509
+ "defaultValue": "Collapsed",
38510
+ "description": "Controls or reports native menu presentation.",
38511
+ "name": "expanded",
38512
+ "values": "Boolean"
38513
+ },
38514
+ {
38515
+ "defaultValue": "false",
38516
+ "description": "Keeps an unavailable item readable but inactive.",
38517
+ "name": "disabled",
38518
+ "values": "Boolean"
38519
+ },
38520
+ {
38521
+ "defaultValue": "false / nil",
38522
+ "description": "Marks a destructive action semantically and visually.",
38523
+ "name": "destructive / role",
38524
+ "values": "Destructive action role"
38525
+ }
38526
+ ],
38527
+ "example": "LumenMenu(\n expanded = showMenu,\n onDismissRequest = { showMenu = false },\n items = actions\n)",
38528
+ "exportName": "LumenMenu",
38529
+ "importStatement": "import com.santi020k.lumen.LumenMenu",
38530
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38531
+ "language": "kotlin",
38532
+ "packageName": "com.santi020k:lumen-compose",
38533
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38534
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
38535
+ "symbol": "LumenMenu"
38536
+ },
38537
+ "react-native": {
38538
+ "api": [
38539
+ {
38540
+ "defaultValue": "Required",
38541
+ "description": "Provides labeled actions and states.",
38542
+ "name": "items",
38543
+ "values": "Native Lumen menu item collection"
38544
+ },
38545
+ {
38546
+ "defaultValue": "Collapsed",
38547
+ "description": "Controls or reports native menu presentation.",
38548
+ "name": "Internal trigger state",
38549
+ "values": "Native Modal"
38550
+ },
38551
+ {
38552
+ "defaultValue": "false",
38553
+ "description": "Keeps an unavailable item readable but inactive.",
38554
+ "name": "disabled",
38555
+ "values": "Boolean"
38556
+ },
38557
+ {
38558
+ "defaultValue": "false / nil",
38559
+ "description": "Marks a destructive action semantically and visually.",
38560
+ "name": "destructive / role",
38561
+ "values": "Destructive action role"
38562
+ }
38563
+ ],
38564
+ "example": "<LumenMenu\n accessibilityLabel=\"More actions\"\n trigger={<MoreIcon />}\n items={actions}\n/>",
38565
+ "exportName": "LumenMenu",
38566
+ "importStatement": "import { LumenMenu } from '@santi020k/lumen-react-native'",
38567
+ "install": "pnpm add @santi020k/lumen-react-native",
38568
+ "language": "tsx",
38569
+ "packageName": "@santi020k/lumen-react-native",
38570
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38571
+ "sourceFile": "packages/react-native/src/index.ts",
38572
+ "symbol": "LumenMenu"
38573
+ },
38574
+ "swiftui": {
38575
+ "api": [
38576
+ {
38577
+ "defaultValue": "Required",
38578
+ "description": "Provides labeled actions and states.",
38579
+ "name": "items",
38580
+ "values": "Native Lumen menu item collection"
38581
+ },
38582
+ {
38583
+ "defaultValue": "Collapsed",
38584
+ "description": "Controls or reports native menu presentation.",
38585
+ "name": "Native Menu state",
38586
+ "values": "Native"
38587
+ },
38588
+ {
38589
+ "defaultValue": "false",
38590
+ "description": "Keeps an unavailable item readable but inactive.",
38591
+ "name": "disabled",
38592
+ "values": "Boolean"
38593
+ },
38594
+ {
38595
+ "defaultValue": "false / nil",
38596
+ "description": "Marks a destructive action semantically and visually.",
38597
+ "name": "destructive / role",
38598
+ "values": "Destructive action role"
38599
+ }
38600
+ ],
38601
+ "example": "LumenMenu(items: actions) {\n Label(\"More\", systemImage: \"ellipsis\")\n}",
38602
+ "exportName": "LumenMenu",
38603
+ "importStatement": "import LumenUI",
38604
+ "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.",
38605
+ "language": "swift",
38606
+ "packageName": "LumenUI",
38607
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38608
+ "sourceFile": "packages/swift/Sources/LumenUI/OverlayComponents.swift",
38609
+ "symbol": "LumenMenu"
38610
+ }
38611
+ },
38612
+ "name": "Menu",
38613
+ "summary": "Present a native anchored action menu with shared item-state semantics.",
38614
+ "tier": "shared"
38615
+ },
38616
+ {
38617
+ "accessibility": "The trigger remains a native labeled button and destination selection is delegated to the operating system share surface.",
38618
+ "category": "Actions",
38619
+ "contract": "Token-aware button that presents the operating system share surface for application-owned content",
38620
+ "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.",
38621
+ "id": "share-button",
38622
+ "implementations": {
38623
+ "compose": {
38624
+ "api": [
38625
+ {
38626
+ "defaultValue": "Required",
38627
+ "description": "Supplies application-owned share content.",
38628
+ "name": "payload",
38629
+ "values": "LumenSharePayload"
38630
+ },
38631
+ {
38632
+ "defaultValue": "Required",
38633
+ "description": "Configures the native share presentation.",
38634
+ "name": "chooserTitle",
38635
+ "values": "String"
38636
+ },
38637
+ {
38638
+ "defaultValue": "Share",
38639
+ "description": "Names the trigger action.",
38640
+ "name": "label",
38641
+ "values": "Native localized text"
38642
+ },
38643
+ {
38644
+ "defaultValue": "No-op",
38645
+ "description": "Lets the application report sharing outcomes where the platform exposes them.",
38646
+ "name": "onFailure",
38647
+ "values": "Platform callback"
38648
+ }
38649
+ ],
38650
+ "example": "LumenShareButton(\n payload = LumenSharePayload(text = reportText),\n chooserTitle = \"Share report\"\n)",
38651
+ "exportName": "LumenShareButton",
38652
+ "importStatement": "import com.santi020k.lumen.LumenShareButton",
38653
+ "install": "Include packages/compose from a shallow Git checkout pinned to the Lumen release tag, then add implementation(project(\":lumen-compose\")).",
38654
+ "language": "kotlin",
38655
+ "packageName": "com.santi020k:lumen-compose",
38656
+ "setup": "Wrap application content in LumenTheme and preserve Compose state and navigation.",
38657
+ "sourceFile": "packages/compose/src/main/kotlin/com/santi020k/lumen/OverlayComponents.kt",
38658
+ "symbol": "LumenShareButton"
38659
+ },
38660
+ "react-native": {
38661
+ "api": [
38662
+ {
38663
+ "defaultValue": "Required",
38664
+ "description": "Supplies application-owned share content.",
38665
+ "name": "content",
38666
+ "values": "ShareContent"
38667
+ },
38668
+ {
38669
+ "defaultValue": "undefined",
38670
+ "description": "Configures the native share presentation.",
38671
+ "name": "options",
38672
+ "values": "ShareOptions"
38673
+ },
38674
+ {
38675
+ "defaultValue": "Share",
38676
+ "description": "Names the trigger action.",
38677
+ "name": "label",
38678
+ "values": "Native localized text"
38679
+ },
38680
+ {
38681
+ "defaultValue": "No-op",
38682
+ "description": "Lets the application report sharing outcomes where the platform exposes them.",
38683
+ "name": "onError / onShared",
38684
+ "values": "Platform callback"
38685
+ }
38686
+ ],
38687
+ "example": "<LumenShareButton\n label=\"Share report\"\n content={{ message: reportText }}\n/>",
38688
+ "exportName": "LumenShareButton",
38689
+ "importStatement": "import { LumenShareButton } from '@santi020k/lumen-react-native'",
38690
+ "install": "pnpm add @santi020k/lumen-react-native",
38691
+ "language": "tsx",
38692
+ "packageName": "@santi020k/lumen-react-native",
38693
+ "setup": "Mount one LumenProvider near the application root. Native adapters do not load CSS or the Astro runtime.",
38694
+ "sourceFile": "packages/react-native/src/index.ts",
38695
+ "symbol": "LumenShareButton"
38696
+ },
38697
+ "swiftui": {
38698
+ "api": [
38699
+ {
38700
+ "defaultValue": "Required",
38701
+ "description": "Supplies application-owned share content.",
38702
+ "name": "item",
38703
+ "values": "Transferable"
38704
+ },
38705
+ {
38706
+ "defaultValue": "System-provided",
38707
+ "description": "Configures the native share presentation.",
38708
+ "name": "Native share title",
38709
+ "values": "System-provided"
38710
+ },
38711
+ {
38712
+ "defaultValue": "Share",
38713
+ "description": "Names the trigger action.",
38714
+ "name": "label",
38715
+ "values": "Native localized text"
38716
+ },
38717
+ {
38718
+ "defaultValue": "No-op",
38719
+ "description": "Lets the application report sharing outcomes where the platform exposes them.",
38720
+ "name": "Native ShareLink result",
38721
+ "values": "Platform callback"
38722
+ }
38723
+ ],
38724
+ "example": "LumenShareButton(\"Share report\", item: reportText)",
38725
+ "exportName": "LumenShareButton",
38726
+ "importStatement": "import LumenUI",
38727
+ "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.",
38728
+ "language": "swift",
38729
+ "packageName": "LumenUI",
38730
+ "setup": "Apply .lumenTheme(...) near the SwiftUI application root and preserve native environment behavior.",
38731
+ "sourceFile": "packages/swift/Sources/LumenUI/OverlayComponents.swift",
38732
+ "symbol": "LumenShareButton"
38733
+ }
38734
+ },
38735
+ "name": "Share button",
38736
+ "summary": "Open the operating system share surface from a token-aware native action.",
38737
+ "tier": "shared"
37077
38738
  }
37078
38739
  ],
37079
38740
  "nativeSources": {
37080
38741
  "compose": {
37081
38742
  "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",
38743
+ "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
38744
  "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
38745
  "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",
38746
+ "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",
38747
+ "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.NavigationBar as MaterialNavigationBar\nimport androidx.compose.material3.NavigationBarItem\nimport androidx.compose.material3.NavigationBarItemDefaults\nimport androidx.compose.material3.SmallFloatingActionButton\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.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\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 enabled: Boolean = true\n)\n\ninternal fun <Value> isLumenNavigationItemSelected(itemValue: Value, selectedValue: Value): Boolean =\n itemValue == selectedValue\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 accessibilityLabel: String = \"Primary navigation\"\n) {\n val colors = LocalLumenTheme.current.colors\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 = { onValueChange(item.value) },\n icon = {\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 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\nenum class LumenFloatingActionButtonIntent {\n Accent,\n Brand,\n Danger\n}\n\nenum class LumenFloatingActionButtonSize {\n Regular,\n Small\n}\n\n@Immutable\ndata class LumenFloatingActionButtonMetrics(\n val dimension: Dp,\n val iconSize: LumenIconSize\n) {\n companion object {\n fun resolve(size: LumenFloatingActionButtonSize): LumenFloatingActionButtonMetrics = when (size) {\n LumenFloatingActionButtonSize.Regular -> LumenFloatingActionButtonMetrics(56.dp, LumenIconSize.Lg)\n LumenFloatingActionButtonSize.Small -> LumenFloatingActionButtonMetrics(40.dp, LumenIconSize.Md)\n }\n }\n}\n\n@Immutable\ninternal data class LumenFloatingActionButtonPalette(\n val background: Color,\n val foreground: Color\n)\n\ninternal fun lumenFloatingActionButtonPalette(\n colors: LumenColorPalette,\n intent: LumenFloatingActionButtonIntent\n): LumenFloatingActionButtonPalette = when (intent) {\n LumenFloatingActionButtonIntent.Accent -> LumenFloatingActionButtonPalette(\n background = colors.accent,\n foreground = colors.canvas\n )\n LumenFloatingActionButtonIntent.Brand -> LumenFloatingActionButtonPalette(\n background = colors.brandSolid,\n foreground = colors.onBrand\n )\n LumenFloatingActionButtonIntent.Danger -> LumenFloatingActionButtonPalette(\n background = colors.danger,\n foreground = colors.onDanger\n )\n}\n\n/**\n * A Material-native floating action button using Lumen semantic colors and icon sizing.\n * The content description is required because floating actions are icon-only.\n */\n@Composable\nfun LumenFloatingActionButton(\n imageVector: ImageVector,\n contentDescription: String,\n onClick: () -> Unit,\n modifier: Modifier = Modifier,\n intent: LumenFloatingActionButtonIntent = LumenFloatingActionButtonIntent.Brand,\n size: LumenFloatingActionButtonSize = LumenFloatingActionButtonSize.Regular\n) {\n val palette = lumenFloatingActionButtonPalette(LocalLumenTheme.current.colors, intent)\n val metrics = LumenFloatingActionButtonMetrics.resolve(size)\n val content: @Composable () -> Unit = {\n LumenIcon(\n imageVector = imageVector,\n contentDescription = contentDescription,\n size = metrics.iconSize,\n tint = palette.foreground\n )\n }\n\n when (size) {\n LumenFloatingActionButtonSize.Regular -> FloatingActionButton(\n onClick = onClick,\n modifier = modifier,\n shape = RoundedCornerShape(LumenRadius.Lg),\n containerColor = palette.background,\n contentColor = palette.foreground,\n content = content\n )\n LumenFloatingActionButtonSize.Small -> SmallFloatingActionButton(\n onClick = onClick,\n modifier = modifier,\n shape = RoundedCornerShape(LumenRadius.Md),\n containerColor = palette.background,\n contentColor = palette.foreground,\n content = content\n )\n }\n}\n",
37086
38748
  "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
38749
  "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
38750
  "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",
@@ -37091,17 +38753,20 @@
37091
38753
  },
37092
38754
  "react-native": {
37093
38755
  "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",
38756
+ "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",
38757
+ "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",
38758
+ "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
38759
  "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
38760
  "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
38761
  "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
38762
  "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",
38763
+ "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 LumenNavigationBar,\n type LumenNavigationBarProps,\n type LumenNavigationItem,\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",
38764
+ "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",
38765
+ "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",
38766
+ "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",
38767
+ "packages/react-native/src/platform-components.tsx": "import { type ComponentRef, type ReactElement, type Ref } from 'react'\nimport {\n type HostInstance,\n Pressable,\n RefreshControl,\n type RefreshControlProps,\n Text,\n type TextStyle,\n View,\n type ViewProps\n} from 'react-native'\n\nimport {\n type LumenRefreshIndicatorTone,\n resolveLumenNavigationItemState,\n resolveLumenRefreshColors\n} from './platform-recipes.js'\nimport {\n LumenIcon,\n type LumenIconGraphic\n} from './primitives.js'\nimport { useLumenTheme } from './theme-context.js'\n\nexport interface LumenNavigationItem {\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 onValueChange: (value: string) => void\n ref?: Ref<HostInstance>\n value: string\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 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 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 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 disabled={item.disabled}\n key={item.value}\n onPress={() => {\n 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 {Graphic ?\n (\n <LumenIcon color={itemState.color} decorative icon={Graphic} size=\"md\" />\n ) :\n null}\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 </View>\n )\n}\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",
38768
+ "packages/react-native/src/platform-recipes.test.ts": "import { describe, expect, test } from 'vitest'\n\nimport {\n resolveLumenNavigationItemState,\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(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 test('resolves selected and disabled destination states', () => {\n expect(resolveLumenNavigationItemState(\n lumenLightTheme.colors,\n 'home',\n 'home',\n false,\n false\n )).toEqual({\n color: lumenLightTheme.colors.brand,\n opacity: 1,\n selected: true\n })\n expect(resolveLumenNavigationItemState(\n lumenDarkTheme.colors,\n 'search',\n 'home',\n true,\n true\n )).toEqual({\n color: lumenDarkTheme.colors.inkMuted,\n opacity: 0.52,\n selected: false\n })\n })\n})\n",
38769
+ "packages/react-native/src/platform-recipes.ts": "import type { LumenTheme } from './theme.js'\n\nexport type LumenRefreshIndicatorTone = 'accent' | 'brand' | 'neutral'\n\nexport interface LumenNavigationItemState {\n color: string\n opacity: number\n selected: boolean\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\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
38770
  "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
38771
  "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
38772
  "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",
@@ -37122,10 +38787,12 @@
37122
38787
  "swiftui": {
37123
38788
  "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
38789
  "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",
38790
+ "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 @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",
37126
38791
  "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
38792
  "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
38793
  "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",
38794
+ "packages/swift/Sources/LumenUI/NavigationComponents.swift": "import SwiftUI\n\npublic struct LumenNavigationItem<Value: Hashable>: Identifiable {\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 disabled: Bool = false\n ) {\n self.label = label\n self.value = value\n self.systemName = systemName\n self.selectedSystemName = selectedSystemName\n self.disabled = disabled\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\n public init(\n _ label: LocalizedStringKey = \"Primary navigation\",\n selection: Binding<Selection>,\n items: [LumenNavigationItem<Selection>]\n ) {\n self.label = label\n _selection = selection\n self.items = items\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 selection = item.value\n } label: {\n VStack(spacing: LumenSpacing.xs) {\n if let systemName = selected ? item.selectedSystemName ?? item.systemName : item.systemName {\n LumenIcon(\n systemName: systemName,\n size: .md,\n color: selected ? theme.colors.brand : theme.colors.inkMuted\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 }\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",
38795
+ "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.\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\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
38796
  "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
38797
  "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
38798
  "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",