From ab110ef069514c18ec9de3e1fbeac5ba6bb1eda7 Mon Sep 17 00:00:00 2001 From: mehdibha Date: Sun, 14 Dec 2025 17:35:31 +0100 Subject: [PATCH 01/48] update lock file --- pnpm-lock.yaml | 646 ------------------------------------------------- 1 file changed, 646 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7955fbb9..0de444ae8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,28 +139,6 @@ importers: specifier: ^4.0.1 version: 4.0.15(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@22.19.2)(typescript@5.9.3))(tsx@4.21.0) - packages/contrast-colors: - dependencies: - apca-w3: - specifier: ^0.1.9 - version: 0.1.9 - chroma-js: - specifier: ^2.4.2 - version: 2.6.0 - ciebase: - specifier: ^0.1.1 - version: 0.1.1 - ciecam02: - specifier: ^0.4.6 - version: 0.4.6 - hsluv: - specifier: ^0.1.0 - version: 0.1.0 - devDependencies: - ava: - specifier: ^6.1.1 - version: 6.4.1(rollup@4.53.3) - packages/db: dependencies: '@dotui/registry': @@ -1700,14 +1678,6 @@ packages: resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} engines: {node: 20 || >=22} - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1730,11 +1700,6 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@mapbox/node-pre-gyp@2.0.3': - resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} - engines: {node: '>=18'} - hasBin: true - '@material/material-color-utilities@0.3.0': resolution: {integrity: sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==} @@ -1854,10 +1819,6 @@ packages: resolution: {integrity: sha512-scSmQBD8eANlMUOglxHrN1JdSW8tDghsPuS83otqealBiIeMukCQMOf/wc0JJjDXomqwNdEQFLXLGHrU6PGxuA==} engines: {node: '>= 20.0.0'} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@puppeteer/browsers@2.11.0': resolution: {integrity: sha512-n6oQX6mYkG8TRPuPXmbPidkUbsSRalhmaaVAQxvH1IkQy63cwsH+kOjB3e4cpCDHg0aSvsiX9bQ4s2VB6mGWUQ==} engines: {node: '>=18'} @@ -2457,15 +2418,6 @@ packages: peerDependencies: react: '>=18.2.0' - '@rollup/pluginutils@5.3.0': - resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - '@rollup/rollup-android-arm-eabi@4.53.3': resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] @@ -2937,11 +2889,6 @@ packages: vue-router: optional: true - '@vercel/nft@0.29.4': - resolution: {integrity: sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==} - engines: {node: '>=18'} - hasBin: true - '@vercel/postgres@0.10.0': resolution: {integrity: sha512-fSD23DxGND40IzSkXjcFcxr53t3Tiym59Is0jSYIFpG4/0f0KO9SGtcp1sXiebvPaGe7N/tU05cH4yt2S6/IPg==} engines: {node: '>=18.14'} @@ -2984,28 +2931,15 @@ packages: '@vitest/utils@4.0.15': resolution: {integrity: sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==} - abbrev@3.0.1: - resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} - engines: {node: ^18.17.0 || >=20.5.0} - accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} - acorn-import-attributes@1.9.5: - resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} - peerDependencies: - acorn: ^8 - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -3059,22 +2993,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-find-index@1.0.2: - resolution: {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==} - engines: {node: '>=0.10.0'} - array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - arrgv@1.0.2: - resolution: {integrity: sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==} - engines: {node: '>=8.0.0'} - - arrify@3.0.0: - resolution: {integrity: sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==} - engines: {node: '>=12'} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -3094,9 +3016,6 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true - async-sema@3.1.1: - resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} - autoprefixer@10.4.22: resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} engines: {node: ^10 || ^12 || >=14} @@ -3104,16 +3023,6 @@ packages: peerDependencies: postcss: ^8.1.0 - ava@6.4.1: - resolution: {integrity: sha512-vxmPbi1gZx9zhAjHBgw81w/iEDKcrokeRk/fqDTyA2DQygZ0o+dUGRHFOtX8RA5N0heGJTTsIk7+xYxitDb61Q==} - engines: {node: ^18.18 || ^20.8 || ^22 || ^23 || >=24} - hasBin: true - peerDependencies: - '@ava/typescript': '*' - peerDependenciesMeta: - '@ava/typescript': - optional: true - b4a@1.7.3: resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} peerDependencies: @@ -3225,12 +3134,6 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - blueimp-md5@2.19.0: - resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} - body-parser@2.2.1: resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} engines: {node: '>=18'} @@ -3273,17 +3176,9 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - callsites@4.2.0: - resolution: {integrity: sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==} - engines: {node: '>=12.20'} - caniuse-lite@1.0.30001760: resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} - cbor@10.0.11: - resolution: {integrity: sha512-vIwORDd/WyB8Nc23o2zNN5RrtFGlR6Fca61TtjkUXueI3Jf2DOZDl1zsshvBntZ3wZHBM9ztjnkXSmzQDaq3WA==} - engines: {node: '>=20'} - ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -3314,10 +3209,6 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - chroma-js@2.6.0: resolution: {integrity: sha512-BLHvCB9s8Z1EV4ethr6xnkl/P2YRFOGqfgvuMG/MyCbZPrTA+NeiByY6XvgF0zP4/2deU2CXnWyMa3zu1LqQ3A==} @@ -3326,20 +3217,10 @@ packages: peerDependencies: devtools-protocol: '*' - chunkd@2.0.1: - resolution: {integrity: sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==} - ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - ci-info@4.3.1: - resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} - engines: {node: '>=8'} - - ci-parallel-vars@1.0.1: - resolution: {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==} - ciebase@0.1.1: resolution: {integrity: sha512-KEnf/WVT5E+Gn+LsfMHGJFATltSOeW0qm7dpzQYL/Qe5PEpyTM+UI5bMoBo16FE81zMpu+U6032USTP6Cz1XNA==} @@ -3354,10 +3235,6 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} - cli-truncate@4.0.0: - resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} - engines: {node: '>=18'} - cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -3380,10 +3257,6 @@ packages: code-block-writer@13.0.3: resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} - code-excerpt@4.0.0: - resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} @@ -3408,20 +3281,9 @@ packages: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} - common-path-prefix@3.0.0: - resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} - compute-scroll-into-view@3.1.1: resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} - concordance@5.0.4: - resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} - engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -3436,10 +3298,6 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - convert-to-spaces@2.0.1: - resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -3489,10 +3347,6 @@ packages: resolution: {integrity: sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - currently-unhandled@0.4.1: - resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} - engines: {node: '>=0.10.0'} - data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -3504,10 +3358,6 @@ packages: dataloader@1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} - date-time@3.1.0: - resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} - engines: {node: '>=6'} - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -3698,9 +3548,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - eciesjs@0.4.16: resolution: {integrity: sha512-dS5cbA9rA2VR4Ybuvhg6jvdmp46ubLn3E+px8cG/35aEDNclrqoCjg6mt0HYZ/M+OoESS3jSkCrqk1kWAEhWAw==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} @@ -3711,19 +3558,12 @@ packages: electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} - emittery@1.2.0: - resolution: {integrity: sha512-KxdRyyFcS85pH3dnU8Y5yFUm2YJdaHwcBZWrfG8o89ZY9a13/f9itbN+YG3ELbBo9Pg5zvIozstmuV8bX13q6g==} - engines: {node: '>=14.16'} - emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -3797,10 +3637,6 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -3844,9 +3680,6 @@ packages: estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -3909,9 +3742,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -3945,9 +3775,6 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -3959,18 +3786,10 @@ packages: find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - find-up-simple@1.0.1: - resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} - engines: {node: '>=18'} - find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -4136,10 +3955,6 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - hasBin: true - glob@13.0.0: resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} engines: {node: 20 || >=22} @@ -4243,10 +4058,6 @@ packages: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} engines: {node: '>=0.10.0'} - ignore-by-default@2.1.0: - resolution: {integrity: sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==} - engines: {node: '>=10 <11 || >=12 <13 || >=14'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -4267,14 +4078,6 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -4292,10 +4095,6 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - irregular-plurals@3.5.0: - resolution: {integrity: sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==} - engines: {node: '>=8'} - is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -4324,10 +4123,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -4354,10 +4149,6 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -4416,9 +4207,6 @@ packages: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -4450,10 +4238,6 @@ packages: react: optional: true - js-string-escape@1.0.1: - resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} - engines: {node: '>= 0.8'} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4584,10 +4368,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - load-json-file@7.0.1: - resolution: {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -4605,9 +4385,6 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.4: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} @@ -4659,18 +4436,10 @@ packages: vue: optional: true - matcher@5.0.0: - resolution: {integrity: sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - md5-hex@3.0.1: - resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} - engines: {node: '>=8'} - mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -4723,10 +4492,6 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} - memoize@10.2.0: - resolution: {integrity: sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==} - engines: {node: '>=18'} - merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -4878,10 +4643,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@3.1.0: - resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} - engines: {node: '>= 18'} - mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} @@ -5002,15 +4763,6 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - nofilter@3.1.0: - resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} - engines: {node: '>=12.19'} - - nopt@8.1.0: - resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} - engines: {node: ^18.17.0 || >=20.5.0} - hasBin: true - normalize-range@0.1.2: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} @@ -5113,10 +4865,6 @@ packages: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} - p-map@7.0.4: - resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} - engines: {node: '>=18'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -5129,10 +4877,6 @@ packages: resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} engines: {node: '>= 14'} - package-config@5.0.0: - resolution: {integrity: sha512-GYTTew2slBcYdvRHqjhwaaydVMvn/qrGC323+nKclYioNSLTDUM/lGgtGTgyHVtYcozb+XkE8CNhwcraOmZ9Mg==} - engines: {node: '>=18'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -5179,10 +4923,6 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.1: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} @@ -5268,10 +5008,6 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} - plur@5.1.0: - resolution: {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - postcss-selector-parser@6.0.10: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} @@ -5492,10 +5228,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -5569,10 +5301,6 @@ packages: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} - serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} - serve-static@2.2.0: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} @@ -5639,10 +5367,6 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} - slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -5687,10 +5411,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -5715,10 +5435,6 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -5780,10 +5496,6 @@ packages: resolution: {integrity: sha512-ay3d+LW/S6yppKoTz3Bq4mG0xrS5bFwfWEBmQfbC7lt5wmtk+Obq0TxVuA9eYRirBTQb1K3eEpBRHMQEo0WyVw==} engines: {node: '>=16'} - supertap@3.0.1: - resolution: {integrity: sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -5827,14 +5539,6 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - tar@7.5.2: - resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} - engines: {node: '>=18'} - - temp-dir@3.0.0: - resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} - engines: {node: '>=14.16'} - term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -5842,10 +5546,6 @@ packages: text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} - time-zone@1.0.0: - resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} - engines: {node: '>=4'} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -5957,10 +5657,6 @@ packages: tw-animate-css@1.4.0: resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} - type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} - type-fest@5.3.1: resolution: {integrity: sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==} engines: {node: '>=20'} @@ -6155,10 +5851,6 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - well-known-symbols@2.0.0: - resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} - engines: {node: '>=6'} - whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -6185,10 +5877,6 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} @@ -6196,10 +5884,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - write-file-atomic@6.0.0: - resolution: {integrity: sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==} - engines: {node: ^18.17.0 || >=20.5.0} - ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -6223,10 +5907,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -7247,19 +6927,6 @@ snapshots: dependencies: '@isaacs/balanced-match': 4.0.1 - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.2 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -7295,19 +6962,6 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@mapbox/node-pre-gyp@2.0.3': - dependencies: - consola: 3.4.2 - detect-libc: 2.1.2 - https-proxy-agent: 7.0.6 - node-fetch: 2.7.0 - nopt: 8.1.0 - semver: 7.7.3 - tar: 7.5.2 - transitivePeerDependencies: - - encoding - - supports-color - '@material/material-color-utilities@0.3.0': {} '@mdx-js/mdx@3.1.1': @@ -7433,9 +7087,6 @@ snapshots: '@orama/orama@3.1.16': {} - '@pkgjs/parseargs@0.11.0': - optional: true - '@puppeteer/browsers@2.11.0': dependencies: debug: 4.4.3 @@ -8498,14 +8149,6 @@ snapshots: dependencies: react: 19.2.1 - '@rollup/pluginutils@5.3.0(rollup@4.53.3)': - dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.3 - optionalDependencies: - rollup: 4.53.3 - '@rollup/rollup-android-arm-eabi@4.53.3': optional: true @@ -8888,25 +8531,6 @@ snapshots: next: 16.0.8(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react: 19.2.1 - '@vercel/nft@0.29.4(rollup@4.53.3)': - dependencies: - '@mapbox/node-pre-gyp': 2.0.3 - '@rollup/pluginutils': 5.3.0(rollup@4.53.3) - acorn: 8.15.0 - acorn-import-attributes: 1.9.5(acorn@8.15.0) - async-sema: 3.1.1 - bindings: 1.5.0 - estree-walker: 2.0.2 - glob: 10.5.0 - graceful-fs: 4.2.11 - node-gyp-build: 4.8.4 - picomatch: 4.0.3 - resolve-from: 5.0.0 - transitivePeerDependencies: - - encoding - - rollup - - supports-color - '@vercel/postgres@0.10.0': dependencies: '@neondatabase/serverless': 0.9.5 @@ -8972,25 +8596,15 @@ snapshots: '@vitest/pretty-format': 4.0.15 tinyrainbow: 3.0.3 - abbrev@3.0.1: {} - accepts@2.0.0: dependencies: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-import-attributes@1.9.5(acorn@8.15.0): - dependencies: - acorn: 8.15.0 - acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 - acorn-walk@8.3.4: - dependencies: - acorn: 8.15.0 - acorn@8.15.0: {} agent-base@7.1.4: {} @@ -9030,14 +8644,8 @@ snapshots: argparse@2.0.1: {} - array-find-index@1.0.2: {} - array-union@2.1.0: {} - arrgv@1.0.2: {} - - arrify@3.0.0: {} - assertion-error@2.0.1: {} ast-types@0.13.4: @@ -9056,8 +8664,6 @@ snapshots: astring@1.9.0: {} - async-sema@3.1.1: {} - autoprefixer@10.4.22(postcss@8.5.6): dependencies: browserslist: 4.28.1 @@ -9068,53 +8674,6 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - ava@6.4.1(rollup@4.53.3): - dependencies: - '@vercel/nft': 0.29.4(rollup@4.53.3) - acorn: 8.15.0 - acorn-walk: 8.3.4 - ansi-styles: 6.2.3 - arrgv: 1.0.2 - arrify: 3.0.0 - callsites: 4.2.0 - cbor: 10.0.11 - chalk: 5.6.2 - chunkd: 2.0.1 - ci-info: 4.3.1 - ci-parallel-vars: 1.0.1 - cli-truncate: 4.0.0 - code-excerpt: 4.0.0 - common-path-prefix: 3.0.0 - concordance: 5.0.4 - currently-unhandled: 0.4.1 - debug: 4.4.3 - emittery: 1.2.0 - figures: 6.1.0 - globby: 14.1.0 - ignore-by-default: 2.1.0 - indent-string: 5.0.0 - is-plain-object: 5.0.0 - is-promise: 4.0.0 - matcher: 5.0.0 - memoize: 10.2.0 - ms: 2.1.3 - p-map: 7.0.4 - package-config: 5.0.0 - picomatch: 4.0.3 - plur: 5.1.0 - pretty-ms: 9.3.0 - resolve-cwd: 3.0.0 - stack-utils: 2.0.6 - strip-ansi: 7.1.2 - supertap: 3.0.1 - temp-dir: 3.0.0 - write-file-atomic: 6.0.0 - yargs: 17.7.2 - transitivePeerDependencies: - - encoding - - rollup - - supports-color - b4a@1.7.3: {} babel-plugin-macros@3.1.0: @@ -9205,12 +8764,6 @@ snapshots: dependencies: is-windows: 1.0.2 - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - - blueimp-md5@2.19.0: {} - body-parser@2.2.1: dependencies: bytes: 3.1.2 @@ -9263,14 +8816,8 @@ snapshots: callsites@3.1.0: {} - callsites@4.2.0: {} - caniuse-lite@1.0.30001760: {} - cbor@10.0.11: - dependencies: - nofilter: 3.1.0 - ccount@2.0.1: {} chai@6.2.1: {} @@ -9291,8 +8838,6 @@ snapshots: dependencies: readdirp: 5.0.0 - chownr@3.0.0: {} - chroma-js@2.6.0: {} chromium-bidi@11.0.0(devtools-protocol@0.0.1534754): @@ -9301,14 +8846,8 @@ snapshots: mitt: 3.0.1 zod: 3.25.76 - chunkd@2.0.1: {} - ci-info@3.9.0: {} - ci-info@4.3.1: {} - - ci-parallel-vars@1.0.1: {} - ciebase@0.1.1: dependencies: mout: 0.11.1 @@ -9324,11 +8863,6 @@ snapshots: cli-spinners@2.9.2: {} - cli-truncate@4.0.0: - dependencies: - slice-ansi: 5.0.0 - string-width: 7.2.0 - cli-width@4.1.0: {} client-only@0.0.1: {} @@ -9349,10 +8883,6 @@ snapshots: code-block-writer@13.0.3: {} - code-excerpt@4.0.0: - dependencies: - convert-to-spaces: 2.0.1 - collapse-white-space@2.1.0: {} color-convert@2.0.1: @@ -9369,23 +8899,8 @@ snapshots: commander@14.0.2: {} - common-path-prefix@3.0.0: {} - compute-scroll-into-view@3.1.1: {} - concordance@5.0.4: - dependencies: - date-time: 3.1.0 - esutils: 2.0.3 - fast-diff: 1.3.0 - js-string-escape: 1.0.1 - lodash: 4.17.21 - md5-hex: 3.0.1 - semver: 7.7.3 - well-known-symbols: 2.0.0 - - consola@3.4.2: {} - content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -9394,8 +8909,6 @@ snapshots: convert-source-map@2.0.0: {} - convert-to-spaces@2.0.1: {} - cookie-signature@1.2.2: {} cookie@0.7.2: {} @@ -9440,20 +8953,12 @@ snapshots: culori@4.0.2: {} - currently-unhandled@0.4.1: - dependencies: - array-find-index: 1.0.2 - data-uri-to-buffer@4.0.1: {} data-uri-to-buffer@6.0.2: {} dataloader@1.4.0: {} - date-time@3.1.0: - dependencies: - time-zone: 1.0.0 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -9540,8 +9045,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - eastasianwidth@0.2.0: {} - eciesjs@0.4.16: dependencies: '@ecies/ciphers': 0.2.5(@noble/ciphers@1.3.0) @@ -9553,14 +9056,10 @@ snapshots: electron-to-chromium@1.5.267: {} - emittery@1.2.0: {} - emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} - encodeurl@2.0.0: {} end-of-stream@1.4.5: @@ -9703,8 +9202,6 @@ snapshots: escape-html@1.0.3: {} - escape-string-regexp@2.0.0: {} - escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -9754,8 +9251,6 @@ snapshots: '@types/estree-jsx': 1.0.5 '@types/unist': 3.0.3 - estree-walker@2.0.2: {} - estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -9862,8 +9357,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-diff@1.3.0: {} - fast-fifo@1.3.2: {} fast-glob@3.3.3: @@ -9897,8 +9390,6 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 - file-uri-to-path@1.0.0: {} - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -9916,18 +9407,11 @@ snapshots: find-root@1.1.0: {} - find-up-simple@1.0.1: {} - find-up@4.1.0: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -10088,15 +9572,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - glob@13.0.0: dependencies: minimatch: 10.1.1 @@ -10253,8 +9728,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ignore-by-default@2.1.0: {} - ignore@5.3.2: {} ignore@7.0.5: {} @@ -10268,10 +9741,6 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - imurmurhash@0.1.4: {} - - indent-string@5.0.0: {} - inherits@2.0.4: {} inline-style-parser@0.2.7: {} @@ -10287,8 +9756,6 @@ snapshots: ipaddr.js@1.9.1: {} - irregular-plurals@3.5.0: {} - is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -10310,8 +9777,6 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-fullwidth-code-point@4.0.0: {} - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -10328,8 +9793,6 @@ snapshots: is-plain-obj@4.1.0: {} - is-plain-object@5.0.0: {} - is-promise@4.0.0: {} is-regexp@3.1.0: {} @@ -10375,12 +9838,6 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jiti@2.6.1: {} jose@6.1.3: {} @@ -10397,8 +9854,6 @@ snapshots: '@types/react': 19.2.7 react: 19.2.1 - js-string-escape@1.0.1: {} - js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -10493,8 +9948,6 @@ snapshots: dependencies: react: 19.2.1 - load-json-file@7.0.1: {} - locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -10510,8 +9963,6 @@ snapshots: longest-streak@3.1.0: {} - lru-cache@10.4.3: {} - lru-cache@11.2.4: {} lru-cache@5.1.1: @@ -10546,16 +9997,8 @@ snapshots: optionalDependencies: react: 19.2.1 - matcher@5.0.0: - dependencies: - escape-string-regexp: 5.0.0 - math-intrinsics@1.1.0: {} - md5-hex@3.0.1: - dependencies: - blueimp-md5: 2.19.0 - mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -10721,10 +10164,6 @@ snapshots: media-typer@1.1.0: {} - memoize@10.2.0: - dependencies: - mimic-function: 5.0.1 - merge-descriptors@2.0.0: {} merge-stream@2.0.0: {} @@ -11022,10 +10461,6 @@ snapshots: minipass@7.1.2: {} - minizlib@3.1.0: - dependencies: - minipass: 7.1.2 - mitt@3.0.1: {} motion-dom@12.23.23: @@ -11131,12 +10566,6 @@ snapshots: node-releases@2.0.27: {} - nofilter@3.1.0: {} - - nopt@8.1.0: - dependencies: - abbrev: 3.0.1 - normalize-range@0.1.2: {} npm-run-path@4.0.1: @@ -11221,8 +10650,6 @@ snapshots: p-map@2.1.0: {} - p-map@7.0.4: {} - p-try@2.2.0: {} pac-proxy-agent@7.2.0: @@ -11243,11 +10670,6 @@ snapshots: degenerator: 5.0.1 netmask: 2.0.2 - package-config@5.0.0: - dependencies: - find-up-simple: 1.0.1 - load-json-file: 7.0.1 - package-json-from-dist@1.0.1: {} package-manager-detector@0.2.11: @@ -11291,11 +10713,6 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - path-scurry@2.0.1: dependencies: lru-cache: 11.2.4 @@ -11370,10 +10787,6 @@ snapshots: pkce-challenge@5.0.1: {} - plur@5.1.0: - dependencies: - irregular-plurals: 3.5.0 - postcss-selector-parser@6.0.10: dependencies: cssesc: 3.0.0 @@ -11757,10 +11170,6 @@ snapshots: require-from-string@2.0.2: {} - resolve-cwd@3.0.0: - dependencies: - resolve-from: 5.0.0 - resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -11864,10 +11273,6 @@ snapshots: transitivePeerDependencies: - supports-color - serialize-error@7.0.1: - dependencies: - type-fest: 0.13.1 - serve-static@2.2.0: dependencies: encodeurl: 2.0.0 @@ -12008,11 +11413,6 @@ snapshots: slash@5.1.0: {} - slice-ansi@5.0.0: - dependencies: - ansi-styles: 6.2.3 - is-fullwidth-code-point: 4.0.0 - smart-buffer@4.2.0: {} socks-proxy-agent@8.0.5: @@ -12052,10 +11452,6 @@ snapshots: sprintf-js@1.0.3: {} - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 - stackback@0.0.2: {} statuses@2.0.2: {} @@ -12081,12 +11477,6 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -12142,13 +11532,6 @@ snapshots: dependencies: copy-anything: 4.0.5 - supertap@3.0.1: - dependencies: - indent-string: 5.0.0 - js-yaml: 3.14.2 - serialize-error: 7.0.1 - strip-ansi: 7.1.2 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -12194,16 +11577,6 @@ snapshots: - bare-abort-controller - react-native-b4a - tar@7.5.2: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.2 - minizlib: 3.1.0 - yallist: 5.0.0 - - temp-dir@3.0.0: {} - term-size@2.2.1: {} text-decoder@1.2.3: @@ -12212,8 +11585,6 @@ snapshots: transitivePeerDependencies: - react-native-b4a - time-zone@1.0.0: {} - tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -12307,8 +11678,6 @@ snapshots: tw-animate-css@1.4.0: {} - type-fest@0.13.1: {} - type-fest@5.3.1: dependencies: tagged-tag: 1.0.0 @@ -12483,8 +11852,6 @@ snapshots: webidl-conversions@3.0.1: {} - well-known-symbols@2.0.0: {} - whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -12515,12 +11882,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 @@ -12529,11 +11890,6 @@ snapshots: wrappy@1.0.2: {} - write-file-atomic@6.0.0: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 4.1.0 - ws@8.18.3(bufferutil@4.0.9): optionalDependencies: bufferutil: 4.0.9 @@ -12544,8 +11900,6 @@ snapshots: yallist@3.1.1: {} - yallist@5.0.0: {} - yaml@1.10.2: {} yargs-parser@21.1.1: {} From 49a851c3c61daae2119dd3edf6a66cb07c2e9d8e Mon Sep 17 00:00:00 2001 From: mehdibha Date: Mon, 15 Dec 2025 01:14:05 +0100 Subject: [PATCH 02/48] feat: colors package --- packages/color-engine/package.json | 16 +- .../color-engine/scripts/compare-libraries.ts | 148 ++++ .../algorithms/leonardo/background-color.ts | 76 ++ .../src/algorithms/leonardo/color.ts | 189 +++++ .../src/algorithms/leonardo/index.ts | 515 +++---------- .../src/algorithms/leonardo/theme.ts | 432 +++++++++++ .../src/algorithms/leonardo/utils.ts | 717 ++++++++++++++++++ .../src/algorithms/material/index.ts | 12 +- .../src/algorithms/radix/index.ts | 410 ---------- packages/color-engine/src/core/index.ts | 6 - packages/color-engine/src/core/types.ts | 247 ------ packages/color-engine/src/core/utils.ts | 384 ---------- packages/color-engine/src/index.ts | 196 +---- packages/color-engine/src/types.ts | 141 ++++ packages/color-engine/src/types/apca-w3.d.ts | 29 - packages/color-engine/src/types/ciebase.d.ts | 50 -- packages/color-engine/src/types/ciecam02.d.ts | 68 -- packages/color-engine/src/types/hsluv.d.ts | 62 -- packages/color-engine/src/utils/curve.ts | 210 +++++ packages/color-engine/tests/index.test.ts | 441 ++++++----- packages/color-engine/tests/leonardo.test.ts | 454 ----------- packages/color-engine/tests/material.test.ts | 14 +- .../tests/migration-baseline.test.ts | 213 ++++++ packages/color-engine/tests/radix.test.ts | 238 ------ packages/color-engine/tests/utils.test.ts | 203 ----- 25 files changed, 2543 insertions(+), 2928 deletions(-) create mode 100644 packages/color-engine/scripts/compare-libraries.ts create mode 100644 packages/color-engine/src/algorithms/leonardo/background-color.ts create mode 100644 packages/color-engine/src/algorithms/leonardo/color.ts create mode 100644 packages/color-engine/src/algorithms/leonardo/theme.ts create mode 100644 packages/color-engine/src/algorithms/leonardo/utils.ts delete mode 100644 packages/color-engine/src/algorithms/radix/index.ts delete mode 100644 packages/color-engine/src/core/index.ts delete mode 100644 packages/color-engine/src/core/types.ts delete mode 100644 packages/color-engine/src/core/utils.ts create mode 100644 packages/color-engine/src/types.ts delete mode 100644 packages/color-engine/src/types/apca-w3.d.ts delete mode 100644 packages/color-engine/src/types/ciebase.d.ts delete mode 100644 packages/color-engine/src/types/ciecam02.d.ts delete mode 100644 packages/color-engine/src/types/hsluv.d.ts create mode 100644 packages/color-engine/src/utils/curve.ts delete mode 100644 packages/color-engine/tests/leonardo.test.ts create mode 100644 packages/color-engine/tests/migration-baseline.test.ts delete mode 100644 packages/color-engine/tests/radix.test.ts delete mode 100644 packages/color-engine/tests/utils.test.ts diff --git a/packages/color-engine/package.json b/packages/color-engine/package.json index 03a6c5ff3..7b8bcbc07 100644 --- a/packages/color-engine/package.json +++ b/packages/color-engine/package.json @@ -1,15 +1,11 @@ { - "name": "@dotui/color-engine", + "name": "@dotui/colors", "private": true, "version": "0.1.0", "type": "module", "license": "MIT", "exports": { - ".": "./src/index.ts", - "./leonardo": "./src/algorithms/leonardo/index.ts", - "./material": "./src/algorithms/material/index.ts", - "./radix": "./src/algorithms/radix/index.ts", - "./utils": "./src/core/utils.ts" + ".": "./src/index.ts" }, "scripts": { "clean": "git clean -xdf .cache .turbo dist node_modules", @@ -19,16 +15,10 @@ }, "dependencies": { "@material/material-color-utilities": "^0.3.0", - "@radix-ui/colors": "^3.0.0", - "apca-w3": "^0.1.9", - "chroma-js": "^2.4.2", - "ciebase": "^0.1.1", - "ciecam02": "^0.4.6", - "hsluv": "^0.1.0" + "colorjs.io": "^0.5.2" }, "devDependencies": { "@dotui/ts-config": "workspace:*", - "@types/chroma-js": "^2.4.4", "typescript": "^5.8.3", "vitest": "^4.0.1" } diff --git a/packages/color-engine/scripts/compare-libraries.ts b/packages/color-engine/scripts/compare-libraries.ts new file mode 100644 index 000000000..2c684198c --- /dev/null +++ b/packages/color-engine/scripts/compare-libraries.ts @@ -0,0 +1,148 @@ +/** + * Comparison script to validate Color.js can match our current implementation + * Run with: npx tsx scripts/compare-libraries.ts + */ + +import { createTheme } from "../src/index"; +import Color from "colorjs.io"; + +// Test configuration +const testConfig = { + colors: [ + { + name: "accent", + colorKeys: ["#6366f1"], + ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15], + }, + { + name: "success", + colorKeys: ["#22c55e"], + ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15], + }, + ], + backgroundColor: "#ffffff", + lightness: 100, +}; + +// Generate theme with current implementation +console.log("=".repeat(60)); +console.log("COMPARISON: Current Implementation vs Color.js Verification"); +console.log("=".repeat(60)); + +const theme = createTheme(testConfig); + +console.log("\nšŸ“‹ Theme Background:", theme.background); + +// Parse HSL string to Color.js color +function parseHsl(hslString: string): Color { + const match = hslString.match(/hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/); + if (!match) throw new Error(`Invalid HSL: ${hslString}`); + return new Color("hsl", [ + parseFloat(match[1]), + parseFloat(match[2]), + parseFloat(match[3]), + ]); +} + +// Verify contrast ratios using Color.js +console.log("\nšŸ” Verifying contrast ratios with Color.js:"); +console.log("-".repeat(60)); + +const bgColor = parseHsl(theme.background); + +for (const [scaleName, scale] of Object.entries(theme.colors)) { + console.log(`\n${scaleName.toUpperCase()}:`); + console.log( + "Step".padEnd(8) + + "HSL".padEnd(24) + + "Target".padEnd(10) + + "Actual".padEnd(10) + + "Status" + ); + + const targetRatios = testConfig.colors.find( + (c) => c.name === scaleName + )?.ratios; + if (!targetRatios || !Array.isArray(targetRatios)) continue; + + let i = 0; + for (const [step, hslValue] of Object.entries(scale)) { + const fgColor = parseHsl(hslValue); + + // Calculate WCAG 2.1 contrast using Color.js + const actualContrast = Math.abs(bgColor.contrast(fgColor, "WCAG21")); + const targetContrast = targetRatios[i] ?? 0; + + // Check if within tolerance (Leonardo uses binary search, small variance expected) + const tolerance = 0.1; + const withinTolerance = + Math.abs(actualContrast - targetContrast) <= tolerance; + const status = withinTolerance ? "āœ…" : "āš ļø"; + + console.log( + step.padEnd(8) + + hslValue.padEnd(24) + + targetContrast.toFixed(2).padEnd(10) + + actualContrast.toFixed(2).padEnd(10) + + status + ); + + i++; + } +} + +// Test Color.js capabilities +console.log("\n" + "=".repeat(60)); +console.log("COLOR.JS CAPABILITIES TEST"); +console.log("=".repeat(60)); + +// HSLuv support +console.log("\nšŸŽØ HSLuv Support:"); +const hsluvColor = new Color("hsluv", [270, 100, 50]); +console.log(` HSLuv(270, 100, 50) → sRGB: ${hsluvColor.to("srgb").toString()}`); + +// CAM16 support +console.log("\nšŸŽØ CAM16 Support:"); +const cam16Color = new Color("cam16-jmh", [50, 30, 270]); +console.log( + ` CAM16(J:50, M:30, h:270) → sRGB: ${cam16Color.to("srgb").toString()}` +); + +// APCA contrast +console.log("\nšŸŽØ APCA Contrast Support:"); +const white = new Color("white"); +const testColor = new Color("#6366f1"); +const apcaContrast = white.contrast(testColor, "APCA"); +console.log(` White vs #6366f1 APCA: ${apcaContrast.toFixed(2)} Lc`); + +// WCAG contrast +const wcagContrast = white.contrast(testColor, "WCAG21"); +console.log(` White vs #6366f1 WCAG: ${wcagContrast.toFixed(2)}:1`); + +// Interpolation in different spaces +console.log("\nšŸŽØ Interpolation Support:"); +const color1 = new Color("#6366f1"); +const color2 = new Color("#22c55e"); + +const oklchMix = color1.mix(color2, 0.5, { space: "oklch" }); +console.log(` OKLCH interpolation: ${oklchMix.to("srgb").toString({ format: "hex" })}`); + +const cam16Mix = color1.mix(color2, 0.5, { space: "cam16-jmh" }); +console.log(` CAM16 interpolation: ${cam16Mix.to("srgb").toString({ format: "hex" })}`); + +console.log("\n" + "=".repeat(60)); +console.log("CONCLUSION"); +console.log("=".repeat(60)); +console.log(` +āœ… Color.js provides: + - HSLuv color space (for background lightness) + - CAM16 (improved successor to CIECAM02) + - APCA contrast calculation + - WCAG 2.1 contrast calculation + - Rich interpolation in any color space + +šŸ“ Migration would: + - Replace 5 packages with 1 + - Use CAM16 instead of CIECAM02 (better) + - Keep same algorithm logic, just different API calls +`); diff --git a/packages/color-engine/src/algorithms/leonardo/background-color.ts b/packages/color-engine/src/algorithms/leonardo/background-color.ts new file mode 100644 index 000000000..693db3eb8 --- /dev/null +++ b/packages/color-engine/src/algorithms/leonardo/background-color.ts @@ -0,0 +1,76 @@ +/** + * BackgroundColor class for Leonardo theme generation + * Ported from Adobe's contrast-colors library for 100% Leonardo parity + */ + +import { Color, ColorOptions } from "./color"; +import { hsluvArray, convertColorValue, createScale, removeDuplicates } from "./utils"; +import type { LeonardoColorspace } from "../../types"; + +export interface BackgroundColorOptions extends Omit { + ratios?: number[] | Record; +} + +export class BackgroundColor extends Color { + private _backgroundColorScale: string[] | null = null; + + constructor(options: BackgroundColorOptions) { + // BackgroundColor doesn't require ratios + super({ + ...options, + ratios: options.ratios || [], + }); + } + + get backgroundColorScale(): string[] { + if (!this._backgroundColorScale) { + this._generateColorScale(); + } + return this._backgroundColorScale!; + } + + /** + * Generate the background color scale (100 lightness steps) + */ + _generateColorScale(): void { + // Call parent's generateColorScale first + super._generateColorScale(); + + // Create massive scale for background + const backgroundColorScale = createScale({ + swatches: 1000, + colorKeys: this.colorKeys, + colorspace: this.colorspace, + shift: 1, + smooth: this.smooth, + }) as string[]; + + // Inject original key colors to ensure they are present + backgroundColorScale.push(...this.colorKeys); + + // Convert to HSLuv and track indices + const colorObj = backgroundColorScale.map((c, i) => ({ + value: Math.round(hsluvArray(c)[2] ?? 0), + index: i, + })); + + // Remove duplicates by lightness value + const colorObjFiltered = removeDuplicates(colorObj, "value"); + + // Map back to colors + const bgColorArrayFiltered = colorObjFiltered.map( + (data) => backgroundColorScale[data.index], + ); + + // Cap at 100 colors, add white back if needed + if (bgColorArrayFiltered.length >= 101) { + bgColorArrayFiltered.length = 100; + bgColorArrayFiltered.push("#ffffff"); + } + + // Convert to output format + this._backgroundColorScale = bgColorArrayFiltered.map((color) => + convertColorValue(color ?? "#ffffff", this.output) as string, + ); + } +} diff --git a/packages/color-engine/src/algorithms/leonardo/color.ts b/packages/color-engine/src/algorithms/leonardo/color.ts new file mode 100644 index 000000000..fcbf2096c --- /dev/null +++ b/packages/color-engine/src/algorithms/leonardo/color.ts @@ -0,0 +1,189 @@ +/** + * Color class for Leonardo theme generation + * Ported from Adobe's contrast-colors library for 100% Leonardo parity + * Migrated to Color.js from chroma-js + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import ColorJS from "colorjs.io"; +import { colorSpaces, createScale } from "./utils"; +import type { LeonardoColorspace } from "../../types"; + +export interface ColorOptions { + name: string; + colorKeys: string[]; + colorspace?: LeonardoColorspace; + ratios: number[] | Record; + smooth?: boolean; + output?: LeonardoColorspace; + saturation?: number; +} + +export class Color { + private _name: string; + private _colorKeys: string[]; + _modifiedKeys: string[]; + _colorspace: LeonardoColorspace; + private _ratios: number[] | Record; + _smooth: boolean; + private _output: LeonardoColorspace; + private _saturation: number; + private _colorScale: ((pos: number) => string) | null; + + constructor({ + name, + colorKeys, + colorspace = "RGB", + ratios, + smooth = false, + output = "HEX", + saturation = 100, + }: ColorOptions) { + this._name = name; + this._colorKeys = colorKeys; + this._modifiedKeys = colorKeys; + this._colorspace = colorspace; + this._ratios = ratios; + this._smooth = smooth; + this._output = output; + this._saturation = saturation; + + if (!this._name) { + throw new Error("Color missing name"); + } + if (!this._colorKeys) { + throw new Error("Color Keys are undefined"); + } + if (!colorSpaces[this._colorspace]) { + throw new Error(`Colorspace "${colorspace}" not supported`); + } + if (!colorSpaces[this._output]) { + throw new Error(`Output "${output}" not supported`); + } + + // Validate color keys using Color.js + for (let i = 0; i < this._colorKeys.length; i++) { + try { + new ColorJS(this._colorKeys[i]!); + } catch { + throw new Error(`Invalid Color Key "${this._colorKeys[i]}"`); + } + } + + this._colorScale = null; + + // Initialize saturation modification if needed + if (this._saturation !== 100) { + this._updateColorSaturation(); + } + } + + // Getters and setters + set colorKeys(colorKeys: string[]) { + this._colorKeys = colorKeys; + this._updateColorSaturation(); + } + + get colorKeys(): string[] { + return this._colorKeys; + } + + set saturation(saturation: number) { + this._saturation = saturation; + this._updateColorSaturation(); + } + + get saturation(): number { + return this._saturation; + } + + set colorspace(colorspace: LeonardoColorspace) { + this._colorspace = colorspace; + this._generateColorScale(); + } + + get colorspace(): LeonardoColorspace { + return this._colorspace; + } + + set ratios(ratios: number[] | Record) { + this._ratios = ratios; + } + + get ratios(): number[] | Record { + return this._ratios; + } + + set name(name: string) { + this._name = name; + } + + get name(): string { + return this._name; + } + + set smooth(smooth: boolean) { + if (smooth === true || (smooth as any) === "true") { + this._smooth = true; + } else { + this._smooth = false; + } + this._generateColorScale(); + } + + get smooth(): boolean { + return this._smooth; + } + + set output(output: LeonardoColorspace) { + this._output = output; + this._colorScale = null; + } + + get output(): LeonardoColorspace { + return this._output; + } + + get colorScale(): (pos: number) => string { + if (!this._colorScale) { + this._generateColorScale(); + } + return this._colorScale!; + } + + /** + * Update color keys with saturation modification using OKLCH + * This is the key to Leonardo parity - uses OKLCH for saturation adjustment + */ + _updateColorSaturation(): void { + const newColorKeys: string[] = []; + + this._colorKeys.forEach((key) => { + const color = new ColorJS(key).to("oklch"); + const currentL = color.coords[0] ?? 0; + const currentC = color.coords[1] ?? 0; + const currentH = color.coords[2] ?? 0; + const newSaturation = currentC * (this._saturation / 100); + const newColor = new ColorJS("oklch", [currentL, newSaturation, currentH]); + newColorKeys.push(newColor.to("srgb").toString({ format: "hex" })); + }); + + this._modifiedKeys = newColorKeys; + this._generateColorScale(); + } + + /** + * Generate the color scale (3000 swatches) + */ + _generateColorScale(): void { + this._colorScale = createScale({ + swatches: 3000, + colorKeys: this._modifiedKeys, + colorspace: this._colorspace, + shift: 1, + smooth: this._smooth, + asFun: true, + }) as (pos: number) => string; + } +} diff --git a/packages/color-engine/src/algorithms/leonardo/index.ts b/packages/color-engine/src/algorithms/leonardo/index.ts index bb934c9b9..aa227e3f9 100644 --- a/packages/color-engine/src/algorithms/leonardo/index.ts +++ b/packages/color-engine/src/algorithms/leonardo/index.ts @@ -1,435 +1,156 @@ /** - * Leonardo Algorithm - Exact Port + * Leonardo theme generation - Functional API + * 100% parity with Adobe's contrast-colors library * - * A faithful TypeScript port of Adobe's Leonardo contrast-based - * color scale generation algorithm using chroma-js for exact parity. + * @example + * import { createTheme } from '@dotui/colors'; * - * Original: https://github.com/adobe/leonardo - * License: Apache-2.0 - */ - -import { APCAcontrast, sRGBtoY } from "apca-w3"; -import chroma from "chroma-js"; -import * as ciebase from "ciebase"; -import * as ciecam02 from "ciecam02"; -import * as hsluv from "hsluv"; -import type { ContrastFormula, LeonardoColorspace, LeonardoOptions, RGB, ScaleOutput } from "../../core/types"; -import { SCALE_STEPS } from "../../core/utils"; - -// ============================================================================ -// CIECAM02 JCh Setup (exact match to original chroma-plus.js) -// ============================================================================ - -const cam = ciecam02.cam( - { - whitePoint: ciebase.illuminant.D65, - adaptingLuminance: 40, - backgroundLuminance: 20, - surroundType: "average", - discounting: false, - }, - ciecam02.cfs("JCh"), -); - -const xyz = ciebase.xyz(ciebase.workspace.sRGB, ciebase.illuminant.D65); - -/** Convert RGB (0-255) to JCh */ -function rgb2jch(rgb: RGB): [number, number, number] { - const rgbNorm: [number, number, number] = [rgb[0] / 255, rgb[1] / 255, rgb[2] / 255]; - const jch = cam.fromXyz(xyz.fromRgb(rgbNorm)); - return [jch.J, jch.C, jch.h]; -} - -/** Convert JCh to RGB (0-255) */ -function jch2rgb(jch: [number, number, number]): RGB { - const rgbNorm = xyz.toRgb(cam.toXyz({ J: jch[0], C: jch[1], h: jch[2] })); - return [Math.round(rgbNorm[0] * 255), Math.round(rgbNorm[1] * 255), Math.round(rgbNorm[2] * 255)]; -} - -// ============================================================================ -// Constants -// ============================================================================ - -/** Number of steps in the internal color scale for binary search precision */ -const SCALE_LENGTH = 3000; - -/** Default contrast ratios for 11-step scale */ -const DEFAULT_RATIOS = [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15]; - -/** Binary search epsilon (convergence threshold) */ -const EPSILON = 0.01; - -/** Maximum binary search iterations */ -const MAX_ITERATIONS = 100; - -/** Colorspace mapping to chroma-js mode names */ -const COLOR_SPACES: Record = { - CAM02: "jab", - CAM02p: "jch", - HEX: "hex", - HSL: "hsl", - HSLuv: "hsluv", - HSV: "hsv", - LAB: "lab", - LCH: "lch", - RGB: "rgb", - OKLAB: "oklab", - OKLCH: "oklch", -}; - -// ============================================================================ -// Utility Functions -// ============================================================================ - -function round(x: number, n = 0): number { - const ten = 10 ** n; - return Math.round(x * ten) / ten; -} - -/** - * Apply contrast multiplier to a ratio (Leonardo-style normalization) - */ -function multiplyRatio(ratio: number, multiplier: number): number { - let r: number; - - if (ratio > 1) { - r = (ratio - 1) * multiplier + 1; - } else if (ratio < -1) { - r = (ratio + 1) * multiplier - 1; - } else { - r = 1; - } - - return round(r, 2); -} - -// ============================================================================ -// Luminance & Contrast Calculations -// ============================================================================ - -/** - * Calculate relative luminance per WCAG 2.0 - */ -function luminance(r: number, g: number, b: number): number { - const a = [r, g, b].map((v) => { - v /= 255; - return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; - }); - return a[0]! * 0.2126 + a[1]! * 0.7152 + a[2]! * 0.0722; -} - -/** - * Calculate contrast with directionality (Leonardo-style) + * const theme = createTheme({ + * colors: [ + * { name: 'accent', colorKeys: ['#6366f1'], ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15] }, + * { name: 'success', colorKeys: ['#22c55e'], ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15] }, + * ], + * backgroundColor: '#ffffff', + * lightness: 97, + * saturation: 100, + * contrast: 1, + * formula: 'wcag2', + * }); * - * For WCAG2: - * - Light themes (baseV >= 0.5): positive = darker than background, negative = lighter - * - Dark themes (baseV < 0.5): positive = lighter than background, negative = darker + * // Returns: + * // { + * // background: 'hsl(0, 0%, 96%)', + * // colors: { + * // accent: { '100': 'hsl(239, 84%, 67%)', '200': 'hsl(238, 78%, 61%)', ... }, + * // success: { '100': 'hsl(142, 76%, 36%)', '200': 'hsl(142, 69%, 31%)', ... }, + * // } + * // } */ -function getContrast( - colorRgb: RGB, - baseRgb: RGB, - baseV: number | undefined, - formula: ContrastFormula = "wcag2", -): number { - // If baseV not provided, calculate from background HSLuv lightness - if (baseV === undefined) { - // Convert RGB (0-255) to RGB (0-1) for hsluv - const rgbNormalized: [number, number, number] = [baseRgb[0] / 255, baseRgb[1] / 255, baseRgb[2] / 255]; - const hsluvValues = hsluv.rgbToHsluv(rgbNormalized); - const baseLightness = hsluvValues[2]; // L is at index 2 - baseV = round(baseLightness / 100, 2); - } - - if (formula === "wcag3") { - // Use APCA contrast - const apcaValue = APCAcontrast(sRGBtoY(colorRgb), sRGBtoY(baseRgb)) as number; - // Flip sign for dark themes to match Leonardo convention - return baseV < 0.5 ? -apcaValue : apcaValue; - } - // WCAG 2 contrast calculation - const colorLum = luminance(colorRgb[0], colorRgb[1], colorRgb[2]); - const baseLum = luminance(baseRgb[0], baseRgb[1], baseRgb[2]); +/* eslint-disable @typescript-eslint/no-explicit-any */ - // cr1 >= 1 when color is darker than base - // cr2 >= 1 when color is lighter than base - const cr1 = (colorLum + 0.05) / (baseLum + 0.05); - const cr2 = (baseLum + 0.05) / (colorLum + 0.05); +import ColorJS from "colorjs.io"; +import { Theme } from "./theme"; +import type { LeonardoColorspace, ContrastFormula } from "../../types"; - if (baseV < 0.5) { - // Dark theme: positive = lighter (higher contrast), negative = darker - if (cr1 >= 1) { - return cr1; // color is darker than background - } - return -cr2; // color is lighter than background - } - - // Light theme: positive = darker (higher contrast), negative = lighter - if (cr1 < 1) { - return cr2; // color is lighter than background - } - if (cr1 === 1) { - return cr1; - } - return -cr1; // color is darker than background +export interface ColorInput { + name: string; + colorKeys: string[]; + colorspace?: LeonardoColorspace; + ratios: number[] | Record; + smooth?: boolean; } -// ============================================================================ -// Scale Creation (exact port from original) -// ============================================================================ - -/** - * Create power scale function for domain mapping - */ -function makePowScale( - exp = 1, - domains: [number, number] = [0, 1], - range: [number, number] = [0, 1], -): (x: number) => number { - const m = (range[1] - range[0]) / (domains[1] ** exp - domains[0] ** exp); - const c = range[0] - m * domains[0] ** exp; - return (x: number) => m * x ** exp + c; +export interface BackgroundColorInput { + name: string; + colorKeys: string[]; + colorspace?: LeonardoColorspace; } -/** - * Create a color scale function (exact port from original createScale) - */ -function createScale( - colorKeys: string[], - colorspace: LeonardoColorspace, - swatches: number, - smooth = false, -): (position: number) => string { - const space = COLOR_SPACES[colorspace]; - if (!space) { - throw new Error(`Colorspace "${colorspace}" not supported`); - } - - // Calculate domains based on JCh lightness (J from CIECAM02) - exact match to original - let domains = colorKeys - .map((key) => { - const rgb = chroma(key).rgb() as RGB; - const jch = rgb2jch(rgb); - return swatches - swatches * (jch[0] / 100); - }) - .sort((a, b) => a - b) - .concat(swatches); - - domains.unshift(0); - - // Apply power scaling for smoother distribution - const sqrtDomains = makePowScale(1, [1, swatches], [1, swatches]); - domains = domains.map((d) => Math.max(0, sqrtDomains(d))); - - // Sort colors by JCh lightness (brightest first) - exact match to original - const sortedColors = colorKeys - .map((c, i) => { - const rgb = chroma(c).rgb() as RGB; - const jch = rgb2jch(rgb); - return { color: jch, index: i }; - }) - .sort((c1, c2) => c2.color[0] - c1.color[0]) - .map((data) => colorKeys[data.index]!); - - // Build full scale with white and black endpoints - const white = space === "lch" ? chroma.lch(...chroma("#fff").lch()) : "#ffffff"; - const black = space === "lch" ? chroma.lch(...chroma("#000").lch()) : "#000000"; - const colorsArray = [white, ...sortedColors, black]; - - // Create chroma scale - const scale = chroma - .scale( - colorsArray.map((color) => { - if (typeof color === "object" && color.constructor.name === "Color") { - return color; - } - return String(color); - }), - ) - .domain(domains) - .mode(space as chroma.InterpolationMode); - - return (pos: number): string => { - return scale(pos).hex(); - }; +export interface CreateThemeInput { + colors: ColorInput[]; + backgroundColor: string | BackgroundColorInput; + lightness?: number; + contrast?: number; + saturation?: number; + formula?: ContrastFormula; } -// ============================================================================ -// Color Search (Binary Search - exact port from original) -// ============================================================================ - -/** - * Search for colors at specific contrast ratios using binary search - */ -function searchColors( - colorKeys: string[], - colorspace: LeonardoColorspace, - bgRgb: RGB, - baseV: number, - ratios: number[], - formula: ContrastFormula, - smooth = false, -): string[] { - const colorLen = SCALE_LENGTH; - const colorScale = createScale(colorKeys, colorspace, colorLen, smooth); - - // Cache for contrast calculations - const ccache: Record = {}; - - const getContrast2 = (i: number): number => { - if (ccache[i] !== undefined) { - return ccache[i]!; - } - const rgb = chroma(colorScale(i)).rgb() as RGB; - const c = getContrast(rgb, bgRgb, baseV, formula); - ccache[i] = c; - return c; - }; - - const colorSearch = (x: number): number => { - const first = getContrast2(0); - const last = getContrast2(colorLen); - const dir = first < last ? 1 : -1; - const epsilon = EPSILON; - x += 0.005 * Math.sign(x); - let step = colorLen / 2; - let dot = step; - let val = getContrast2(dot); - let counter = MAX_ITERATIONS; - - while (Math.abs(val - x) > epsilon && counter) { - counter--; - step /= 2; - if (val < x) { - dot += step * dir; - } else { - dot -= step * dir; - } - val = getContrast2(dot); - } - - return round(dot, 3); - }; - - const outputColors: string[] = []; - ratios.forEach((ratio) => { - outputColors.push(colorScale(colorSearch(+ratio))); - }); - - return outputColors; +export interface CreateThemeOutput { + background: string; + colors: Record>; } -// ============================================================================ -// Saturation Modification -// ============================================================================ - /** - * Modify color saturation using OKLCH chroma + * Convert a color value to HSL string format */ -function modifySaturation(hex: string, saturationPercent: number): string { - const oklch = chroma(hex).oklch(); - const newChroma = oklch[1] * (saturationPercent / 100); - return chroma.oklch(oklch[0], newChroma, oklch[2]).hex(); +function toHslString(color: string): string { + const hsl = new ColorJS(color).to("hsl"); + const h = Math.round(isNaN(hsl.coords[0] ?? 0) ? 0 : (hsl.coords[0] ?? 0)); + const s = Math.round(hsl.coords[1] ?? 0); + const l = Math.round(hsl.coords[2] ?? 0); + return `hsl(${h}, ${s}%, ${l}%)`; } -// ============================================================================ -// Main API -// ============================================================================ - /** - * Generate a color scale using the Leonardo algorithm + * Create a theme with contrast-based color scales * - * This is an exact port of Adobe's Leonardo algorithm with: - * - Binary search for exact contrast ratios - * - WCAG 2 and WCAG 3 (APCA) support - * - Multiple colorspace interpolation (LAB, LCH, OKLCH, etc.) - * - JCh lightness for domain positioning (exact match to original) + * Uses Leonardo's algorithm for 100% parity with Adobe's contrast-colors library. + * Always outputs HSL format for human readability and ColorPicker compatibility. * - * @param options - Configuration options - * @returns ScaleOutput with 11 steps (50-950) + * @param input - Theme configuration + * @returns Theme output with background and color scales in HSL format */ -export function generateScale(options: LeonardoOptions): ScaleOutput { +export function createTheme(input: CreateThemeInput): CreateThemeOutput { const { - color, - background, - colorKeys = [], - ratios = DEFAULT_RATIOS, - colorspace = "LAB", - saturation = 100, + colors, + backgroundColor, + lightness, contrast = 1, - smooth = false, + saturation = 100, formula = "wcag2", - } = options; - - // Combine primary color with additional color keys - const allColorKeys = [color, ...colorKeys]; - - // Apply saturation modification if needed - const modifiedColorKeys = - saturation < 100 ? allColorKeys.map((c) => modifySaturation(c, saturation)) : allColorKeys; - - // Parse background - const bgRgb = chroma(background).rgb() as RGB; - // Use HSLuv lightness for baseV (exact match to original) - const rgbNormalized: [number, number, number] = [bgRgb[0] / 255, bgRgb[1] / 255, bgRgb[2] / 255]; - const hsluvValues = hsluv.rgbToHsluv(rgbNormalized); - const baseLightness = hsluvValues[2]; // L is at index 2 - const baseV = round(baseLightness / 100, 2); + } = input; + + // Create the Theme instance (internal) + const theme = new Theme({ + colors: colors.map((c) => ({ + name: c.name, + colorKeys: c.colorKeys, + colorspace: c.colorspace, + ratios: c.ratios, + smooth: c.smooth, + })), + backgroundColor: + typeof backgroundColor === "string" + ? backgroundColor + : { + name: backgroundColor.name, + colorKeys: backgroundColor.colorKeys, + colorspace: backgroundColor.colorspace, + }, + lightness, + contrast, + saturation, + output: "HEX", // Internal processing in HEX, convert to HSL at output + formula, + }); - // Apply contrast multiplier to ratios - const adjustedRatios = ratios.map((r) => multiplyRatio(r, contrast)); + // Get contrast colors from the theme + const contrastColors = theme.contrastColors; - // Search for colors at each ratio - const colors = searchColors(modifiedColorKeys, colorspace, bgRgb, baseV, adjustedRatios, formula, smooth); + // Build the output + const result: CreateThemeOutput = { + background: toHslString(theme.backgroundColorValue), + colors: {}, + }; - // Map to standard scale output - const output: Partial = {}; - SCALE_STEPS.forEach((step, index) => { - if (index < colors.length) { - output[step] = colors[index]; + // Process each color scale + for (const item of contrastColors) { + if ("background" in item) { + // Skip the background entry + continue; } - }); - return output as ScaleOutput; -} + const colorScale: Record = {}; + const colorItem = item as { name: string; values: Array<{ name: string; value: string }> }; -// ============================================================================ -// Additional Exports -// ============================================================================ + for (const value of colorItem.values) { + // Extract step number from the name (e.g., "accent100" -> "100") + const stepMatch = value.name.match(/(\d+)$/); + if (stepMatch && stepMatch[1]) { + const step = stepMatch[1]; + colorScale[step] = toHslString(value.value); + } else { + // For named ratios (object format), use the name as key + colorScale[value.name] = toHslString(value.value); + } + } -/** - * Calculate contrast between two colors with directionality - * - * @param foreground - Foreground color as RGB tuple - * @param background - Background color as RGB tuple - * @param baseV - Background lightness (0-1), optional - * @param formula - Contrast formula ('wcag2' or 'wcag3') - * @returns Signed contrast value (positive = darker than bg in light themes) - */ -export function contrast( - foreground: RGB, - background: RGB, - baseV?: number, - formula: ContrastFormula = "wcag2", -): number { - return getContrast(foreground, background, baseV, formula); -} + result.colors[colorItem.name] = colorScale; + } -/** - * Create a color scale function for custom use - * - * @param colorKeys - Array of hex color strings - * @param colorspace - Colorspace for interpolation - * @param swatches - Number of steps in the scale (default: 3000) - * @returns Function that takes a position and returns a hex color - */ -export function createColorScale( - colorKeys: string[], - colorspace: LeonardoColorspace = "LAB", - swatches = SCALE_LENGTH, -): (position: number) => string { - return createScale(colorKeys, colorspace, swatches); + return result; } // Re-export types -export type { LeonardoOptions, LeonardoColorspace, ContrastFormula }; +export type { LeonardoColorspace, ContrastFormula } from "../../types"; diff --git a/packages/color-engine/src/algorithms/leonardo/theme.ts b/packages/color-engine/src/algorithms/leonardo/theme.ts new file mode 100644 index 000000000..96c2d989e --- /dev/null +++ b/packages/color-engine/src/algorithms/leonardo/theme.ts @@ -0,0 +1,432 @@ +/** + * Theme class for Leonardo theme generation + * Ported from Adobe's contrast-colors library for 100% Leonardo parity + * Migrated to Color.js from chroma-js + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import ColorJS from "colorjs.io"; +import { Color } from "./color"; +import { BackgroundColor } from "./background-color"; +import { + colorSpaces, + convertColorValue, + multiplyRatios, + ratioName, + searchColors, + round, +} from "./utils"; +import type { LeonardoColorspace, ContrastFormula } from "../../types"; + +export interface ThemeColorInput { + name: string; + colorKeys: string[]; + colorspace?: LeonardoColorspace; + ratios: number[] | Record; + smooth?: boolean; +} + +export interface ThemeBackgroundInput { + name: string; + colorKeys: string[]; + colorspace?: LeonardoColorspace; +} + +export interface ThemeOptions { + colors: (Color | ThemeColorInput)[]; + backgroundColor: string | BackgroundColor | ThemeBackgroundInput; + lightness?: number; + contrast?: number; + saturation?: number; + output?: LeonardoColorspace; + formula?: ContrastFormula; +} + +export interface ContrastColorValue { + name: string; + contrast: number; + value: string; +} + +export interface ContrastColor { + name: string; + values: ContrastColorValue[]; +} + +export type ContrastColors = Array<{ background: string } | ContrastColor>; +export type ContrastColorPairs = Record; +export type ContrastColorValues = string[]; + +export class Theme { + private _output: LeonardoColorspace; + private _colors: Color[]; + private _lightness: number; + private _saturation: number; + private _formula: ContrastFormula; + private _backgroundColor!: BackgroundColor; + private _backgroundColorValue: string; + private _contrast: number; + private _contrastColors: ContrastColors; + private _contrastColorPairs: ContrastColorPairs; + private _contrastColorValues: ContrastColorValues; + + constructor({ + colors, + backgroundColor, + lightness, + contrast = 1, + saturation = 100, + output = "HEX", + formula = "wcag2", + }: ThemeOptions) { + this._output = output; + this._saturation = saturation; + this._formula = formula; + this._contrast = contrast; + this._lightness = lightness ?? 100; + this._backgroundColorValue = ""; + + // Convert color inputs to Color instances + this._colors = colors.map((color) => { + if (color instanceof Color) { + return color; + } + return new Color({ + ...color, + output: this._output, + saturation: this._saturation, + }); + }); + + // Set up background color + this._setBackgroundColor(backgroundColor); + this._setBackgroundColorValue(); + + // Validation + if (!this._colors || this._colors.length === 0) { + throw new Error("No colors are defined"); + } + if (!this._backgroundColor) { + throw new Error("Background color is undefined"); + } + + this._colors.forEach((color) => { + if (!color.ratios) { + throw new Error(`Color ${color.name}'s ratios are undefined`); + } + }); + + if (!colorSpaces[this._output]) { + throw new Error(`Output "${output}" not supported`); + } + + // Update saturation if needed + if (this._saturation < 100) { + this._updateColorSaturation(this._saturation); + } + + // Initialize contrast colors + this._contrastColors = []; + this._contrastColorPairs = {}; + this._contrastColorValues = []; + + // Generate contrast colors + this._findContrastColors(); + this._findContrastColorPairs(); + this._findContrastColorValues(); + } + + // Getters and setters + set formula(formula: ContrastFormula) { + this._formula = formula; + this._findContrastColors(); + } + + get formula(): ContrastFormula { + return this._formula; + } + + set contrast(contrast: number) { + this._contrast = contrast; + this._findContrastColors(); + } + + get contrast(): number { + return this._contrast; + } + + set lightness(lightness: number) { + this._lightness = lightness; + this._setBackgroundColor(this._backgroundColor); + this._findContrastColors(); + } + + get lightness(): number { + return this._lightness; + } + + set saturation(saturation: number) { + this._saturation = saturation; + this._updateColorSaturation(saturation); + this._findContrastColors(); + } + + get saturation(): number { + return this._saturation; + } + + set backgroundColor(backgroundColor: string | BackgroundColor | ThemeBackgroundInput) { + this._setBackgroundColor(backgroundColor); + this._findContrastColors(); + } + + get backgroundColorValue(): string { + return this._backgroundColorValue; + } + + get backgroundColor(): BackgroundColor { + return this._backgroundColor; + } + + set colors(colors: Color[]) { + this._colors = colors; + this._findContrastColors(); + } + + get colors(): Color[] { + return this._colors; + } + + set addColor(color: Color) { + this._colors.push(color); + this._findContrastColors(); + } + + set removeColor(color: { name: string }) { + const filteredColors = this._colors.filter((entry) => entry.name !== color.name); + this._colors = filteredColors; + this._findContrastColors(); + } + + set updateColor( + param: + | { + color: string; + name?: string; + colorKeys?: string[]; + ratios?: number[] | Record; + colorspace?: LeonardoColorspace; + smooth?: boolean; + } + | Array<{ + color: string; + name?: string; + colorKeys?: string[]; + ratios?: number[] | Record; + colorspace?: LeonardoColorspace; + smooth?: boolean; + }>, + ) { + if (Array.isArray(param)) { + for (const p of param) { + this._updateSingleColor(p); + } + } else { + this._updateSingleColor(param); + } + this._findContrastColors(); + } + + private _updateSingleColor(param: { + color: string; + name?: string; + colorKeys?: string[]; + ratios?: number[] | Record; + colorspace?: LeonardoColorspace; + smooth?: boolean; + }): void { + const currentColor = this._colors.find((entry) => entry.name === param.color); + if (!currentColor) return; + + const index = this._colors.indexOf(currentColor); + const filteredColors = this._colors.filter((entry) => entry.name !== param.color); + + if (param.name) currentColor.name = param.name; + if (param.colorKeys) currentColor.colorKeys = param.colorKeys; + if (param.ratios) currentColor.ratios = param.ratios; + if (param.colorspace) currentColor.colorspace = param.colorspace; + if (param.smooth !== undefined) currentColor.smooth = param.smooth; + + currentColor._generateColorScale(); + + filteredColors.splice(index, 0, currentColor); + this._colors = filteredColors; + } + + set output(output: LeonardoColorspace) { + this._output = output; + this._colors.forEach((element) => { + element.output = this._output; + }); + this._backgroundColor.output = this._output; + this._findContrastColors(); + } + + get output(): LeonardoColorspace { + return this._output; + } + + get contrastColors(): ContrastColors { + return this._contrastColors; + } + + get contrastColorPairs(): ContrastColorPairs { + return this._contrastColorPairs; + } + + get contrastColorValues(): ContrastColorValues { + return this._contrastColorValues; + } + + private _setBackgroundColor( + backgroundColor: string | BackgroundColor | ThemeBackgroundInput, + ): void { + if (typeof backgroundColor === "string") { + // String input - convert to BackgroundColor + const newBackgroundColor = new BackgroundColor({ + name: "background", + colorKeys: [backgroundColor], + output: "RGB", + }); + // Use Color.js HSLuv for lightness calculation + const hsluvColor = new ColorJS(String(backgroundColor)).to("hsluv"); + const calcLightness = round(hsluvColor.coords[2] ?? 0); + + this._backgroundColor = newBackgroundColor; + this._lightness = calcLightness; + this._backgroundColorValue = (newBackgroundColor.backgroundColorScale[this._lightness] ?? "#ffffff"); + } else if (backgroundColor instanceof BackgroundColor) { + backgroundColor.output = "RGB"; + const calcBackgroundColorValue = + backgroundColor.backgroundColorScale[this._lightness] ?? "#ffffff"; + + this._backgroundColor = backgroundColor; + this._backgroundColorValue = calcBackgroundColorValue; + } else { + // ThemeBackgroundInput object + const newBackgroundColor = new BackgroundColor({ + ...backgroundColor, + output: "RGB", + }); + const calcBackgroundColorValue = + newBackgroundColor.backgroundColorScale[this._lightness] ?? "#ffffff"; + + this._backgroundColor = newBackgroundColor; + this._backgroundColorValue = calcBackgroundColorValue; + } + } + + private _setBackgroundColorValue(): void { + this._backgroundColorValue = + this._backgroundColor.backgroundColorScale[this._lightness] ?? "#ffffff"; + } + + private _updateColorSaturation(saturation: number): void { + this._colors.forEach((color) => { + color.saturation = saturation; + }); + } + + private _findContrastColors(): ContrastColors { + // Use Color.js for RGB conversion + const bgColor = new ColorJS(String(this._backgroundColorValue)).to("srgb"); + const bgRgbArray = [ + Math.round((bgColor.coords[0] ?? 0) * 255), + Math.round((bgColor.coords[1] ?? 0) * 255), + Math.round((bgColor.coords[2] ?? 0) * 255), + ]; + const baseV = this._lightness / 100; + const convertedBackgroundColorValue = convertColorValue( + this._backgroundColorValue, + this._output, + ) as string; + const baseObj = { background: convertedBackgroundColorValue }; + + const returnColors: ContrastColors = []; + const returnColorValues: ContrastColorValues = []; + const returnColorPairs: ContrastColorPairs = { ...baseObj }; + returnColors.push(baseObj); + + this._colors.forEach((color) => { + if (color.ratios !== undefined) { + let swatchNames: string[] | undefined; + const newArr: ContrastColorValue[] = []; + const colorObj: ContrastColor = { + name: color.name, + values: newArr, + }; + + let ratioValues: number[]; + + if (Array.isArray(color.ratios)) { + ratioValues = color.ratios; + } else { + swatchNames = Object.keys(color.ratios); + ratioValues = Object.values(color.ratios); + } + + // Modify target ratios based on contrast multiplier + ratioValues = ratioValues.map((ratio) => + multiplyRatios(+ratio, this._contrast), + ); + + const contrastColors = searchColors( + color, + bgRgbArray, + baseV, + ratioValues, + this._formula, + ).map((clr) => convertColorValue(clr, this._output) as string); + + for (let i = 0; i < contrastColors.length; i++) { + let n: string; + if (!swatchNames) { + const rVal = ratioName( + Array.isArray(color.ratios) + ? color.ratios + : Object.values(color.ratios), + this._formula, + )[i] ?? 0; + n = color.name.concat(String(rVal)).replace(/\s+/g, ""); + } else { + n = swatchNames[i] ?? ""; + } + + const obj: ContrastColorValue = { + name: n, + contrast: ratioValues[i] ?? 0, + value: contrastColors[i] ?? "", + }; + newArr.push(obj); + returnColorPairs[n] = contrastColors[i] ?? ""; + returnColorValues.push(contrastColors[i] ?? ""); + } + returnColors.push(colorObj); + } + }); + + this._contrastColorValues = returnColorValues; + this._contrastColorPairs = returnColorPairs; + this._contrastColors = returnColors; + return this._contrastColors; + } + + private _findContrastColorPairs(): ContrastColorPairs { + return this._contrastColorPairs; + } + + private _findContrastColorValues(): ContrastColorValues { + return this._contrastColorValues; + } +} diff --git a/packages/color-engine/src/algorithms/leonardo/utils.ts b/packages/color-engine/src/algorithms/leonardo/utils.ts new file mode 100644 index 000000000..83fb350c4 --- /dev/null +++ b/packages/color-engine/src/algorithms/leonardo/utils.ts @@ -0,0 +1,717 @@ +/** + * Leonardo algorithm utilities + * Migrated to Color.js from chroma-js/ciebase/ciecam02/hsluv/apca-w3 + */ + +import Color from "colorjs.io"; +import { catmullRom2bezier, prepareCurve } from "../../utils/curve"; +import type { LeonardoColorspace, ContrastFormula } from "../../types"; + +// Colorspace mapping from Leonardo names to Color.js space names +export const colorSpaces: Record = { + CAM02: "cam16-jmh", // Using CAM16 as successor to CIECAM02 + CAM02p: "cam16-jmh", + HEX: "srgb", + HSL: "hsl", + HSLuv: "hsluv", + HSV: "hsv", + LAB: "lab", + LCH: "lch", + RGB: "srgb", + OKLAB: "oklab", + OKLCH: "oklch", +}; + +/** + * Round a number to specified decimal places + */ +export function round(x: number, n = 0): number { + const ten = 10 ** n; + return Math.round(x * ten) / ten; +} + +/** + * Multiply contrast ratios while preserving normalized behavior + */ +export function multiplyRatios(ratio: number, multiplier: number): number { + let r: number; + if (ratio > 1) { + r = (ratio - 1) * multiplier + 1; + } else if (ratio < -1) { + r = (ratio + 1) * multiplier - 1; + } else { + r = 1; + } + return round(r, 2); +} + +/** + * Convert color to CAM16 JMh array (replaces CIECAM02 JCh) + * Returns [J, M, h] where J is lightness (0-100) + */ +export function cArray(c: string): number[] { + const color = new Color(String(c)).to("cam16-jmh"); + return [color.coords[0] ?? 0, color.coords[1] ?? 0, color.coords[2] ?? 0]; +} + +/** + * Convert color to HSLuv array [H, S, L] + */ +export function hsluvArray(c: string): number[] { + const color = new Color(String(c)).to("hsluv"); + return [color.coords[0] ?? 0, color.coords[1] ?? 0, color.coords[2] ?? 0]; +} + +/** + * Filter NaN values to 0 + */ +function filterNaN(x: number): number { + return Number.isNaN(x) ? 0 : x; +} + +/** + * Get color in a specific color space as array + */ +function colorToSpaceArray(color: string, space: string): number[] { + const c = new Color(String(color)).to(space); + return [...c.coords]; +} + +/** + * Create color from space array + */ +function colorFromSpaceArray(coords: number[], space: string): string { + const color = new Color(space, coords as [number, number, number]); + return color.to("srgb").toString({ format: "hex" }); +} + +/** + * Smooth scale interpolation using Catmull-Rom to Bezier conversion + */ +function smoothScale( + ColorsArray: number[][], + domains: number[], + space: string, +): (d: number) => string { + const points: number[][] = [[], [], []]; + + ColorsArray.forEach((color, i) => + points.forEach((point, j) => point.push(domains[i] ?? 0, color[j] ?? 0)), + ); + + // Handle NaN values in chroma for lch + if (space === "lch") { + const point = points[1]; + if (point) { + for (let i = 1; i < point.length; i += 2) { + if (Number.isNaN(point[i])) { + point[i] = 0; + } + } + } + } + + // Handle leading, trailing, and middle NaN values + points.forEach((point) => { + if (!point) return; + const nans: number[] = []; + + // Leading NaNs + for (let i = 1; i < point.length; i += 2) { + if (Number.isNaN(point[i])) { + nans.push(i); + } else { + nans.forEach((j) => { + point[j] = point[i] ?? 0; + }); + nans.length = 0; + break; + } + } + + // All are grey case - use a safe hue value + if (nans.length) { + const safeHue = 0; + nans.forEach((j) => { + point[j] = safeHue; + }); + } + nans.length = 0; + + // Trailing NaNs + for (let i = point.length - 1; i > 0; i -= 2) { + if (Number.isNaN(point[i])) { + nans.push(i); + } else { + nans.forEach((j) => { + point[j] = point[i] ?? 0; + }); + break; + } + } + + // Other NaNs - remove them + for (let i = 1; i < point.length; i += 2) { + if (Number.isNaN(point[i])) { + point.splice(i - 1, 2); + i -= 2; + } + } + + // Force hue to go on shortest route for hue-based spaces + if (["lch", "hsl", "hsluv", "hsv", "cam16-jmh"].includes(space)) { + let prev = point[1] ?? 0; + let addon = 0; + for (let i = 3; i < point.length; i += 2) { + const p = (point[i] ?? 0) + addon; + const zero = Math.abs(prev - p); + const plus = Math.abs(prev - (p + 360)); + const minus = Math.abs(prev - (p - 360)); + if (plus < zero && plus < minus) { + addon += 360; + } + if (minus < zero && minus < plus) { + addon -= 360; + } + point[i] = (point[i] ?? 0) + addon; + prev = point[i] ?? 0; + } + } + }); + + const prep = points.map((point) => + catmullRom2bezier(point).map((curve) => + prepareCurve( + curve[0] ?? 0, + curve[1] ?? 0, + curve[2] ?? 0, + curve[3] ?? 0, + curve[4] ?? 0, + curve[5] ?? 0, + curve[6] ?? 0, + curve[7] ?? 0, + ), + ), + ); + + return (d: number): string => { + const ch = prep.map((p) => { + for (let i = 0; i < p.length; i++) { + const fn = p[i]; + if (fn) { + const res = fn(d); + if (res != null) { + return res; + } + } + } + return 0; + }) as number[]; + + // Clamp negative chroma/colorfulness for CAM16 + if (space === "cam16-jmh" && (ch[1] ?? 0) < 0) { + ch[1] = 0; + } + + return colorFromSpaceArray(ch, space); + }; +} + +/** + * Create a power scale function + */ +function makePowScale( + exp = 1, + domains = [0, 1], + range = [0, 1], +): (x: number) => number { + const d0 = domains[0] ?? 0; + const d1 = domains[1] ?? 1; + const r0 = range[0] ?? 0; + const r1 = range[1] ?? 1; + const m = (r1 - r0) / (d1 ** exp - d0 ** exp); + const c = r0 - m * d0 ** exp; + return (x: number) => m * x ** exp + c; +} + +export interface CreateScaleOptions { + swatches: number; + colorKeys: string[]; + colorspace?: LeonardoColorspace; + shift?: number; + fullScale?: boolean; + smooth?: boolean; + distributeLightness?: "linear" | "polynomial"; + sortColor?: boolean; + asFun?: boolean; +} + +/** + * Create a color scale from color keys + */ +export function createScale({ + swatches, + colorKeys, + colorspace = "LAB", + shift = 1, + fullScale = true, + smooth = false, + distributeLightness = "linear", + sortColor = true, + asFun = false, +}: CreateScaleOptions): string[] | ((pos: number) => string) { + const space = colorSpaces[colorspace]; + if (!space) { + throw new Error(`Colorspace "${colorspace}" not supported`); + } + if (!colorKeys) { + throw new Error(`Colorkeys missing: returned "${colorKeys}"`); + } + + let domains: number[]; + + if (fullScale) { + // Set domain based on CAM16 J lightness against full black-to-white scale + domains = colorKeys + .map((key) => { + const cam16 = cArray(key); + return swatches - swatches * ((cam16[0] ?? 0) / 100); + }) + .sort((a, b) => a - b) + .concat(swatches); + + domains.unshift(0); + } else { + // Domains as percentage of available luminosity range + const lums = colorKeys.map((c) => (cArray(c)[0] ?? 0) / 100); + const min = Math.min(...lums); + const max = Math.max(...lums); + + domains = lums + .map((lum) => { + if (lum === 0 || isNaN((lum - min) / (max - min))) return 0; + return swatches - ((lum - min) / (max - min)) * swatches; + }) + .sort((a, b) => a - b); + } + + // Apply power scale transformation + const sqrtDomains = makePowScale(shift, [1, swatches], [1, swatches]); + const transformedDomains = domains.map((d) => Math.max(0, sqrtDomains(d))); + domains = transformedDomains; + + if (distributeLightness === "polynomial") { + const polynomial = (x: number): number => { + return Math.sqrt(Math.sqrt((Math.pow(x, 2.25) + Math.pow(x, 4)) / 2)); + }; + + const percDomains = domains.map((d) => d / swatches); + domains = percDomains.map((d) => polynomial(d) * swatches); + } + + // Sort colors by lightness (CAM16 J) + const sortedColor = colorKeys + .map((c, i) => ({ colorKeys: cArray(c), index: i })) + .sort((c1, c2) => (c2.colorKeys[0] ?? 0) - (c1.colorKeys[0] ?? 0)) + .map((data) => colorKeys[data.index]!); + + let ColorsArray: string[]; + + if (fullScale) { + ColorsArray = ["#ffffff", ...sortedColor, "#000000"]; + } else { + ColorsArray = sortColor ? sortedColor : colorKeys; + } + + let scale: (pos: number) => string; + let smoothScaleArray: string[] | undefined; + + if (smooth) { + const ColorsSpaceArray = ColorsArray.map((d) => colorToSpaceArray(String(d), space)); + + // Handle NaN in hue for grey colors in LCH + if (space === "lch") { + ColorsSpaceArray.forEach((c) => { + if (c && c[1] !== undefined) { + c[1] = Number.isNaN(c[1]) ? 0 : c[1]; + } + }); + } + + // For CAM16, check if color is grey (low chroma) and mark hue as NaN + if (space === "cam16-jmh") { + for (let i = 0; i < ColorsArray.length; i++) { + const lch = colorToSpaceArray(ColorsArray[i]!, "lch"); + if (Number.isNaN(lch[2]) || (lch[1] ?? 0) < 0.01) { + if (ColorsSpaceArray[i]) { + ColorsSpaceArray[i]![2] = NaN; + } + } + } + } + + scale = smoothScale(ColorsSpaceArray, domains, space); + smoothScaleArray = new Array(swatches).fill(null).map((_, d) => scale(d)); + } else { + // Create scale function using Color.js range interpolation + scale = (pos: number): string => { + // Find which segment we're in based on domains + const clampedPos = Math.max(0, Math.min(swatches - 1, pos)); + + // Find the two domain indices that bracket our position + let lowerIdx = 0; + for (let i = 0; i < domains.length - 1; i++) { + if (clampedPos >= (domains[i] ?? 0)) { + lowerIdx = i; + } + } + const upperIdx = Math.min(lowerIdx + 1, domains.length - 1); + + const lowerDomain = domains[lowerIdx] ?? 0; + const upperDomain = domains[upperIdx] ?? swatches; + const lowerColor = ColorsArray[lowerIdx] ?? ColorsArray[0]!; + const upperColor = ColorsArray[upperIdx] ?? ColorsArray[ColorsArray.length - 1]!; + + // Calculate interpolation position within this segment + const segmentRange = upperDomain - lowerDomain; + const t = segmentRange > 0 ? (clampedPos - lowerDomain) / segmentRange : 0; + + // Interpolate + const c1 = new Color(lowerColor); + const c2 = new Color(upperColor); + const range = Color.range(c1, c2, { space }); + return range(Math.max(0, Math.min(1, t))).to("srgb").toString({ format: "hex" }); + }; + } + + if (asFun) { + return scale; + } + + if (smooth && smoothScaleArray) { + return smoothScaleArray.filter((el: string | null) => el != null); + } + + // Generate colors array + const Colors: string[] = []; + for (let i = 0; i < swatches; i++) { + Colors.push(scale(i)); + } + return Colors; +} + +/** + * Calculate WCAG 2.1 relative luminance + */ +export function luminance(r: number, g: number, b: number): number { + const a = [r, g, b].map((v) => { + v /= 255; + return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; + }); + return (a[0] ?? 0) * 0.2126 + (a[1] ?? 0) * 0.7152 + (a[2] ?? 0) * 0.0722; +} + +/** + * Get contrast ratio with direction (positive/negative based on theme) + */ +export function getContrast( + color: number[], + base: number[], + baseV?: number, + method: ContrastFormula = "wcag2", +): number { + if (baseV === undefined) { + const baseColor = new Color("srgb", [ + (base[0] ?? 0) / 255, + (base[1] ?? 0) / 255, + (base[2] ?? 0) / 255, + ]); + const baseLightness = baseColor.to("hsluv").coords[2] ?? 0; + baseV = round(baseLightness / 100, 2); + } + + const fgColor = new Color("srgb", [ + (color[0] ?? 0) / 255, + (color[1] ?? 0) / 255, + (color[2] ?? 0) / 255, + ]); + const bgColor = new Color("srgb", [ + (base[0] ?? 0) / 255, + (base[1] ?? 0) / 255, + (base[2] ?? 0) / 255, + ]); + + if (method === "wcag2") { + const colorLum = luminance(color[0] ?? 0, color[1] ?? 0, color[2] ?? 0); + const baseLum = luminance(base[0] ?? 0, base[1] ?? 0, base[2] ?? 0); + + const cr1 = (colorLum + 0.05) / (baseLum + 0.05); + const cr2 = (baseLum + 0.05) / (colorLum + 0.05); + + if (baseV < 0.5) { + // Dark themes + if (cr1 >= 1) { + return cr1; + } + return -cr2; + } + // Light themes + if (cr1 < 1) { + return cr2; + } + if (cr1 === 1) { + return cr1; + } + return -cr1; + } else if (method === "wcag3") { + // Use Color.js APCA contrast + const apcaResult = bgColor.contrast(fgColor, "APCA"); + // Apply direction based on theme + return baseV < 0.5 ? -apcaResult : apcaResult; + } else { + throw new Error( + `Contrast calculation method ${method} unsupported; use 'wcag2' or 'wcag3'`, + ); + } +} + +/** + * Find minimum positive ratio value + */ +export function minPositive(r: number[], formula: ContrastFormula): number { + if (!r) { + throw new Error("Array undefined"); + } + if (!Array.isArray(r)) { + throw new Error("Passed object is not an array"); + } + const min = formula === "wcag2" ? 0 : 1; + return Math.min(...r.filter((val) => val >= min)); +} + +/** + * Generate ratio names for output + */ +export function ratioName(r: number[], formula: ContrastFormula): number[] { + if (!r) { + throw new Error("Ratios undefined"); + } + r = r.sort((a, b) => a - b); + + const min = minPositive(r, formula); + const minIndex = r.indexOf(min); + const nArr: number[] = []; + + const rNeg = r.slice(0, minIndex); + const rPos = r.slice(minIndex, r.length); + + // Name negative values + for (let i = 0; i < rNeg.length; i++) { + const d = 1 / (rNeg.length + 1); + const m = d * 100; + const nVal = m * (i + 1); + nArr.push(round(nVal)); + } + + // Name positive values + for (let i = 0; i < rPos.length; i++) { + nArr.push((i + 1) * 100); + } + + nArr.sort((a, b) => a - b); + return nArr; +} + +/** + * Remove duplicates from array by property + */ +export function removeDuplicates>( + originalArray: T[], + prop: string, +): T[] { + const newArray: T[] = []; + const lookupObject: Record = {}; + + for (let i = 0; i < originalArray.length; i++) { + const item = originalArray[i]; + if (item) { + lookupObject[String(item[prop])] = item; + } + } + + Object.keys(lookupObject).forEach((i) => { + const item = lookupObject[i]; + if (item) { + newArray.push(item); + } + }); + return newArray; +} + +/** + * Convert color value to specified output format + */ +export function convertColorValue( + color: string, + format: LeonardoColorspace, + object = false, +): string | Record { + if (!color) { + throw new Error(`Cannot convert color value of "${color}"`); + } + if (!colorSpaces[format]) { + throw new Error(`Cannot convert to colorspace "${format}"`); + } + + const space = colorSpaces[format]!; + const colorObj = new Color(String(color)).to(space); + const coords = [...colorObj.coords]; + + if (format === "HEX") { + if (object) { + const srgb = colorObj.to("srgb"); + return { + r: Math.round((srgb.coords[0] ?? 0) * 255), + g: Math.round((srgb.coords[1] ?? 0) * 255), + b: Math.round((srgb.coords[2] ?? 0) * 255), + }; + } + return colorObj.to("srgb").toString({ format: "hex" }); + } + + const colorObject: Record = {}; + const newColorObj = coords.map(filterNaN); + + // Build output based on color space + const spaceLetters: Record = { + "cam16-jmh": ["J", "M", "h"], + hsluv: ["H", "S", "L"], + hsl: ["h", "s", "l"], + hsv: ["h", "s", "v"], + lab: ["l", "a", "b"], + lch: ["l", "c", "h"], + oklab: ["l", "a", "b"], + oklch: ["l", "c", "h"], + srgb: ["r", "g", "b"], + }; + + const letters = spaceLetters[space] || ["x", "y", "z"]; + + const formattedValues = newColorObj.map((ch, i) => { + const letter = letters[i] ?? "x"; + let rnd: string | number = round(ch); + colorObject[letter] = rnd; + + if (["lab", "lch", "cam16-jmh"].includes(space)) { + if (!object) { + if (letter.toLowerCase() === "l" || letter === "J") { + rnd = rnd + "%"; + } + if (letter.toLowerCase() === "h") { + rnd = rnd + "deg"; + } + } + } else if (space === "srgb") { + // sRGB coords are 0-1, need to scale to 0-255 for RGB output + const scaled = round(ch * 255); + colorObject[letter] = scaled; + rnd = scaled; + } else if (space !== "hsluv") { + if (["s", "l", "v"].includes(letter)) { + colorObject[letter] = round(ch, 2); + if (!object) { + rnd = round(ch * 100); + rnd = rnd + "%"; + } + } else if (letter === "h" && !object) { + rnd = rnd + "deg"; + } + } + return rnd; + }); + + if (object) { + return colorObject; + } + + // Map space names for output + const outputSpaceName: Record = { + "cam16-jmh": "cam16", + srgb: "rgb", + }; + const stringName = outputSpaceName[space] || space; + return `${stringName}(${formattedValues.join(", ")})`; +} + +export interface ColorWithModifiedKeys { + _modifiedKeys: string[]; + _colorspace: LeonardoColorspace; + _smooth: boolean; +} + +/** + * Search for colors matching target contrast ratios + * Uses binary search for efficiency + */ +export function searchColors( + color: ColorWithModifiedKeys, + bgRgbArray: number[], + baseV: number, + ratioValues: number[], + formula: ContrastFormula, +): string[] { + const colorLen = 3000; + const colorScale = createScale({ + swatches: colorLen, + colorKeys: color._modifiedKeys, + colorspace: color._colorspace, + shift: 1, + smooth: color._smooth, + asFun: true, + }) as (pos: number) => string; + + const ccache: Record = {}; + + const getContrast2 = (i: number): number => { + if (ccache[i]) { + return ccache[i]; + } + const c = new Color(colorScale(i)).to("srgb"); + const rgb = [ + Math.round((c.coords[0] ?? 0) * 255), + Math.round((c.coords[1] ?? 0) * 255), + Math.round((c.coords[2] ?? 0) * 255), + ]; + const contrast = getContrast(rgb, bgRgbArray, baseV, formula); + ccache[i] = contrast; + return contrast; + }; + + const colorSearch = (x: number): number => { + const first = getContrast2(0); + const last = getContrast2(colorLen); + const dir = first < last ? 1 : -1; + const epsilon = 0.01; + x += 0.005 * Math.sign(x); + let step = colorLen / 2; + let dot = step; + let val = getContrast2(dot); + let counter = 100; + + while (Math.abs(val - x) > epsilon && counter) { + counter--; + step /= 2; + if (val < x) { + dot += step * dir; + } else { + dot -= step * dir; + } + val = getContrast2(dot); + } + return round(dot, 3); + }; + + const outputColors: string[] = []; + ratioValues.forEach((ratio) => outputColors.push(colorScale(colorSearch(+ratio)))); + return outputColors; +} diff --git a/packages/color-engine/src/algorithms/material/index.ts b/packages/color-engine/src/algorithms/material/index.ts index ebd30e465..49e7d89ba 100644 --- a/packages/color-engine/src/algorithms/material/index.ts +++ b/packages/color-engine/src/algorithms/material/index.ts @@ -11,9 +11,10 @@ import { Hct, TonalPalette, } from "@material/material-color-utilities"; +import Color from "colorjs.io"; -import type { MaterialOptions, ScaleOutput } from "../../core/types"; -import { hexToRgb, SCALE_STEPS } from "../../core/utils"; +import type { MaterialOptions, ScaleOutput } from "../../types"; +import { SCALE_STEPS } from "../../types"; // ============================================================================ // Default Values @@ -34,8 +35,11 @@ const DEFAULT_TONES = [99, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10]; * Convert hex to ARGB integer (Material format) */ function hexToArgb(hex: string): number { - const rgb = hexToRgb(hex); - return (255 << 24) | (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + const color = new Color(hex).to("srgb"); + const r = Math.round((color.coords[0] ?? 0) * 255); + const g = Math.round((color.coords[1] ?? 0) * 255); + const b = Math.round((color.coords[2] ?? 0) * 255); + return (255 << 24) | (r << 16) | (g << 8) | b; } /** diff --git a/packages/color-engine/src/algorithms/radix/index.ts b/packages/color-engine/src/algorithms/radix/index.ts deleted file mode 100644 index 233fa9be8..000000000 --- a/packages/color-engine/src/algorithms/radix/index.ts +++ /dev/null @@ -1,410 +0,0 @@ -/** - * Radix Colors Algorithm - * - * A simplified implementation of Radix UI's color scale generation. - * Uses Delta E OK distance to find the closest predefined scales - * and blends between them based on the input color. - * - * Original: https://github.com/radix-ui/colors - * License: MIT - */ - -import type { RadixOptions, ScaleOutput, RGB, OKLCH } from "../../core/types"; -import { - hexToRgb, - rgbToHex, - rgbToOklch, - oklchToRgb, - luminance, - lerp, - SCALE_STEPS, -} from "../../core/utils"; - -// ============================================================================ -// Predefined Radix Color Scales (Step 6 as reference) -// ============================================================================ - -/** - * Reference colors at step 6 (middle of scale) for each Radix color - * These are used to find the closest scale via Delta E - */ -const RADIX_REFERENCE_COLORS: Record = { - // Grays - gray: "#868e96", - mauve: "#8e8c99", - slate: "#8b8d98", - sage: "#868e8b", - olive: "#898e87", - sand: "#8f8b82", - - // Colors - tomato: "#ec5e42", - red: "#e5484d", - ruby: "#e54666", - crimson: "#e93d82", - pink: "#d6409f", - plum: "#ab4aba", - purple: "#8e4ec6", - violet: "#6e56cf", - iris: "#5b5bd6", - indigo: "#3e63dd", - blue: "#0090ff", - cyan: "#00a2c7", - teal: "#12a594", - jade: "#29a383", - green: "#30a46c", - grass: "#46a758", - brown: "#ad7f58", - bronze: "#a18072", - gold: "#978365", - sky: "#7ce2fe", - mint: "#86ead4", - lime: "#bdee63", - yellow: "#ffe629", - amber: "#ffc53d", - orange: "#f76b15", -}; - -/** - * Full 12-step scales for light mode - * Step 1 = lightest, Step 12 = darkest - */ -const RADIX_LIGHT_SCALES: Record = { - gray: [ - "#fcfcfc", "#f9f9f9", "#f0f0f0", "#e8e8e8", "#e0e0e0", "#d9d9d9", - "#cecece", "#bbbbbb", "#8d8d8d", "#838383", "#646464", "#202020", - ], - mauve: [ - "#fdfcfd", "#faf9fb", "#f2eff3", "#eae7ec", "#e3dfe6", "#dbd8e0", - "#d0cdd7", "#bcbac7", "#8e8c99", "#84828e", "#65636d", "#211f26", - ], - slate: [ - "#fcfcfd", "#f9f9fb", "#f0f0f3", "#e8e8ec", "#e0e1e6", "#d9d9e0", - "#cdced6", "#b9bbc6", "#8b8d98", "#80838d", "#60646c", "#1c2024", - ], - blue: [ - "#fbfdff", "#f4faff", "#e6f4fe", "#d5efff", "#c2e5ff", "#acd8fc", - "#8ec8f6", "#5eb1ef", "#0090ff", "#0588f0", "#0d74ce", "#113264", - ], - green: [ - "#fbfefc", "#f4fbf6", "#e6f6eb", "#d6f1df", "#c4e8d1", "#adddc0", - "#8eceaa", "#5bb98b", "#30a46c", "#2b9a66", "#218358", "#193b2d", - ], - red: [ - "#fffcfc", "#fff7f7", "#feebec", "#ffdbdc", "#ffcdce", "#fdbdbe", - "#f4a9aa", "#eb8e90", "#e5484d", "#dc3e42", "#ce2c31", "#641723", - ], - orange: [ - "#fefcfb", "#fff7ed", "#ffefd6", "#ffdfb5", "#ffd19a", "#ffc182", - "#f5ae73", "#ec9455", "#f76b15", "#ef5f00", "#cc4e00", "#582d1d", - ], - yellow: [ - "#fdfdf9", "#fefce9", "#fffab8", "#fff394", "#ffe770", "#f3d768", - "#e4c767", "#d5ae49", "#ffe629", "#ffdc00", "#9e6c00", "#473b1f", - ], - purple: [ - "#fefcfe", "#fbf7fe", "#f7edfe", "#f2e2fc", "#ead5f9", "#e0c4f4", - "#d1afec", "#be93e4", "#8e4ec6", "#8347b9", "#6f3a9e", "#2b0e44", - ], - pink: [ - "#fffcfe", "#fef7fb", "#fee9f5", "#fbdcef", "#f6cee7", "#efbfdd", - "#e7acd0", "#dd93c2", "#d6409f", "#cf3897", "#c2298a", "#651249", - ], - cyan: [ - "#fafdfe", "#f2fafb", "#def7f9", "#caf1f6", "#b5e9f0", "#9ddde7", - "#7dcedc", "#52bece", "#00a2c7", "#0797b9", "#107d98", "#0d3c48", - ], - teal: [ - "#fafefd", "#f3fbf9", "#e0f8f3", "#ccf3eb", "#b8ece1", "#a1e3d4", - "#83d6c4", "#53c5af", "#12a594", "#0d9b8a", "#067a6f", "#0d3d38", - ], -}; - -/** - * Full 12-step scales for dark mode - */ -const RADIX_DARK_SCALES: Record = { - gray: [ - "#111111", "#191919", "#222222", "#2a2a2a", "#313131", "#3a3a3a", - "#484848", "#606060", "#6e6e6e", "#7b7b7b", "#b4b4b4", "#eeeeee", - ], - mauve: [ - "#121113", "#1a191b", "#232225", "#2b292d", "#323035", "#3c393f", - "#49474e", "#625f69", "#6f6d78", "#7c7a85", "#b5b2bc", "#eeeef0", - ], - slate: [ - "#111113", "#18191b", "#212225", "#272a2d", "#2e3135", "#363a3f", - "#43484e", "#5a6169", "#696e77", "#777b84", "#b0b4ba", "#edeef0", - ], - blue: [ - "#0d1520", "#111927", "#0d2847", "#003362", "#004074", "#104d87", - "#205d9e", "#2870bd", "#0090ff", "#3b9eff", "#70b8ff", "#c2e6ff", - ], - green: [ - "#0e1512", "#121b17", "#132d21", "#113b29", "#174933", "#20573e", - "#28684a", "#2f7c57", "#30a46c", "#33b074", "#3dd68c", "#b1f1cb", - ], - red: [ - "#191111", "#201314", "#3b1219", "#500f1c", "#611623", "#72232d", - "#8c333a", "#b54548", "#e5484d", "#ec5d5e", "#ff9592", "#ffd1d9", - ], - orange: [ - "#17120e", "#1e160f", "#331e0b", "#462100", "#562800", "#66350c", - "#7e451d", "#a35829", "#f76b15", "#ff801f", "#ffa057", "#ffe0c2", - ], - yellow: [ - "#14120b", "#1b180f", "#2d2305", "#362b00", "#433500", "#524202", - "#665417", "#836a21", "#ffe629", "#ffff57", "#f5e147", "#f6eeb4", - ], - purple: [ - "#18111b", "#1e1523", "#301c3b", "#3d224e", "#48295c", "#54346b", - "#664282", "#7e5ba7", "#8e4ec6", "#9a5cd0", "#c191f2", "#ecd9fa", - ], - pink: [ - "#191117", "#21121d", "#37172f", "#4b143d", "#591c47", "#692955", - "#833869", "#a84f85", "#d6409f", "#de51a8", "#ff8dcc", "#fdd1ea", - ], - cyan: [ - "#0b161a", "#101b20", "#082c36", "#003848", "#004558", "#045468", - "#12677e", "#11809c", "#00a2c7", "#23afd0", "#4ccce6", "#b6ecf7", - ], - teal: [ - "#0d1514", "#111c1b", "#0d2d2a", "#023b37", "#084843", "#145750", - "#1c6961", "#207e73", "#12a594", "#0eb39e", "#0bd8b6", "#adf0dd", - ], -}; - -// ============================================================================ -// Delta E OK Distance -// ============================================================================ - -/** - * Calculate Delta E OK distance between two colors - * This is a perceptually uniform color difference metric - */ -function deltaEOk(color1: OKLCH, color2: OKLCH): number { - const dL = color1.l - color2.l; - const dC = color1.c - color2.c; - - // Handle hue difference (shortest path around circle) - let dH = color1.h - color2.h; - if (dH > 180) dH -= 360; - if (dH < -180) dH += 360; - - // Convert hue difference to chord length - const avgC = (color1.c + color2.c) / 2; - const dHChord = 2 * avgC * Math.sin((dH * Math.PI) / 360); - - return Math.sqrt(dL * dL + dC * dC + dHChord * dHChord); -} - -/** - * Find the closest Radix scale to a given color - */ -function findClosestScale(color: string): { name: string; distance: number }[] { - const inputOklch = rgbToOklch(hexToRgb(color)); - - const distances = Object.entries(RADIX_REFERENCE_COLORS).map(([name, refHex]) => { - const refOklch = rgbToOklch(hexToRgb(refHex)); - return { - name, - distance: deltaEOk(inputOklch, refOklch), - }; - }); - - // Sort by distance (closest first) - return distances.sort((a, b) => a.distance - b.distance); -} - -// ============================================================================ -// Color Blending -// ============================================================================ - -/** - * Blend two OKLCH colors - */ -function blendOklch(c1: OKLCH, c2: OKLCH, t: number): OKLCH { - // Handle hue interpolation (shortest path) - let h1 = c1.h; - let h2 = c2.h; - const diff = h2 - h1; - - if (diff > 180) h1 += 360; - else if (diff < -180) h2 += 360; - - let h = lerp(h1, h2, t) % 360; - if (h < 0) h += 360; - - return { - l: lerp(c1.l, c2.l, t), - c: lerp(c1.c, c2.c, t), - h, - }; -} - -/** - * Blend two hex colors - */ -function blendHex(hex1: string, hex2: string, t: number): string { - const c1 = rgbToOklch(hexToRgb(hex1)); - const c2 = rgbToOklch(hexToRgb(hex2)); - const blended = blendOklch(c1, c2, t); - return rgbToHex(oklchToRgb(blended)); -} - -/** - * Blend two color scales - */ -function blendScales(scale1: string[], scale2: string[], t: number): string[] { - return scale1.map((color, i) => blendHex(color, scale2[i] ?? color, t)); -} - -// ============================================================================ -// Background Adaptation -// ============================================================================ - -/** - * Determine if background is light or dark - */ -function isLightBackground(background: string): boolean { - const rgb = hexToRgb(background); - return luminance(rgb) > 0.5; -} - -/** - * Adjust scale for a specific background - * This shifts the scale to ensure proper contrast - */ -function adjustScaleForBackground( - scale: string[], - background: string, - isLight: boolean -): string[] { - const bgOklch = rgbToOklch(hexToRgb(background)); - - return scale.map((hex, index) => { - const oklch = rgbToOklch(hexToRgb(hex)); - - // Adjust lightness based on step and background - // Steps 1-2 should be close to background - // Steps 11-12 should have high contrast - if (isLight) { - // Light mode: step 1 should be near-white, step 12 near-black - const targetL = 1 - (index / 11) * 0.95; - const adjusted: OKLCH = { - ...oklch, - l: lerp(oklch.l, targetL, 0.3), // Gentle adjustment - }; - return rgbToHex(oklchToRgb(adjusted)); - } else { - // Dark mode: step 1 should be near-black, step 12 near-white - const targetL = (index / 11) * 0.95; - const adjusted: OKLCH = { - ...oklch, - l: lerp(oklch.l, targetL, 0.3), - }; - return rgbToHex(oklchToRgb(adjusted)); - } - }); -} - -// ============================================================================ -// Main API -// ============================================================================ - -/** - * Generate a color scale using Radix-style blending - * - * The algorithm: - * 1. Find the two closest predefined Radix scales via Delta E OK - * 2. Calculate blend ratio based on distances - * 3. Blend the scales in OKLCH space - * 4. Adjust for the target background - * - * @param options - Configuration options - * @returns ScaleOutput with 11 steps (50-950) - */ -export function generateScale(options: RadixOptions): ScaleOutput { - const { accent, background, gray } = options; - - // Determine light/dark mode from background - const isLight = isLightBackground(background); - const scales = isLight ? RADIX_LIGHT_SCALES : RADIX_DARK_SCALES; - - // Find closest scales - const closest = findClosestScale(accent); - const primary = closest[0] ?? { name: "gray", distance: 0 }; - const secondary = closest[1] ?? { name: "gray", distance: 0 }; - - // Get the scales (fallback to gray if not found) - const grayScale = scales.gray!; - const primaryScale = scales[primary.name] ?? grayScale; - const secondaryScale = scales[secondary.name] ?? grayScale; - - // Calculate blend ratio using tangent-based method (Radix style) - // When distance is 0, use 100% primary - // As distance increases, blend more with secondary - const totalDist = primary.distance + secondary.distance; - const blendRatio = totalDist > 0 ? secondary.distance / totalDist : 1; - - // Blend the two scales - let blendedScale = blendScales(primaryScale, secondaryScale, 1 - blendRatio); - - // Adjust hue toward the accent color - const accentOklch = rgbToOklch(hexToRgb(accent)); - blendedScale = blendedScale.map((hex, index) => { - const oklch = rgbToOklch(hexToRgb(hex)); - // Stronger hue influence in the middle steps (4-9) - const hueInfluence = index >= 3 && index <= 8 ? 0.6 : 0.3; - const adjusted = blendOklch(oklch, { ...oklch, h: accentOklch.h }, hueInfluence); - return rgbToHex(oklchToRgb(adjusted)); - }); - - // Adjust for background - blendedScale = adjustScaleForBackground(blendedScale, background, isLight); - - // Map 12-step Radix scale to 11-step output - // We'll skip step 1 (too close to background) and use steps 2-12 - const mappedScale = [ - blendedScale[1], // 50 - blendedScale[2], // 100 - blendedScale[3], // 200 - blendedScale[4], // 300 - blendedScale[5], // 400 - blendedScale[6], // 500 - blendedScale[7], // 600 - blendedScale[8], // 700 - blendedScale[9], // 800 - blendedScale[10], // 900 - blendedScale[11], // 950 - ]; - - // Build output - const output: Partial = {}; - SCALE_STEPS.forEach((step, index) => { - output[step] = mappedScale[index]; - }); - - return output as ScaleOutput; -} - -/** - * Get a predefined Radix scale by name - */ -export function getRadixScale( - name: string, - mode: "light" | "dark" = "light" -): string[] | null { - const scales = mode === "light" ? RADIX_LIGHT_SCALES : RADIX_DARK_SCALES; - return scales[name] || null; -} - -/** - * Get all available Radix scale names (only those with full scale definitions) - */ -export function getRadixScaleNames(): string[] { - return Object.keys(RADIX_LIGHT_SCALES); -} - -// Re-export types -export type { RadixOptions }; diff --git a/packages/color-engine/src/core/index.ts b/packages/color-engine/src/core/index.ts deleted file mode 100644 index 5aa7a4f9a..000000000 --- a/packages/color-engine/src/core/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Core exports for @dotui/color-engine - */ - -export * from "./types"; -export * from "./utils"; diff --git a/packages/color-engine/src/core/types.ts b/packages/color-engine/src/core/types.ts deleted file mode 100644 index 0825487c9..000000000 --- a/packages/color-engine/src/core/types.ts +++ /dev/null @@ -1,247 +0,0 @@ -/** - * Core types for @dotui/color-engine - */ - -// ============================================================================ -// Color Types -// ============================================================================ - -/** RGB color as tuple [r, g, b] where values are 0-255 */ -export type RGB = [number, number, number]; - -/** OKLCH color object */ -export interface OKLCH { - l: number; // Lightness 0-1 - c: number; // Chroma 0-0.4+ - h: number; // Hue 0-360 -} - -/** HSL color object */ -export interface HSL { - h: number; // Hue 0-360 - s: number; // Saturation 0-100 - l: number; // Lightness 0-100 -} - -// ============================================================================ -// Scale Output -// ============================================================================ - -/** Standard 11-step scale output (50-950) */ -export interface ScaleOutput { - "50": string; - "100": string; - "200": string; - "300": string; - "400": string; - "500": string; - "600": string; - "700": string; - "800": string; - "900": string; - "950": string; -} - -/** Step names for 11-step scale */ -export const SCALE_STEPS = [ - "50", - "100", - "200", - "300", - "400", - "500", - "600", - "700", - "800", - "900", - "950", -] as const; - -export type ScaleStep = (typeof SCALE_STEPS)[number]; - -// ============================================================================ -// Algorithm Types -// ============================================================================ - -export type Algorithm = "leonardo" | "material" | "radix"; - -// ============================================================================ -// Leonardo Types -// ============================================================================ - -/** Colorspaces supported by Leonardo for interpolation */ -export type LeonardoColorspace = - | "RGB" - | "HEX" - | "HSL" - | "HSLuv" - | "HSV" - | "LAB" - | "LCH" - | "OKLAB" - | "OKLCH" - | "CAM02" - | "CAM02p"; - -/** Contrast formula options */ -export type ContrastFormula = "wcag2" | "wcag3"; - -/** Leonardo algorithm options */ -export interface LeonardoOptions { - /** Primary color (hex) */ - color: string; - - /** Background color for contrast calculations (hex) */ - background: string; - - /** Additional color keys for gradient interpolation */ - colorKeys?: string[]; - - /** - * Target contrast ratios for each step - * @default [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15] - */ - ratios?: number[]; - - /** - * Colorspace for interpolation - * @default 'OKLCH' - */ - colorspace?: LeonardoColorspace; - - /** - * Saturation modifier (0-100) - * @default 100 - */ - saturation?: number; - - /** - * Contrast multiplier - scales all ratios - * @default 1 - */ - contrast?: number; - - /** - * Use smooth Bezier interpolation - * @default false - */ - smooth?: boolean; - - /** - * Contrast calculation formula - * @default 'wcag2' - */ - formula?: ContrastFormula; -} - -// ============================================================================ -// Material Types -// ============================================================================ - -/** Material algorithm options */ -export interface MaterialOptions { - /** Source color (hex) */ - color: string; - - /** - * Specific tone values to generate (0-100) - * @default [99, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10] - */ - tones?: number[]; - - /** - * Override chroma value (0-150+) - * If not provided, uses source color's chroma - */ - chroma?: number; - - /** - * Override hue value (0-360) - * If not provided, uses source color's hue - */ - hue?: number; - - /** - * Contrast level adjustment (-1 to 1) - * -1 = reduced, 0 = standard, 1 = high - * @default 0 - */ - contrastLevel?: number; -} - -// ============================================================================ -// Radix Types -// ============================================================================ - -/** Radix algorithm options */ -export interface RadixOptions { - /** Accent/brand color (hex) */ - accent: string; - - /** Background color (hex) */ - background: string; - - /** - * Gray base color (hex) - * If not provided, auto-detected from accent - */ - gray?: string; -} - -/** Available predefined Radix scale names */ -export type RadixScaleName = - // Grays - | "gray" - | "mauve" - | "slate" - | "sage" - | "olive" - | "sand" - // Colors - | "tomato" - | "red" - | "ruby" - | "crimson" - | "pink" - | "plum" - | "purple" - | "violet" - | "iris" - | "indigo" - | "blue" - | "cyan" - | "teal" - | "jade" - | "green" - | "grass" - | "brown" - | "bronze" - | "gold" - | "sky" - | "mint" - | "lime" - | "yellow" - | "amber" - | "orange"; - -// ============================================================================ -// Unified API Types -// ============================================================================ - -/** Input for Leonardo algorithm via unified API */ -export interface LeonardoInput extends LeonardoOptions { - algorithm: "leonardo"; -} - -/** Input for Material algorithm via unified API */ -export interface MaterialInput extends MaterialOptions { - algorithm: "material"; -} - -/** Input for Radix algorithm via unified API */ -export interface RadixInput extends RadixOptions { - algorithm: "radix"; -} - -/** Unified generateScale input (discriminated union) */ -export type GenerateScaleInput = LeonardoInput | MaterialInput | RadixInput; diff --git a/packages/color-engine/src/core/utils.ts b/packages/color-engine/src/core/utils.ts deleted file mode 100644 index 291111942..000000000 --- a/packages/color-engine/src/core/utils.ts +++ /dev/null @@ -1,384 +0,0 @@ -/** - * Core color utilities for @dotui/color-engine - * - * Pure functions for color conversions and contrast calculations. - * No external dependencies - just math. - */ - -import type { HSL, OKLCH, RGB } from "./types"; - -export { SCALE_STEPS } from "./types"; - -// ============================================================================ -// Hex <-> RGB Conversions -// ============================================================================ - -/** - * Parse a hex color string to RGB tuple - * Supports #RGB, #RRGGBB, RGB, RRGGBB formats - */ -export function hexToRgb(hex: string): RGB { - // Remove # if present - const cleanHex = hex.replace(/^#/, ""); - - // Handle shorthand (#RGB) - const fullHex = - cleanHex.length === 3 - ? cleanHex - .split("") - .map((c) => c + c) - .join("") - : cleanHex; - - if (fullHex.length !== 6) { - throw new Error(`Invalid hex color: ${hex}`); - } - - const num = parseInt(fullHex, 16); - if (Number.isNaN(num)) { - throw new Error(`Invalid hex color: ${hex}`); - } - - return [(num >> 16) & 255, (num >> 8) & 255, num & 255]; -} - -/** - * Convert RGB tuple to hex string - */ -export function rgbToHex(rgb: RGB): string { - const r = Math.round(clamp(rgb[0], 0, 255)); - const g = Math.round(clamp(rgb[1], 0, 255)); - const b = Math.round(clamp(rgb[2], 0, 255)); - return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; -} - -// ============================================================================ -// RGB <-> HSL Conversions -// ============================================================================ - -/** - * Convert RGB to HSL - */ -export function rgbToHsl(rgb: RGB): HSL { - const r = rgb[0] / 255; - const g = rgb[1] / 255; - const b = rgb[2] / 255; - - const max = Math.max(r, g, b); - const min = Math.min(r, g, b); - const l = (max + min) / 2; - - if (max === min) { - return { h: 0, s: 0, l: l * 100 }; - } - - const d = max - min; - const s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - - let h = 0; - if (max === r) { - h = ((g - b) / d + (g < b ? 6 : 0)) / 6; - } else if (max === g) { - h = ((b - r) / d + 2) / 6; - } else { - h = ((r - g) / d + 4) / 6; - } - - return { - h: h * 360, - s: s * 100, - l: l * 100, - }; -} - -/** - * Convert HSL to RGB - */ -export function hslToRgb(hsl: HSL): RGB { - const h = hsl.h / 360; - const s = hsl.s / 100; - const l = hsl.l / 100; - - if (s === 0) { - const gray = Math.round(l * 255); - return [gray, gray, gray]; - } - - const hue2rgb = (p: number, q: number, t: number): number => { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - }; - - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - - return [ - Math.round(hue2rgb(p, q, h + 1 / 3) * 255), - Math.round(hue2rgb(p, q, h) * 255), - Math.round(hue2rgb(p, q, h - 1 / 3) * 255), - ]; -} - -// ============================================================================ -// RGB <-> OKLCH Conversions -// ============================================================================ - -/** - * Convert RGB to linear RGB (remove gamma) - */ -function rgbToLinear(rgb: RGB): [number, number, number] { - return rgb.map((v) => { - const c = v / 255; - return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; - }) as [number, number, number]; -} - -/** - * Convert linear RGB to RGB (apply gamma) - */ -function linearToRgb(linear: [number, number, number]): RGB { - return linear.map((c) => { - const v = c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055; - return Math.round(clamp(v * 255, 0, 255)); - }) as RGB; -} - -/** - * Convert RGB to OKLCH - */ -export function rgbToOklch(rgb: RGB): OKLCH { - const [lr, lg, lb] = rgbToLinear(rgb); - - // RGB to OKLab - const l_ = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb; - const m_ = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb; - const s_ = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb; - - const l = Math.cbrt(l_); - const m = Math.cbrt(m_); - const s = Math.cbrt(s_); - - const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s; - const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s; - const b = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s; - - // OKLab to OKLCH - const C = Math.sqrt(a * a + b * b); - let H = (Math.atan2(b, a) * 180) / Math.PI; - if (H < 0) H += 360; - - return { l: L, c: C, h: H }; -} - -/** - * Convert OKLCH to RGB - */ -export function oklchToRgb(oklch: OKLCH): RGB { - const { l: L, c: C, h: H } = oklch; - - // OKLCH to OKLab - const hRad = (H * Math.PI) / 180; - const a = C * Math.cos(hRad); - const b = C * Math.sin(hRad); - - // OKLab to linear RGB - const l = L + 0.3963377774 * a + 0.2158037573 * b; - const m = L - 0.1055613458 * a - 0.0638541728 * b; - const s = L - 0.0894841775 * a - 1.291485548 * b; - - const l_ = l * l * l; - const m_ = m * m * m; - const s_ = s * s * s; - - const lr = 4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_; - const lg = -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_; - const lb = -0.0041960863 * l_ - 0.7034186147 * m_ + 1.707614701 * s_; - - return linearToRgb([lr, lg, lb]); -} - -// ============================================================================ -// Contrast Calculations (WCAG 2.1) -// ============================================================================ - -/** - * Calculate relative luminance of an RGB color - * https://www.w3.org/TR/WCAG21/#dfn-relative-luminance - */ -export function luminance(rgb: RGB): number { - const linearize = (v: number): number => { - const c = v / 255; - return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; - }; - const r = linearize(rgb[0]); - const g = linearize(rgb[1]); - const b = linearize(rgb[2]); - return 0.2126 * r + 0.7152 * g + 0.0722 * b; -} - -/** - * Calculate WCAG 2.1 contrast ratio between two colors - * Returns a value between 1 and 21 - */ -export function contrastRatio(rgb1: RGB, rgb2: RGB): number { - const l1 = luminance(rgb1); - const l2 = luminance(rgb2); - const lighter = Math.max(l1, l2); - const darker = Math.min(l1, l2); - return (lighter + 0.05) / (darker + 0.05); -} - -/** - * Calculate contrast with directionality (for Leonardo compatibility) - * Positive = foreground is darker than background - * Negative = foreground is lighter than background - */ -export function contrastWithDirection(foreground: RGB, background: RGB, backgroundLightness?: number): number { - const bgLum = backgroundLightness !== undefined ? backgroundLightness / 100 : luminance(background); - const fgLum = luminance(foreground); - - const l1 = Math.max(fgLum, bgLum); - const l2 = Math.min(fgLum, bgLum); - const ratio = (l1 + 0.05) / (l2 + 0.05); - - // Light background: darker colors are positive - // Dark background: lighter colors are positive - if (bgLum >= 0.5) { - return fgLum < bgLum ? ratio : -ratio; - } else { - return fgLum > bgLum ? ratio : -ratio; - } -} - -// ============================================================================ -// APCA Contrast (WCAG 3 Draft) -// ============================================================================ - -/** - * Calculate APCA contrast - * Returns a value between -108 and 106 - * Based on APCA-W3 algorithm - */ -export function apcaContrast(foreground: RGB, background: RGB): number { - // Linearize with sRGB TRC - const [fR, fG, fB] = foreground.map((v) => (v / 255) ** 2.4) as RGB; - const [bR, bG, bB] = background.map((v) => (v / 255) ** 2.4) as RGB; - - // Calculate Y (luminance) using APCA coefficients - const fY = 0.2126729 * fR + 0.7151522 * fG + 0.072175 * fB; - const bY = 0.2126729 * bR + 0.7151522 * bG + 0.072175 * bB; - - // APCA constants - const normBG = 0.56; - const normTXT = 0.57; - const revTXT = 0.62; - const revBG = 0.65; - const blkThrs = 0.022; - const blkClmp = 1.414; - const scaleBoW = 1.14; - const scaleWoB = 1.14; - const loBoWoffset = 0.027; - const loWoBoffset = 0.027; - - // Clamp luminance - const txtY = fY > blkThrs ? fY : fY + (blkThrs - fY) ** blkClmp; - const bgY = bY > blkThrs ? bY : bY + (blkThrs - bY) ** blkClmp; - - // Calculate raw contrast - let contrast: number; - if (bgY > txtY) { - // Dark text on light background - contrast = (bgY ** normBG - txtY ** normTXT) * scaleBoW; - contrast = contrast < loBoWoffset ? 0 : contrast - loBoWoffset; - } else { - // Light text on dark background - contrast = (bgY ** revBG - txtY ** revTXT) * scaleWoB; - contrast = contrast > -loWoBoffset ? 0 : contrast + loWoBoffset; - } - - return contrast * 100; -} - -// ============================================================================ -// Utility Functions -// ============================================================================ - -/** - * Clamp a value between min and max - */ -export function clamp(value: number, min: number, max: number): number { - return Math.min(Math.max(value, min), max); -} - -/** - * Round a number to specified decimal places - */ -export function round(value: number, decimals = 0): number { - const factor = 10 ** decimals; - return Math.round(value * factor) / factor; -} - -/** - * Linear interpolation between two values - */ -export function lerp(a: number, b: number, t: number): number { - return a + (b - a) * t; -} - -/** - * Interpolate between two RGB colors - */ -export function interpolateRgb(rgb1: RGB, rgb2: RGB, t: number): RGB { - return [ - Math.round(lerp(rgb1[0], rgb2[0], t)), - Math.round(lerp(rgb1[1], rgb2[1], t)), - Math.round(lerp(rgb1[2], rgb2[2], t)), - ]; -} - -/** - * Interpolate between two OKLCH colors - * Handles hue interpolation correctly (shortest path) - */ -export function interpolateOklch(oklch1: OKLCH, oklch2: OKLCH, t: number): OKLCH { - // Handle hue interpolation (shortest path around the circle) - let h1 = oklch1.h; - let h2 = oklch2.h; - - const hDiff = h2 - h1; - if (hDiff > 180) { - h1 += 360; - } else if (hDiff < -180) { - h2 += 360; - } - - let h = lerp(h1, h2, t) % 360; - if (h < 0) h += 360; - - return { - l: lerp(oklch1.l, oklch2.l, t), - c: lerp(oklch1.c, oklch2.c, t), - h, - }; -} - -/** - * Check if a hex color string is valid - */ -export function isValidHex(hex: string): boolean { - return /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(hex); -} - -/** - * Get the best text color (black or white) for a given background - */ -export function getContrastTextColor(background: string): string { - const rgb = hexToRgb(background); - const blackContrast = contrastRatio(rgb, [0, 0, 0]); - const whiteContrast = contrastRatio(rgb, [255, 255, 255]); - return blackContrast > whiteContrast ? "#000000" : "#ffffff"; -} diff --git a/packages/color-engine/src/index.ts b/packages/color-engine/src/index.ts index 31bbbf816..4994662ae 100644 --- a/packages/color-engine/src/index.ts +++ b/packages/color-engine/src/index.ts @@ -1,174 +1,46 @@ /** - * @dotui/color-engine + * @dotui/colors * - * A unified color palette generator supporting multiple algorithms: - * - Leonardo: Adobe's contrast-based color generation (WCAG compliant) - * - Material: Google's HCT perceptual color system - * - Radix: Radix UI's hand-tuned scale blending + * A unified color palette generator with 100% Leonardo parity. * * @example - * // Unified API (loads all algorithms) - * import { generateScale } from '@dotui/color-engine'; - * - * const scale = generateScale({ - * algorithm: 'leonardo', - * color: '#6366f1', - * background: '#ffffff', + * import { createTheme } from '@dotui/colors'; + * + * const theme = createTheme({ + * colors: [ + * { name: 'accent', colorKeys: ['#6366f1'], ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15] }, + * { name: 'success', colorKeys: ['#22c55e'], ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15] }, + * ], + * backgroundColor: '#ffffff', + * lightness: 97, * }); * - * @example - * // Tree-shakeable direct imports - * import { generateScale } from '@dotui/color-engine/leonardo'; - * - * const scale = generateScale({ - * color: '#6366f1', - * background: '#ffffff', - * }); + * // Returns: + * // { + * // background: 'hsl(0, 0%, 96%)', + * // colors: { + * // accent: { '100': 'hsl(239, 84%, 67%)', ... }, + * // success: { '100': 'hsl(142, 76%, 36%)', ... }, + * // } + * // } */ -// ============================================================================ -// Types -// ============================================================================ +export { createTheme } from "./algorithms/leonardo"; +// Re-export Leonardo types export type { - // Core types - RGB, - HSL, - OKLCH, - ScaleOutput, - ScaleStep, - Algorithm, - // Algorithm-specific input types - LeonardoOptions, - LeonardoColorspace, - ContrastFormula, - MaterialOptions, - RadixOptions, - RadixScaleName, - // Unified input type - GenerateScaleInput, - LeonardoInput, - MaterialInput, - RadixInput, -} from "./core/types"; - -export { SCALE_STEPS } from "./core/types"; - -// ============================================================================ -// Algorithm Imports -// ============================================================================ - -import { generateScale as generateLeonardoScale } from "./algorithms/leonardo"; -import { generateScale as generateMaterialScale } from "./algorithms/material"; -import { generateScale as generateRadixScale } from "./algorithms/radix"; - -import type { GenerateScaleInput, ScaleOutput } from "./core/types"; - -// ============================================================================ -// Unified API -// ============================================================================ - -/** - * Generate a color scale using the specified algorithm - * - * @param input - Configuration including algorithm selection and options - * @returns ScaleOutput with 11 steps (50, 100, 200, ..., 900, 950) - * - * @example - * // Leonardo (contrast-based) - * const scale = generateScale({ - * algorithm: 'leonardo', - * color: '#6366f1', - * background: '#ffffff', - * ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15], - * }); - * - * @example - * // Material HCT (perceptual) - * const scale = generateScale({ - * algorithm: 'material', - * color: '#6366f1', - * tones: [99, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10], - * }); - * - * @example - * // Radix (hand-tuned blending) - * const scale = generateScale({ - * algorithm: 'radix', - * accent: '#6366f1', - * background: '#ffffff', - * }); - */ -export function generateScale(input: GenerateScaleInput): ScaleOutput { - switch (input.algorithm) { - case "leonardo": { - const { algorithm: _, ...options } = input; - return generateLeonardoScale(options); - } - case "material": { - const { algorithm: _, ...options } = input; - return generateMaterialScale(options); - } - case "radix": { - const { algorithm: _, ...options } = input; - return generateRadixScale(options); - } - default: { - // TypeScript exhaustiveness check - const _exhaustive: never = input; - throw new Error(`Unknown algorithm: ${(_exhaustive as GenerateScaleInput).algorithm}`); - } - } -} - -// ============================================================================ -// Named Algorithm Exports (for direct use) -// ============================================================================ - -export { - generateLeonardoScale, - generateMaterialScale, - generateRadixScale, -}; - -// ============================================================================ -// Utility Re-exports -// ============================================================================ + CreateThemeInput, + CreateThemeOutput, + ColorInput, + BackgroundColorInput, +} from "./algorithms/leonardo"; -export { - // Color conversions - hexToRgb, - rgbToHex, - rgbToHsl, - hslToRgb, - rgbToOklch, - oklchToRgb, - // Contrast calculations - luminance, - contrastRatio, - contrastWithDirection, - apcaContrast, - // Utilities - clamp, - round, - lerp, - interpolateRgb, - interpolateOklch, - isValidHex, - getContrastTextColor, -} from "./core/utils"; - -// ============================================================================ -// Algorithm-specific Utilities -// ============================================================================ - -export { - getHct, - fromHct, - getContrastTones, -} from "./algorithms/material"; +// Re-export shared types +export type { + LeonardoColorspace, + ContrastFormula, + MaterialOptions, + ScaleOutput, +} from "./types"; -export { - getRadixScale, - getRadixScaleNames, -} from "./algorithms/radix"; +export { SCALE_STEPS } from "./types"; diff --git a/packages/color-engine/src/types.ts b/packages/color-engine/src/types.ts new file mode 100644 index 000000000..0f90d6cd4 --- /dev/null +++ b/packages/color-engine/src/types.ts @@ -0,0 +1,141 @@ +/** + * Core types for @dotui/colors + */ + +/** RGB color as tuple [r, g, b] where values are 0-255 */ +export type RGB = [number, number, number]; + +/** HSL color object */ +export interface HSL { + h: number; // Hue 0-360 + s: number; // Saturation 0-100 + l: number; // Lightness 0-100 +} + +/** Standard 11-step scale output (50-950) */ +export interface ScaleOutput { + "50": string; + "100": string; + "200": string; + "300": string; + "400": string; + "500": string; + "600": string; + "700": string; + "800": string; + "900": string; + "950": string; +} + +/** Step names for 11-step scale */ +export const SCALE_STEPS = [ + "50", + "100", + "200", + "300", + "400", + "500", + "600", + "700", + "800", + "900", + "950", +] as const; + +export type ScaleStep = (typeof SCALE_STEPS)[number]; + +/** Colorspaces supported by Leonardo for interpolation */ +export type LeonardoColorspace = + | "RGB" + | "HEX" + | "HSL" + | "HSLuv" + | "HSV" + | "LAB" + | "LCH" + | "OKLAB" + | "OKLCH" + | "CAM02" + | "CAM02p"; + +/** Contrast formula options */ +export type ContrastFormula = "wcag2" | "wcag3"; + +/** Leonardo algorithm options */ +export interface LeonardoOptions { + /** Primary color (hex) */ + color: string; + + /** Background color for contrast calculations (hex) */ + background: string; + + /** Additional color keys for gradient interpolation */ + colorKeys?: string[]; + + /** + * Target contrast ratios for each step + * @default [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15] + */ + ratios?: number[]; + + /** + * Colorspace for interpolation + * @default 'OKLCH' + */ + colorspace?: LeonardoColorspace; + + /** + * Saturation modifier (0-100) + * @default 100 + */ + saturation?: number; + + /** + * Contrast multiplier - scales all ratios + * @default 1 + */ + contrast?: number; + + /** + * Use smooth Bezier interpolation + * @default false + */ + smooth?: boolean; + + /** + * Contrast calculation formula + * @default 'wcag2' + */ + formula?: ContrastFormula; +} + +/** Material algorithm options */ +export interface MaterialOptions { + /** Source color (hex) */ + color: string; + + /** + * Specific tone values to generate (0-100) + * @default [99, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10] + */ + tones?: number[]; + + /** + * Override chroma value (0-150+) + * If not provided, uses source color's chroma + */ + chroma?: number; + + /** + * Override hue value (0-360) + * If not provided, uses source color's hue + */ + hue?: number; + + /** + * Contrast level adjustment (-1 to 1) + * -1 = reduced, 0 = standard, 1 = high + * @default 0 + */ + contrastLevel?: number; +} diff --git a/packages/color-engine/src/types/apca-w3.d.ts b/packages/color-engine/src/types/apca-w3.d.ts deleted file mode 100644 index 0c0a9ef92..000000000 --- a/packages/color-engine/src/types/apca-w3.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Type declarations for apca-w3 - * https://github.com/nicholastsui/apca-w3 - */ - -declare module "apca-w3" { - /** - * Calculate APCA contrast between two colors - * @param textY - Luminance of text color - * @param bgY - Luminance of background color - * @returns APCA contrast value (roughly -108 to 106) - */ - export function APCAcontrast(textY: number, bgY: number): number; - - /** - * Convert sRGB color to Y (luminance) - * @param rgb - RGB array [r, g, b] where values are 0-255 - * @returns Luminance value - */ - export function sRGBtoY(rgb: [number, number, number]): number; - - /** - * Calculate contrast from hex colors - * @param textColor - Text color as hex string - * @param bgColor - Background color as hex string - * @returns APCA contrast value - */ - export function calcAPCA(textColor: string, bgColor: string): number; -} diff --git a/packages/color-engine/src/types/ciebase.d.ts b/packages/color-engine/src/types/ciebase.d.ts deleted file mode 100644 index b4fd21a32..000000000 --- a/packages/color-engine/src/types/ciebase.d.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Type declarations for ciebase - * https://github.com/nickshanks/ciebase - */ - -declare module "ciebase" { - /** XYZ illuminant values */ - export interface Illuminant { - x: number; - y: number; - z: number; - } - - /** Standard illuminants */ - export const illuminant: { - D50: Illuminant; - D55: Illuminant; - D65: Illuminant; - D75: Illuminant; - }; - - /** Color workspace */ - export interface Workspace { - toXyz: number[][]; - fromXyz: number[][]; - gamma: number; - } - - /** Standard workspaces */ - export const workspace: { - sRGB: Workspace; - AdobeRGB: Workspace; - AppleRGB: Workspace; - }; - - /** RGB conversion utilities */ - export const rgb: { - fromHex(hex: string): [number, number, number]; - toHex(rgb: [number, number, number]): string; - }; - - /** XYZ colorspace converter */ - export interface XyzConverter { - fromRgb(rgb: [number, number, number]): { x: number; y: number; z: number }; - toRgb(xyz: { x: number; y: number; z: number }): [number, number, number]; - } - - /** Create XYZ converter for a workspace and illuminant */ - export function xyz(workspace: Workspace, illuminant: Illuminant): XyzConverter; -} diff --git a/packages/color-engine/src/types/ciecam02.d.ts b/packages/color-engine/src/types/ciecam02.d.ts deleted file mode 100644 index 5e2976ae9..000000000 --- a/packages/color-engine/src/types/ciecam02.d.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Type declarations for ciecam02 - * https://github.com/baskerville/ciecam02 - */ - -declare module "ciecam02" { - import type { Illuminant } from "ciebase"; - - /** CIECAM02 viewing conditions */ - export interface ViewingConditions { - /** White point illuminant */ - whitePoint: Illuminant; - /** Adapting luminance (cd/m²), typically 40 */ - adaptingLuminance: number; - /** Background luminance (% of white), typically 20 */ - backgroundLuminance: number; - /** Surround type: 'average', 'dim', or 'dark' */ - surroundType: "average" | "dim" | "dark"; - /** Whether to discount the illuminant */ - discounting: boolean; - } - - /** JCh color appearance correlates */ - export interface JChCorrelates { - /** Lightness (0-100) */ - J: number; - /** Chroma */ - C: number; - /** Hue angle (0-360) */ - h: number; - } - - /** Full CAM02 correlates */ - export interface CamCorrelates extends JChCorrelates { - /** Colorfulness */ - M: number; - /** Saturation */ - s: number; - /** Hue composition */ - H: number; - /** Brightness */ - Q: number; - } - - /** CAM converter */ - export interface CamConverter { - fromXyz(xyz: { x: number; y: number; z: number }): T; - toXyz(cam: T): { x: number; y: number; z: number }; - } - - /** - * Correlate filter string - * @example 'JCh' for J, C, h correlates only - */ - export function cfs(correlates: string): string; - - /** - * Create CAM converter with viewing conditions - * @param conditions - Viewing conditions - * @param correlates - Optional correlate filter (from cfs()) - */ - export function cam(conditions: ViewingConditions, correlates?: string): CamConverter; - - /** - * Create gamut mapping for a colorspace and CAM - */ - export function gamut(xyz: unknown, cam: CamConverter): unknown; -} diff --git a/packages/color-engine/src/types/hsluv.d.ts b/packages/color-engine/src/types/hsluv.d.ts deleted file mode 100644 index 678d59292..000000000 --- a/packages/color-engine/src/types/hsluv.d.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Type declarations for hsluv - * https://github.com/hsluv/hsluv - */ - -declare module "hsluv" { - /** - * Convert HSLuv to RGB - * @param tuple - [H, S, L] where H is 0-360, S is 0-100, L is 0-100 - * @returns [R, G, B] where values are 0-1 - */ - export function hsluvToRgb(tuple: [number, number, number]): [number, number, number]; - - /** - * Convert RGB to HSLuv - * @param tuple - [R, G, B] where values are 0-1 - * @returns [H, S, L] where H is 0-360, S is 0-100, L is 0-100 - */ - export function rgbToHsluv(tuple: [number, number, number]): [number, number, number]; - - /** - * Convert HPLuv to RGB - * @param tuple - [H, P, L] - * @returns [R, G, B] where values are 0-1 - */ - export function hpluvToRgb(tuple: [number, number, number]): [number, number, number]; - - /** - * Convert RGB to HPLuv - * @param tuple - [R, G, B] where values are 0-1 - * @returns [H, P, L] - */ - export function rgbToHpluv(tuple: [number, number, number]): [number, number, number]; - - /** - * Convert HSLuv to hex - * @param tuple - [H, S, L] - * @returns Hex color string - */ - export function hsluvToHex(tuple: [number, number, number]): string; - - /** - * Convert hex to HSLuv - * @param hex - Hex color string - * @returns [H, S, L] - */ - export function hexToHsluv(hex: string): [number, number, number]; - - /** - * Convert HPLuv to hex - * @param tuple - [H, P, L] - * @returns Hex color string - */ - export function hpluvToHex(tuple: [number, number, number]): string; - - /** - * Convert hex to HPLuv - * @param hex - Hex color string - * @returns [H, P, L] - */ - export function hexToHpluv(hex: string): [number, number, number]; -} diff --git a/packages/color-engine/src/utils/curve.ts b/packages/color-engine/src/utils/curve.ts new file mode 100644 index 000000000..89e89c5c8 --- /dev/null +++ b/packages/color-engine/src/utils/curve.ts @@ -0,0 +1,210 @@ +/** + * Bezier curve utilities for smooth color interpolation + * Ported from Adobe's contrast-colors library + */ + +interface Point { + x: number; + y: number; +} + +/** + * Bezier curve base calculation + */ +function base3(t: number, p1: number, p2: number, p3: number, p4: number): number { + const t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4; + const t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3; + return t * t2 - 3 * p1 + 3 * p2; +} + +/** + * Calculate Bezier curve length using Gaussian quadrature + */ +export function bezlen( + x1: number, + y1: number, + x2: number, + y2: number, + x3: number, + y3: number, + x4: number, + y4: number, + z?: number, +): number { + if (z == null) { + z = 1; + } + z = Math.max(0, Math.min(z, 1)); + const z2 = z / 2; + const n = 12; + const Tvalues = [ + -0.1252, 0.1252, -0.3678, 0.3678, -0.5873, 0.5873, -0.7699, 0.7699, + -0.9041, 0.9041, -0.9816, 0.9816, + ]; + const Cvalues = [ + 0.2491, 0.2491, 0.2335, 0.2335, 0.2032, 0.2032, 0.1601, 0.1601, 0.1069, + 0.1069, 0.0472, 0.0472, + ]; + let sum = 0; + for (let i = 0; i < n; i++) { + const ct = z2 * (Tvalues[i] ?? 0) + z2; + const xbase = base3(ct, x1, x2, x3, x4); + const ybase = base3(ct, y1, y2, y3, y4); + const comb = xbase * xbase + ybase * ybase; + sum += (Cvalues[i] ?? 0) * Math.sqrt(comb); + } + return z2 * sum; +} + +/** + * Find point on Bezier curve at parameter t + */ +export function findDotsAtSegment( + p1x: number, + p1y: number, + c1x: number, + c1y: number, + c2x: number, + c2y: number, + p2x: number, + p2y: number, + t: number, +): Point { + const t1 = 1 - t; + const t12 = t1 * t1; + const t13 = t12 * t1; + const t2 = t * t; + const t3 = t2 * t; + const x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x; + const y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y; + return { x, y }; +} + +/** + * Convert Catmull-Rom spline to Bezier curves + */ +export function catmullRom2bezier(crp: number[], z?: boolean): number[][] { + const d: number[][] = []; + let end: Point = { x: +(crp[0] ?? 0), y: +(crp[1] ?? 0) }; + + for (let i = 0, iLen = crp.length; iLen - 2 * (z ? 0 : 1) > i; i += 2) { + const p: Point[] = [ + { x: +(crp[i - 2] ?? 0), y: +(crp[i - 1] ?? 0) }, + { x: +(crp[i] ?? 0), y: +(crp[i + 1] ?? 0) }, + { x: +(crp[i + 2] ?? 0), y: +(crp[i + 3] ?? 0) }, + { x: +(crp[i + 4] ?? 0), y: +(crp[i + 5] ?? 0) }, + ]; + + if (z) { + if (!i) { + p[0] = { x: +(crp[iLen - 2] ?? 0), y: +(crp[iLen - 1] ?? 0) }; + } else if (iLen - 4 === i) { + p[3] = { x: +(crp[0] ?? 0), y: +(crp[1] ?? 0) }; + } else if (iLen - 2 === i) { + p[2] = { x: +(crp[0] ?? 0), y: +(crp[1] ?? 0) }; + p[3] = { x: +(crp[2] ?? 0), y: +(crp[3] ?? 0) }; + } + } else if (iLen - 4 === i) { + p[3] = p[2]!; + } else if (!i) { + p[0] = { x: +(crp[i] ?? 0), y: +(crp[i + 1] ?? 0) }; + } + + const p0 = p[0]!; + const p1 = p[1]!; + const p2 = p[2]!; + const p3 = p[3]!; + + d.push([ + end.x, + end.y, + (-p0.x + 6 * p1.x + p2.x) / 6, + (-p0.y + 6 * p1.y + p2.y) / 6, + (p1.x + 6 * p2.x - p3.x) / 6, + (p1.y + 6 * p2.y - p3.y) / 6, + p2.x, + p2.y, + ]); + end = p2; + } + + return d; +} + +/** + * Approximate Bezier curve length using linear segments + */ +export function bezlen2( + p1x: number, + p1y: number, + c1x: number, + c1y: number, + c2x: number, + c2y: number, + p2x: number, + p2y: number, +): number { + const n = 5; + let x0 = p1x; + let y0 = p1y; + let len = 0; + + for (let i = 1; i < n; i++) { + const { x, y } = findDotsAtSegment( + p1x, + p1y, + c1x, + c1y, + c2x, + c2y, + p2x, + p2y, + i / n, + ); + len += Math.hypot(x - x0, y - y0); + x0 = x; + y0 = y; + } + + len += Math.hypot(p2x - x0, p2y - y0); + return len; +} + +/** + * Create a lookup table for a Bezier curve segment + * Returns a function that maps x to y values + */ +export function prepareCurve( + p1x: number, + p1y: number, + c1x: number, + c1y: number, + c2x: number, + c2y: number, + p2x: number, + p2y: number, +): (x: number) => number | null { + const len = Math.floor( + bezlen2(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) * 0.75, + ); + const fs: number[] = []; + let oldi = 0; + + for (let i = 0; i <= len; i++) { + const t = i / len; + const xy = findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t); + const index = Math.round(xy.x); + fs[index] = xy.y; + + if (index - oldi > 1) { + const s = fs[oldi] ?? 0; + const f = fs[index] ?? 0; + for (let j = oldi + 1; j < index; j++) { + fs[j] = s + ((f - s) / (index - oldi)) * (j - oldi); + } + } + oldi = index; + } + + return (x: number): number | null => fs[Math.round(x)] ?? null; +} diff --git a/packages/color-engine/tests/index.test.ts b/packages/color-engine/tests/index.test.ts index aee020a40..9502f5c32 100644 --- a/packages/color-engine/tests/index.test.ts +++ b/packages/color-engine/tests/index.test.ts @@ -1,208 +1,251 @@ /** - * Tests for unified API + * Tests for createTheme API */ import { describe, it, expect } from "vitest"; -import { - generateScale, - generateLeonardoScale, - generateMaterialScale, - generateRadixScale, - SCALE_STEPS, +import { createTheme } from "../src"; +import type { + CreateThemeInput, + CreateThemeOutput, + ColorInput, + BackgroundColorInput, + LeonardoColorspace, + ContrastFormula, } from "../src"; -import { hexToRgb } from "../src/core/utils"; - -describe("Unified generateScale", () => { - describe("algorithm selection", () => { - it("should route to Leonardo algorithm", () => { - const unified = generateScale({ - algorithm: "leonardo", - color: "#6366f1", - background: "#ffffff", - }); - - const direct = generateLeonardoScale({ - color: "#6366f1", - background: "#ffffff", - }); - - // Should produce identical results - expect(unified).toEqual(direct); - }); - - it("should route to Material algorithm", () => { - const unified = generateScale({ - algorithm: "material", - color: "#6366f1", - }); - - const direct = generateMaterialScale({ - color: "#6366f1", - }); - - expect(unified).toEqual(direct); - }); - - it("should route to Radix algorithm", () => { - const unified = generateScale({ - algorithm: "radix", - accent: "#6366f1", - background: "#ffffff", - }); - - const direct = generateRadixScale({ - accent: "#6366f1", - background: "#ffffff", - }); - - expect(unified).toEqual(direct); - }); - }); - - describe("all algorithms produce consistent output shape", () => { - const algorithms = [ - { - algorithm: "leonardo" as const, - color: "#6366f1", - background: "#ffffff", - }, - { - algorithm: "material" as const, - color: "#6366f1", - }, - { - algorithm: "radix" as const, - accent: "#6366f1", - background: "#ffffff", - }, - ]; - - algorithms.forEach((input) => { - it(`should produce 11 valid hex colors for ${input.algorithm}`, () => { - const scale = generateScale(input); - - expect(Object.keys(scale)).toHaveLength(11); - SCALE_STEPS.forEach((step) => { - expect(scale[step]).toBeDefined(); - expect(scale[step]).toMatch(/^#[0-9a-f]{6}$/); - expect(() => hexToRgb(scale[step])).not.toThrow(); - }); - }); - }); - }); - - describe("algorithm options pass through", () => { - it("should pass Leonardo options", () => { - const scale = generateScale({ - algorithm: "leonardo", - color: "#6366f1", - background: "#ffffff", - ratios: [1.1, 1.5, 2, 3, 4.5, 6, 8, 10, 12, 14, 16], - saturation: 80, - contrast: 1.2, - }); - - expect(Object.keys(scale)).toHaveLength(11); - }); - - it("should pass Material options", () => { - const scale = generateScale({ - algorithm: "material", - color: "#6366f1", - tones: [98, 92, 85, 75, 65, 55, 45, 35, 25, 15, 5], - hue: 250, - chroma: 60, - contrastLevel: 0.5, - }); - - expect(Object.keys(scale)).toHaveLength(11); - }); - - it("should pass Radix options", () => { - const scale = generateScale({ - algorithm: "radix", - accent: "#6366f1", - background: "#121212", - gray: "#6b7280", - }); - - expect(Object.keys(scale)).toHaveLength(11); - }); - }); -}); -describe("Named exports", () => { - it("should export individual algorithm functions", () => { - expect(typeof generateLeonardoScale).toBe("function"); - expect(typeof generateMaterialScale).toBe("function"); - expect(typeof generateRadixScale).toBe("function"); - }); - - it("should export SCALE_STEPS constant", () => { - expect(SCALE_STEPS).toEqual([ - "50", - "100", - "200", - "300", - "400", - "500", - "600", - "700", - "800", - "900", - "950", - ]); - }); +describe("createTheme", () => { + describe("basic functionality", () => { + it("should create a theme with single color", () => { + const theme = createTheme({ + colors: [ + { + name: "accent", + colorKeys: ["#6366f1"], + ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15], + }, + ], + backgroundColor: "#ffffff", + }); + + expect(theme.background).toBeDefined(); + expect(theme.colors.accent).toBeDefined(); + expect(Object.keys(theme.colors.accent).length).toBe(11); + }); + + it("should create a theme with multiple colors", () => { + const theme = createTheme({ + colors: [ + { + name: "accent", + colorKeys: ["#6366f1"], + ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15], + }, + { + name: "success", + colorKeys: ["#22c55e"], + ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15], + }, + ], + backgroundColor: "#ffffff", + }); + + expect(theme.colors.accent).toBeDefined(); + expect(theme.colors.success).toBeDefined(); + }); + + it("should output HSL format", () => { + const theme = createTheme({ + colors: [ + { + name: "accent", + colorKeys: ["#6366f1"], + ratios: [3], + }, + ], + backgroundColor: "#ffffff", + }); + + expect(theme.background).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/); + const firstStep = Object.values(theme.colors.accent)[0]; + expect(firstStep).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/); + }); + }); + + describe("options", () => { + it("should respect lightness option with background object", () => { + const lightTheme = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: { + name: "neutral", + colorKeys: ["#6366f1"], + }, + lightness: 97, + }); + + const darkTheme = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: { + name: "neutral", + colorKeys: ["#6366f1"], + }, + lightness: 10, + }); + + expect(lightTheme.background).not.toBe(darkTheme.background); + }); + + it("should respect saturation option", () => { + const fullSat = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: "#ffffff", + saturation: 100, + }); + + const lowSat = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: "#ffffff", + saturation: 50, + }); + + expect(fullSat.colors.accent).not.toEqual(lowSat.colors.accent); + }); + + it("should respect contrast option", () => { + const normalContrast = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: "#ffffff", + contrast: 1, + }); + + const highContrast = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: "#ffffff", + contrast: 1.5, + }); + + expect(normalContrast.colors.accent).not.toEqual( + highContrast.colors.accent + ); + }); + + it("should support wcag2 and wcag3 formula", () => { + const wcag2 = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [4.5] }, + ], + backgroundColor: "#ffffff", + formula: "wcag2", + }); + + const wcag3 = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [60] }, + ], + backgroundColor: "#ffffff", + formula: "wcag3", + }); + + expect(wcag2.colors.accent).toBeDefined(); + expect(wcag3.colors.accent).toBeDefined(); + }); + }); + + describe("background color input", () => { + it("should accept string background", () => { + const theme = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: "#f5f5f5", + }); + + expect(theme.background).toBeDefined(); + }); + + it("should accept object background", () => { + const theme = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: { + name: "neutral", + colorKeys: ["#f5f5f5"], + }, + lightness: 95, + }); + + expect(theme.background).toBeDefined(); + }); + }); + + describe("named ratios", () => { + it("should support object ratios with named keys", () => { + const theme = createTheme({ + colors: [ + { + name: "accent", + colorKeys: ["#6366f1"], + ratios: { + light: 1.5, + base: 3, + dark: 7, + }, + }, + ], + backgroundColor: "#ffffff", + }); + + expect(theme.colors.accent.light).toBeDefined(); + expect(theme.colors.accent.base).toBeDefined(); + expect(theme.colors.accent.dark).toBeDefined(); + }); + }); }); -describe("Algorithm comparison", () => { - const testColor = "#3b82f6"; // Blue - - it("all algorithms should produce different results (they use different methods)", () => { - const leonardo = generateScale({ - algorithm: "leonardo", - color: testColor, - background: "#ffffff", - }); - - const material = generateScale({ - algorithm: "material", - color: testColor, - }); - - const radix = generateScale({ - algorithm: "radix", - accent: testColor, - background: "#ffffff", - }); - - // They should not be identical (different algorithms) - expect(leonardo["500"]).not.toBe(material["500"]); - expect(material["500"]).not.toBe(radix["500"]); - }); - - it("all algorithms should produce blue-ish results for blue input", () => { - const leonardo = generateScale({ - algorithm: "leonardo", - color: testColor, - background: "#ffffff", - }); - - const material = generateScale({ - algorithm: "material", - color: testColor, - }); - - const radix = generateScale({ - algorithm: "radix", - accent: testColor, - background: "#ffffff", - }); - - // Check that mid-tones are blue-ish (more blue than red) - [leonardo, material, radix].forEach((scale) => { - const rgb = hexToRgb(scale["500"]); - expect(rgb[2]).toBeGreaterThan(rgb[0]); // Blue > Red - }); - }); +describe("Type exports", () => { + it("should export all required types", () => { + // These are compile-time checks - if the types don't exist, TypeScript will fail + const input: CreateThemeInput = { + colors: [], + backgroundColor: "#ffffff", + }; + + const output: CreateThemeOutput = { + background: "hsl(0, 0%, 100%)", + colors: {}, + }; + + const colorInput: ColorInput = { + name: "test", + colorKeys: ["#000000"], + ratios: [3], + }; + + const bgInput: BackgroundColorInput = { + name: "bg", + colorKeys: ["#ffffff"], + }; + + const colorspace: LeonardoColorspace = "RGB"; + const formula: ContrastFormula = "wcag2"; + + expect(input).toBeDefined(); + expect(output).toBeDefined(); + expect(colorInput).toBeDefined(); + expect(bgInput).toBeDefined(); + expect(colorspace).toBe("RGB"); + expect(formula).toBe("wcag2"); + }); }); diff --git a/packages/color-engine/tests/leonardo.test.ts b/packages/color-engine/tests/leonardo.test.ts deleted file mode 100644 index 69940221d..000000000 --- a/packages/color-engine/tests/leonardo.test.ts +++ /dev/null @@ -1,454 +0,0 @@ -/** - * Tests for Leonardo algorithm (exact port) - * - * These tests verify the Leonardo algorithm produces EXACT same output as - * Adobe's original implementation. Test values are taken directly from - * the original contrast-colors test suite. - */ - -import { describe, it, expect } from "vitest"; -import { generateScale, contrast, createColorScale } from "../src/algorithms/leonardo"; -import { hexToRgb, contrastRatio } from "../src/core/utils"; -import { SCALE_STEPS } from "../src/core/types"; -import type { RGB } from "../src/core/types"; - -// ============================================================================ -// Helper to convert hex to RGB string like original tests -// ============================================================================ - -function hexToRgbString(hex: string): string { - const rgb = hexToRgb(hex); - return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`; -} - -// ============================================================================ -// Contrast Calculation Tests (exact values from original) -// ============================================================================ - -describe("Leonardo contrast function (exact parity)", () => { - describe("WCAG2 contrast", () => { - it("should provide negative contrast in light theme (-1.55...)", () => { - // white is UI color, gray is base. Should return negative whole number - const contrastValue = contrast([255, 255, 255], [207, 207, 207]); - expect(contrastValue).toBeCloseTo(-1.5579550563651177, 10); - }); - - it("should provide positive contrast in light theme (1.55...)", () => { - // gray is UI color, white is base. Should return positive whole number - const contrastValue = contrast([207, 207, 207], [255, 255, 255]); - expect(contrastValue).toBeCloseTo(1.5579550563651177, 10); - }); - - it("should provide negative contrast in dark theme (-1.56...)", () => { - // darker gray is UI color, gray is base. Should return negative whole number - const contrastValue = contrast([8, 8, 8], [50, 50, 50]); - expect(contrastValue).toBeCloseTo(-1.5620602707250844, 10); - }); - - it("should provide positive contrast in dark theme (1.57...)", () => { - // lighter gray is UI color, gray is base - const contrastValue = contrast([79, 79, 79], [50, 50, 50]); - expect(contrastValue).toBeCloseTo(1.5652458000121365, 10); - }); - - it("should provide contrast when passing base value (5.64...)", () => { - const contrastValue = contrast([79, 79, 79], [214, 214, 214], 0.86); - expect(contrastValue).toBeCloseTo(5.635834988986869, 10); - }); - }); - - describe("WCAG3 (APCA) contrast", () => { - it("should provide APCA contrast of ~ 75.6", () => { - const contrastValue = contrast([18, 52, 176], [233, 228, 208], undefined, "wcag3"); - expect(contrastValue).toBeCloseTo(75.57062523197818, 5); - }); - - it("should provide APCA contrast of ~ 78.3", () => { - const contrastValue = contrast([233, 228, 208], [18, 52, 176], undefined, "wcag3"); - expect(contrastValue).toBeCloseTo(78.28508284557655, 5); - }); - - it("should provide APCA contrast of ~ 38.7", () => { - const contrastValue = contrast([255, 162, 0], [255, 255, 255], undefined, "wcag3"); - expect(contrastValue).toBeCloseTo(38.67214116963013, 5); - }); - - it("should provide APCA contrast of ~ -43.1 since bg lum is greater than 50%", () => { - const contrastValue = contrast([255, 255, 255], [255, 162, 0], undefined, "wcag3"); - expect(contrastValue).toBeCloseTo(-43.12544505836451, 5); - }); - - it("should provide APCA contrast of ~ 107.9", () => { - const contrastValue = contrast([255, 255, 255], [0, 0, 0], undefined, "wcag3"); - expect(contrastValue).toBeCloseTo(107.88473318309848, 5); - }); - - it("should provide APCA contrast of ~ 106", () => { - const contrastValue = contrast([0, 0, 0], [255, 255, 255], undefined, "wcag3"); - expect(contrastValue).toBeCloseTo(106.04067321268862, 5); - }); - - it("should provide APCA contrast less than APCA officially supports", () => { - const contrastValue = contrast([238, 238, 238], [255, 255, 255], undefined, "wcag3"); - expect(contrastValue).toBeCloseTo(7.567424744881627, 5); - }); - }); -}); - -// ============================================================================ -// Search Colors Tests (exact values from original) -// ============================================================================ - -describe("Leonardo searchColors (exact parity)", () => { - it("should return blue color of 3.12:1 against white", () => { - const scale = generateScale({ - color: "#0000ff", - background: "#ffffff", - colorspace: "LAB", - ratios: [3.12], - }); - expect(hexToRgbString(scale["50"])).toBe("rgb(163, 121, 255)"); - }); - - it("should return blue color of 3.12:1 against black", () => { - const scale = generateScale({ - color: "#0000ff", - background: "#000000", - colorspace: "LAB", - ratios: [3.12], - }); - expect(hexToRgbString(scale["50"])).toBe("rgb(80, 43, 255)"); - }); - - it("should return blue colors of 3:1 and 4.5:1 against white", () => { - const scale = generateScale({ - color: "#0000ff", - background: "#ffffff", - colorspace: "LAB", - ratios: [3, 4.5], - }); - expect(hexToRgbString(scale["50"])).toBe("rgb(167, 124, 255)"); - expect(hexToRgbString(scale["100"])).toBe("rgb(129, 84, 255)"); - }); - - it("should return blue colors of 3:1 and 4.5:1 against black", () => { - const scale = generateScale({ - color: "#0000ff", - background: "#000000", - colorspace: "LAB", - ratios: [3, 4.5], - }); - expect(hexToRgbString(scale["50"])).toBe("rgb(73, 38, 255)"); - expect(hexToRgbString(scale["100"])).toBe("rgb(126, 81, 255)"); - }); - - it("should return blue color of -1.3 against light gray", () => { - const scale = generateScale({ - color: "#0000ff", - background: "#a6a6a6", // rgb(166, 166, 166) - colorspace: "LAB", - ratios: [-1.3], - }); - expect(hexToRgbString(scale["50"])).toBe("rgb(207, 176, 255)"); - }); - - it("should return blue color of -2 against dark gray", () => { - const scale = generateScale({ - color: "#0000ff", - background: "#636363", // rgb(99, 99, 99) - colorspace: "LAB", - ratios: [-2], - }); - // Note: Original test expects rgb(167, 125, 255) but that's due to a bug where - // baseV=40 (0-100 scale) was passed but compared with < 0.5 (0-1 scale), - // incorrectly treating dark gray as a light theme. - // Our implementation correctly calculates baseV=0.42 (dark theme), producing - // a darker blue for negative contrast ratio. - expect(hexToRgbString(scale["50"])).toBe("rgb(36, 15, 172)"); - }); -}); - -// ============================================================================ -// Create Scale Tests (exact values from original) -// ============================================================================ - -describe("Leonardo createScale (exact parity)", () => { - // Note: The original uses chroma.scale().colors(n) which samples n evenly-spaced colors - // across the full domain (0 to swatches), including both endpoints. - // For n colors over domain [0, swatches], positions are: 0, swatches/(n-1), 2*swatches/(n-1), ..., swatches - const sampleColors = (scale: (pos: number) => string, n: number, swatches: number): string[] => { - return Array.from({ length: n }, (_, i) => scale((i * swatches) / (n - 1))); - }; - - it("should generate 8 colors in Lab", () => { - const swatches = 8; - const scale = createColorScale(["#CCFFA9", "#FEFEC5", "#5F0198"], "LAB", swatches); - const colors = sampleColors(scale, 8, swatches); - expect(colors).toEqual([ - "#ffffff", - "#c6eba9", - "#b6bda8", - "#a48fa5", - "#8e62a1", - "#73329c", - "#470d6e", - "#000000", - ]); - }); - - it("should generate 8 colors in OKlab", () => { - const swatches = 8; - const scale = createColorScale(["#CCFFA9", "#FEFEC5", "#5F0198"], "OKLAB", swatches); - const colors = sampleColors(scale, 8, swatches); - expect(colors).toEqual([ - "#ffffff", - "#c3ecac", - "#adc0ae", - "#9795ac", - "#8169a7", - "#6c399f", - "#3d0064", - "#000000", - ]); - }); - - it("should generate 8 colors in OKLCh", () => { - const swatches = 8; - const scale = createColorScale(["#CCFFA9", "#FEFEC5", "#5F0198"], "OKLCH", swatches); - const colors = sampleColors(scale, 8, swatches); - expect(colors).toEqual([ - "#ffffff", - "#a1f5ac", - "#00d8c0", - "#00aed5", - "#0079d9", - "#503cbd", - "#440077", - "#000000", - ]); - }); -}); - -// ============================================================================ -// Generate Scale Tests (basic functionality) -// ============================================================================ - -describe("Leonardo generateScale", () => { - describe("basic functionality", () => { - it("should generate 11 steps", () => { - const scale = generateScale({ - color: "#6366f1", - background: "#ffffff", - }); - - expect(Object.keys(scale)).toHaveLength(11); - SCALE_STEPS.forEach((step) => { - expect(scale[step]).toBeDefined(); - expect(scale[step]).toMatch(/^#[0-9a-f]{6}$/); - }); - }); - - it("should generate valid hex colors", () => { - const scale = generateScale({ - color: "#ff0000", - background: "#ffffff", - }); - - Object.values(scale).forEach((color) => { - expect(color).toMatch(/^#[0-9a-f]{6}$/); - expect(() => hexToRgb(color)).not.toThrow(); - }); - }); - }); - - describe("light mode (white background)", () => { - const scale = generateScale({ - color: "#3b82f6", - background: "#ffffff", - }); - - it("should have lighter colors at lower steps", () => { - const step50 = hexToRgb(scale["50"]); - const step950 = hexToRgb(scale["950"]); - - const avg50 = (step50[0] + step50[1] + step50[2]) / 3; - const avg950 = (step950[0] + step950[1] + step950[2]) / 3; - - expect(avg50).toBeGreaterThan(avg950); - }); - - it("should increase contrast as steps increase", () => { - const bgRgb = hexToRgb("#ffffff"); - - const contrast50 = contrastRatio(hexToRgb(scale["50"]), bgRgb); - const contrast500 = contrastRatio(hexToRgb(scale["500"]), bgRgb); - const contrast950 = contrastRatio(hexToRgb(scale["950"]), bgRgb); - - expect(contrast500).toBeGreaterThan(contrast50); - expect(contrast950).toBeGreaterThan(contrast500); - }); - }); - - describe("dark mode (black background)", () => { - const scale = generateScale({ - color: "#3b82f6", - background: "#000000", - }); - - it("should have darker colors at lower steps", () => { - const step50 = hexToRgb(scale["50"]); - const step950 = hexToRgb(scale["950"]); - - const avg50 = (step50[0] + step50[1] + step50[2]) / 3; - const avg950 = (step950[0] + step950[1] + step950[2]) / 3; - - expect(avg50).toBeLessThan(avg950); - }); - }); - - describe("options", () => { - it("should respect custom ratios", () => { - const customRatios = [1.1, 1.5, 2, 3, 4.5, 7, 10, 12, 14, 16, 18]; - const scale = generateScale({ - color: "#6366f1", - background: "#ffffff", - ratios: customRatios, - }); - - expect(Object.keys(scale)).toHaveLength(11); - }); - - it("should handle saturation reduction", () => { - const fullSat = generateScale({ - color: "#ff0000", - background: "#ffffff", - saturation: 100, - }); - - const halfSat = generateScale({ - color: "#ff0000", - background: "#ffffff", - saturation: 50, - }); - - const fullRgb = hexToRgb(fullSat["500"]); - const halfRgb = hexToRgb(halfSat["500"]); - - const fullSpread = Math.max(...fullRgb) - Math.min(...fullRgb); - const halfSpread = Math.max(...halfRgb) - Math.min(...halfRgb); - - expect(halfSpread).toBeLessThan(fullSpread); - }); - - it("should handle contrast multiplier", () => { - const normal = generateScale({ - color: "#6366f1", - background: "#ffffff", - contrast: 1, - }); - - const increased = generateScale({ - color: "#6366f1", - background: "#ffffff", - contrast: 1.5, - }); - - const normalRgb = hexToRgb(normal["950"]); - const increasedRgb = hexToRgb(increased["950"]); - - const normalAvg = (normalRgb[0] + normalRgb[1] + normalRgb[2]) / 3; - const increasedAvg = (increasedRgb[0] + increasedRgb[1] + increasedRgb[2]) / 3; - - expect(increasedAvg).toBeLessThan(normalAvg); - }); - - it("should support different colorspaces", () => { - const labScale = generateScale({ - color: "#6366f1", - background: "#ffffff", - colorspace: "LAB", - }); - - const oklchScale = generateScale({ - color: "#6366f1", - background: "#ffffff", - colorspace: "OKLCH", - }); - - // Different colorspaces should produce different colors - expect(labScale["500"]).not.toBe(oklchScale["500"]); - }); - }); - - describe("WCAG compliance", () => { - it("should generate colors with appropriate contrast for light mode", () => { - const scale = generateScale({ - color: "#6366f1", - background: "#ffffff", - ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15], - }); - - const bgRgb = hexToRgb("#ffffff"); - - // Step 600 (index 6, ratio 4.5) should have ~4.5:1 contrast - const contrast600 = contrastRatio(hexToRgb(scale["600"]), bgRgb); - expect(contrast600).toBeGreaterThan(3.5); - expect(contrast600).toBeLessThan(6); - - // Step 900 (index 9, ratio 12) should have high contrast - const contrast900 = contrastRatio(hexToRgb(scale["900"]), bgRgb); - expect(contrast900).toBeGreaterThan(8); - }); - }); - - describe("different colors", () => { - const colors = [ - "#ef4444", // red - "#22c55e", // green - "#3b82f6", // blue - "#f59e0b", // amber - "#8b5cf6", // violet - "#6b7280", // gray - ]; - - colors.forEach((color) => { - it(`should generate valid scale for ${color}`, () => { - const scale = generateScale({ - color, - background: "#ffffff", - }); - - expect(Object.keys(scale)).toHaveLength(11); - Object.values(scale).forEach((hex) => { - expect(hex).toMatch(/^#[0-9a-f]{6}$/); - }); - }); - }); - }); -}); - -describe("Leonardo edge cases", () => { - it("should handle pure white input", () => { - const scale = generateScale({ - color: "#ffffff", - background: "#000000", - }); - expect(Object.keys(scale)).toHaveLength(11); - }); - - it("should handle pure black input", () => { - const scale = generateScale({ - color: "#000000", - background: "#ffffff", - }); - expect(Object.keys(scale)).toHaveLength(11); - }); - - it("should handle gray input", () => { - const scale = generateScale({ - color: "#808080", - background: "#ffffff", - }); - expect(Object.keys(scale)).toHaveLength(11); - }); -}); diff --git a/packages/color-engine/tests/material.test.ts b/packages/color-engine/tests/material.test.ts index cf08b4f30..8f67458ab 100644 --- a/packages/color-engine/tests/material.test.ts +++ b/packages/color-engine/tests/material.test.ts @@ -3,9 +3,19 @@ */ import { describe, it, expect } from "vitest"; +import Color from "colorjs.io"; import { generateScale, getHct, fromHct, getContrastTones } from "../src/algorithms/material"; -import { hexToRgb } from "../src/core/utils"; -import { SCALE_STEPS } from "../src/core/types"; +import { SCALE_STEPS } from "../src/types"; + +// Helper to convert hex to RGB array using Color.js +function hexToRgb(hex: string): [number, number, number] { + const color = new Color(hex).to("srgb"); + return [ + Math.round((color.coords[0] ?? 0) * 255), + Math.round((color.coords[1] ?? 0) * 255), + Math.round((color.coords[2] ?? 0) * 255), + ]; +} describe("Material generateScale", () => { describe("basic functionality", () => { diff --git a/packages/color-engine/tests/migration-baseline.test.ts b/packages/color-engine/tests/migration-baseline.test.ts new file mode 100644 index 000000000..b34312180 --- /dev/null +++ b/packages/color-engine/tests/migration-baseline.test.ts @@ -0,0 +1,213 @@ +/** + * Migration baseline test + * Captures outputs from current implementation to validate Color.js migration + */ + +import { describe, it, expect } from "vitest"; +import { createTheme } from "../src"; + +// Standard test configuration +const TEST_CONFIG = { + colors: [ + { + name: "accent", + colorKeys: ["#6366f1"], + ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15], + }, + { + name: "success", + colorKeys: ["#22c55e"], + ratios: [1.05, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15], + }, + ], + backgroundColor: "#ffffff", + lightness: 100, +}; + +describe("Migration Baseline", () => { + describe("Theme generation outputs", () => { + it("should generate 11 steps per color scale", () => { + const theme = createTheme(TEST_CONFIG); + + expect(Object.keys(theme.colors.accent)).toHaveLength(11); + expect(Object.keys(theme.colors.success)).toHaveLength(11); + }); + + it("should output valid HSL format", () => { + const theme = createTheme(TEST_CONFIG); + + // Check background + expect(theme.background).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/); + + // Check all color values + for (const scale of Object.values(theme.colors)) { + for (const value of Object.values(scale)) { + expect(value).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/); + } + } + }); + + it("should produce consistent outputs for same inputs", () => { + const theme1 = createTheme(TEST_CONFIG); + const theme2 = createTheme(TEST_CONFIG); + + expect(theme1).toEqual(theme2); + }); + }); + + describe("Contrast ratio accuracy", () => { + it("should produce colors that meet target contrast ratios", () => { + const theme = createTheme({ + colors: [ + { + name: "test", + colorKeys: ["#6366f1"], + ratios: [3, 4.5, 7], + }, + ], + backgroundColor: "#ffffff", + }); + + // We verify outputs exist - the comparison script validates actual ratios + expect(theme.colors.test).toBeDefined(); + expect(Object.keys(theme.colors.test)).toHaveLength(3); + }); + }); + + describe("Saturation modification", () => { + it("should produce different outputs with different saturation", () => { + const fullSat = createTheme({ + ...TEST_CONFIG, + saturation: 100, + }); + + const halfSat = createTheme({ + ...TEST_CONFIG, + saturation: 50, + }); + + // Colors should differ + expect(fullSat.colors.accent).not.toEqual(halfSat.colors.accent); + }); + }); + + describe("Contrast multiplier", () => { + it("should produce different outputs with different contrast", () => { + const normalContrast = createTheme({ + ...TEST_CONFIG, + contrast: 1, + }); + + const highContrast = createTheme({ + ...TEST_CONFIG, + contrast: 1.5, + }); + + expect(normalContrast.colors.accent).not.toEqual( + highContrast.colors.accent + ); + }); + }); + + describe("Background lightness", () => { + it("should produce different backgrounds with different lightness", () => { + const light = createTheme({ + colors: [ + { name: "test", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: { + name: "bg", + colorKeys: ["#6366f1"], + }, + lightness: 95, + }); + + const dark = createTheme({ + colors: [ + { name: "test", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: { + name: "bg", + colorKeys: ["#6366f1"], + }, + lightness: 10, + }); + + expect(light.background).not.toEqual(dark.background); + }); + }); + + describe("WCAG3 formula support", () => { + it("should work with wcag3 formula", () => { + const theme = createTheme({ + colors: [ + { + name: "test", + colorKeys: ["#6366f1"], + ratios: [30, 60, 90], // APCA values + }, + ], + backgroundColor: "#ffffff", + formula: "wcag3", + }); + + expect(theme.colors.test).toBeDefined(); + expect(Object.keys(theme.colors.test)).toHaveLength(3); + }); + }); + + describe("Smooth interpolation", () => { + it("should work with smooth option", () => { + const smooth = createTheme({ + colors: [ + { + name: "test", + colorKeys: ["#6366f1"], + ratios: [1.5, 3, 4.5], + smooth: true, + }, + ], + backgroundColor: "#ffffff", + }); + + const notSmooth = createTheme({ + colors: [ + { + name: "test", + colorKeys: ["#6366f1"], + ratios: [1.5, 3, 4.5], + smooth: false, + }, + ], + backgroundColor: "#ffffff", + }); + + // Both should produce valid outputs + expect(Object.keys(smooth.colors.test)).toHaveLength(3); + expect(Object.keys(notSmooth.colors.test)).toHaveLength(3); + }); + }); + + describe("Named ratios", () => { + it("should support object ratios with named keys", () => { + const theme = createTheme({ + colors: [ + { + name: "accent", + colorKeys: ["#6366f1"], + ratios: { + subtle: 1.5, + default: 3, + strong: 7, + }, + }, + ], + backgroundColor: "#ffffff", + }); + + expect(theme.colors.accent.subtle).toBeDefined(); + expect(theme.colors.accent.default).toBeDefined(); + expect(theme.colors.accent.strong).toBeDefined(); + }); + }); +}); diff --git a/packages/color-engine/tests/radix.test.ts b/packages/color-engine/tests/radix.test.ts deleted file mode 100644 index 004bf5426..000000000 --- a/packages/color-engine/tests/radix.test.ts +++ /dev/null @@ -1,238 +0,0 @@ -/** - * Tests for Radix blending algorithm - */ - -import { describe, it, expect } from "vitest"; -import { - generateScale, - getRadixScale, - getRadixScaleNames, -} from "../src/algorithms/radix"; -import { hexToRgb, luminance } from "../src/core/utils"; -import { SCALE_STEPS } from "../src/core/types"; - -describe("Radix generateScale", () => { - describe("basic functionality", () => { - it("should generate 11 steps", () => { - const scale = generateScale({ - accent: "#6366f1", - background: "#ffffff", - }); - - expect(Object.keys(scale)).toHaveLength(11); - SCALE_STEPS.forEach((step) => { - expect(scale[step]).toBeDefined(); - expect(scale[step]).toMatch(/^#[0-9a-f]{6}$/); - }); - }); - - it("should generate valid hex colors", () => { - const scale = generateScale({ - accent: "#ff0000", - background: "#ffffff", - }); - - Object.values(scale).forEach((color) => { - expect(color).toMatch(/^#[0-9a-f]{6}$/); - expect(() => hexToRgb(color)).not.toThrow(); - }); - }); - }); - - describe("light mode", () => { - const scale = generateScale({ - accent: "#3b82f6", - background: "#ffffff", - }); - - it("should have lighter colors at lower steps", () => { - const step50Lum = luminance(hexToRgb(scale["50"])); - const step950Lum = luminance(hexToRgb(scale["950"])); - - expect(step50Lum).toBeGreaterThan(step950Lum); - }); - - it("should maintain monotonic luminance progression", () => { - const luminances = SCALE_STEPS.map((step) => - luminance(hexToRgb(scale[step])) - ); - - // Each step should be darker (lower luminance) than the previous - for (let i = 1; i < luminances.length; i++) { - expect(luminances[i]).toBeLessThanOrEqual(luminances[i - 1] + 0.05); // Small tolerance - } - }); - }); - - describe("dark mode", () => { - const scale = generateScale({ - accent: "#3b82f6", - background: "#000000", - }); - - it("should have darker colors at lower steps", () => { - const step50Lum = luminance(hexToRgb(scale["50"])); - const step950Lum = luminance(hexToRgb(scale["950"])); - - expect(step50Lum).toBeLessThan(step950Lum); - }); - }); - - describe("color influence", () => { - it("should produce blue-ish scale for blue accent", () => { - const scale = generateScale({ - accent: "#3b82f6", - background: "#ffffff", - }); - - // Check mid-step for blue dominance - const midRgb = hexToRgb(scale["500"]); - expect(midRgb[2]).toBeGreaterThan(midRgb[0]); // More blue than red - }); - - it("should produce red-ish scale for red accent", () => { - const scale = generateScale({ - accent: "#ef4444", - background: "#ffffff", - }); - - const midRgb = hexToRgb(scale["500"]); - expect(midRgb[0]).toBeGreaterThan(midRgb[2]); // More red than blue - }); - - it("should produce green-ish scale for green accent", () => { - const scale = generateScale({ - accent: "#22c55e", - background: "#ffffff", - }); - - const midRgb = hexToRgb(scale["500"]); - expect(midRgb[1]).toBeGreaterThan(midRgb[0]); // More green than red - }); - }); - - describe("different accent colors", () => { - const colors = [ - "#ef4444", // red - "#22c55e", // green - "#3b82f6", // blue - "#f59e0b", // amber - "#8b5cf6", // violet - "#ec4899", // pink - "#14b8a6", // teal - "#f97316", // orange - ]; - - colors.forEach((accent) => { - it(`should generate valid scale for ${accent}`, () => { - const scale = generateScale({ - accent, - background: "#ffffff", - }); - - expect(Object.keys(scale)).toHaveLength(11); - Object.values(scale).forEach((hex) => { - expect(hex).toMatch(/^#[0-9a-f]{6}$/); - }); - }); - }); - }); - - describe("edge cases", () => { - it("should handle pure white accent", () => { - const scale = generateScale({ - accent: "#ffffff", - background: "#ffffff", - }); - - expect(Object.keys(scale)).toHaveLength(11); - }); - - it("should handle pure black accent", () => { - const scale = generateScale({ - accent: "#000000", - background: "#ffffff", - }); - - expect(Object.keys(scale)).toHaveLength(11); - }); - - it("should handle gray accent", () => { - const scale = generateScale({ - accent: "#808080", - background: "#ffffff", - }); - - expect(Object.keys(scale)).toHaveLength(11); - }); - }); -}); - -describe("Radix utilities", () => { - describe("getRadixScale", () => { - it("should return predefined light scale", () => { - const blueScale = getRadixScale("blue", "light"); - - expect(blueScale).not.toBeNull(); - expect(blueScale).toHaveLength(12); - blueScale!.forEach((color) => { - expect(color).toMatch(/^#[0-9a-f]{6}$/); - }); - }); - - it("should return predefined dark scale", () => { - const blueScale = getRadixScale("blue", "dark"); - - expect(blueScale).not.toBeNull(); - expect(blueScale).toHaveLength(12); - }); - - it("should return null for unknown scale", () => { - const unknown = getRadixScale("notacolor", "light"); - expect(unknown).toBeNull(); - }); - - it("should have proper light mode progression", () => { - const scale = getRadixScale("blue", "light")!; - - // First should be lightest - const firstLum = luminance(hexToRgb(scale[0])); - const lastLum = luminance(hexToRgb(scale[11])); - - expect(firstLum).toBeGreaterThan(lastLum); - }); - - it("should have proper dark mode progression", () => { - const scale = getRadixScale("blue", "dark")!; - - // First should be darkest - const firstLum = luminance(hexToRgb(scale[0])); - const lastLum = luminance(hexToRgb(scale[11])); - - expect(firstLum).toBeLessThan(lastLum); - }); - }); - - describe("getRadixScaleNames", () => { - it("should return array of scale names", () => { - const names = getRadixScaleNames(); - - expect(Array.isArray(names)).toBe(true); - expect(names.length).toBeGreaterThan(5); - - // Should include common colors - expect(names).toContain("blue"); - expect(names).toContain("red"); - expect(names).toContain("green"); - expect(names).toContain("gray"); - }); - - it("should only return valid scale names", () => { - const names = getRadixScaleNames(); - - names.forEach((name) => { - expect(getRadixScale(name, "light")).not.toBeNull(); - }); - }); - }); -}); diff --git a/packages/color-engine/tests/utils.test.ts b/packages/color-engine/tests/utils.test.ts deleted file mode 100644 index c96f25164..000000000 --- a/packages/color-engine/tests/utils.test.ts +++ /dev/null @@ -1,203 +0,0 @@ -/** - * Tests for core utility functions - */ - -import { describe, it, expect } from "vitest"; -import { - hexToRgb, - rgbToHex, - rgbToHsl, - hslToRgb, - rgbToOklch, - oklchToRgb, - luminance, - contrastRatio, - isValidHex, - getContrastTextColor, - clamp, -} from "../src/core/utils"; - -describe("hexToRgb", () => { - it("should convert 6-digit hex to RGB", () => { - expect(hexToRgb("#ffffff")).toEqual([255, 255, 255]); - expect(hexToRgb("#000000")).toEqual([0, 0, 0]); - expect(hexToRgb("#ff0000")).toEqual([255, 0, 0]); - expect(hexToRgb("#00ff00")).toEqual([0, 255, 0]); - expect(hexToRgb("#0000ff")).toEqual([0, 0, 255]); - }); - - it("should convert 3-digit hex to RGB", () => { - expect(hexToRgb("#fff")).toEqual([255, 255, 255]); - expect(hexToRgb("#000")).toEqual([0, 0, 0]); - expect(hexToRgb("#f00")).toEqual([255, 0, 0]); - }); - - it("should handle hex without #", () => { - expect(hexToRgb("ffffff")).toEqual([255, 255, 255]); - expect(hexToRgb("000")).toEqual([0, 0, 0]); - }); - - it("should throw for invalid hex", () => { - expect(() => hexToRgb("#gg0000")).toThrow(); - expect(() => hexToRgb("#12345")).toThrow(); - }); -}); - -describe("rgbToHex", () => { - it("should convert RGB to hex", () => { - expect(rgbToHex([255, 255, 255])).toBe("#ffffff"); - expect(rgbToHex([0, 0, 0])).toBe("#000000"); - expect(rgbToHex([255, 0, 0])).toBe("#ff0000"); - }); - - it("should clamp values", () => { - expect(rgbToHex([300, 0, 0])).toBe("#ff0000"); - expect(rgbToHex([-10, 0, 0])).toBe("#000000"); - }); -}); - -describe("rgbToHsl / hslToRgb", () => { - it("should round-trip white", () => { - const rgb: [number, number, number] = [255, 255, 255]; - const hsl = rgbToHsl(rgb); - const back = hslToRgb(hsl); - expect(back).toEqual(rgb); - }); - - it("should round-trip black", () => { - const rgb: [number, number, number] = [0, 0, 0]; - const hsl = rgbToHsl(rgb); - const back = hslToRgb(hsl); - expect(back).toEqual(rgb); - }); - - it("should round-trip red", () => { - const rgb: [number, number, number] = [255, 0, 0]; - const hsl = rgbToHsl(rgb); - expect(hsl.h).toBeCloseTo(0); - expect(hsl.s).toBeCloseTo(100); - expect(hsl.l).toBeCloseTo(50); - }); - - it("should round-trip gray", () => { - const rgb: [number, number, number] = [128, 128, 128]; - const hsl = rgbToHsl(rgb); - const back = hslToRgb(hsl); - expect(back[0]).toBeCloseTo(rgb[0], -1); - expect(back[1]).toBeCloseTo(rgb[1], -1); - expect(back[2]).toBeCloseTo(rgb[2], -1); - }); -}); - -describe("rgbToOklch / oklchToRgb", () => { - it("should round-trip white", () => { - const rgb: [number, number, number] = [255, 255, 255]; - const oklch = rgbToOklch(rgb); - expect(oklch.l).toBeCloseTo(1, 1); - expect(oklch.c).toBeCloseTo(0, 1); - - const back = oklchToRgb(oklch); - expect(back[0]).toBeCloseTo(255, -1); - expect(back[1]).toBeCloseTo(255, -1); - expect(back[2]).toBeCloseTo(255, -1); - }); - - it("should round-trip black", () => { - const rgb: [number, number, number] = [0, 0, 0]; - const oklch = rgbToOklch(rgb); - expect(oklch.l).toBeCloseTo(0, 1); - - const back = oklchToRgb(oklch); - expect(back[0]).toBeCloseTo(0, -1); - expect(back[1]).toBeCloseTo(0, -1); - expect(back[2]).toBeCloseTo(0, -1); - }); - - it("should round-trip saturated colors", () => { - const rgb: [number, number, number] = [100, 150, 200]; - const oklch = rgbToOklch(rgb); - const back = oklchToRgb(oklch); - expect(back[0]).toBeCloseTo(rgb[0], -1); - expect(back[1]).toBeCloseTo(rgb[1], -1); - expect(back[2]).toBeCloseTo(rgb[2], -1); - }); -}); - -describe("luminance", () => { - it("should return 1 for white", () => { - expect(luminance([255, 255, 255])).toBeCloseTo(1, 2); - }); - - it("should return 0 for black", () => { - expect(luminance([0, 0, 0])).toBeCloseTo(0, 2); - }); - - it("should handle gray correctly", () => { - const gray = luminance([128, 128, 128]); - expect(gray).toBeGreaterThan(0.1); - expect(gray).toBeLessThan(0.3); - }); -}); - -describe("contrastRatio", () => { - it("should return 21 for black on white", () => { - expect(contrastRatio([0, 0, 0], [255, 255, 255])).toBeCloseTo(21, 0); - }); - - it("should return 21 for white on black", () => { - expect(contrastRatio([255, 255, 255], [0, 0, 0])).toBeCloseTo(21, 0); - }); - - it("should return 1 for same colors", () => { - expect(contrastRatio([128, 128, 128], [128, 128, 128])).toBeCloseTo(1, 1); - }); - - it("should calculate gray on white correctly", () => { - // #cfcfcf on white should be around 1.55:1 - const ratio = contrastRatio([207, 207, 207], [255, 255, 255]); - expect(ratio).toBeGreaterThan(1.4); - expect(ratio).toBeLessThan(1.7); - }); -}); - -describe("isValidHex", () => { - it("should accept valid hex colors", () => { - expect(isValidHex("#fff")).toBe(true); - expect(isValidHex("#ffffff")).toBe(true); - expect(isValidHex("fff")).toBe(true); - expect(isValidHex("FFFFFF")).toBe(true); - expect(isValidHex("#ABC")).toBe(true); - }); - - it("should reject invalid hex colors", () => { - expect(isValidHex("#gg0000")).toBe(false); - expect(isValidHex("#12345")).toBe(false); - expect(isValidHex("not a color")).toBe(false); - expect(isValidHex("#1234567")).toBe(false); - }); -}); - -describe("getContrastTextColor", () => { - it("should return black for white background", () => { - expect(getContrastTextColor("#ffffff")).toBe("#000000"); - }); - - it("should return white for black background", () => { - expect(getContrastTextColor("#000000")).toBe("#ffffff"); - }); - - it("should return appropriate color for mid-tones", () => { - // Light gray should get black text - expect(getContrastTextColor("#dddddd")).toBe("#000000"); - // Dark gray should get white text - expect(getContrastTextColor("#333333")).toBe("#ffffff"); - }); -}); - -describe("clamp", () => { - it("should clamp values", () => { - expect(clamp(5, 0, 10)).toBe(5); - expect(clamp(-5, 0, 10)).toBe(0); - expect(clamp(15, 0, 10)).toBe(10); - }); -}); From 85d54dbba032cebdb01b73410d27952a179eb974 Mon Sep 17 00:00:00 2001 From: mehdibha Date: Mon, 15 Dec 2025 03:50:08 +0100 Subject: [PATCH 03/48] add playground, fix some bugs --- .gitignore | 3 +- packages/color-engine/package.json | 5 +- .../src/algorithms/leonardo/index.ts | 27 +- .../src/algorithms/leonardo/theme.ts | 20 +- packages/color-engine/tests/index.test.ts | 25 + packages/registry/src/ui/slider/basic.tsx | 2 + pnpm-lock.yaml | 43 +- spectrum-design-data | 1 + www/app/(internal)/internal/page.tsx | 544 +++++++++++++++++- www/package.json | 1 + 10 files changed, 623 insertions(+), 48 deletions(-) create mode 160000 spectrum-design-data diff --git a/.gitignore b/.gitignore index 7f2aa21fe..1ef290fb5 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ dist/ .turbo # fumadocs -.source \ No newline at end of file +.source +contrast-colors \ No newline at end of file diff --git a/packages/color-engine/package.json b/packages/color-engine/package.json index 7b8bcbc07..1636e5463 100644 --- a/packages/color-engine/package.json +++ b/packages/color-engine/package.json @@ -5,7 +5,10 @@ "type": "module", "license": "MIT", "exports": { - ".": "./src/index.ts" + ".": "./src/index.ts", + "./algorithms/leonardo": "./src/algorithms/leonardo/index.ts", + "./algorithms/material": "./src/algorithms/material/index.ts", + "./types": "./src/types.ts" }, "scripts": { "clean": "git clean -xdf .cache .turbo dist node_modules", diff --git a/packages/color-engine/src/algorithms/leonardo/index.ts b/packages/color-engine/src/algorithms/leonardo/index.ts index aa227e3f9..220db91d7 100644 --- a/packages/color-engine/src/algorithms/leonardo/index.ts +++ b/packages/color-engine/src/algorithms/leonardo/index.ts @@ -32,6 +32,7 @@ import ColorJS from "colorjs.io"; import { Theme } from "./theme"; import type { LeonardoColorspace, ContrastFormula } from "../../types"; +import { SCALE_STEPS } from "../../types"; export interface ColorInput { name: string; @@ -134,15 +135,25 @@ export function createTheme(input: CreateThemeInput): CreateThemeOutput { const colorScale: Record = {}; const colorItem = item as { name: string; values: Array<{ name: string; value: string }> }; - for (const value of colorItem.values) { - // Extract step number from the name (e.g., "accent100" -> "100") - const stepMatch = value.name.match(/(\d+)$/); - if (stepMatch && stepMatch[1]) { - const step = stepMatch[1]; - colorScale[step] = toHslString(value.value); + // Map values to SCALE_STEPS by index (Leonardo generates 100, 200... but we want 50, 100, 200...) + for (let i = 0; i < colorItem.values.length; i++) { + const value = colorItem.values[i]; + if (!value) continue; + + // Use SCALE_STEPS if we have 11 values (standard scale), otherwise use Leonardo's naming + if (colorItem.values.length === SCALE_STEPS.length) { + const step = SCALE_STEPS[i]; + if (step) { + colorScale[step] = toHslString(value.value); + } } else { - // For named ratios (object format), use the name as key - colorScale[value.name] = toHslString(value.value); + // For non-standard ratios, extract step from name or use index + const stepMatch = value.name.match(/(\d+)$/); + if (stepMatch && stepMatch[1]) { + colorScale[stepMatch[1]] = toHslString(value.value); + } else { + colorScale[value.name] = toHslString(value.value); + } } } diff --git a/packages/color-engine/src/algorithms/leonardo/theme.ts b/packages/color-engine/src/algorithms/leonardo/theme.ts index 96c2d989e..6b1222f7d 100644 --- a/packages/color-engine/src/algorithms/leonardo/theme.ts +++ b/packages/color-engine/src/algorithms/leonardo/theme.ts @@ -87,6 +87,9 @@ export class Theme { this._lightness = lightness ?? 100; this._backgroundColorValue = ""; + // Track if lightness was explicitly provided + const lightnessWasProvided = lightness !== undefined; + // Convert color inputs to Color instances this._colors = colors.map((color) => { if (color instanceof Color) { @@ -99,8 +102,8 @@ export class Theme { }); }); - // Set up background color - this._setBackgroundColor(backgroundColor); + // Set up background color (preserve lightness if explicitly provided) + this._setBackgroundColor(backgroundColor, lightnessWasProvided); this._setBackgroundColorValue(); // Validation @@ -291,6 +294,7 @@ export class Theme { private _setBackgroundColor( backgroundColor: string | BackgroundColor | ThemeBackgroundInput, + preserveLightness = false, ): void { if (typeof backgroundColor === "string") { // String input - convert to BackgroundColor @@ -299,12 +303,16 @@ export class Theme { colorKeys: [backgroundColor], output: "RGB", }); - // Use Color.js HSLuv for lightness calculation - const hsluvColor = new ColorJS(String(backgroundColor)).to("hsluv"); - const calcLightness = round(hsluvColor.coords[2] ?? 0); this._backgroundColor = newBackgroundColor; - this._lightness = calcLightness; + + // Only calculate lightness from the color if not explicitly provided + if (!preserveLightness) { + const hsluvColor = new ColorJS(String(backgroundColor)).to("hsluv"); + const calcLightness = round(hsluvColor.coords[2] ?? 0); + this._lightness = calcLightness; + } + this._backgroundColorValue = (newBackgroundColor.backgroundColorScale[this._lightness] ?? "#ffffff"); } else if (backgroundColor instanceof BackgroundColor) { backgroundColor.output = "RGB"; diff --git a/packages/color-engine/tests/index.test.ts b/packages/color-engine/tests/index.test.ts index 9502f5c32..0a6fe3f23 100644 --- a/packages/color-engine/tests/index.test.ts +++ b/packages/color-engine/tests/index.test.ts @@ -98,6 +98,31 @@ describe("createTheme", () => { expect(lightTheme.background).not.toBe(darkTheme.background); }); + it("should respect lightness option with string backgroundColor", () => { + const lightTheme = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: "#6b7280", + lightness: 97, + }); + + const darkTheme = createTheme({ + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [3] }, + ], + backgroundColor: "#6b7280", + lightness: 10, + }); + + // Light theme should have a light background + expect(lightTheme.background).toMatch(/hsl\(\d+, \d+%, 9[0-9]%\)/); + // Dark theme should have a dark background + expect(darkTheme.background).toMatch(/hsl\(\d+, \d+%, [0-2]?[0-9]%\)/); + // They should be different + expect(lightTheme.background).not.toBe(darkTheme.background); + }); + it("should respect saturation option", () => { const fullSat = createTheme({ colors: [ diff --git a/packages/registry/src/ui/slider/basic.tsx b/packages/registry/src/ui/slider/basic.tsx index f112639e4..e90fec591 100644 --- a/packages/registry/src/ui/slider/basic.tsx +++ b/packages/registry/src/ui/slider/basic.tsx @@ -88,6 +88,8 @@ const SliderControl = ({ className, ...props }: SliderControlProps) => { return ( track({ orientation, className: cn }), )} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0de444ae8..8068930ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,31 +107,13 @@ importers: '@material/material-color-utilities': specifier: ^0.3.0 version: 0.3.0 - '@radix-ui/colors': - specifier: ^3.0.0 - version: 3.0.0 - apca-w3: - specifier: ^0.1.9 - version: 0.1.9 - chroma-js: - specifier: ^2.4.2 - version: 2.6.0 - ciebase: - specifier: ^0.1.1 - version: 0.1.1 - ciecam02: - specifier: ^0.4.6 - version: 0.4.6 - hsluv: - specifier: ^0.1.0 - version: 0.1.0 + colorjs.io: + specifier: ^0.5.2 + version: 0.5.2 devDependencies: '@dotui/ts-config': specifier: workspace:* version: link:../../config/ts-config - '@types/chroma-js': - specifier: ^2.4.4 - version: 2.4.5 typescript: specifier: ^5.8.3 version: 5.9.3 @@ -401,6 +383,9 @@ importers: '@dotui/auth': specifier: workspace:* version: link:../packages/auth + '@dotui/colors': + specifier: workspace:* + version: link:../packages/color-engine '@dotui/db': specifier: workspace:* version: link:../packages/db @@ -1824,9 +1809,6 @@ packages: engines: {node: '>=18'} hasBin: true - '@radix-ui/colors@3.0.0': - resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==} - '@react-aria/autocomplete@3.0.0-rc.3': resolution: {integrity: sha512-vemf7h3hvIDk3MxiiPryysfYgJDg8R72X46dRIeg0+cXKYxjPYou64/DTucSV2z5J6RC5JalINu0jIDaLhEILw==} peerDependencies: @@ -2786,9 +2768,6 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/chroma-js@2.4.5': - resolution: {integrity: sha512-6ISjhzJViaPCy2q2e6PgK+8HcHQDQ0V2LDiKmYAh+jJlLqDa6HbwDh0wOevHY0kHHUx0iZwjSRbVD47WOUx5EQ==} - '@types/culori@4.0.1': resolution: {integrity: sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ==} @@ -3267,6 +3246,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorjs.io@0.5.2: + resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} + colorparsley@0.1.8: resolution: {integrity: sha512-rObESTTTE6G5qO5WFwFxWPggpw4KfpxnLC6Ssl8bITBnNVRhDsyCeTRAUxWQNVTx2pRknKlO2nddYTxjkdNFaw==} @@ -4720,6 +4702,7 @@ packages: next@16.0.8: resolution: {integrity: sha512-LmcZzG04JuzNXi48s5P+TnJBsTGPJunViNKV/iE4uM6kstjTQsQhvsAv+xF6MJxU2Pr26tl15eVbp0jQnsv6/g==} engines: {node: '>=20.9.0'} + deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details. hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -7102,8 +7085,6 @@ snapshots: - react-native-b4a - supports-color - '@radix-ui/colors@3.0.0': {} - '@react-aria/autocomplete@3.0.0-rc.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@react-aria/combobox': 3.14.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -8449,8 +8430,6 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 - '@types/chroma-js@2.4.5': {} - '@types/culori@4.0.1': {} '@types/debug@4.1.12': @@ -8891,6 +8870,8 @@ snapshots: color-name@1.1.4: {} + colorjs.io@0.5.2: {} + colorparsley@0.1.8: {} comma-separated-tokens@2.0.3: {} diff --git a/spectrum-design-data b/spectrum-design-data new file mode 160000 index 000000000..e7141c702 --- /dev/null +++ b/spectrum-design-data @@ -0,0 +1 @@ +Subproject commit e7141c702f1ff93ba8dfa7a32a8a47fcf9e63986 diff --git a/www/app/(internal)/internal/page.tsx b/www/app/(internal)/internal/page.tsx index 17053c13b..e76cdf843 100644 --- a/www/app/(internal)/internal/page.tsx +++ b/www/app/(internal)/internal/page.tsx @@ -1,3 +1,545 @@ +"use client"; + +import React from "react"; +import { + BackgroundColor as AdobeBackgroundColor, + Color as AdobeColor, + Theme as AdobeTheme, +} from "@adobe/leonardo-contrast-colors"; +import { parseColor } from "react-aria-components"; +import type { Color } from "react-aria-components"; + +import { createTheme } from "@dotui/colors/algorithms/leonardo"; +import type { LeonardoColorspace, ContrastFormula } from "@dotui/colors/algorithms/leonardo"; +import { generateScale } from "@dotui/colors/algorithms/material"; +import { SCALE_STEPS } from "@dotui/colors/types"; +import { ColorEditor } from "@dotui/registry/ui/color-editor"; +import { ColorPicker, ColorPickerContent, ColorPickerTrigger } from "@dotui/registry/ui/color-picker"; +import { ColorSwatch } from "@dotui/registry/ui/color-swatch"; +import { Label } from "@dotui/registry/ui/field"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@dotui/registry/ui/select"; +import { Slider, SliderControl, SliderOutput } from "@dotui/registry/ui/slider"; +import { Switch } from "@dotui/registry/ui/switch"; +import { ToggleButton } from "@dotui/registry/ui/toggle-button"; +import { ToggleButtonGroup } from "@dotui/registry/ui/toggle-button-group"; + +// Default ratios for 11 steps (50-950) +const DEFAULT_RATIOS = [1, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15]; + +// Material tones for light/dark mode +const LIGHT_TONES = [99, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10]; // Default: light to dark +const DARK_TONES = [10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99]; // Reversed: dark to light + +// Leonardo colorspace options +const COLORSPACE_OPTIONS: { id: LeonardoColorspace; label: string }[] = [ + { id: "LAB", label: "LAB" }, + { id: "LCH", label: "LCH" }, + { id: "OKLAB", label: "OKLAB" }, + { id: "OKLCH", label: "OKLCH" }, + { id: "HSL", label: "HSL" }, + { id: "HSLuv", label: "HSLuv" }, + { id: "HSV", label: "HSV" }, + { id: "RGB", label: "RGB" }, + { id: "CAM02", label: "CAM02" }, + { id: "CAM02p", label: "CAM02p" }, +]; + +type ColorMode = "light" | "dark"; + +// Color scale definitions +const SCALE_NAMES = ["neutral", "accent", "success", "danger", "warning", "info"] as const; +type ScaleName = (typeof SCALE_NAMES)[number]; + +const DEFAULT_COLORS: Record = { + neutral: "#6b7280", + accent: "#6366f1", + success: "#22c55e", + danger: "#ef4444", + warning: "#f59e0b", + info: "#3b82f6", +}; + +const SCALE_LABELS: Record = { + neutral: "Neutral", + accent: "Accent", + success: "Success", + danger: "Danger", + warning: "Warning", + info: "Info", +}; + +// Types for theme output +interface ScaleValues { + [step: string]: string; +} + +interface ThemeOutput { + background: string; + scales: Record; +} + +type AlgorithmId = "adobe" | "ours" | "material"; + export default function InternalPage() { - return
; + // Active algorithm + const [activeAlgorithm, setActiveAlgorithm] = React.useState("adobe"); + + // Light/dark mode + const [colorMode, setColorMode] = React.useState("light"); + + // Source colors for each scale + const [colors, setColors] = React.useState>(() => ({ + neutral: parseColor(DEFAULT_COLORS.neutral), + accent: parseColor(DEFAULT_COLORS.accent), + success: parseColor(DEFAULT_COLORS.success), + danger: parseColor(DEFAULT_COLORS.danger), + warning: parseColor(DEFAULT_COLORS.warning), + info: parseColor(DEFAULT_COLORS.info), + })); + + // Leonardo-specific controls + const [lightness, setLightness] = React.useState(97); + const [saturation, setSaturation] = React.useState(100); + const [contrast, setContrast] = React.useState(100); + const [formula, setFormula] = React.useState("wcag2"); + const [colorspace, setColorspace] = React.useState("LAB"); + const [smooth, setSmooth] = React.useState(false); + + // Material-specific controls + const [contrastLevel, setContrastLevel] = React.useState(0); + const [chromaOverride, setChromaOverride] = React.useState(50); + const [chromaEnabled, setChromaEnabled] = React.useState(false); + const [hueOverride, setHueOverride] = React.useState(180); + const [hueEnabled, setHueEnabled] = React.useState(false); + + // Update lightness when mode changes (for Leonardo) + React.useEffect(() => { + setLightness(colorMode === "light" ? 97 : 10); + }, [colorMode]); + + const updateColor = (name: ScaleName, color: Color) => { + setColors((prev) => ({ ...prev, [name]: color })); + }; + + // Get hex colors + const hexColors = React.useMemo( + () => + Object.fromEntries(SCALE_NAMES.map((name) => [name, colors[name].toString("hex")])) as Record, + [colors], + ); + + // Background color (neutral key color for all algorithms) + const backgroundColor = hexColors.neutral; + + // Generate Adobe Leonardo theme + const adobeTheme = React.useMemo(() => { + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const neutralBg = new AdobeBackgroundColor({ + name: "neutral", + colorKeys: [hexColors.neutral], + ratios: DEFAULT_RATIOS, + colorspace, + } as any); + + // Include neutral as a regular color too (for the scale) + const colorInstances = SCALE_NAMES.map( + (name) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new AdobeColor({ + name, + colorKeys: [hexColors[name]], + ratios: DEFAULT_RATIOS, + colorspace, + smooth, + } as any), + ); + + const theme = new AdobeTheme({ + backgroundColor: neutralBg, + colors: colorInstances, + lightness, + saturation, + contrast: contrast / 100, + output: "HEX", + formula, + }); + + const scales: Record = { + neutral: {}, + accent: {}, + success: {}, + danger: {}, + warning: {}, + info: {}, + }; + + // Extract colors from theme (including neutral now) + for (const colorResult of theme.contrastColors) { + if ("background" in colorResult) continue; + const scaleName = colorResult.name as ScaleName; + if (!SCALE_NAMES.includes(scaleName)) continue; + + for (let i = 0; i < colorResult.values.length; i++) { + const step = SCALE_STEPS[i]; + const colorValue = colorResult.values[i]; + if (step && colorValue) { + scales[scaleName][step] = colorValue.value; + } + } + } + + return { + background: theme.contrastColors[0] && "background" in theme.contrastColors[0] + ? theme.contrastColors[0].background + : backgroundColor, + scales, + }; + } catch (e) { + console.error("Adobe Leonardo error:", e); + return null; + } + }, [hexColors, lightness, saturation, contrast, backgroundColor, formula, colorspace, smooth]); + + // Generate Our Leonardo theme + const ourTheme = React.useMemo(() => { + try { + // Include all colors including neutral + const theme = createTheme({ + colors: SCALE_NAMES.map((name) => ({ + name, + colorKeys: [hexColors[name]], + ratios: DEFAULT_RATIOS, + colorspace, + smooth, + })), + backgroundColor: hexColors.neutral, + lightness, + saturation, + contrast: contrast / 100, + formula, + }); + + const scales: Record = { + neutral: {}, + accent: {}, + success: {}, + danger: {}, + warning: {}, + info: {}, + }; + + // Copy all color scales including neutral + for (const name of SCALE_NAMES) { + if (theme.colors[name]) { + scales[name] = theme.colors[name]; + } + } + + return { + background: theme.background, + scales, + }; + } catch (e) { + console.error("Our Leonardo error:", e); + return null; + } + }, [hexColors, lightness, saturation, contrast, backgroundColor, formula, colorspace, smooth]); + + // Generate Material HCT scales + const materialTheme = React.useMemo(() => { + try { + const scales: Record = { + neutral: {}, + accent: {}, + success: {}, + danger: {}, + warning: {}, + info: {}, + }; + + const tones = colorMode === "light" ? LIGHT_TONES : DARK_TONES; + + for (const name of SCALE_NAMES) { + const scale = generateScale({ + color: hexColors[name], + contrastLevel, + tones, + chroma: chromaEnabled ? chromaOverride : undefined, + hue: hueEnabled ? hueOverride : undefined, + }); + for (const step of SCALE_STEPS) { + scales[name][step] = scale[step]; + } + } + + return { + background: backgroundColor, + scales, + }; + } catch (e) { + console.error("Material HCT error:", e); + return null; + } + }, [hexColors, contrastLevel, backgroundColor, colorMode, chromaEnabled, chromaOverride, hueEnabled, hueOverride]); + + const isLeonardo = activeAlgorithm === "adobe" || activeAlgorithm === "ours"; + + return ( +
+
+ {/* Controls Panel */} +
+ {/* Algorithm & Mode Selectors */} +
+
+

Algorithm

+ setActiveAlgorithm([...keys].at(0) as AlgorithmId)} + size="sm" + > + Adobe Leonardo + Our Leonardo + Material HCT + +
+
+

Mode

+ setColorMode([...keys].at(0) as ColorMode)} + size="sm" + > + Light + Dark + +
+
+ {/* Source Colors */} +
+

Source Colors

+
+ {SCALE_NAMES.map((name) => ( + updateColor(name, c)}> + + + {SCALE_LABELS[name]} + + + + + + ))} +
+
+ + {/* Algorithm-specific controls */} + {isLeonardo && ( +
+ {/* Sliders row */} +
+ setLightness(v as number)} minValue={0} maxValue={100}> +
+ + +
+ +
+ + setSaturation(v as number)} minValue={0} maxValue={100}> +
+ + +
+ +
+ + setContrast(v as number)} minValue={50} maxValue={200}> +
+ + +
+ +
+
+ + {/* Advanced options row */} +
+ {/* Formula */} +
+ + setFormula([...keys].at(0) as ContrastFormula)} + size="sm" + > + WCAG 2 + WCAG 3 + +
+ + {/* Colorspace */} +
+ + +
+ + {/* Smooth */} +
+ + +
+
+
+ )} + + {activeAlgorithm === "material" && ( +
+ {/* Main controls */} +
+ setContrastLevel(v as number)} + minValue={-1} + maxValue={1} + step={0.1} + > +
+ + +
+ +
+ + {/* Chroma override */} +
+
+ + +
+ {chromaEnabled && ( + setChromaOverride(v as number)} + minValue={0} + maxValue={150} + > +
+ + +
+ +
+ )} +
+ + {/* Hue override */} +
+
+ + +
+ {hueEnabled && ( + setHueOverride(v as number)} + minValue={0} + maxValue={360} + > +
+ + +
+ +
+ )} +
+
+ +
+ Contrast: -1 = Reduced | 0 = Standard | 1 = High + {chromaEnabled &&  |  Chroma: 0-150 (colorfulness)} + {hueEnabled &&  |  Hue: 0-360°} +
+
+ )} +
+ + {/* Output Display */} +
+
+

+ {activeAlgorithm === "adobe" && "Adobe Leonardo"} + {activeAlgorithm === "ours" && "Our Leonardo"} + {activeAlgorithm === "material" && "Material HCT"} +

+ + {activeAlgorithm === "adobe" && "— Original contrast-colors library"} + {activeAlgorithm === "ours" && "— Color.js-based implementation"} + {activeAlgorithm === "material" && "— Tone-based (HCT color space)"} + +
+ + {activeAlgorithm === "adobe" && adobeTheme && } + {activeAlgorithm === "ours" && ourTheme && } + {activeAlgorithm === "material" && materialTheme && } +
+
+
+ ); +} + +// Theme display component +function ThemeDisplay({ theme }: { theme: ThemeOutput }) { + return ( +
+ {/* Background */} +
+ Background +
+ {theme.background} +
+ + {/* Color Scales */} +
+ {SCALE_NAMES.map((name) => ( + + ))} +
+
+ ); +} + +// Color scale row component +function ColorScaleRow({ name, scale }: { name: string; scale: ScaleValues }) { + return ( +
+ {name} +
+ {SCALE_STEPS.map((step) => ( +
+
+
+ {step} +
+
+ ))} +
+
+ ); } diff --git a/www/package.json b/www/package.json index 9d8ea2f65..55d5e7d49 100644 --- a/www/package.json +++ b/www/package.json @@ -23,6 +23,7 @@ "@adobe/leonardo-contrast-colors": "^1.0.0", "@dotui/api": "workspace:*", "@dotui/auth": "workspace:*", + "@dotui/colors": "workspace:*", "@dotui/db": "workspace:*", "@dotui/registry": "workspace:*", "@dotui/shadcn-adapter": "workspace:*", From 88e982717747e68c725b758bd23c251b2789d268 Mon Sep 17 00:00:00 2001 From: mehdibha Date: Mon, 15 Dec 2025 05:16:13 +0100 Subject: [PATCH 04/48] refactor from class to functional --- .changeset/config.json | 2 +- .../algorithms/leonardo/background-color.ts | 76 --- .../src/algorithms/leonardo/color.ts | 189 ------ .../src/algorithms/leonardo/theme.ts | 440 -------------- .../{color-engine => colors}/package.json | 0 .../scripts/compare-libraries.ts | 0 .../src/algorithms/leonardo/generate.ts | 383 ++++++++++++ .../src/algorithms/leonardo/index.ts | 34 +- .../src/algorithms/leonardo/utils.ts | 0 .../src/algorithms/material/index.ts | 0 .../{color-engine => colors}/src/index.ts | 0 .../{color-engine => colors}/src/types.ts | 0 .../src/utils/curve.ts | 0 .../snapshot-baseline.test.ts.snap | 545 ++++++++++++++++++ .../tests/index.test.ts | 0 .../tests/material.test.ts | 0 .../tests/migration-baseline.test.ts | 0 .../colors/tests/snapshot-baseline.test.ts | 387 +++++++++++++ .../{color-engine => colors}/tsconfig.json | 2 +- pnpm-lock.yaml | 4 +- 20 files changed, 1330 insertions(+), 732 deletions(-) delete mode 100644 packages/color-engine/src/algorithms/leonardo/background-color.ts delete mode 100644 packages/color-engine/src/algorithms/leonardo/color.ts delete mode 100644 packages/color-engine/src/algorithms/leonardo/theme.ts rename packages/{color-engine => colors}/package.json (100%) rename packages/{color-engine => colors}/scripts/compare-libraries.ts (100%) create mode 100644 packages/colors/src/algorithms/leonardo/generate.ts rename packages/{color-engine => colors}/src/algorithms/leonardo/index.ts (81%) rename packages/{color-engine => colors}/src/algorithms/leonardo/utils.ts (100%) rename packages/{color-engine => colors}/src/algorithms/material/index.ts (100%) rename packages/{color-engine => colors}/src/index.ts (100%) rename packages/{color-engine => colors}/src/types.ts (100%) rename packages/{color-engine => colors}/src/utils/curve.ts (100%) create mode 100644 packages/colors/tests/__snapshots__/snapshot-baseline.test.ts.snap rename packages/{color-engine => colors}/tests/index.test.ts (100%) rename packages/{color-engine => colors}/tests/material.test.ts (100%) rename packages/{color-engine => colors}/tests/migration-baseline.test.ts (100%) create mode 100644 packages/colors/tests/snapshot-baseline.test.ts rename packages/{color-engine => colors}/tsconfig.json (81%) diff --git a/.changeset/config.json b/.changeset/config.json index 98b9e3e87..8300cdbba 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -15,7 +15,7 @@ "@dotui/db", "@dotui/registry", "@dotui/shadcn-adapter", - "@dotui/color-engine", + "@dotui/colors", "@dotui/style-system", "@dotui/transformers", "@dotui/types" diff --git a/packages/color-engine/src/algorithms/leonardo/background-color.ts b/packages/color-engine/src/algorithms/leonardo/background-color.ts deleted file mode 100644 index 693db3eb8..000000000 --- a/packages/color-engine/src/algorithms/leonardo/background-color.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * BackgroundColor class for Leonardo theme generation - * Ported from Adobe's contrast-colors library for 100% Leonardo parity - */ - -import { Color, ColorOptions } from "./color"; -import { hsluvArray, convertColorValue, createScale, removeDuplicates } from "./utils"; -import type { LeonardoColorspace } from "../../types"; - -export interface BackgroundColorOptions extends Omit { - ratios?: number[] | Record; -} - -export class BackgroundColor extends Color { - private _backgroundColorScale: string[] | null = null; - - constructor(options: BackgroundColorOptions) { - // BackgroundColor doesn't require ratios - super({ - ...options, - ratios: options.ratios || [], - }); - } - - get backgroundColorScale(): string[] { - if (!this._backgroundColorScale) { - this._generateColorScale(); - } - return this._backgroundColorScale!; - } - - /** - * Generate the background color scale (100 lightness steps) - */ - _generateColorScale(): void { - // Call parent's generateColorScale first - super._generateColorScale(); - - // Create massive scale for background - const backgroundColorScale = createScale({ - swatches: 1000, - colorKeys: this.colorKeys, - colorspace: this.colorspace, - shift: 1, - smooth: this.smooth, - }) as string[]; - - // Inject original key colors to ensure they are present - backgroundColorScale.push(...this.colorKeys); - - // Convert to HSLuv and track indices - const colorObj = backgroundColorScale.map((c, i) => ({ - value: Math.round(hsluvArray(c)[2] ?? 0), - index: i, - })); - - // Remove duplicates by lightness value - const colorObjFiltered = removeDuplicates(colorObj, "value"); - - // Map back to colors - const bgColorArrayFiltered = colorObjFiltered.map( - (data) => backgroundColorScale[data.index], - ); - - // Cap at 100 colors, add white back if needed - if (bgColorArrayFiltered.length >= 101) { - bgColorArrayFiltered.length = 100; - bgColorArrayFiltered.push("#ffffff"); - } - - // Convert to output format - this._backgroundColorScale = bgColorArrayFiltered.map((color) => - convertColorValue(color ?? "#ffffff", this.output) as string, - ); - } -} diff --git a/packages/color-engine/src/algorithms/leonardo/color.ts b/packages/color-engine/src/algorithms/leonardo/color.ts deleted file mode 100644 index fcbf2096c..000000000 --- a/packages/color-engine/src/algorithms/leonardo/color.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Color class for Leonardo theme generation - * Ported from Adobe's contrast-colors library for 100% Leonardo parity - * Migrated to Color.js from chroma-js - */ - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import ColorJS from "colorjs.io"; -import { colorSpaces, createScale } from "./utils"; -import type { LeonardoColorspace } from "../../types"; - -export interface ColorOptions { - name: string; - colorKeys: string[]; - colorspace?: LeonardoColorspace; - ratios: number[] | Record; - smooth?: boolean; - output?: LeonardoColorspace; - saturation?: number; -} - -export class Color { - private _name: string; - private _colorKeys: string[]; - _modifiedKeys: string[]; - _colorspace: LeonardoColorspace; - private _ratios: number[] | Record; - _smooth: boolean; - private _output: LeonardoColorspace; - private _saturation: number; - private _colorScale: ((pos: number) => string) | null; - - constructor({ - name, - colorKeys, - colorspace = "RGB", - ratios, - smooth = false, - output = "HEX", - saturation = 100, - }: ColorOptions) { - this._name = name; - this._colorKeys = colorKeys; - this._modifiedKeys = colorKeys; - this._colorspace = colorspace; - this._ratios = ratios; - this._smooth = smooth; - this._output = output; - this._saturation = saturation; - - if (!this._name) { - throw new Error("Color missing name"); - } - if (!this._colorKeys) { - throw new Error("Color Keys are undefined"); - } - if (!colorSpaces[this._colorspace]) { - throw new Error(`Colorspace "${colorspace}" not supported`); - } - if (!colorSpaces[this._output]) { - throw new Error(`Output "${output}" not supported`); - } - - // Validate color keys using Color.js - for (let i = 0; i < this._colorKeys.length; i++) { - try { - new ColorJS(this._colorKeys[i]!); - } catch { - throw new Error(`Invalid Color Key "${this._colorKeys[i]}"`); - } - } - - this._colorScale = null; - - // Initialize saturation modification if needed - if (this._saturation !== 100) { - this._updateColorSaturation(); - } - } - - // Getters and setters - set colorKeys(colorKeys: string[]) { - this._colorKeys = colorKeys; - this._updateColorSaturation(); - } - - get colorKeys(): string[] { - return this._colorKeys; - } - - set saturation(saturation: number) { - this._saturation = saturation; - this._updateColorSaturation(); - } - - get saturation(): number { - return this._saturation; - } - - set colorspace(colorspace: LeonardoColorspace) { - this._colorspace = colorspace; - this._generateColorScale(); - } - - get colorspace(): LeonardoColorspace { - return this._colorspace; - } - - set ratios(ratios: number[] | Record) { - this._ratios = ratios; - } - - get ratios(): number[] | Record { - return this._ratios; - } - - set name(name: string) { - this._name = name; - } - - get name(): string { - return this._name; - } - - set smooth(smooth: boolean) { - if (smooth === true || (smooth as any) === "true") { - this._smooth = true; - } else { - this._smooth = false; - } - this._generateColorScale(); - } - - get smooth(): boolean { - return this._smooth; - } - - set output(output: LeonardoColorspace) { - this._output = output; - this._colorScale = null; - } - - get output(): LeonardoColorspace { - return this._output; - } - - get colorScale(): (pos: number) => string { - if (!this._colorScale) { - this._generateColorScale(); - } - return this._colorScale!; - } - - /** - * Update color keys with saturation modification using OKLCH - * This is the key to Leonardo parity - uses OKLCH for saturation adjustment - */ - _updateColorSaturation(): void { - const newColorKeys: string[] = []; - - this._colorKeys.forEach((key) => { - const color = new ColorJS(key).to("oklch"); - const currentL = color.coords[0] ?? 0; - const currentC = color.coords[1] ?? 0; - const currentH = color.coords[2] ?? 0; - const newSaturation = currentC * (this._saturation / 100); - const newColor = new ColorJS("oklch", [currentL, newSaturation, currentH]); - newColorKeys.push(newColor.to("srgb").toString({ format: "hex" })); - }); - - this._modifiedKeys = newColorKeys; - this._generateColorScale(); - } - - /** - * Generate the color scale (3000 swatches) - */ - _generateColorScale(): void { - this._colorScale = createScale({ - swatches: 3000, - colorKeys: this._modifiedKeys, - colorspace: this._colorspace, - shift: 1, - smooth: this._smooth, - asFun: true, - }) as (pos: number) => string; - } -} diff --git a/packages/color-engine/src/algorithms/leonardo/theme.ts b/packages/color-engine/src/algorithms/leonardo/theme.ts deleted file mode 100644 index 6b1222f7d..000000000 --- a/packages/color-engine/src/algorithms/leonardo/theme.ts +++ /dev/null @@ -1,440 +0,0 @@ -/** - * Theme class for Leonardo theme generation - * Ported from Adobe's contrast-colors library for 100% Leonardo parity - * Migrated to Color.js from chroma-js - */ - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import ColorJS from "colorjs.io"; -import { Color } from "./color"; -import { BackgroundColor } from "./background-color"; -import { - colorSpaces, - convertColorValue, - multiplyRatios, - ratioName, - searchColors, - round, -} from "./utils"; -import type { LeonardoColorspace, ContrastFormula } from "../../types"; - -export interface ThemeColorInput { - name: string; - colorKeys: string[]; - colorspace?: LeonardoColorspace; - ratios: number[] | Record; - smooth?: boolean; -} - -export interface ThemeBackgroundInput { - name: string; - colorKeys: string[]; - colorspace?: LeonardoColorspace; -} - -export interface ThemeOptions { - colors: (Color | ThemeColorInput)[]; - backgroundColor: string | BackgroundColor | ThemeBackgroundInput; - lightness?: number; - contrast?: number; - saturation?: number; - output?: LeonardoColorspace; - formula?: ContrastFormula; -} - -export interface ContrastColorValue { - name: string; - contrast: number; - value: string; -} - -export interface ContrastColor { - name: string; - values: ContrastColorValue[]; -} - -export type ContrastColors = Array<{ background: string } | ContrastColor>; -export type ContrastColorPairs = Record; -export type ContrastColorValues = string[]; - -export class Theme { - private _output: LeonardoColorspace; - private _colors: Color[]; - private _lightness: number; - private _saturation: number; - private _formula: ContrastFormula; - private _backgroundColor!: BackgroundColor; - private _backgroundColorValue: string; - private _contrast: number; - private _contrastColors: ContrastColors; - private _contrastColorPairs: ContrastColorPairs; - private _contrastColorValues: ContrastColorValues; - - constructor({ - colors, - backgroundColor, - lightness, - contrast = 1, - saturation = 100, - output = "HEX", - formula = "wcag2", - }: ThemeOptions) { - this._output = output; - this._saturation = saturation; - this._formula = formula; - this._contrast = contrast; - this._lightness = lightness ?? 100; - this._backgroundColorValue = ""; - - // Track if lightness was explicitly provided - const lightnessWasProvided = lightness !== undefined; - - // Convert color inputs to Color instances - this._colors = colors.map((color) => { - if (color instanceof Color) { - return color; - } - return new Color({ - ...color, - output: this._output, - saturation: this._saturation, - }); - }); - - // Set up background color (preserve lightness if explicitly provided) - this._setBackgroundColor(backgroundColor, lightnessWasProvided); - this._setBackgroundColorValue(); - - // Validation - if (!this._colors || this._colors.length === 0) { - throw new Error("No colors are defined"); - } - if (!this._backgroundColor) { - throw new Error("Background color is undefined"); - } - - this._colors.forEach((color) => { - if (!color.ratios) { - throw new Error(`Color ${color.name}'s ratios are undefined`); - } - }); - - if (!colorSpaces[this._output]) { - throw new Error(`Output "${output}" not supported`); - } - - // Update saturation if needed - if (this._saturation < 100) { - this._updateColorSaturation(this._saturation); - } - - // Initialize contrast colors - this._contrastColors = []; - this._contrastColorPairs = {}; - this._contrastColorValues = []; - - // Generate contrast colors - this._findContrastColors(); - this._findContrastColorPairs(); - this._findContrastColorValues(); - } - - // Getters and setters - set formula(formula: ContrastFormula) { - this._formula = formula; - this._findContrastColors(); - } - - get formula(): ContrastFormula { - return this._formula; - } - - set contrast(contrast: number) { - this._contrast = contrast; - this._findContrastColors(); - } - - get contrast(): number { - return this._contrast; - } - - set lightness(lightness: number) { - this._lightness = lightness; - this._setBackgroundColor(this._backgroundColor); - this._findContrastColors(); - } - - get lightness(): number { - return this._lightness; - } - - set saturation(saturation: number) { - this._saturation = saturation; - this._updateColorSaturation(saturation); - this._findContrastColors(); - } - - get saturation(): number { - return this._saturation; - } - - set backgroundColor(backgroundColor: string | BackgroundColor | ThemeBackgroundInput) { - this._setBackgroundColor(backgroundColor); - this._findContrastColors(); - } - - get backgroundColorValue(): string { - return this._backgroundColorValue; - } - - get backgroundColor(): BackgroundColor { - return this._backgroundColor; - } - - set colors(colors: Color[]) { - this._colors = colors; - this._findContrastColors(); - } - - get colors(): Color[] { - return this._colors; - } - - set addColor(color: Color) { - this._colors.push(color); - this._findContrastColors(); - } - - set removeColor(color: { name: string }) { - const filteredColors = this._colors.filter((entry) => entry.name !== color.name); - this._colors = filteredColors; - this._findContrastColors(); - } - - set updateColor( - param: - | { - color: string; - name?: string; - colorKeys?: string[]; - ratios?: number[] | Record; - colorspace?: LeonardoColorspace; - smooth?: boolean; - } - | Array<{ - color: string; - name?: string; - colorKeys?: string[]; - ratios?: number[] | Record; - colorspace?: LeonardoColorspace; - smooth?: boolean; - }>, - ) { - if (Array.isArray(param)) { - for (const p of param) { - this._updateSingleColor(p); - } - } else { - this._updateSingleColor(param); - } - this._findContrastColors(); - } - - private _updateSingleColor(param: { - color: string; - name?: string; - colorKeys?: string[]; - ratios?: number[] | Record; - colorspace?: LeonardoColorspace; - smooth?: boolean; - }): void { - const currentColor = this._colors.find((entry) => entry.name === param.color); - if (!currentColor) return; - - const index = this._colors.indexOf(currentColor); - const filteredColors = this._colors.filter((entry) => entry.name !== param.color); - - if (param.name) currentColor.name = param.name; - if (param.colorKeys) currentColor.colorKeys = param.colorKeys; - if (param.ratios) currentColor.ratios = param.ratios; - if (param.colorspace) currentColor.colorspace = param.colorspace; - if (param.smooth !== undefined) currentColor.smooth = param.smooth; - - currentColor._generateColorScale(); - - filteredColors.splice(index, 0, currentColor); - this._colors = filteredColors; - } - - set output(output: LeonardoColorspace) { - this._output = output; - this._colors.forEach((element) => { - element.output = this._output; - }); - this._backgroundColor.output = this._output; - this._findContrastColors(); - } - - get output(): LeonardoColorspace { - return this._output; - } - - get contrastColors(): ContrastColors { - return this._contrastColors; - } - - get contrastColorPairs(): ContrastColorPairs { - return this._contrastColorPairs; - } - - get contrastColorValues(): ContrastColorValues { - return this._contrastColorValues; - } - - private _setBackgroundColor( - backgroundColor: string | BackgroundColor | ThemeBackgroundInput, - preserveLightness = false, - ): void { - if (typeof backgroundColor === "string") { - // String input - convert to BackgroundColor - const newBackgroundColor = new BackgroundColor({ - name: "background", - colorKeys: [backgroundColor], - output: "RGB", - }); - - this._backgroundColor = newBackgroundColor; - - // Only calculate lightness from the color if not explicitly provided - if (!preserveLightness) { - const hsluvColor = new ColorJS(String(backgroundColor)).to("hsluv"); - const calcLightness = round(hsluvColor.coords[2] ?? 0); - this._lightness = calcLightness; - } - - this._backgroundColorValue = (newBackgroundColor.backgroundColorScale[this._lightness] ?? "#ffffff"); - } else if (backgroundColor instanceof BackgroundColor) { - backgroundColor.output = "RGB"; - const calcBackgroundColorValue = - backgroundColor.backgroundColorScale[this._lightness] ?? "#ffffff"; - - this._backgroundColor = backgroundColor; - this._backgroundColorValue = calcBackgroundColorValue; - } else { - // ThemeBackgroundInput object - const newBackgroundColor = new BackgroundColor({ - ...backgroundColor, - output: "RGB", - }); - const calcBackgroundColorValue = - newBackgroundColor.backgroundColorScale[this._lightness] ?? "#ffffff"; - - this._backgroundColor = newBackgroundColor; - this._backgroundColorValue = calcBackgroundColorValue; - } - } - - private _setBackgroundColorValue(): void { - this._backgroundColorValue = - this._backgroundColor.backgroundColorScale[this._lightness] ?? "#ffffff"; - } - - private _updateColorSaturation(saturation: number): void { - this._colors.forEach((color) => { - color.saturation = saturation; - }); - } - - private _findContrastColors(): ContrastColors { - // Use Color.js for RGB conversion - const bgColor = new ColorJS(String(this._backgroundColorValue)).to("srgb"); - const bgRgbArray = [ - Math.round((bgColor.coords[0] ?? 0) * 255), - Math.round((bgColor.coords[1] ?? 0) * 255), - Math.round((bgColor.coords[2] ?? 0) * 255), - ]; - const baseV = this._lightness / 100; - const convertedBackgroundColorValue = convertColorValue( - this._backgroundColorValue, - this._output, - ) as string; - const baseObj = { background: convertedBackgroundColorValue }; - - const returnColors: ContrastColors = []; - const returnColorValues: ContrastColorValues = []; - const returnColorPairs: ContrastColorPairs = { ...baseObj }; - returnColors.push(baseObj); - - this._colors.forEach((color) => { - if (color.ratios !== undefined) { - let swatchNames: string[] | undefined; - const newArr: ContrastColorValue[] = []; - const colorObj: ContrastColor = { - name: color.name, - values: newArr, - }; - - let ratioValues: number[]; - - if (Array.isArray(color.ratios)) { - ratioValues = color.ratios; - } else { - swatchNames = Object.keys(color.ratios); - ratioValues = Object.values(color.ratios); - } - - // Modify target ratios based on contrast multiplier - ratioValues = ratioValues.map((ratio) => - multiplyRatios(+ratio, this._contrast), - ); - - const contrastColors = searchColors( - color, - bgRgbArray, - baseV, - ratioValues, - this._formula, - ).map((clr) => convertColorValue(clr, this._output) as string); - - for (let i = 0; i < contrastColors.length; i++) { - let n: string; - if (!swatchNames) { - const rVal = ratioName( - Array.isArray(color.ratios) - ? color.ratios - : Object.values(color.ratios), - this._formula, - )[i] ?? 0; - n = color.name.concat(String(rVal)).replace(/\s+/g, ""); - } else { - n = swatchNames[i] ?? ""; - } - - const obj: ContrastColorValue = { - name: n, - contrast: ratioValues[i] ?? 0, - value: contrastColors[i] ?? "", - }; - newArr.push(obj); - returnColorPairs[n] = contrastColors[i] ?? ""; - returnColorValues.push(contrastColors[i] ?? ""); - } - returnColors.push(colorObj); - } - }); - - this._contrastColorValues = returnColorValues; - this._contrastColorPairs = returnColorPairs; - this._contrastColors = returnColors; - return this._contrastColors; - } - - private _findContrastColorPairs(): ContrastColorPairs { - return this._contrastColorPairs; - } - - private _findContrastColorValues(): ContrastColorValues { - return this._contrastColorValues; - } -} diff --git a/packages/color-engine/package.json b/packages/colors/package.json similarity index 100% rename from packages/color-engine/package.json rename to packages/colors/package.json diff --git a/packages/color-engine/scripts/compare-libraries.ts b/packages/colors/scripts/compare-libraries.ts similarity index 100% rename from packages/color-engine/scripts/compare-libraries.ts rename to packages/colors/scripts/compare-libraries.ts diff --git a/packages/colors/src/algorithms/leonardo/generate.ts b/packages/colors/src/algorithms/leonardo/generate.ts new file mode 100644 index 000000000..d0a86b6fa --- /dev/null +++ b/packages/colors/src/algorithms/leonardo/generate.ts @@ -0,0 +1,383 @@ +/** + * Pure functional implementation of Leonardo color generation + * + * This module provides the same functionality as the class-based implementation + * but using pure functions for better testability and composability. + */ + +import ColorJS from "colorjs.io"; +import { + colorSpaces, + createScale, + hsluvArray, + removeDuplicates, + convertColorValue, + getContrast, + multiplyRatios, + ratioName, + round, +} from "./utils"; +import type { LeonardoColorspace, ContrastFormula } from "../../types"; + +// ============================================================================ +// Types +// ============================================================================ + +export interface ColorScaleOptions { + colorKeys: string[]; + colorspace?: LeonardoColorspace; + smooth?: boolean; + saturation?: number; +} + +export interface BackgroundScaleOptions { + colorKeys: string[]; + colorspace?: LeonardoColorspace; +} + +export interface SearchContrastOptions { + colorKeys: string[]; + colorspace?: LeonardoColorspace; + smooth?: boolean; + ratios: number[]; + backgroundRgb: [number, number, number]; + backgroundLightness: number; // 0-1 range + formula: ContrastFormula; +} + +export interface GenerateThemeColorInput { + name: string; + colorKeys: string[]; + colorspace?: LeonardoColorspace; + ratios: number[] | Record; + smooth?: boolean; +} + +export interface GenerateThemeBackgroundInput { + name: string; + colorKeys: string[]; + colorspace?: LeonardoColorspace; +} + +export interface GenerateThemeOptions { + colors: GenerateThemeColorInput[]; + backgroundColor: string | GenerateThemeBackgroundInput; + lightness?: number; + saturation?: number; + contrast?: number; + formula?: ContrastFormula; +} + +export interface ContrastColorValue { + name: string; + contrast: number; + value: string; +} + +export interface GeneratedTheme { + background: string; + backgroundScale: string[]; + colors: Record; +} + +// ============================================================================ +// Pure Functions +// ============================================================================ + +/** + * Apply saturation modification to color keys using OKLCH colorspace + * This is the key to Leonardo parity - uses OKLCH for saturation adjustment + */ +export function applyColorSaturation(colorKeys: string[], saturation: number): string[] { + if (saturation === 100) { + return colorKeys; + } + + return colorKeys.map((key) => { + const color = new ColorJS(key).to("oklch"); + const currentL = color.coords[0] ?? 0; + const currentC = color.coords[1] ?? 0; + const currentH = color.coords[2] ?? 0; + const newSaturation = currentC * (saturation / 100); + const newColor = new ColorJS("oklch", [currentL, newSaturation, currentH]); + return newColor.to("srgb").toString({ format: "hex" }); + }); +} + +/** + * Generate a color scale function (3000 swatches) for contrast searching + */ +export function generateColorScaleFn(options: ColorScaleOptions): (pos: number) => string { + const { colorKeys, colorspace = "RGB", smooth = false, saturation = 100 } = options; + + const modifiedKeys = applyColorSaturation(colorKeys, saturation); + + return createScale({ + swatches: 3000, + colorKeys: modifiedKeys, + colorspace, + shift: 1, + smooth, + asFun: true, + }) as (pos: number) => string; +} + +/** + * Generate a background color scale (100 lightness steps indexed 0-100) + * Returns array where index = lightness value in HSLuv + */ +export function generateBackgroundScale(options: BackgroundScaleOptions): string[] { + const { colorKeys, colorspace = "RGB" } = options; + + // Create massive scale for background + const backgroundColorScale = createScale({ + swatches: 1000, + colorKeys, + colorspace, + shift: 1, + smooth: false, + }) as string[]; + + // Inject original key colors to ensure they are present + backgroundColorScale.push(...colorKeys); + + // Convert to HSLuv and track indices + const colorObj = backgroundColorScale.map((c, i) => ({ + value: Math.round(hsluvArray(c)[2] ?? 0), + index: i, + })); + + // Remove duplicates by lightness value + const colorObjFiltered = removeDuplicates(colorObj, "value"); + + // Map back to colors + const bgColorArrayFiltered = colorObjFiltered.map( + (data) => backgroundColorScale[data.index], + ); + + // Cap at 100 colors, add white back if needed + if (bgColorArrayFiltered.length >= 101) { + bgColorArrayFiltered.length = 100; + bgColorArrayFiltered.push("#ffffff"); + } + + // Convert to RGB format (internal processing format) + return bgColorArrayFiltered.map((color) => + convertColorValue(color ?? "#ffffff", "RGB") as string, + ); +} + +/** + * Search for colors matching target contrast ratios using binary search + */ +export function searchContrastColors(options: SearchContrastOptions): string[] { + const { + colorKeys, + colorspace = "RGB", + smooth = false, + ratios, + backgroundRgb, + backgroundLightness, + formula, + } = options; + + const colorLen = 3000; + const colorScale = createScale({ + swatches: colorLen, + colorKeys, + colorspace, + shift: 1, + smooth, + asFun: true, + }) as (pos: number) => string; + + const ccache: Record = {}; + + const getContrast2 = (i: number): number => { + if (ccache[i]) { + return ccache[i]; + } + const c = new ColorJS(colorScale(i)).to("srgb"); + const rgb = [ + Math.round((c.coords[0] ?? 0) * 255), + Math.round((c.coords[1] ?? 0) * 255), + Math.round((c.coords[2] ?? 0) * 255), + ]; + const contrast = getContrast(rgb, backgroundRgb, backgroundLightness, formula); + ccache[i] = contrast; + return contrast; + }; + + const colorSearch = (x: number): number => { + const first = getContrast2(0); + const last = getContrast2(colorLen); + const dir = first < last ? 1 : -1; + const epsilon = 0.01; + x += 0.005 * Math.sign(x); + let step = colorLen / 2; + let dot = step; + let val = getContrast2(dot); + let counter = 100; + + while (Math.abs(val - x) > epsilon && counter) { + counter--; + step /= 2; + if (val < x) { + dot += step * dir; + } else { + dot -= step * dir; + } + val = getContrast2(dot); + } + return round(dot, 3); + }; + + const outputColors: string[] = []; + ratios.forEach((ratio) => outputColors.push(colorScale(colorSearch(+ratio)))); + return outputColors; +} + +/** + * Parse background input into normalized form + */ +function parseBackgroundInput( + backgroundColor: string | GenerateThemeBackgroundInput, +): { colorKeys: string[]; colorspace: LeonardoColorspace } { + if (typeof backgroundColor === "string") { + return { + colorKeys: [backgroundColor], + colorspace: "RGB", + }; + } + return { + colorKeys: backgroundColor.colorKeys, + colorspace: backgroundColor.colorspace ?? "RGB", + }; +} + +/** + * Calculate lightness from a color using HSLuv + */ +function calculateLightnessFromColor(color: string): number { + const hsluvColor = new ColorJS(String(color)).to("hsluv"); + return round(hsluvColor.coords[2] ?? 0); +} + +/** + * Convert color to RGB array [r, g, b] in 0-255 range + */ +function colorToRgb(color: string): [number, number, number] { + const c = new ColorJS(String(color)).to("srgb"); + return [ + Math.round((c.coords[0] ?? 0) * 255), + Math.round((c.coords[1] ?? 0) * 255), + Math.round((c.coords[2] ?? 0) * 255), + ]; +} + +/** + * Generate a complete theme with contrast-based color scales + * + * This is the main orchestrator function that replaces the Theme class. + * It generates all color scales based on the provided configuration. + */ +export function generateTheme(options: GenerateThemeOptions): GeneratedTheme { + const { + colors, + backgroundColor, + lightness: inputLightness, + saturation = 100, + contrast = 1, + formula = "wcag2", + } = options; + + // Parse background input + const bgInput = parseBackgroundInput(backgroundColor); + + // Generate background scale + const backgroundScale = generateBackgroundScale({ + colorKeys: bgInput.colorKeys, + colorspace: bgInput.colorspace, + }); + + // Determine lightness + let finalLightness: number; + if (inputLightness !== undefined) { + finalLightness = inputLightness; + } else if (typeof backgroundColor === "string") { + finalLightness = calculateLightnessFromColor(backgroundColor); + } else { + finalLightness = 100; // Default to white + } + + // Get background color value at the target lightness + const bgValue = backgroundScale[finalLightness] ?? "#ffffff"; + const bgRgb = colorToRgb(bgValue); + const baseV = finalLightness / 100; + + // Generate colors for each input color + const generatedColors: Record = {}; + + for (const color of colors) { + // Apply saturation modification + const modifiedKeys = applyColorSaturation(color.colorKeys, saturation); + + // Get ratio values and names + let ratioValues: number[]; + let swatchNames: string[] | undefined; + + if (Array.isArray(color.ratios)) { + ratioValues = color.ratios; + } else { + swatchNames = Object.keys(color.ratios); + ratioValues = Object.values(color.ratios); + } + + // Modify target ratios based on contrast multiplier + const adjustedRatios = ratioValues.map((ratio) => + multiplyRatios(+ratio, contrast), + ); + + // Search for contrast-matching colors + const contrastColors = searchContrastColors({ + colorKeys: modifiedKeys, + colorspace: color.colorspace, + smooth: color.smooth, + ratios: adjustedRatios, + backgroundRgb: bgRgb, + backgroundLightness: baseV, + formula, + }); + + // Build output array + const colorValues: ContrastColorValue[] = []; + for (let i = 0; i < contrastColors.length; i++) { + let name: string; + if (!swatchNames) { + const rVal = ratioName( + Array.isArray(color.ratios) + ? color.ratios + : Object.values(color.ratios), + formula, + )[i] ?? 0; + name = color.name.concat(String(rVal)).replace(/\s+/g, ""); + } else { + name = swatchNames[i] ?? ""; + } + + colorValues.push({ + name, + contrast: adjustedRatios[i] ?? 0, + value: contrastColors[i] ?? "", + }); + } + + generatedColors[color.name] = colorValues; + } + + return { + background: bgValue, + backgroundScale, + colors: generatedColors, + }; +} diff --git a/packages/color-engine/src/algorithms/leonardo/index.ts b/packages/colors/src/algorithms/leonardo/index.ts similarity index 81% rename from packages/color-engine/src/algorithms/leonardo/index.ts rename to packages/colors/src/algorithms/leonardo/index.ts index 220db91d7..8322d548f 100644 --- a/packages/color-engine/src/algorithms/leonardo/index.ts +++ b/packages/colors/src/algorithms/leonardo/index.ts @@ -27,10 +27,8 @@ * // } */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - import ColorJS from "colorjs.io"; -import { Theme } from "./theme"; +import { generateTheme } from "./generate"; import type { LeonardoColorspace, ContrastFormula } from "../../types"; import { SCALE_STEPS } from "../../types"; @@ -92,8 +90,8 @@ export function createTheme(input: CreateThemeInput): CreateThemeOutput { formula = "wcag2", } = input; - // Create the Theme instance (internal) - const theme = new Theme({ + // Use the pure functional implementation + const generated = generateTheme({ colors: colors.map((c) => ({ name: c.name, colorKeys: c.colorKeys, @@ -112,42 +110,32 @@ export function createTheme(input: CreateThemeInput): CreateThemeOutput { lightness, contrast, saturation, - output: "HEX", // Internal processing in HEX, convert to HSL at output formula, }); - // Get contrast colors from the theme - const contrastColors = theme.contrastColors; - - // Build the output + // Build the output with HSL conversion const result: CreateThemeOutput = { - background: toHslString(theme.backgroundColorValue), + background: toHslString(generated.background), colors: {}, }; // Process each color scale - for (const item of contrastColors) { - if ("background" in item) { - // Skip the background entry - continue; - } - + for (const [colorName, values] of Object.entries(generated.colors)) { const colorScale: Record = {}; - const colorItem = item as { name: string; values: Array<{ name: string; value: string }> }; // Map values to SCALE_STEPS by index (Leonardo generates 100, 200... but we want 50, 100, 200...) - for (let i = 0; i < colorItem.values.length; i++) { - const value = colorItem.values[i]; + for (let i = 0; i < values.length; i++) { + const value = values[i]; if (!value) continue; // Use SCALE_STEPS if we have 11 values (standard scale), otherwise use Leonardo's naming - if (colorItem.values.length === SCALE_STEPS.length) { + if (values.length === SCALE_STEPS.length) { const step = SCALE_STEPS[i]; if (step) { colorScale[step] = toHslString(value.value); } } else { - // For non-standard ratios, extract step from name or use index + // For non-standard ratios, extract step from name or use the name directly const stepMatch = value.name.match(/(\d+)$/); if (stepMatch && stepMatch[1]) { colorScale[stepMatch[1]] = toHslString(value.value); @@ -157,7 +145,7 @@ export function createTheme(input: CreateThemeInput): CreateThemeOutput { } } - result.colors[colorItem.name] = colorScale; + result.colors[colorName] = colorScale; } return result; diff --git a/packages/color-engine/src/algorithms/leonardo/utils.ts b/packages/colors/src/algorithms/leonardo/utils.ts similarity index 100% rename from packages/color-engine/src/algorithms/leonardo/utils.ts rename to packages/colors/src/algorithms/leonardo/utils.ts diff --git a/packages/color-engine/src/algorithms/material/index.ts b/packages/colors/src/algorithms/material/index.ts similarity index 100% rename from packages/color-engine/src/algorithms/material/index.ts rename to packages/colors/src/algorithms/material/index.ts diff --git a/packages/color-engine/src/index.ts b/packages/colors/src/index.ts similarity index 100% rename from packages/color-engine/src/index.ts rename to packages/colors/src/index.ts diff --git a/packages/color-engine/src/types.ts b/packages/colors/src/types.ts similarity index 100% rename from packages/color-engine/src/types.ts rename to packages/colors/src/types.ts diff --git a/packages/color-engine/src/utils/curve.ts b/packages/colors/src/utils/curve.ts similarity index 100% rename from packages/color-engine/src/utils/curve.ts rename to packages/colors/src/utils/curve.ts diff --git a/packages/colors/tests/__snapshots__/snapshot-baseline.test.ts.snap b/packages/colors/tests/__snapshots__/snapshot-baseline.test.ts.snap new file mode 100644 index 000000000..980e0c66f --- /dev/null +++ b/packages/colors/tests/__snapshots__/snapshot-baseline.test.ts.snap @@ -0,0 +1,545 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Snapshot Baseline > Exact snapshot values > 'background-object' - matches snapshot > background-object 1`] = ` +{ + "background": "hsl(240, 5%, 96%)", + "colors": { + "accent": { + "100": "hsl(238, 81%, 94%)", + "200": "hsl(238, 83%, 91%)", + "300": "hsl(239, 84%, 88%)", + "400": "hsl(239, 84%, 81%)", + "50": "hsl(240, 85%, 97%)", + "500": "hsl(239, 84%, 73%)", + "600": "hsl(239, 71%, 63%)", + "700": "hsl(239, 46%, 52%)", + "800": "hsl(239, 41%, 42%)", + "900": "hsl(239, 41%, 27%)", + "950": "hsl(238, 41%, 18%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'basic-single-color-dark' - matches snapshot > basic-single-color-dark 1`] = ` +{ + "background": "hsl(0, 0%, 0%)", + "colors": { + "accent": { + "100": "hsl(238, 42%, 12%)", + "200": "hsl(238, 41%, 19%)", + "300": "hsl(239, 42%, 25%)", + "400": "hsl(238, 42%, 36%)", + "50": "hsl(240, 43%, 1%)", + "500": "hsl(238, 42%, 50%)", + "600": "hsl(239, 78%, 65%)", + "700": "hsl(239, 83%, 72%)", + "800": "hsl(238, 83%, 77%)", + "900": "hsl(239, 83%, 86%)", + "950": "hsl(240, 82%, 91%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'basic-single-color-light' - matches snapshot > basic-single-color-light 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(240, 80%, 96%)", + "200": "hsl(238, 83%, 93%)", + "300": "hsl(239, 85%, 90%)", + "400": "hsl(238, 84%, 83%)", + "50": "hsl(240, 100%, 100%)", + "500": "hsl(239, 83%, 75%)", + "600": "hsl(239, 82%, 66%)", + "700": "hsl(238, 52%, 55%)", + "800": "hsl(239, 42%, 45%)", + "900": "hsl(239, 41%, 31%)", + "950": "hsl(240, 41%, 22%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'colorspace-CAM02' - matches snapshot > colorspace-CAM02 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(197, 60%, 93%)", + "200": "hsl(201, 66%, 87%)", + "300": "hsl(205, 73%, 82%)", + "400": "hsl(212, 82%, 75%)", + "50": "hsl(180, 50%, 99%)", + "500": "hsl(223, 86%, 69%)", + "600": "hsl(240, 82%, 67%)", + "700": "hsl(257, 42%, 52%)", + "800": "hsl(272, 36%, 39%)", + "900": "hsl(298, 28%, 23%)", + "950": "hsl(315, 28%, 17%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'colorspace-HSLuv' - matches snapshot > colorspace-HSLuv 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(340, 9%, 94%)", + "200": "hsl(338, 14%, 89%)", + "300": "hsl(325, 23%, 84%)", + "400": "hsl(306, 41%, 75%)", + "50": "hsl(0, 0%, 100%)", + "500": "hsl(275, 60%, 68%)", + "600": "hsl(240, 82%, 67%)", + "700": "hsl(270, 56%, 51%)", + "800": "hsl(287, 40%, 37%)", + "900": "hsl(311, 28%, 24%)", + "950": "hsl(325, 22%, 17%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'colorspace-LAB' - matches snapshot > colorspace-LAB 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(250, 90%, 96%)", + "200": "hsl(249, 89%, 93%)", + "300": "hsl(249, 89%, 90%)", + "400": "hsl(247, 88%, 83%)", + "50": "hsl(240, 100%, 100%)", + "500": "hsl(244, 84%, 75%)", + "600": "hsl(239, 82%, 66%)", + "700": "hsl(239, 50%, 55%)", + "800": "hsl(241, 39%, 45%)", + "900": "hsl(242, 36%, 29%)", + "950": "hsl(243, 33%, 21%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'colorspace-LCH' - matches snapshot > colorspace-LCH 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(250, 90%, 96%)", + "200": "hsl(249, 89%, 93%)", + "300": "hsl(249, 89%, 90%)", + "400": "hsl(247, 88%, 83%)", + "50": "hsl(240, 100%, 100%)", + "500": "hsl(244, 84%, 75%)", + "600": "hsl(239, 82%, 66%)", + "700": "hsl(239, 50%, 55%)", + "800": "hsl(241, 39%, 45%)", + "900": "hsl(242, 36%, 29%)", + "950": "hsl(243, 33%, 21%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'colorspace-OKLCH' - matches snapshot > colorspace-OKLCH 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(229, 100%, 96%)", + "200": "hsl(230, 100%, 93%)", + "300": "hsl(231, 100%, 89%)", + "400": "hsl(232, 96%, 82%)", + "50": "hsl(240, 100%, 100%)", + "500": "hsl(235, 88%, 74%)", + "600": "hsl(239, 82%, 66%)", + "700": "hsl(239, 54%, 56%)", + "800": "hsl(239, 43%, 46%)", + "900": "hsl(238, 45%, 32%)", + "950": "hsl(239, 47%, 23%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'combined-options' - matches snapshot > combined-options 1`] = ` +{ + "background": "hsl(0, 0%, 96%)", + "colors": { + "accent": { + "100": "hsl(231, 89%, 93%)", + "200": "hsl(231, 85%, 90%)", + "300": "hsl(231, 84%, 85%)", + "400": "hsl(233, 80%, 78%)", + "50": "hsl(226, 87%, 97%)", + "500": "hsl(235, 75%, 69%)", + "600": "hsl(237, 55%, 58%)", + "700": "hsl(237, 40%, 48%)", + "800": "hsl(237, 42%, 38%)", + "900": "hsl(237, 45%, 24%)", + "950": "hsl(237, 50%, 14%)", + }, + "success": { + "100": "hsl(129, 48%, 86%)", + "200": "hsl(131, 49%, 79%)", + "300": "hsl(132, 49%, 70%)", + "400": "hsl(138, 52%, 50%)", + "50": "hsl(128, 52%, 94%)", + "500": "hsl(139, 52%, 40%)", + "600": "hsl(138, 53%, 31%)", + "700": "hsl(138, 55%, 25%)", + "800": "hsl(138, 59%, 20%)", + "900": "hsl(138, 66%, 12%)", + "950": "hsl(134, 81%, 6%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'contrast-0.8' - matches snapshot > contrast-0.8 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(240, 87%, 97%)", + "200": "hsl(238, 86%, 94%)", + "300": "hsl(238, 82%, 91%)", + "400": "hsl(238, 84%, 85%)", + "50": "hsl(240, 100%, 100%)", + "500": "hsl(239, 84%, 77%)", + "600": "hsl(239, 83%, 70%)", + "700": "hsl(239, 69%, 62%)", + "800": "hsl(239, 45%, 52%)", + "900": "hsl(239, 42%, 38%)", + "950": "hsl(239, 42%, 30%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'contrast-1.2' - matches snapshot > contrast-1.2 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(240, 82%, 96%)", + "200": "hsl(238, 85%, 92%)", + "300": "hsl(239, 84%, 88%)", + "400": "hsl(239, 84%, 81%)", + "50": "hsl(240, 100%, 100%)", + "500": "hsl(238, 83%, 72%)", + "600": "hsl(239, 65%, 61%)", + "700": "hsl(238, 42%, 50%)", + "800": "hsl(239, 42%, 40%)", + "900": "hsl(239, 42%, 24%)", + "950": "hsl(238, 41%, 13%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'formula-wcag3' - matches snapshot > formula-wcag3 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(240, 83%, 93%)", + "200": "hsl(238, 82%, 87%)", + "300": "hsl(239, 84%, 80%)", + "400": "hsl(238, 84%, 73%)", + "500": "hsl(239, 66%, 61%)", + "600": "hsl(239, 42%, 42%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'gray-color' - matches snapshot > gray-color 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "neutral": { + "100": "hsl(220, 10%, 94%)", + "200": "hsl(225, 7%, 89%)", + "300": "hsl(220, 7%, 83%)", + "400": "hsl(218, 8%, 72%)", + "50": "hsl(0, 0%, 100%)", + "500": "hsl(221, 8%, 60%)", + "600": "hsl(219, 8%, 48%)", + "700": "hsl(218, 9%, 40%)", + "800": "hsl(223, 8%, 33%)", + "900": "hsl(222, 9%, 22%)", + "950": "hsl(214, 9%, 15%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'lightness-boundary-1' - matches snapshot > lightness-boundary-1 1`] = ` +{ + "background": "hsl(0, 0%, 1%)", + "colors": { + "accent": { + "100": "hsl(238, 42%, 13%)", + "200": "hsl(239, 41%, 19%)", + "300": "hsl(239, 42%, 26%)", + "400": "hsl(238, 42%, 36%)", + "50": "hsl(240, 38%, 3%)", + "500": "hsl(239, 42%, 50%)", + "600": "hsl(239, 80%, 65%)", + "700": "hsl(239, 83%, 72%)", + "800": "hsl(238, 84%, 77%)", + "900": "hsl(238, 83%, 86%)", + "950": "hsl(238, 81%, 92%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'lightness-boundary-99' - matches snapshot > lightness-boundary-99 1`] = ` +{ + "background": "hsl(0, 0%, 98%)", + "colors": { + "accent": { + "100": "hsl(237, 84%, 95%)", + "200": "hsl(238, 85%, 92%)", + "300": "hsl(239, 83%, 89%)", + "400": "hsl(238, 84%, 82%)", + "50": "hsl(240, 71%, 99%)", + "500": "hsl(239, 83%, 74%)", + "600": "hsl(239, 78%, 65%)", + "700": "hsl(239, 49%, 54%)", + "800": "hsl(239, 41%, 44%)", + "900": "hsl(239, 41%, 29%)", + "950": "hsl(240, 41%, 21%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'mid-lightness-50' - matches snapshot > mid-lightness-50 1`] = ` +{ + "background": "hsl(222, 8%, 48%)", + "colors": { + "accent": { + "100": "hsl(239, 63%, 60%)", + "200": "hsl(238, 53%, 56%)", + "300": "hsl(238, 43%, 51%)", + "400": "hsl(239, 42%, 40%)", + "50": "hsl(239, 80%, 65%)", + "500": "hsl(239, 42%, 26%)", + "600": "hsl(228, 45%, 2%)", + "700": "hsl(0, 0%, 0%)", + "800": "hsl(0, 0%, 0%)", + "900": "hsl(0, 0%, 0%)", + "950": "hsl(0, 0%, 0%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'multiple-colors' - matches snapshot > multiple-colors 1`] = ` +{ + "background": "hsl(0, 0%, 96%)", + "colors": { + "accent": { + "100": "hsl(238, 81%, 94%)", + "200": "hsl(238, 83%, 91%)", + "300": "hsl(239, 84%, 88%)", + "400": "hsl(239, 84%, 81%)", + "50": "hsl(240, 85%, 97%)", + "500": "hsl(239, 84%, 73%)", + "600": "hsl(239, 71%, 63%)", + "700": "hsl(239, 46%, 52%)", + "800": "hsl(239, 41%, 42%)", + "900": "hsl(239, 41%, 27%)", + "950": "hsl(238, 41%, 18%)", + }, + "danger": { + "100": "hsl(0, 83%, 93%)", + "200": "hsl(0, 85%, 89%)", + "300": "hsl(0, 84%, 85%)", + "400": "hsl(0, 84%, 77%)", + "50": "hsl(0, 87%, 97%)", + "500": "hsl(0, 84%, 65%)", + "600": "hsl(0, 60%, 52%)", + "700": "hsl(0, 55%, 43%)", + "800": "hsl(0, 56%, 35%)", + "900": "hsl(0, 56%, 23%)", + "950": "hsl(0, 56%, 15%)", + }, + "success": { + "100": "hsl(142, 59%, 85%)", + "200": "hsl(142, 59%, 77%)", + "300": "hsl(142, 59%, 68%)", + "400": "hsl(142, 63%, 48%)", + "50": "hsl(143, 56%, 94%)", + "500": "hsl(142, 71%, 37%)", + "600": "hsl(142, 71%, 30%)", + "700": "hsl(142, 70%, 25%)", + "800": "hsl(142, 70%, 20%)", + "900": "hsl(142, 70%, 13%)", + "950": "hsl(143, 72%, 8%)", + }, + "warning": { + "100": "hsl(46, 85%, 82%)", + "200": "hsl(45, 85%, 71%)", + "300": "hsl(46, 84%, 60%)", + "400": "hsl(45, 93%, 45%)", + "50": "hsl(46, 85%, 92%)", + "500": "hsl(45, 93%, 36%)", + "600": "hsl(45, 93%, 29%)", + "700": "hsl(45, 93%, 24%)", + "800": "hsl(45, 94%, 19%)", + "900": "hsl(46, 94%, 12%)", + "950": "hsl(46, 95%, 8%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'multiple-key-colors' - matches snapshot > multiple-key-colors 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "gradient": { + "100": "hsl(24, 94%, 93%)", + "200": "hsl(25, 94%, 87%)", + "300": "hsl(24, 96%, 82%)", + "400": "hsl(25, 95%, 69%)", + "50": "hsl(30, 100%, 99%)", + "500": "hsl(16, 91%, 59%)", + "600": "hsl(268, 57%, 59%)", + "700": "hsl(238, 52%, 55%)", + "800": "hsl(239, 42%, 45%)", + "900": "hsl(239, 41%, 31%)", + "950": "hsl(240, 41%, 22%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'named-ratios' - matches snapshot > named-ratios 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "contrast": "hsl(238, 42%, 50%)", + "default": "hsl(239, 83%, 75%)", + "strong": "hsl(239, 82%, 66%)", + "subtle": "hsl(240, 80%, 96%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'near-dark-10' - matches snapshot > near-dark-10 1`] = ` +{ + "background": "hsl(216, 9%, 11%)", + "colors": { + "accent": { + "100": "hsl(239, 42%, 22%)", + "200": "hsl(239, 42%, 27%)", + "300": "hsl(238, 42%, 32%)", + "400": "hsl(239, 42%, 43%)", + "50": "hsl(238, 42%, 15%)", + "500": "hsl(239, 56%, 57%)", + "600": "hsl(239, 83%, 70%)", + "700": "hsl(239, 84%, 75%)", + "800": "hsl(238, 83%, 81%)", + "900": "hsl(240, 83%, 91%)", + "950": "hsl(236, 80%, 96%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'saturated-color' - matches snapshot > saturated-color 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(0, 100%, 96%)", + "200": "hsl(0, 100%, 92%)", + "300": "hsl(0, 100%, 88%)", + "400": "hsl(0, 100%, 81%)", + "50": "hsl(0, 100%, 100%)", + "500": "hsl(0, 100%, 68%)", + "600": "hsl(0, 100%, 47%)", + "700": "hsl(0, 100%, 40%)", + "800": "hsl(0, 100%, 33%)", + "900": "hsl(0, 100%, 23%)", + "950": "hsl(0, 100%, 17%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'saturation-30' - matches snapshot > saturation-30 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(230, 21%, 95%)", + "200": "hsl(229, 21%, 90%)", + "300": "hsl(229, 20%, 84%)", + "400": "hsl(230, 19%, 75%)", + "50": "hsl(0, 0%, 100%)", + "500": "hsl(232, 20%, 63%)", + "600": "hsl(230, 18%, 52%)", + "700": "hsl(232, 17%, 43%)", + "800": "hsl(232, 17%, 35%)", + "900": "hsl(231, 17%, 24%)", + "950": "hsl(232, 17%, 17%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'saturation-80' - matches snapshot > saturation-80 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(236, 64%, 96%)", + "200": "hsl(238, 60%, 92%)", + "300": "hsl(237, 61%, 88%)", + "400": "hsl(236, 62%, 81%)", + "50": "hsl(240, 100%, 100%)", + "500": "hsl(235, 61%, 71%)", + "600": "hsl(236, 58%, 62%)", + "700": "hsl(236, 38%, 52%)", + "800": "hsl(235, 36%, 42%)", + "900": "hsl(235, 36%, 28%)", + "950": "hsl(235, 36%, 20%)", + }, + }, +} +`; + +exports[`Snapshot Baseline > Exact snapshot values > 'smooth-interpolation' - matches snapshot > smooth-interpolation 1`] = ` +{ + "background": "hsl(0, 0%, 100%)", + "colors": { + "accent": { + "100": "hsl(237, 100%, 96%)", + "200": "hsl(238, 100%, 94%)", + "300": "hsl(238, 100%, 90%)", + "400": "hsl(239, 100%, 84%)", + "50": "hsl(240, 100%, 100%)", + "500": "hsl(239, 100%, 76%)", + "600": "hsl(239, 83%, 66%)", + "700": "hsl(239, 61%, 57%)", + "800": "hsl(239, 48%, 48%)", + "900": "hsl(239, 50%, 33%)", + "950": "hsl(239, 50%, 24%)", + }, + }, +} +`; diff --git a/packages/color-engine/tests/index.test.ts b/packages/colors/tests/index.test.ts similarity index 100% rename from packages/color-engine/tests/index.test.ts rename to packages/colors/tests/index.test.ts diff --git a/packages/color-engine/tests/material.test.ts b/packages/colors/tests/material.test.ts similarity index 100% rename from packages/color-engine/tests/material.test.ts rename to packages/colors/tests/material.test.ts diff --git a/packages/color-engine/tests/migration-baseline.test.ts b/packages/colors/tests/migration-baseline.test.ts similarity index 100% rename from packages/color-engine/tests/migration-baseline.test.ts rename to packages/colors/tests/migration-baseline.test.ts diff --git a/packages/colors/tests/snapshot-baseline.test.ts b/packages/colors/tests/snapshot-baseline.test.ts new file mode 100644 index 000000000..38681fcc0 --- /dev/null +++ b/packages/colors/tests/snapshot-baseline.test.ts @@ -0,0 +1,387 @@ +/** + * Snapshot baseline tests for class-to-functional refactoring + * + * These tests capture exact outputs from the class-based implementation + * to verify byte-for-byte parity after refactoring to pure functions. + * + * DO NOT modify expected values - they represent the baseline truth. + */ + +import { describe, it, expect } from "vitest"; +import { createTheme } from "../src"; +import type { CreateThemeInput } from "../src"; + +// Standard ratios used throughout the library +const STANDARD_RATIOS = [1, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15]; + +// Test cases that cover all major code paths +const SNAPSHOT_CASES: Array<{ name: string; input: CreateThemeInput }> = [ + // Case 1: Basic single color, light theme + { + name: "basic-single-color-light", + input: { + colors: [ + { + name: "accent", + colorKeys: ["#6366f1"], + ratios: STANDARD_RATIOS, + }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 2: Basic single color, dark theme + { + name: "basic-single-color-dark", + input: { + colors: [ + { + name: "accent", + colorKeys: ["#6366f1"], + ratios: STANDARD_RATIOS, + }, + ], + backgroundColor: "#000000", + lightness: 0, + }, + }, + // Case 3: Multiple colors + { + name: "multiple-colors", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS }, + { name: "success", colorKeys: ["#22c55e"], ratios: STANDARD_RATIOS }, + { name: "warning", colorKeys: ["#eab308"], ratios: STANDARD_RATIOS }, + { name: "danger", colorKeys: ["#ef4444"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: "#ffffff", + lightness: 97, + }, + }, + // Case 4: Saturation modification + { + name: "saturation-80", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: "#ffffff", + lightness: 100, + saturation: 80, + }, + }, + // Case 5: Low saturation (desaturated) + { + name: "saturation-30", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: "#ffffff", + lightness: 100, + saturation: 30, + }, + }, + // Case 6: Contrast multiplier > 1 + { + name: "contrast-1.2", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: "#ffffff", + lightness: 100, + contrast: 1.2, + }, + }, + // Case 7: Contrast multiplier < 1 + { + name: "contrast-0.8", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: "#ffffff", + lightness: 100, + contrast: 0.8, + }, + }, + // Case 8: LAB colorspace + { + name: "colorspace-LAB", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS, colorspace: "LAB" }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 9: LCH colorspace + { + name: "colorspace-LCH", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS, colorspace: "LCH" }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 10: OKLCH colorspace + { + name: "colorspace-OKLCH", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS, colorspace: "OKLCH" }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 11: CAM02 colorspace + { + name: "colorspace-CAM02", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS, colorspace: "CAM02" }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 12: HSLuv colorspace + { + name: "colorspace-HSLuv", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS, colorspace: "HSLuv" }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 13: WCAG3 formula + { + name: "formula-wcag3", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: [15, 30, 45, 60, 75, 90] }, + ], + backgroundColor: "#ffffff", + lightness: 100, + formula: "wcag3", + }, + }, + // Case 14: Smooth interpolation + { + name: "smooth-interpolation", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS, smooth: true }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 15: Object backgroundColor + { + name: "background-object", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: { + name: "neutral", + colorKeys: ["#6b7280"], + }, + lightness: 97, + }, + }, + // Case 16: Named ratios + { + name: "named-ratios", + input: { + colors: [ + { + name: "accent", + colorKeys: ["#6366f1"], + ratios: { + subtle: 1.15, + default: 3, + strong: 4.5, + contrast: 7, + }, + }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 17: Multiple key colors (gradient) + { + name: "multiple-key-colors", + input: { + colors: [ + { + name: "gradient", + colorKeys: ["#6366f1", "#ec4899", "#f97316"], + ratios: STANDARD_RATIOS, + }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 18: Mid-lightness theme + { + name: "mid-lightness-50", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: { + name: "neutral", + colorKeys: ["#6b7280"], + }, + lightness: 50, + }, + }, + // Case 19: Near-dark theme + { + name: "near-dark-10", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: { + name: "neutral", + colorKeys: ["#6b7280"], + }, + lightness: 10, + }, + }, + // Case 20: Combined options (saturation + contrast + colorspace) + { + name: "combined-options", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS, colorspace: "OKLCH" }, + { name: "success", colorKeys: ["#22c55e"], ratios: STANDARD_RATIOS, colorspace: "OKLCH" }, + ], + backgroundColor: "#ffffff", + lightness: 97, + saturation: 90, + contrast: 1.1, + }, + }, + // Case 21: Gray/neutral color + { + name: "gray-color", + input: { + colors: [ + { name: "neutral", colorKeys: ["#6b7280"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 22: Very saturated color + { + name: "saturated-color", + input: { + colors: [ + { name: "accent", colorKeys: ["#ff0000"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: "#ffffff", + lightness: 100, + }, + }, + // Case 23: Edge case - lightness at boundary + { + name: "lightness-boundary-1", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: { + name: "neutral", + colorKeys: ["#6b7280"], + }, + lightness: 1, + }, + }, + // Case 24: Edge case - lightness at 99 + { + name: "lightness-boundary-99", + input: { + colors: [ + { name: "accent", colorKeys: ["#6366f1"], ratios: STANDARD_RATIOS }, + ], + backgroundColor: { + name: "neutral", + colorKeys: ["#6b7280"], + }, + lightness: 99, + }, + }, +]; + +describe("Snapshot Baseline", () => { + describe("Output structure validation", () => { + it.each(SNAPSHOT_CASES)("$name - has valid structure", ({ input }) => { + const result = createTheme(input); + expect(result).toBeDefined(); + expect(result.background).toBeDefined(); + expect(result.colors).toBeDefined(); + + // Verify all colors are present + for (const color of input.colors) { + expect(result.colors[color.name]).toBeDefined(); + } + }); + + it.each(SNAPSHOT_CASES)("$name - has correct number of steps", ({ input }) => { + const result = createTheme(input); + + for (const color of input.colors) { + const scale = result.colors[color.name]; + const expectedSteps = Array.isArray(color.ratios) + ? color.ratios.length + : Object.keys(color.ratios).length; + expect(Object.keys(scale ?? {}).length).toBe(expectedSteps); + } + }); + + it.each(SNAPSHOT_CASES)("$name - outputs HSL format", ({ input }) => { + const result = createTheme(input); + + // Check background + expect(result.background).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/); + + // Check all color values + for (const scale of Object.values(result.colors ?? {})) { + for (const value of Object.values(scale)) { + expect(value).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/); + } + } + }); + }); + + describe("Determinism validation", () => { + it.each(SNAPSHOT_CASES)("$name - produces identical output on repeated calls", ({ input }) => { + const result1 = createTheme(input); + const result2 = createTheme(input); + expect(result1).toEqual(result2); + }); + }); + + describe("Exact snapshot values", () => { + it.each(SNAPSHOT_CASES)("$name - matches snapshot", ({ name, input }) => { + const result = createTheme(input); + expect(result).toMatchSnapshot(name); + }); + }); +}); diff --git a/packages/color-engine/tsconfig.json b/packages/colors/tsconfig.json similarity index 81% rename from packages/color-engine/tsconfig.json rename to packages/colors/tsconfig.json index bb0db6bc4..ce20d6c69 100644 --- a/packages/color-engine/tsconfig.json +++ b/packages/colors/tsconfig.json @@ -4,7 +4,7 @@ "lib": ["ES2022"], "baseUrl": ".", "paths": { - "@dotui/color-engine/*": ["./src/*"] + "@dotui/colors/*": ["./src/*"] } }, "include": ["src"], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8068930ef..9ad6bf654 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,7 +102,7 @@ importers: specifier: ^5.8.3 version: 5.9.3 - packages/color-engine: + packages/colors: dependencies: '@material/material-color-utilities': specifier: ^0.3.0 @@ -385,7 +385,7 @@ importers: version: link:../packages/auth '@dotui/colors': specifier: workspace:* - version: link:../packages/color-engine + version: link:../packages/colors '@dotui/db': specifier: workspace:* version: link:../packages/db From 8f1f67775b652f3ff83cf9ebc4c103353dcfa9f1 Mon Sep 17 00:00:00 2001 From: mehdibha Date: Mon, 15 Dec 2025 05:54:36 +0100 Subject: [PATCH 05/48] update colors playground --- www/app/(internal)/internal/page.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/www/app/(internal)/internal/page.tsx b/www/app/(internal)/internal/page.tsx index e76cdf843..69413f321 100644 --- a/www/app/(internal)/internal/page.tsx +++ b/www/app/(internal)/internal/page.tsx @@ -24,7 +24,11 @@ import { ToggleButton } from "@dotui/registry/ui/toggle-button"; import { ToggleButtonGroup } from "@dotui/registry/ui/toggle-button-group"; // Default ratios for 11 steps (50-950) -const DEFAULT_RATIOS = [1, 1.15, 1.3, 1.5, 2, 3, 4.5, 6, 8, 12, 15]; +// WCAG2: contrast ratios 1:1 to 21:1 (max theoretical is 21:1) +const WCAG2_RATIOS = [1, 1.15, 1.3, 1.5, 2, 3, 4.5, 7, 11, 16, 21]; +// WCAG3/APCA: lightness contrast values (Lc) - max is ~108 for black/white +// Lc 15 ā‰ˆ 1.5:1, Lc 30 ā‰ˆ 2:1, Lc 45 ā‰ˆ 3:1, Lc 60 ā‰ˆ 4.5:1, Lc 75 ā‰ˆ 7:1, Lc 90 ā‰ˆ 11:1 +const WCAG3_RATIOS = [0, 7, 15, 25, 40, 55, 70, 80, 90, 100, 180]; // Material tones for light/dark mode const LIGHT_TONES = [99, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10]; // Default: light to dark @@ -134,11 +138,14 @@ export default function InternalPage() { // Generate Adobe Leonardo theme const adobeTheme = React.useMemo(() => { try { + // Use appropriate ratios based on formula + const ratios = formula === "wcag3" ? WCAG3_RATIOS : WCAG2_RATIOS; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const neutralBg = new AdobeBackgroundColor({ name: "neutral", colorKeys: [hexColors.neutral], - ratios: DEFAULT_RATIOS, + ratios, colorspace, } as any); @@ -149,7 +156,7 @@ export default function InternalPage() { new AdobeColor({ name, colorKeys: [hexColors[name]], - ratios: DEFAULT_RATIOS, + ratios, colorspace, smooth, } as any), @@ -204,12 +211,15 @@ export default function InternalPage() { // Generate Our Leonardo theme const ourTheme = React.useMemo(() => { try { + // Use appropriate ratios based on formula + const ratios = formula === "wcag3" ? WCAG3_RATIOS : WCAG2_RATIOS; + // Include all colors including neutral const theme = createTheme({ colors: SCALE_NAMES.map((name) => ({ name, colorKeys: [hexColors[name]], - ratios: DEFAULT_RATIOS, + ratios, colorspace, smooth, })), From 5dad39122a888d6d7e49788640ff1812e4b0c659 Mon Sep 17 00:00:00 2001 From: mehdibha Date: Mon, 15 Dec 2025 12:36:51 +0100 Subject: [PATCH 06/48] remove spectrum --- spectrum-design-data | 1 - 1 file changed, 1 deletion(-) delete mode 160000 spectrum-design-data diff --git a/spectrum-design-data b/spectrum-design-data deleted file mode 160000 index e7141c702..000000000 --- a/spectrum-design-data +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e7141c702f1ff93ba8dfa7a32a8a47fcf9e63986 From 52ba243d635c218ad852d4da937132e50a40427f Mon Sep 17 00:00:00 2001 From: mehdibha Date: Mon, 15 Dec 2025 14:23:34 +0100 Subject: [PATCH 07/48] refactor registry wip --- packages/core/package.json | 22 + .../styles.css => core/src/index.ts} | 0 .../src/utils/create-dynamic-component.tsx | 10 + packages/core/tsconfig.json | 14 + packages/registry/biome.json | 5 + packages/registry/package.json | 159 +- packages/registry/scripts/build-registry.ts | 289 +- .../registry/src/__generated__/blocks.tsx | 24 + packages/registry/src/__generated__/demos.tsx | 1636 +++++++++++ packages/registry/src/__generated__/icons.tsx | 237 ++ packages/registry/src/base/base.css | 25 + packages/registry/src/base/base.ts | 25 + packages/registry/src/base/bg-patterns.css | 0 .../registry.ts => base/bg-patterns.ts} | 0 packages/registry/src/base/colors.css | 147 + packages/registry/src/base/fonts.css | 4 + .../src/{fonts/registry.ts => base/fonts.ts} | 0 .../registry.ts => base/textures.ts} | 0 packages/registry/src/base/theme.css | 126 + .../{tokens/registry.ts => base/tokens.ts} | 0 packages/registry/src/blocks/__blocks__.tsx | 68 - packages/registry/src/blocks/index.ts | 2 +- .../animation/components/animation.tsx | 2 + .../showcase/cards/components/cards.tsx | 2 + packages/registry/src/constants.ts | 222 -- packages/registry/src/icons/__icons__.tsx | 352 --- packages/registry/src/icons/index.ts | 2 +- packages/registry/src/index.ts | 15 - .../registry/src/lib/focus-styles/basic.css | 0 packages/registry/src/providers.tsx | 60 - packages/registry/src/schemas.test.ts | 31 - packages/registry/src/schemas.ts | 352 --- packages/registry/src/styles.css | 4 + packages/registry/src/types.ts | 11 +- packages/registry/src/ui/__demos__.tsx | 2449 ----------------- packages/registry/src/ui/accordion/index.tsx | 13 +- packages/registry/src/ui/alert/index.tsx | 52 +- packages/registry/src/ui/avatar/index.tsx | 70 +- packages/registry/src/ui/badge/index.tsx | 9 +- .../registry/src/ui/breadcrumbs/index.tsx | 47 +- packages/registry/src/ui/button/index.tsx | 28 +- packages/registry/src/ui/calendar/index.tsx | 81 +- packages/registry/src/ui/card/index.tsx | 80 +- .../registry/src/ui/checkbox-group/index.tsx | 10 +- packages/registry/src/ui/checkbox/index.tsx | 17 +- packages/registry/src/ui/color-area/index.tsx | 9 +- .../registry/src/ui/color-editor/index.tsx | 10 +- .../registry/src/ui/color-field/index.tsx | 9 +- .../registry/src/ui/color-picker/index.tsx | 42 +- .../registry/src/ui/color-slider/index.tsx | 43 +- .../src/ui/color-swatch-picker/index.tsx | 28 +- .../registry/src/ui/color-swatch/index.tsx | 10 +- .../registry/src/ui/color-thumb/index.tsx | 9 +- packages/registry/src/ui/combobox/index.tsx | 65 +- packages/registry/src/ui/command/index.tsx | 54 +- .../src/ui/create-dynamic-component.tsx | 213 -- packages/registry/src/ui/date-field/index.tsx | 11 +- .../registry/src/ui/date-picker/index.tsx | 35 +- packages/registry/src/ui/dialog/index.tsx | 93 +- packages/registry/src/ui/disclosure/basic.css | 3 + packages/registry/src/ui/disclosure/index.tsx | 39 +- packages/registry/src/ui/drawer/basic.css | 3 + packages/registry/src/ui/drawer/index.tsx | 9 +- packages/registry/src/ui/drop-zone/index.tsx | 17 +- packages/registry/src/ui/empty/index.tsx | 70 +- packages/registry/src/ui/field/index.tsx | 91 +- .../registry/src/ui/file-trigger/index.tsx | 10 +- packages/registry/src/ui/group/index.tsx | 9 +- packages/registry/src/ui/input/index.tsx | 65 +- packages/registry/src/ui/kbd/index.tsx | 16 +- packages/registry/src/ui/link/index.tsx | 9 +- packages/registry/src/ui/list-box/index.tsx | 95 +- packages/registry/src/ui/loader/index.tsx | 15 +- packages/registry/src/ui/menu/index.tsx | 75 +- packages/registry/src/ui/modal/index.tsx | 9 +- .../registry/src/ui/number-field/index.tsx | 10 +- packages/registry/src/ui/overlay/index.tsx | 9 +- packages/registry/src/ui/popover/index.tsx | 9 +- .../registry/src/ui/progress-bar/basic.css | 25 + .../registry/src/ui/progress-bar/index.tsx | 48 +- .../registry/src/ui/radio-group/index.tsx | 24 +- .../registry/src/ui/search-field/index.tsx | 9 +- packages/registry/src/ui/select/index.tsx | 103 +- packages/registry/src/ui/separator/index.tsx | 9 +- packages/registry/src/ui/slider/index.tsx | 58 +- packages/registry/src/ui/switch/index.tsx | 30 +- packages/registry/src/ui/table/index.tsx | 98 +- packages/registry/src/ui/tabs/index.tsx | 46 +- packages/registry/src/ui/tag-group/index.tsx | 25 +- packages/registry/src/ui/text-field/index.tsx | 9 +- packages/registry/src/ui/text/index.tsx | 9 +- packages/registry/src/ui/time-field/index.tsx | 9 +- packages/registry/src/ui/toast/index.tsx | 9 +- .../src/ui/toggle-button-group/index.tsx | 10 +- .../registry/src/ui/toggle-button/index.tsx | 18 +- packages/registry/src/ui/tooltip/index.tsx | 17 +- pnpm-lock.yaml | 23 +- 97 files changed, 3232 insertions(+), 5243 deletions(-) create mode 100644 packages/core/package.json rename packages/{registry/src/lib/focus-styles/styles.css => core/src/index.ts} (100%) create mode 100644 packages/core/src/utils/create-dynamic-component.tsx create mode 100644 packages/core/tsconfig.json create mode 100644 packages/registry/biome.json create mode 100644 packages/registry/src/__generated__/blocks.tsx create mode 100644 packages/registry/src/__generated__/demos.tsx create mode 100644 packages/registry/src/__generated__/icons.tsx create mode 100644 packages/registry/src/base/base.css create mode 100644 packages/registry/src/base/base.ts create mode 100644 packages/registry/src/base/bg-patterns.css rename packages/registry/src/{background-patterns/registry.ts => base/bg-patterns.ts} (100%) create mode 100644 packages/registry/src/base/colors.css create mode 100644 packages/registry/src/base/fonts.css rename packages/registry/src/{fonts/registry.ts => base/fonts.ts} (100%) rename packages/registry/src/{textures/registry.ts => base/textures.ts} (100%) create mode 100644 packages/registry/src/base/theme.css rename packages/registry/src/{tokens/registry.ts => base/tokens.ts} (100%) delete mode 100644 packages/registry/src/blocks/__blocks__.tsx delete mode 100644 packages/registry/src/constants.ts delete mode 100644 packages/registry/src/icons/__icons__.tsx delete mode 100644 packages/registry/src/index.ts create mode 100644 packages/registry/src/lib/focus-styles/basic.css delete mode 100644 packages/registry/src/providers.tsx delete mode 100644 packages/registry/src/schemas.test.ts delete mode 100644 packages/registry/src/schemas.ts create mode 100644 packages/registry/src/styles.css delete mode 100644 packages/registry/src/ui/__demos__.tsx delete mode 100644 packages/registry/src/ui/create-dynamic-component.tsx create mode 100644 packages/registry/src/ui/disclosure/basic.css create mode 100644 packages/registry/src/ui/drawer/basic.css create mode 100644 packages/registry/src/ui/progress-bar/basic.css diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 000000000..682c6cb48 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,22 @@ +{ + "name": "@dotui/core", + "private": true, + "version": "0.0.0", + "type": "module", + "license": "MIT", + "exports": { + "./utils/create-dynamic-component": "./src/utils/create-dynamic-component.tsx" + }, + "scripts": { + "clean": "git clean -xdf .cache .turbo dist node_modules", + "typecheck": "tsc --noEmit --emitDeclarationOnly false" + }, + "peerDependencies": { + "react": "^19.2.1", + "react-dom": "^19.2.1" + }, + "devDependencies": { + "@dotui/ts-config": "workspace:*", + "typescript": "^5.8.3" + } +} diff --git a/packages/registry/src/lib/focus-styles/styles.css b/packages/core/src/index.ts similarity index 100% rename from packages/registry/src/lib/focus-styles/styles.css rename to packages/core/src/index.ts diff --git a/packages/core/src/utils/create-dynamic-component.tsx b/packages/core/src/utils/create-dynamic-component.tsx new file mode 100644 index 000000000..8f5f0c469 --- /dev/null +++ b/packages/core/src/utils/create-dynamic-component.tsx @@ -0,0 +1,10 @@ +export const createDynamicComponent = >( + componentName: string, + slotName: string, + DefaultComponent: React.FC, + variants: Record>, +) => { + return (props: Props) => { + return ; + }; +}; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 000000000..690309d49 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@dotui/ts-config/base.json", + "compilerOptions": { + "lib": ["ES2022", "dom", "dom.iterable"], + "jsx": "preserve", + "jsxImportSource": "react", + "baseUrl": ".", + "paths": { + "@dotui/style-system/*": ["./src/*"] + } + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/packages/registry/biome.json b/packages/registry/biome.json new file mode 100644 index 000000000..68761e1a8 --- /dev/null +++ b/packages/registry/biome.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json", + "root": false, + "extends": ["@dotui/biome-config/base", "@dotui/biome-config/react-internal"] +} diff --git a/packages/registry/package.json b/packages/registry/package.json index 7129fe78b..b17d22464 100644 --- a/packages/registry/package.json +++ b/packages/registry/package.json @@ -1,80 +1,83 @@ { - "name": "@dotui/registry", - "private": true, - "version": "0.1.0", - "type": "module", - "main": "src/index.ts", - "module": "src/index.ts", - "types": "src/index.ts", - "license": "MIT", - "exports": { - ".": "./src/index.ts", - "./types": "./src/types.ts", - "./providers": "./src/providers.tsx", - "./background-patterns/registry": "./src/background-patterns/registry.ts", - "./base/registry": "./src/base/registry.ts", - "./blocks": "./src/blocks/index.ts", - "./blocks/registry": "./src/blocks/registry.ts", - "./components/icons/*": "./src/components/icons/*.tsx", - "./fonts/registry": "./src/fonts/registry.ts", - "./hooks/registry": "./src/hooks/registry.ts", - "./hooks/*": "./src/hooks/*.ts", - "./icons/registry": "./src/icons/registry.ts", - "./icons": "./src/icons", - "./lib/registry": "./src/lib/registry.ts", - "./lib/focus-styles.css": "./src/lib/focus-styles/styles.css", - "./lib/utils": "./src/lib/utils/index.ts", - "./textures/registry": "./src/textures/registry.ts", - "./tokens/registry": "./src/tokens/registry.ts", - "./ui/registry": "./src/ui/registry.ts", - "./ui/demos": "./src/ui/__demos__.tsx", - "./ui/*": "./src/ui/*/index.tsx", - "./ui/*/meta": "./src/ui/*/meta.ts", - "./ui/*/playground": "./src/ui/*/demos/playground.tsx", - "./lib/*/meta": "./src/lib/*/meta.ts", - "./schemas": "./src/schemas.ts", - "./constants": "./src/constants.ts" - }, - "scripts": { - "build": "tsx scripts/build-registry.ts", - "clean": "git clean -xdf .cache .turbo dist node_modules", - "test": "vitest run", - "typecheck": "tsc --noEmit --emitDeclarationOnly false" - }, - "peerDependencies": { - "@hookform/resolvers": "^5.2.2", - "@internationalized/date": "^3.10.0", - "@react-stately/utils": "^3.10.8", - "@remixicon/react": "^4.2.0", - "@tanstack/react-form": "^1.23.4", - "clsx": "^2.1.0", - "lucide-react": "^0.546.0", - "motion": "^12.23.22", - "react": "^19.2.1", - "react-aria": "^3.44.0", - "react-aria-components": "^1.13.0", - "react-dom": "^19.2.1", - "react-hook-form": "^7.54.2", - "react-stately": "^3.42.0", - "tailwind-merge": "^3.0.2", - "tailwind-variants": "^3.1.1" - }, - "devDependencies": { - "@dotui/ts-config": "workspace:*", - "@types/culori": "^4.0.1", - "@types/react": "^19.2.2", - "@types/react-dom": "^19.2.1", - "next": "^16.0.5", - "rimraf": "^6.0.1", - "shadcn": "^3.2.1", - "tsx": "^4.19.2", - "typescript": "^5.8.3" - }, - "dependencies": { - "@dotui/style-system": "workspace:*", - "@react-aria/utils": "^3.31.0", - "@react-types/shared": "^3.26.0", - "react-error-boundary": "^6.0.0", - "zod": "^4.0.5" - } + "name": "@dotui/registry", + "private": true, + "version": "0.1.0", + "type": "module", + "main": "src/index.ts", + "module": "src/index.ts", + "types": "src/index.ts", + "license": "MIT", + "exports": { + ".": "./src/index.ts", + "./types": "./src/types.ts", + "./providers": "./src/providers.tsx", + "./base/registry": "./src/base/registry.ts", + "./blocks": "./src/blocks/index.ts", + "./blocks/registry": "./src/blocks/registry.ts", + "./components/icons/*": "./src/components/icons/*.tsx", + "./effects/textures": "./src/effects/textures.ts", + "./effects/patterns": "./src/effects/bg-patterns.ts", + "./fonts/registry": "./src/fonts/registry.ts", + "./hooks/registry": "./src/hooks/registry.ts", + "./hooks/*": "./src/hooks/*.ts", + "./icons/registry": "./src/icons/registry.ts", + "./icons/create-icon": "./src/icons/create-icon.tsx", + "./icons": "./src/__generated__/icons.tsx", + "./lib/registry": "./src/lib/registry.ts", + "./lib/focus-styles.css": "./src/lib/focus-styles/basic.css", + "./lib/utils": "./src/lib/utils/index.ts", + "./ui/registry": "./src/ui/registry.ts", + "./ui/*": "./src/ui/*/index.tsx", + "./ui/*/meta": "./src/ui/*/meta.ts", + "./ui/*/playground": "./src/ui/*/demos/playground.tsx", + "./lib/*/meta": "./src/lib/*/meta.ts", + "./schemas": "./src/schemas.ts", + "./constants": "./src/constants.ts", + "./__generated__/demos": "./src/__generated__/demos.tsx", + "./__generated__/blocks": "./src/__generated__/blocks.tsx", + "./__generated__/icons": "./src/__generated__/icons.tsx" + }, + "scripts": { + "build": "tsx scripts/build-registry.ts", + "clean": "git clean -xdf .cache .turbo dist node_modules", + "test": "vitest run", + "typecheck": "tsc --noEmit --emitDeclarationOnly false" + }, + "peerDependencies": { + "@hookform/resolvers": "^5.2.2", + "@internationalized/date": "^3.10.0", + "@react-stately/utils": "^3.10.8", + "@remixicon/react": "^4.2.0", + "@tanstack/react-form": "^1.23.4", + "clsx": "^2.1.0", + "lucide-react": "^0.546.0", + "motion": "^12.23.22", + "react": "^19.2.1", + "react-aria": "^3.44.0", + "react-aria-components": "^1.13.0", + "react-dom": "^19.2.1", + "react-hook-form": "^7.54.2", + "react-stately": "^3.42.0", + "tailwind-merge": "^3.0.2", + "tailwind-variants": "^3.1.1" + }, + "devDependencies": { + "@dotui/biome-config": "workspace:*", + "@dotui/ts-config": "workspace:*", + "@types/culori": "^4.0.1", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.1", + "next": "^16.0.5", + "rimraf": "^6.0.1", + "shadcn": "^3.2.1", + "tsx": "^4.19.2", + "typescript": "^5.8.3" + }, + "dependencies": { + "@dotui/core": "workspace:*", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.26.0", + "react-error-boundary": "^6.0.0", + "zod": "^4.0.5" + } } diff --git a/packages/registry/scripts/build-registry.ts b/packages/registry/scripts/build-registry.ts index 92400a6c3..27e018dcf 100644 --- a/packages/registry/scripts/build-registry.ts +++ b/packages/registry/scripts/build-registry.ts @@ -2,20 +2,24 @@ import { existsSync, promises as fs } from "node:fs"; import path from "node:path"; import { rimraf } from "rimraf"; -import { registryBlocks } from "@dotui/registry/blocks/registry"; -import { iconLibraries, registryIcons } from "@dotui/registry/icons/registry"; +import { registryBlocks } from "../src/blocks/registry.js"; +import { iconLibraries, registryIcons } from "../src/icons/registry.js"; + +const GENERATED_DIR = path.join(process.cwd(), "src/__generated__"); + +async function ensureGeneratedDir() { + await rimraf(GENERATED_DIR); + await fs.mkdir(GENERATED_DIR, { recursive: true }); +} async function buildBlocks() { - const TARGET_PATH = path.join(process.cwd(), "src/blocks/__blocks__.tsx"); - await rimraf(TARGET_PATH); + const targetPath = path.join(GENERATED_DIR, "blocks.tsx"); - let index = `/* eslint-disable @typescript-eslint/ban-ts-comment */ -// @ts-nocheck -// This file is autogenerated by scripts/build-registry.ts -// Do not edit this file directly. -import * as React from "react" + let content = `// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build" to regenerate +import * as React from "react"; -export const Index: Record< +export const BlocksIndex: Record< string, { files: string[]; @@ -24,78 +28,76 @@ export const Index: Record< > = { `; - for (const block of registryBlocks) { - const blockFiles = - block.files?.map((file) => { - const filePath = typeof file === "string" ? file : file.path; - return filePath; - }) || []; - - let componentPath = `@dotui/registry/blocks/${block.name}`; - - if (block.files && block.files.length > 0) { - const files = block.files.map((file) => - typeof file === "string" ? { type: "registry:page", path: file } : file, - ); - if (files?.length && files[0]) { - const firstFilePath = files[0].path.replace(/\.tsx?$/, ""); - componentPath = `@dotui/registry/${firstFilePath}`; - } - } - - index += ` - "${block.name}": { + for (const block of registryBlocks) { + const blockFiles = + block.files?.map((file) => + typeof file === "string" ? file : file.path, + ) || []; + + let componentPath = `@dotui/registry/blocks/${block.name}`; + + if (block.files && block.files.length > 0) { + const files = block.files.map((file) => + typeof file === "string" ? { type: "registry:page", path: file } : file, + ); + if (files[0]) { + const firstFilePath = files[0].path.replace(/\.tsx?$/, ""); + componentPath = `@dotui/registry/${firstFilePath}`; + } + } + + content += ` "${block.name}": { files: ${JSON.stringify(blockFiles)}, - component: React.lazy(async () => { - const mod = await import("${componentPath}") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name - return { default: mod.default || mod[exportName] } - }) - },`; - } + component: React.lazy(() => import("${componentPath}")), + }, +`; + } - index += ` -} + content += `}; `; - await fs.writeFile(TARGET_PATH, index, "utf8"); + await fs.writeFile(targetPath, content, "utf8"); + console.log(" āœ“ blocks.tsx"); } async function processDirectory( - dirPath: string, - relativePath: string, - index: string[], + dirPath: string, + relativePath: string, + entries: string[], ): Promise { - const entries = await fs.readdir(dirPath, { withFileTypes: true }); + const dirEntries = await fs.readdir(dirPath, { withFileTypes: true }); + + for (const entry of dirEntries) { + if (entry.isFile() && entry.name.endsWith(".tsx")) { + if (entry.name === "playground.tsx") continue; - for (const entry of entries) { - if (entry.isFile() && entry.name.endsWith(".tsx")) { - if (entry.name === "playground.tsx") continue; - const demoName = `${relativePath}/${entry.name.replace(".tsx", "")}`; - const demoPath = `@dotui/registry/ui/${relativePath}/${entry.name.replace(".tsx", "")}`; - const filePath = `ui/${relativePath}/${entry.name}`; + const demoName = entry.name.replace(".tsx", ""); + const key = `${relativePath}/${demoName}`; + const importPath = `@dotui/registry/ui/${relativePath}/${demoName}`; + const filePath = `ui/${relativePath}/${entry.name}`; - index.push(` - "${demoName}": { + entries.push(` "${key}": { files: ["${filePath}"], - component: React.lazy(() => import("${demoPath}")), - },`); - } else if (entry.isDirectory()) { - const subDirPath = path.join(dirPath, entry.name); - const subRelativePath = `${relativePath}/${entry.name}`; - await processDirectory(subDirPath, subRelativePath, index); - } - } + component: React.lazy(() => import("${importPath}")), + }, +`); + } else if (entry.isDirectory()) { + const subDirPath = path.join(dirPath, entry.name); + const subRelativePath = `${relativePath}/${entry.name}`; + await processDirectory(subDirPath, subRelativePath, entries); + } + } } async function buildDemos() { - const targetPath = path.join(process.cwd(), "src/ui/__demos__.tsx"); + const targetPath = path.join(GENERATED_DIR, "demos.tsx"); + const sourcePath = path.join(process.cwd(), "src/ui"); - let index = `// This file is autogenerated by scripts/build-registry.ts -// Do not edit this file directly. -import * as React from "react" + let content = `// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build" to regenerate +import * as React from "react"; -export const Index: Record< +export const DemosIndex: Record< string, { files: string[]; @@ -104,100 +106,117 @@ export const Index: Record< > = { `; - const sourcePath = path.join(process.cwd(), "src/ui"); - const componentFolders = await fs.readdir(sourcePath); - - const indexEntries: string[] = []; + const componentFolders = await fs.readdir(sourcePath); + const entries: string[] = []; - for (const componentFolder of componentFolders) { - const componentPath = path.join(sourcePath, componentFolder); - const stats = await fs.stat(componentPath); + for (const componentFolder of componentFolders) { + const componentPath = path.join(sourcePath, componentFolder); + const stats = await fs.stat(componentPath); - if (!stats.isDirectory()) continue; + if (!stats.isDirectory()) continue; - const demosPath = path.join(componentPath, "demos"); + const demosPath = path.join(componentPath, "demos"); + if (!existsSync(demosPath)) continue; - if (!existsSync(demosPath)) continue; + await processDirectory(demosPath, `${componentFolder}/demos`, entries); + } - await processDirectory(demosPath, `${componentFolder}/demos`, indexEntries); - } - - index += indexEntries.join(""); - - index += ` -} + content += entries.join(""); + content += `}; `; - const outputDir = path.dirname(targetPath); - await fs.mkdir(outputDir, { recursive: true }); - - await fs.writeFile(targetPath, index, "utf8"); + await fs.writeFile(targetPath, content, "utf8"); + console.log(" āœ“ demos.tsx"); } async function buildIcons() { - const targetPath = path.join(process.cwd(), "src/icons/__icons__.tsx"); - - const iconKeys = Object.keys(registryIcons); - - const iconExports = iconKeys - .map((iconKey) => { - const iconMapping = registryIcons[iconKey]; - if (!iconMapping) { - throw new Error(`Icon mapping not found for: ${iconKey}`); - } - - const libraryMappings = iconLibraries - .map((library) => { - const iconName = iconMapping[library.name]; - if (!iconName) { - throw new Error( - `Icon "${iconKey}" not found for library "${library.name}"`, - ); - } - - if (library.name === "lucide") { - return ` lucide: Lucide.${iconName}`; - } else { - return ` ${library.name}: React.lazy(() => import("${library.import}").then(mod => ({ default: mod.${iconName} })))`; - } - }) - .join(",\n"); - - return `export const ${iconKey} = createIcon({ + const targetPath = path.join(GENERATED_DIR, "icons.tsx"); + + const iconKeys = Object.keys(registryIcons); + + const iconExports = iconKeys + .map((iconKey) => { + const iconMapping = registryIcons[iconKey]; + if (!iconMapping) { + throw new Error(`Icon mapping not found for: ${iconKey}`); + } + + const libraryMappings = iconLibraries + .map((library) => { + const iconName = iconMapping[library.name]; + if (!iconName) { + throw new Error( + `Icon "${iconKey}" not found for library "${library.name}"`, + ); + } + + if (library.name === "lucide") { + return ` lucide: Lucide.${iconName},`; + } + return ` ${library.name}: React.lazy(() => import("${library.import}").then(mod => ({ default: mod.${iconName} }))),`; + }) + .join("\n"); + + return `export const ${iconKey} = createIcon({ ${libraryMappings} });`; - }) - .join("\n\n"); + }) + .join("\n\n"); - const content = `// This file is autogenerated by scripts/build-registry.ts -// Do not edit this file directly. + const content = `// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build" to regenerate "use client"; import * as React from "react"; import * as Lucide from "lucide-react"; -import { createIcon } from "./create-icon"; +import { createIcon } from "@dotui/registry/icons/create-icon"; ${iconExports} `; - const outputDir = path.dirname(targetPath); - await fs.mkdir(outputDir, { recursive: true }); + await fs.writeFile(targetPath, content, "utf8"); + console.log(" āœ“ icons.tsx"); +} - await fs.writeFile(targetPath, content, "utf8"); +async function cleanOldGenerated() { + // Clean up old generated files from previous locations + const oldFiles = [ + path.join(process.cwd(), "src/icons/__icons__.tsx"), + path.join(process.cwd(), "src/ui/__demos__.tsx"), + path.join(process.cwd(), "src/blocks/__blocks__.tsx"), + ]; + + for (const file of oldFiles) { + if (existsSync(file)) { + await fs.unlink(file); + console.log(` šŸ—‘ļø Removed old file: ${path.basename(file)}`); + } + } + + // Remove empty __internal__ folder if it exists + const internalDir = path.join(process.cwd(), "src/__internal__"); + if (existsSync(internalDir)) { + await rimraf(internalDir); + console.log(" šŸ—‘ļø Removed __internal__ folder"); + } } async function main() { - console.log("šŸ”Ø Building registry..."); - - try { - await buildBlocks(); - await buildDemos(); - await buildIcons(); - console.log("āœ… Registry built successfully!"); - } catch (error) { - console.error("āŒ Error building registry:", error); - process.exit(1); - } + console.log("šŸ”Ø Building registry...\n"); + + try { + await cleanOldGenerated(); + await ensureGeneratedDir(); + + await buildBlocks(); + await buildDemos(); + await buildIcons(); + + console.log("\nāœ… Registry built successfully!"); + } catch (error) { + console.error("\nāŒ Error building registry:", error); + process.exit(1); + } } main(); diff --git a/packages/registry/src/__generated__/blocks.tsx b/packages/registry/src/__generated__/blocks.tsx new file mode 100644 index 000000000..4abed1d80 --- /dev/null +++ b/packages/registry/src/__generated__/blocks.tsx @@ -0,0 +1,24 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build" to regenerate +import * as React from "react"; + +export const BlocksIndex: Record< + string, + { + files: string[]; + component: React.LazyExoticComponent>; + } +> = { + "login": { + files: ["blocks/auth/login/page.tsx","blocks/auth/login/components/login-form.tsx"], + component: React.lazy(() => import("@dotui/registry/blocks/auth/login/page")), + }, + "cards": { + files: ["blocks/showcase/cards/components/cards.tsx","blocks/showcase/cards/components/account-menu.tsx","blocks/showcase/cards/components/backlog.tsx","blocks/showcase/cards/components/booking.tsx","blocks/showcase/cards/components/color-editor.tsx","blocks/showcase/cards/components/filters.tsx","blocks/showcase/cards/components/invite-members.tsx","blocks/showcase/cards/components/login-form.tsx","blocks/showcase/cards/components/notifications.tsx","blocks/showcase/cards/components/team-name.tsx"], + component: React.lazy(() => import("@dotui/registry/blocks/showcase/cards/components/cards")), + }, + "animation": { + files: ["blocks/showcase/animation/components/animation.tsx"], + component: React.lazy(() => import("@dotui/registry/blocks/showcase/animation/components/animation")), + }, +}; diff --git a/packages/registry/src/__generated__/demos.tsx b/packages/registry/src/__generated__/demos.tsx new file mode 100644 index 000000000..363caee23 --- /dev/null +++ b/packages/registry/src/__generated__/demos.tsx @@ -0,0 +1,1636 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build" to regenerate +import * as React from "react"; + +export const DemosIndex: Record< + string, + { + files: string[]; + component: React.LazyExoticComponent>; + } +> = { + "accordion/demos/allows-multiple": { + files: ["ui/accordion/demos/allows-multiple.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/accordion/demos/allows-multiple")), + }, + "accordion/demos/basic": { + files: ["ui/accordion/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/accordion/demos/basic")), + }, + "accordion/demos/controlled": { + files: ["ui/accordion/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/accordion/demos/controlled")), + }, + "accordion/demos/default-expanded": { + files: ["ui/accordion/demos/default-expanded.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/accordion/demos/default-expanded")), + }, + "accordion/demos/disabled": { + files: ["ui/accordion/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/accordion/demos/disabled")), + }, + "alert/demos/action": { + files: ["ui/alert/demos/action.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/alert/demos/action")), + }, + "alert/demos/custom-icon": { + files: ["ui/alert/demos/custom-icon.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/alert/demos/custom-icon")), + }, + "alert/demos/danger": { + files: ["ui/alert/demos/danger.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/alert/demos/danger")), + }, + "alert/demos/default": { + files: ["ui/alert/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/alert/demos/default")), + }, + "alert/demos/success": { + files: ["ui/alert/demos/success.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/alert/demos/success")), + }, + "alert/demos/warning": { + files: ["ui/alert/demos/warning.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/alert/demos/warning")), + }, + "avatar/demos/composition": { + files: ["ui/avatar/demos/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/avatar/demos/composition")), + }, + "avatar/demos/default": { + files: ["ui/avatar/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/avatar/demos/default")), + }, + "avatar/demos/shape": { + files: ["ui/avatar/demos/shape.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/avatar/demos/shape")), + }, + "avatar/demos/sizes": { + files: ["ui/avatar/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/avatar/demos/sizes")), + }, + "badge/demos/default": { + files: ["ui/badge/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/badge/demos/default")), + }, + "badge/demos/icon": { + files: ["ui/badge/demos/icon.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/badge/demos/icon")), + }, + "badge/demos/sizes": { + files: ["ui/badge/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/badge/demos/sizes")), + }, + "badge/demos/variants": { + files: ["ui/badge/demos/variants.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/badge/demos/variants")), + }, + "breadcrumbs/demos/basic": { + files: ["ui/breadcrumbs/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/breadcrumbs/demos/basic")), + }, + "breadcrumbs/demos/composition": { + files: ["ui/breadcrumbs/demos/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/breadcrumbs/demos/composition")), + }, + "breadcrumbs/demos/disabled": { + files: ["ui/breadcrumbs/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/breadcrumbs/demos/disabled")), + }, + "breadcrumbs/demos/icon": { + files: ["ui/breadcrumbs/demos/icon.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/breadcrumbs/demos/icon")), + }, + "breadcrumbs/demos/router-integration": { + files: ["ui/breadcrumbs/demos/router-integration.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/breadcrumbs/demos/router-integration")), + }, + "button/demos/default": { + files: ["ui/button/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/button/demos/default")), + }, + "button/demos/disabled": { + files: ["ui/button/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/button/demos/disabled")), + }, + "button/demos/link-button": { + files: ["ui/button/demos/link-button.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/button/demos/link-button")), + }, + "button/demos/loading": { + files: ["ui/button/demos/loading.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/button/demos/loading")), + }, + "button/demos/prefix-and-suffix": { + files: ["ui/button/demos/prefix-and-suffix.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/button/demos/prefix-and-suffix")), + }, + "button/demos/shapes": { + files: ["ui/button/demos/shapes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/button/demos/shapes")), + }, + "button/demos/sizes": { + files: ["ui/button/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/button/demos/sizes")), + }, + "button/demos/variants": { + files: ["ui/button/demos/variants.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/button/demos/variants")), + }, + "calendar/demos/calendar/composition": { + files: ["ui/calendar/demos/calendar/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/composition")), + }, + "calendar/demos/calendar/controlled": { + files: ["ui/calendar/demos/calendar/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/controlled")), + }, + "calendar/demos/calendar/default": { + files: ["ui/calendar/demos/calendar/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/default")), + }, + "calendar/demos/calendar/disabled": { + files: ["ui/calendar/demos/calendar/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/disabled")), + }, + "calendar/demos/calendar/error-message": { + files: ["ui/calendar/demos/calendar/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/error-message")), + }, + "calendar/demos/calendar/page-behaviour": { + files: ["ui/calendar/demos/calendar/page-behaviour.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/page-behaviour")), + }, + "calendar/demos/calendar/read-only": { + files: ["ui/calendar/demos/calendar/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/read-only")), + }, + "calendar/demos/calendar/uncontrolled": { + files: ["ui/calendar/demos/calendar/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/uncontrolled")), + }, + "calendar/demos/calendar/unvailable-dates": { + files: ["ui/calendar/demos/calendar/unvailable-dates.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/unvailable-dates")), + }, + "calendar/demos/calendar/validation": { + files: ["ui/calendar/demos/calendar/validation.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/validation")), + }, + "calendar/demos/calendar/variant": { + files: ["ui/calendar/demos/calendar/variant.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/variant")), + }, + "calendar/demos/calendar/visible-months": { + files: ["ui/calendar/demos/calendar/visible-months.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/calendar/visible-months")), + }, + "calendar/demos/range-calendar/composition": { + files: ["ui/calendar/demos/range-calendar/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/composition")), + }, + "calendar/demos/range-calendar/controlled": { + files: ["ui/calendar/demos/range-calendar/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/controlled")), + }, + "calendar/demos/range-calendar/default": { + files: ["ui/calendar/demos/range-calendar/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/default")), + }, + "calendar/demos/range-calendar/disabled": { + files: ["ui/calendar/demos/range-calendar/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/disabled")), + }, + "calendar/demos/range-calendar/error-message": { + files: ["ui/calendar/demos/range-calendar/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/error-message")), + }, + "calendar/demos/range-calendar/non-contiguous-ranges": { + files: ["ui/calendar/demos/range-calendar/non-contiguous-ranges.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/non-contiguous-ranges")), + }, + "calendar/demos/range-calendar/page-behaviour": { + files: ["ui/calendar/demos/range-calendar/page-behaviour.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/page-behaviour")), + }, + "calendar/demos/range-calendar/read-only": { + files: ["ui/calendar/demos/range-calendar/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/read-only")), + }, + "calendar/demos/range-calendar/uncontrolled": { + files: ["ui/calendar/demos/range-calendar/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/uncontrolled")), + }, + "calendar/demos/range-calendar/unvailable-dates": { + files: ["ui/calendar/demos/range-calendar/unvailable-dates.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/unvailable-dates")), + }, + "calendar/demos/range-calendar/validation": { + files: ["ui/calendar/demos/range-calendar/validation.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/validation")), + }, + "calendar/demos/range-calendar/variant": { + files: ["ui/calendar/demos/range-calendar/variant.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/variant")), + }, + "calendar/demos/range-calendar/visible-months": { + files: ["ui/calendar/demos/range-calendar/visible-months.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/calendar/demos/range-calendar/visible-months")), + }, + "checkbox/demos/card": { + files: ["ui/checkbox/demos/card.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox/demos/card")), + }, + "checkbox/demos/composition": { + files: ["ui/checkbox/demos/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox/demos/composition")), + }, + "checkbox/demos/controlled": { + files: ["ui/checkbox/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox/demos/controlled")), + }, + "checkbox/demos/default": { + files: ["ui/checkbox/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox/demos/default")), + }, + "checkbox/demos/disabled": { + files: ["ui/checkbox/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox/demos/disabled")), + }, + "checkbox/demos/indeterminate": { + files: ["ui/checkbox/demos/indeterminate.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox/demos/indeterminate")), + }, + "checkbox/demos/read-only": { + files: ["ui/checkbox/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox/demos/read-only")), + }, + "checkbox/demos/uncontrolled": { + files: ["ui/checkbox/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox/demos/uncontrolled")), + }, + "checkbox-group/demos/cards": { + files: ["ui/checkbox-group/demos/cards.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox-group/demos/cards")), + }, + "checkbox-group/demos/controlled": { + files: ["ui/checkbox-group/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox-group/demos/controlled")), + }, + "checkbox-group/demos/default": { + files: ["ui/checkbox-group/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox-group/demos/default")), + }, + "checkbox-group/demos/description": { + files: ["ui/checkbox-group/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox-group/demos/description")), + }, + "checkbox-group/demos/disabled": { + files: ["ui/checkbox-group/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox-group/demos/disabled")), + }, + "checkbox-group/demos/error-message": { + files: ["ui/checkbox-group/demos/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox-group/demos/error-message")), + }, + "checkbox-group/demos/label": { + files: ["ui/checkbox-group/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox-group/demos/label")), + }, + "checkbox-group/demos/read-only": { + files: ["ui/checkbox-group/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox-group/demos/read-only")), + }, + "checkbox-group/demos/required": { + files: ["ui/checkbox-group/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox-group/demos/required")), + }, + "checkbox-group/demos/uncontrolled": { + files: ["ui/checkbox-group/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/checkbox-group/demos/uncontrolled")), + }, + "color-area/demos/channels": { + files: ["ui/color-area/demos/channels.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-area/demos/channels")), + }, + "color-area/demos/controlled": { + files: ["ui/color-area/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-area/demos/controlled")), + }, + "color-area/demos/default": { + files: ["ui/color-area/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-area/demos/default")), + }, + "color-area/demos/disabled": { + files: ["ui/color-area/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-area/demos/disabled")), + }, + "color-area/demos/uncontrolled": { + files: ["ui/color-area/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-area/demos/uncontrolled")), + }, + "color-editor/demos/default": { + files: ["ui/color-editor/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-editor/demos/default")), + }, + "color-field/demos/color-channel": { + files: ["ui/color-field/demos/color-channel.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/color-channel")), + }, + "color-field/demos/controlled": { + files: ["ui/color-field/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/controlled")), + }, + "color-field/demos/default": { + files: ["ui/color-field/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/default")), + }, + "color-field/demos/description": { + files: ["ui/color-field/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/description")), + }, + "color-field/demos/disabled": { + files: ["ui/color-field/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/disabled")), + }, + "color-field/demos/error-message": { + files: ["ui/color-field/demos/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/error-message")), + }, + "color-field/demos/label": { + files: ["ui/color-field/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/label")), + }, + "color-field/demos/prefix-and-suffix": { + files: ["ui/color-field/demos/prefix-and-suffix.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/prefix-and-suffix")), + }, + "color-field/demos/read-only": { + files: ["ui/color-field/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/read-only")), + }, + "color-field/demos/required": { + files: ["ui/color-field/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/required")), + }, + "color-field/demos/sizes": { + files: ["ui/color-field/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/sizes")), + }, + "color-field/demos/uncontrolled": { + files: ["ui/color-field/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-field/demos/uncontrolled")), + }, + "color-picker/demos/channel-sliders": { + files: ["ui/color-picker/demos/channel-sliders.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-picker/demos/channel-sliders")), + }, + "color-picker/demos/controlled": { + files: ["ui/color-picker/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-picker/demos/controlled")), + }, + "color-picker/demos/default": { + files: ["ui/color-picker/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-picker/demos/default")), + }, + "color-picker/demos/swatches": { + files: ["ui/color-picker/demos/swatches.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-picker/demos/swatches")), + }, + "color-picker/demos/uncontrolled": { + files: ["ui/color-picker/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-picker/demos/uncontrolled")), + }, + "color-slider/demos/channel": { + files: ["ui/color-slider/demos/channel.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-slider/demos/channel")), + }, + "color-slider/demos/controlled": { + files: ["ui/color-slider/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-slider/demos/controlled")), + }, + "color-slider/demos/default": { + files: ["ui/color-slider/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-slider/demos/default")), + }, + "color-slider/demos/disabled": { + files: ["ui/color-slider/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-slider/demos/disabled")), + }, + "color-slider/demos/label": { + files: ["ui/color-slider/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-slider/demos/label")), + }, + "color-slider/demos/uncontrolled": { + files: ["ui/color-slider/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-slider/demos/uncontrolled")), + }, + "color-slider/demos/vertical": { + files: ["ui/color-slider/demos/vertical.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-slider/demos/vertical")), + }, + "color-swatch/demos/default": { + files: ["ui/color-swatch/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-swatch/demos/default")), + }, + "color-swatch-picker/demos/basic": { + files: ["ui/color-swatch-picker/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-swatch-picker/demos/basic")), + }, + "color-swatch-picker/demos/controlled": { + files: ["ui/color-swatch-picker/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-swatch-picker/demos/controlled")), + }, + "color-swatch-picker/demos/disabled": { + files: ["ui/color-swatch-picker/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/color-swatch-picker/demos/disabled")), + }, + "combobox/demos/async-loading": { + files: ["ui/combobox/demos/async-loading.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/async-loading")), + }, + "combobox/demos/basic": { + files: ["ui/combobox/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/basic")), + }, + "combobox/demos/controlled": { + files: ["ui/combobox/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/controlled")), + }, + "combobox/demos/custom-value": { + files: ["ui/combobox/demos/custom-value.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/custom-value")), + }, + "combobox/demos/description": { + files: ["ui/combobox/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/description")), + }, + "combobox/demos/disabled": { + files: ["ui/combobox/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/disabled")), + }, + "combobox/demos/label": { + files: ["ui/combobox/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/label")), + }, + "combobox/demos/loading": { + files: ["ui/combobox/demos/loading.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/loading")), + }, + "combobox/demos/required": { + files: ["ui/combobox/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/required")), + }, + "combobox/demos/sections": { + files: ["ui/combobox/demos/sections.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/sections")), + }, + "combobox/demos/uncontrolled": { + files: ["ui/combobox/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/uncontrolled")), + }, + "combobox/demos/validation": { + files: ["ui/combobox/demos/validation.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/combobox/demos/validation")), + }, + "command/demos/basic": { + files: ["ui/command/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/command/demos/basic")), + }, + "command/demos/dialog": { + files: ["ui/command/demos/dialog.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/command/demos/dialog")), + }, + "date-field/demos/controlled": { + files: ["ui/date-field/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/controlled")), + }, + "date-field/demos/default": { + files: ["ui/date-field/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/default")), + }, + "date-field/demos/description": { + files: ["ui/date-field/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/description")), + }, + "date-field/demos/disabled": { + files: ["ui/date-field/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/disabled")), + }, + "date-field/demos/error-message": { + files: ["ui/date-field/demos/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/error-message")), + }, + "date-field/demos/granularity": { + files: ["ui/date-field/demos/granularity.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/granularity")), + }, + "date-field/demos/hide-time-zone": { + files: ["ui/date-field/demos/hide-time-zone.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/hide-time-zone")), + }, + "date-field/demos/hour-cycle": { + files: ["ui/date-field/demos/hour-cycle.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/hour-cycle")), + }, + "date-field/demos/label": { + files: ["ui/date-field/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/label")), + }, + "date-field/demos/placeholder": { + files: ["ui/date-field/demos/placeholder.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/placeholder")), + }, + "date-field/demos/prefix-and-suffix": { + files: ["ui/date-field/demos/prefix-and-suffix.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/prefix-and-suffix")), + }, + "date-field/demos/read-only": { + files: ["ui/date-field/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/read-only")), + }, + "date-field/demos/required": { + files: ["ui/date-field/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/required")), + }, + "date-field/demos/sizes": { + files: ["ui/date-field/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/sizes")), + }, + "date-field/demos/time-zones": { + files: ["ui/date-field/demos/time-zones.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/time-zones")), + }, + "date-field/demos/uncontrolled": { + files: ["ui/date-field/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-field/demos/uncontrolled")), + }, + "date-picker/demos/composition": { + files: ["ui/date-picker/demos/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/composition")), + }, + "date-picker/demos/controlled": { + files: ["ui/date-picker/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/controlled")), + }, + "date-picker/demos/default": { + files: ["ui/date-picker/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/default")), + }, + "date-picker/demos/description": { + files: ["ui/date-picker/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/description")), + }, + "date-picker/demos/disabled": { + files: ["ui/date-picker/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/disabled")), + }, + "date-picker/demos/error-message": { + files: ["ui/date-picker/demos/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/error-message")), + }, + "date-picker/demos/granularity": { + files: ["ui/date-picker/demos/granularity.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/granularity")), + }, + "date-picker/demos/hide-time-zone": { + files: ["ui/date-picker/demos/hide-time-zone.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/hide-time-zone")), + }, + "date-picker/demos/hour-cycle": { + files: ["ui/date-picker/demos/hour-cycle.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/hour-cycle")), + }, + "date-picker/demos/label": { + files: ["ui/date-picker/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/label")), + }, + "date-picker/demos/placeholder": { + files: ["ui/date-picker/demos/placeholder.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/placeholder")), + }, + "date-picker/demos/range/composition": { + files: ["ui/date-picker/demos/range/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/composition")), + }, + "date-picker/demos/range/controlled": { + files: ["ui/date-picker/demos/range/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/controlled")), + }, + "date-picker/demos/range/default": { + files: ["ui/date-picker/demos/range/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/default")), + }, + "date-picker/demos/range/description": { + files: ["ui/date-picker/demos/range/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/description")), + }, + "date-picker/demos/range/disabled": { + files: ["ui/date-picker/demos/range/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/disabled")), + }, + "date-picker/demos/range/error-message": { + files: ["ui/date-picker/demos/range/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/error-message")), + }, + "date-picker/demos/range/granularity": { + files: ["ui/date-picker/demos/range/granularity.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/granularity")), + }, + "date-picker/demos/range/hide-time-zone": { + files: ["ui/date-picker/demos/range/hide-time-zone.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/hide-time-zone")), + }, + "date-picker/demos/range/hour-cycle": { + files: ["ui/date-picker/demos/range/hour-cycle.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/hour-cycle")), + }, + "date-picker/demos/range/label": { + files: ["ui/date-picker/demos/range/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/label")), + }, + "date-picker/demos/range/placeholder": { + files: ["ui/date-picker/demos/range/placeholder.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/placeholder")), + }, + "date-picker/demos/range/read-only": { + files: ["ui/date-picker/demos/range/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/read-only")), + }, + "date-picker/demos/range/required": { + files: ["ui/date-picker/demos/range/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/required")), + }, + "date-picker/demos/range/time-zones": { + files: ["ui/date-picker/demos/range/time-zones.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/time-zones")), + }, + "date-picker/demos/range/uncontrolled": { + files: ["ui/date-picker/demos/range/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/range/uncontrolled")), + }, + "date-picker/demos/read-only": { + files: ["ui/date-picker/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/read-only")), + }, + "date-picker/demos/required": { + files: ["ui/date-picker/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/required")), + }, + "date-picker/demos/time-zones": { + files: ["ui/date-picker/demos/time-zones.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/time-zones")), + }, + "date-picker/demos/uncontrolled": { + files: ["ui/date-picker/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/date-picker/demos/uncontrolled")), + }, + "dialog/demos/alert-dialog": { + files: ["ui/dialog/demos/alert-dialog.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/alert-dialog")), + }, + "dialog/demos/async-form-submission": { + files: ["ui/dialog/demos/async-form-submission.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/async-form-submission")), + }, + "dialog/demos/basic": { + files: ["ui/dialog/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/basic")), + }, + "dialog/demos/composition": { + files: ["ui/dialog/demos/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/composition")), + }, + "dialog/demos/controlled": { + files: ["ui/dialog/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/controlled")), + }, + "dialog/demos/description": { + files: ["ui/dialog/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/description")), + }, + "dialog/demos/dismissable": { + files: ["ui/dialog/demos/dismissable.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/dismissable")), + }, + "dialog/demos/drawer": { + files: ["ui/dialog/demos/drawer.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/drawer")), + }, + "dialog/demos/inset-content": { + files: ["ui/dialog/demos/inset-content.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/inset-content")), + }, + "dialog/demos/nested": { + files: ["ui/dialog/demos/nested.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/nested")), + }, + "dialog/demos/popover": { + files: ["ui/dialog/demos/popover.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/popover")), + }, + "dialog/demos/title": { + files: ["ui/dialog/demos/title.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/title")), + }, + "dialog/demos/types": { + files: ["ui/dialog/demos/types.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/dialog/demos/types")), + }, + "disclosure/demos/advanced-composition": { + files: ["ui/disclosure/demos/advanced-composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/disclosure/demos/advanced-composition")), + }, + "disclosure/demos/basic": { + files: ["ui/disclosure/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/disclosure/demos/basic")), + }, + "disclosure/demos/controlled": { + files: ["ui/disclosure/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/disclosure/demos/controlled")), + }, + "disclosure/demos/default-expanded": { + files: ["ui/disclosure/demos/default-expanded.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/disclosure/demos/default-expanded")), + }, + "disclosure/demos/disabled": { + files: ["ui/disclosure/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/disclosure/demos/disabled")), + }, + "drawer/demos/basic": { + files: ["ui/drawer/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/drawer/demos/basic")), + }, + "drawer/demos/placement": { + files: ["ui/drawer/demos/placement.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/drawer/demos/placement")), + }, + "drop-zone/demos/basic": { + files: ["ui/drop-zone/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/drop-zone/demos/basic")), + }, + "drop-zone/demos/disabled": { + files: ["ui/drop-zone/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/drop-zone/demos/disabled")), + }, + "drop-zone/demos/events": { + files: ["ui/drop-zone/demos/events.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/drop-zone/demos/events")), + }, + "drop-zone/demos/file-trigger": { + files: ["ui/drop-zone/demos/file-trigger.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/drop-zone/demos/file-trigger")), + }, + "drop-zone/demos/label": { + files: ["ui/drop-zone/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/drop-zone/demos/label")), + }, + "drop-zone/demos/visual-feedback": { + files: ["ui/drop-zone/demos/visual-feedback.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/drop-zone/demos/visual-feedback")), + }, + "empty/demos/empty-projects": { + files: ["ui/empty/demos/empty-projects.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/empty/demos/empty-projects")), + }, + "file-trigger/demos/default": { + files: ["ui/file-trigger/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/file-trigger/demos/default")), + }, + "file-trigger/demos/directory-selection": { + files: ["ui/file-trigger/demos/directory-selection.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/file-trigger/demos/directory-selection")), + }, + "file-trigger/demos/file-types": { + files: ["ui/file-trigger/demos/file-types.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/file-trigger/demos/file-types")), + }, + "file-trigger/demos/media-capture": { + files: ["ui/file-trigger/demos/media-capture.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/file-trigger/demos/media-capture")), + }, + "file-trigger/demos/multiple-files": { + files: ["ui/file-trigger/demos/multiple-files.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/file-trigger/demos/multiple-files")), + }, + "form/demos/basic": { + files: ["ui/form/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/form/demos/basic")), + }, + "form/demos/react-aria": { + files: ["ui/form/demos/react-aria.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/form/demos/react-aria")), + }, + "group/demos/default": { + files: ["ui/group/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/group/demos/default")), + }, + "list-box/demos/async-loading": { + files: ["ui/list-box/demos/async-loading.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/async-loading")), + }, + "list-box/demos/basic": { + files: ["ui/list-box/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/basic")), + }, + "list-box/demos/composition": { + files: ["ui/list-box/demos/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/composition")), + }, + "list-box/demos/controlled": { + files: ["ui/list-box/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/controlled")), + }, + "list-box/demos/disabled-items": { + files: ["ui/list-box/demos/disabled-items.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/disabled-items")), + }, + "list-box/demos/empty-state": { + files: ["ui/list-box/demos/empty-state.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/empty-state")), + }, + "list-box/demos/grid": { + files: ["ui/list-box/demos/grid.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/grid")), + }, + "list-box/demos/horizontal": { + files: ["ui/list-box/demos/horizontal.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/horizontal")), + }, + "list-box/demos/item-variant": { + files: ["ui/list-box/demos/item-variant.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/item-variant")), + }, + "list-box/demos/label-and-description": { + files: ["ui/list-box/demos/label-and-description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/label-and-description")), + }, + "list-box/demos/links": { + files: ["ui/list-box/demos/links.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/links")), + }, + "list-box/demos/loading": { + files: ["ui/list-box/demos/loading.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/loading")), + }, + "list-box/demos/prefix-and-suffix": { + files: ["ui/list-box/demos/prefix-and-suffix.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/prefix-and-suffix")), + }, + "list-box/demos/sections": { + files: ["ui/list-box/demos/sections.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/sections")), + }, + "list-box/demos/selection-behavior": { + files: ["ui/list-box/demos/selection-behavior.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/selection-behavior")), + }, + "list-box/demos/selection-mode": { + files: ["ui/list-box/demos/selection-mode.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/selection-mode")), + }, + "list-box/demos/separator": { + files: ["ui/list-box/demos/separator.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/separator")), + }, + "list-box/demos/uncontrolled": { + files: ["ui/list-box/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/list-box/demos/uncontrolled")), + }, + "menu/demos/basic": { + files: ["ui/menu/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/basic")), + }, + "menu/demos/controlled": { + files: ["ui/menu/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/controlled")), + }, + "menu/demos/disabled-items": { + files: ["ui/menu/demos/disabled-items.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/disabled-items")), + }, + "menu/demos/item-variant": { + files: ["ui/menu/demos/item-variant.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/item-variant")), + }, + "menu/demos/label-and-description": { + files: ["ui/menu/demos/label-and-description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/label-and-description")), + }, + "menu/demos/link-items": { + files: ["ui/menu/demos/link-items.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/link-items")), + }, + "menu/demos/long-press": { + files: ["ui/menu/demos/long-press.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/long-press")), + }, + "menu/demos/multiple-selection": { + files: ["ui/menu/demos/multiple-selection.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/multiple-selection")), + }, + "menu/demos/overlay-type": { + files: ["ui/menu/demos/overlay-type.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/overlay-type")), + }, + "menu/demos/placement": { + files: ["ui/menu/demos/placement.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/placement")), + }, + "menu/demos/prefix-and-suffix": { + files: ["ui/menu/demos/prefix-and-suffix.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/prefix-and-suffix")), + }, + "menu/demos/section": { + files: ["ui/menu/demos/section.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/section")), + }, + "menu/demos/separator": { + files: ["ui/menu/demos/separator.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/separator")), + }, + "menu/demos/shortcut": { + files: ["ui/menu/demos/shortcut.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/shortcut")), + }, + "menu/demos/single-selection": { + files: ["ui/menu/demos/single-selection.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/single-selection")), + }, + "menu/demos/submenus": { + files: ["ui/menu/demos/submenus.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/menu/demos/submenus")), + }, + "modal/demos/basic": { + files: ["ui/modal/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/modal/demos/basic")), + }, + "number-field/demos/controlled": { + files: ["ui/number-field/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/controlled")), + }, + "number-field/demos/default": { + files: ["ui/number-field/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/default")), + }, + "number-field/demos/description": { + files: ["ui/number-field/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/description")), + }, + "number-field/demos/disabled": { + files: ["ui/number-field/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/disabled")), + }, + "number-field/demos/error-message": { + files: ["ui/number-field/demos/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/error-message")), + }, + "number-field/demos/format-options": { + files: ["ui/number-field/demos/format-options.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/format-options")), + }, + "number-field/demos/label": { + files: ["ui/number-field/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/label")), + }, + "number-field/demos/read-only": { + files: ["ui/number-field/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/read-only")), + }, + "number-field/demos/required": { + files: ["ui/number-field/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/required")), + }, + "number-field/demos/sizes": { + files: ["ui/number-field/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/sizes")), + }, + "number-field/demos/uncontrolled": { + files: ["ui/number-field/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/number-field/demos/uncontrolled")), + }, + "overlay/demos/basic": { + files: ["ui/overlay/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/overlay/demos/basic")), + }, + "overlay/demos/type": { + files: ["ui/overlay/demos/type.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/overlay/demos/type")), + }, + "popover/demos/basic": { + files: ["ui/popover/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/popover/demos/basic")), + }, + "progress-bar/demos/custom-value-label": { + files: ["ui/progress-bar/demos/custom-value-label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/custom-value-label")), + }, + "progress-bar/demos/default": { + files: ["ui/progress-bar/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/default")), + }, + "progress-bar/demos/duration": { + files: ["ui/progress-bar/demos/duration.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/duration")), + }, + "progress-bar/demos/format-options": { + files: ["ui/progress-bar/demos/format-options.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/format-options")), + }, + "progress-bar/demos/indeterminate": { + files: ["ui/progress-bar/demos/indeterminate.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/indeterminate")), + }, + "progress-bar/demos/label": { + files: ["ui/progress-bar/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/label")), + }, + "progress-bar/demos/min-max-values": { + files: ["ui/progress-bar/demos/min-max-values.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/min-max-values")), + }, + "progress-bar/demos/shape": { + files: ["ui/progress-bar/demos/shape.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/shape")), + }, + "progress-bar/demos/sizes": { + files: ["ui/progress-bar/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/sizes")), + }, + "progress-bar/demos/value-label": { + files: ["ui/progress-bar/demos/value-label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/value-label")), + }, + "progress-bar/demos/variants": { + files: ["ui/progress-bar/demos/variants.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/progress-bar/demos/variants")), + }, + "radio-group/demos/cards": { + files: ["ui/radio-group/demos/cards.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/cards")), + }, + "radio-group/demos/controlled": { + files: ["ui/radio-group/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/controlled")), + }, + "radio-group/demos/default": { + files: ["ui/radio-group/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/default")), + }, + "radio-group/demos/description": { + files: ["ui/radio-group/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/description")), + }, + "radio-group/demos/disabled": { + files: ["ui/radio-group/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/disabled")), + }, + "radio-group/demos/error-message": { + files: ["ui/radio-group/demos/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/error-message")), + }, + "radio-group/demos/label": { + files: ["ui/radio-group/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/label")), + }, + "radio-group/demos/orientation": { + files: ["ui/radio-group/demos/orientation.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/orientation")), + }, + "radio-group/demos/read-only": { + files: ["ui/radio-group/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/read-only")), + }, + "radio-group/demos/required": { + files: ["ui/radio-group/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/required")), + }, + "radio-group/demos/uncontrolled": { + files: ["ui/radio-group/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/radio-group/demos/uncontrolled")), + }, + "react-hook-form/demos/register": { + files: ["ui/react-hook-form/demos/register.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/react-hook-form/demos/register")), + }, + "search-field/demos/controlled": { + files: ["ui/search-field/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/search-field/demos/controlled")), + }, + "search-field/demos/default": { + files: ["ui/search-field/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/search-field/demos/default")), + }, + "search-field/demos/description": { + files: ["ui/search-field/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/search-field/demos/description")), + }, + "search-field/demos/disabled": { + files: ["ui/search-field/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/search-field/demos/disabled")), + }, + "search-field/demos/error-message": { + files: ["ui/search-field/demos/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/search-field/demos/error-message")), + }, + "search-field/demos/label": { + files: ["ui/search-field/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/search-field/demos/label")), + }, + "search-field/demos/read-only": { + files: ["ui/search-field/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/search-field/demos/read-only")), + }, + "search-field/demos/required": { + files: ["ui/search-field/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/search-field/demos/required")), + }, + "search-field/demos/sizes": { + files: ["ui/search-field/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/search-field/demos/sizes")), + }, + "search-field/demos/uncontrolled": { + files: ["ui/search-field/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/search-field/demos/uncontrolled")), + }, + "select/demos/async-loading": { + files: ["ui/select/demos/async-loading.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/async-loading")), + }, + "select/demos/basic": { + files: ["ui/select/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/basic")), + }, + "select/demos/composition": { + files: ["ui/select/demos/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/composition")), + }, + "select/demos/controlled": { + files: ["ui/select/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/controlled")), + }, + "select/demos/description": { + files: ["ui/select/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/description")), + }, + "select/demos/disabled": { + files: ["ui/select/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/disabled")), + }, + "select/demos/label": { + files: ["ui/select/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/label")), + }, + "select/demos/links": { + files: ["ui/select/demos/links.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/links")), + }, + "select/demos/loading": { + files: ["ui/select/demos/loading.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/loading")), + }, + "select/demos/placeholder": { + files: ["ui/select/demos/placeholder.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/placeholder")), + }, + "select/demos/required": { + files: ["ui/select/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/required")), + }, + "select/demos/sections": { + files: ["ui/select/demos/sections.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/sections")), + }, + "select/demos/uncontrolled": { + files: ["ui/select/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/uncontrolled")), + }, + "select/demos/validation": { + files: ["ui/select/demos/validation.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/select/demos/validation")), + }, + "separator/demos/card": { + files: ["ui/separator/demos/card.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/separator/demos/card")), + }, + "separator/demos/orientation": { + files: ["ui/separator/demos/orientation.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/separator/demos/orientation")), + }, + "skeleton/demos/api-simulation": { + files: ["ui/skeleton/demos/api-simulation.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/skeleton/demos/api-simulation")), + }, + "skeleton/demos/card": { + files: ["ui/skeleton/demos/card.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/skeleton/demos/card")), + }, + "skeleton/demos/children": { + files: ["ui/skeleton/demos/children.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/skeleton/demos/children")), + }, + "skeleton/demos/fixed-size-children": { + files: ["ui/skeleton/demos/fixed-size-children.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/skeleton/demos/fixed-size-children")), + }, + "skeleton/demos/show": { + files: ["ui/skeleton/demos/show.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/skeleton/demos/show")), + }, + "slider/demos/composition": { + files: ["ui/slider/demos/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/composition")), + }, + "slider/demos/controlled": { + files: ["ui/slider/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/controlled")), + }, + "slider/demos/default": { + files: ["ui/slider/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/default")), + }, + "slider/demos/description": { + files: ["ui/slider/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/description")), + }, + "slider/demos/disabled": { + files: ["ui/slider/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/disabled")), + }, + "slider/demos/format-options": { + files: ["ui/slider/demos/format-options.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/format-options")), + }, + "slider/demos/label": { + files: ["ui/slider/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/label")), + }, + "slider/demos/range": { + files: ["ui/slider/demos/range.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/range")), + }, + "slider/demos/sizes": { + files: ["ui/slider/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/sizes")), + }, + "slider/demos/step": { + files: ["ui/slider/demos/step.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/step")), + }, + "slider/demos/uncontrolled": { + files: ["ui/slider/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/uncontrolled")), + }, + "slider/demos/value-label": { + files: ["ui/slider/demos/value-label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/value-label")), + }, + "slider/demos/value-scale": { + files: ["ui/slider/demos/value-scale.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/value-scale")), + }, + "slider/demos/vertical": { + files: ["ui/slider/demos/vertical.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/slider/demos/vertical")), + }, + "switch/demos/card": { + files: ["ui/switch/demos/card.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/switch/demos/card")), + }, + "switch/demos/composition": { + files: ["ui/switch/demos/composition.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/switch/demos/composition")), + }, + "switch/demos/controlled": { + files: ["ui/switch/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/switch/demos/controlled")), + }, + "switch/demos/default": { + files: ["ui/switch/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/switch/demos/default")), + }, + "switch/demos/disabled": { + files: ["ui/switch/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/switch/demos/disabled")), + }, + "switch/demos/label": { + files: ["ui/switch/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/switch/demos/label")), + }, + "switch/demos/sizes": { + files: ["ui/switch/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/switch/demos/sizes")), + }, + "switch/demos/uncontrolled": { + files: ["ui/switch/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/switch/demos/uncontrolled")), + }, + "table/demos/basic": { + files: ["ui/table/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/basic")), + }, + "table/demos/column-resizing": { + files: ["ui/table/demos/column-resizing.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/column-resizing")), + }, + "table/demos/controlled": { + files: ["ui/table/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/controlled")), + }, + "table/demos/disabled-rows": { + files: ["ui/table/demos/disabled-rows.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/disabled-rows")), + }, + "table/demos/disallow-empty-selection": { + files: ["ui/table/demos/disallow-empty-selection.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/disallow-empty-selection")), + }, + "table/demos/dynamic-collection": { + files: ["ui/table/demos/dynamic-collection.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/dynamic-collection")), + }, + "table/demos/empty-state": { + files: ["ui/table/demos/empty-state.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/empty-state")), + }, + "table/demos/links": { + files: ["ui/table/demos/links.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/links")), + }, + "table/demos/reordable": { + files: ["ui/table/demos/reordable.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/reordable")), + }, + "table/demos/row-action": { + files: ["ui/table/demos/row-action.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/row-action")), + }, + "table/demos/selection-behavior": { + files: ["ui/table/demos/selection-behavior.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/selection-behavior")), + }, + "table/demos/selection-mode": { + files: ["ui/table/demos/selection-mode.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/selection-mode")), + }, + "table/demos/selection": { + files: ["ui/table/demos/selection.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/selection")), + }, + "table/demos/sorting": { + files: ["ui/table/demos/sorting.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/sorting")), + }, + "table/demos/static-row-action": { + files: ["ui/table/demos/static-row-action.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/static-row-action")), + }, + "table/demos/uncontrolled": { + files: ["ui/table/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/table/demos/uncontrolled")), + }, + "tabs/demos/basic": { + files: ["ui/tabs/demos/basic.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tabs/demos/basic")), + }, + "tabs/demos/controlled": { + files: ["ui/tabs/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tabs/demos/controlled")), + }, + "tabs/demos/disabled": { + files: ["ui/tabs/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tabs/demos/disabled")), + }, + "tabs/demos/keyboard-activation": { + files: ["ui/tabs/demos/keyboard-activation.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tabs/demos/keyboard-activation")), + }, + "tabs/demos/variant": { + files: ["ui/tabs/demos/variant.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tabs/demos/variant")), + }, + "tabs/demos/vertical": { + files: ["ui/tabs/demos/vertical.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tabs/demos/vertical")), + }, + "tag-group/demos/default": { + files: ["ui/tag-group/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tag-group/demos/default")), + }, + "text-area/demos/controlled": { + files: ["ui/text-area/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-area/demos/controlled")), + }, + "text-area/demos/default": { + files: ["ui/text-area/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-area/demos/default")), + }, + "text-area/demos/description": { + files: ["ui/text-area/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-area/demos/description")), + }, + "text-area/demos/disabled": { + files: ["ui/text-area/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-area/demos/disabled")), + }, + "text-area/demos/error-message": { + files: ["ui/text-area/demos/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-area/demos/error-message")), + }, + "text-area/demos/label": { + files: ["ui/text-area/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-area/demos/label")), + }, + "text-area/demos/prefix-and-suffix": { + files: ["ui/text-area/demos/prefix-and-suffix.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-area/demos/prefix-and-suffix")), + }, + "text-area/demos/read-only": { + files: ["ui/text-area/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-area/demos/read-only")), + }, + "text-area/demos/required": { + files: ["ui/text-area/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-area/demos/required")), + }, + "text-area/demos/uncontrolled": { + files: ["ui/text-area/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-area/demos/uncontrolled")), + }, + "text-field/demos/controlled": { + files: ["ui/text-field/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/controlled")), + }, + "text-field/demos/default": { + files: ["ui/text-field/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/default")), + }, + "text-field/demos/description": { + files: ["ui/text-field/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/description")), + }, + "text-field/demos/disabled": { + files: ["ui/text-field/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/disabled")), + }, + "text-field/demos/error-message": { + files: ["ui/text-field/demos/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/error-message")), + }, + "text-field/demos/label": { + files: ["ui/text-field/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/label")), + }, + "text-field/demos/prefix-and-suffix": { + files: ["ui/text-field/demos/prefix-and-suffix.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/prefix-and-suffix")), + }, + "text-field/demos/read-only": { + files: ["ui/text-field/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/read-only")), + }, + "text-field/demos/required": { + files: ["ui/text-field/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/required")), + }, + "text-field/demos/sizes": { + files: ["ui/text-field/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/sizes")), + }, + "text-field/demos/uncontrolled": { + files: ["ui/text-field/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/text-field/demos/uncontrolled")), + }, + "time-field/demos/controlled": { + files: ["ui/time-field/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/controlled")), + }, + "time-field/demos/default": { + files: ["ui/time-field/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/default")), + }, + "time-field/demos/description": { + files: ["ui/time-field/demos/description.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/description")), + }, + "time-field/demos/disabled": { + files: ["ui/time-field/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/disabled")), + }, + "time-field/demos/error-message": { + files: ["ui/time-field/demos/error-message.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/error-message")), + }, + "time-field/demos/granularity": { + files: ["ui/time-field/demos/granularity.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/granularity")), + }, + "time-field/demos/hide-time-zone": { + files: ["ui/time-field/demos/hide-time-zone.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/hide-time-zone")), + }, + "time-field/demos/hour-cycle": { + files: ["ui/time-field/demos/hour-cycle.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/hour-cycle")), + }, + "time-field/demos/label": { + files: ["ui/time-field/demos/label.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/label")), + }, + "time-field/demos/placeholder": { + files: ["ui/time-field/demos/placeholder.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/placeholder")), + }, + "time-field/demos/prefix-and-suffix": { + files: ["ui/time-field/demos/prefix-and-suffix.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/prefix-and-suffix")), + }, + "time-field/demos/read-only": { + files: ["ui/time-field/demos/read-only.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/read-only")), + }, + "time-field/demos/required": { + files: ["ui/time-field/demos/required.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/required")), + }, + "time-field/demos/sizes": { + files: ["ui/time-field/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/sizes")), + }, + "time-field/demos/time-zones": { + files: ["ui/time-field/demos/time-zones.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/time-zones")), + }, + "time-field/demos/uncontrolled": { + files: ["ui/time-field/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/time-field/demos/uncontrolled")), + }, + "toggle-button/demos/controlled": { + files: ["ui/toggle-button/demos/controlled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/toggle-button/demos/controlled")), + }, + "toggle-button/demos/default": { + files: ["ui/toggle-button/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/toggle-button/demos/default")), + }, + "toggle-button/demos/disabled": { + files: ["ui/toggle-button/demos/disabled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/toggle-button/demos/disabled")), + }, + "toggle-button/demos/shapes": { + files: ["ui/toggle-button/demos/shapes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/toggle-button/demos/shapes")), + }, + "toggle-button/demos/sizes": { + files: ["ui/toggle-button/demos/sizes.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/toggle-button/demos/sizes")), + }, + "toggle-button/demos/uncontrolled": { + files: ["ui/toggle-button/demos/uncontrolled.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/toggle-button/demos/uncontrolled")), + }, + "toggle-button/demos/variants": { + files: ["ui/toggle-button/demos/variants.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/toggle-button/demos/variants")), + }, + "toggle-button-group/demos/default": { + files: ["ui/toggle-button-group/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/toggle-button-group/demos/default")), + }, + "tooltip/demos/arrow": { + files: ["ui/tooltip/demos/arrow.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tooltip/demos/arrow")), + }, + "tooltip/demos/container-padding": { + files: ["ui/tooltip/demos/container-padding.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tooltip/demos/container-padding")), + }, + "tooltip/demos/default": { + files: ["ui/tooltip/demos/default.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tooltip/demos/default")), + }, + "tooltip/demos/delay": { + files: ["ui/tooltip/demos/delay.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tooltip/demos/delay")), + }, + "tooltip/demos/flip": { + files: ["ui/tooltip/demos/flip.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tooltip/demos/flip")), + }, + "tooltip/demos/offset": { + files: ["ui/tooltip/demos/offset.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tooltip/demos/offset")), + }, + "tooltip/demos/placement": { + files: ["ui/tooltip/demos/placement.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tooltip/demos/placement")), + }, + "tooltip/demos/variant": { + files: ["ui/tooltip/demos/variant.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tooltip/demos/variant")), + }, + "tooltip/demos/with-arrow": { + files: ["ui/tooltip/demos/with-arrow.tsx"], + component: React.lazy(() => import("@dotui/registry/ui/tooltip/demos/with-arrow")), + }, +}; diff --git a/packages/registry/src/__generated__/icons.tsx b/packages/registry/src/__generated__/icons.tsx new file mode 100644 index 000000000..f234ced2e --- /dev/null +++ b/packages/registry/src/__generated__/icons.tsx @@ -0,0 +1,237 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build" to regenerate +"use client"; + +import * as React from "react"; +import * as Lucide from "lucide-react"; +import { createIcon } from "@dotui/registry/icons/create-icon"; + +export const Loader2Icon = createIcon({ + lucide: Lucide.Loader2Icon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiLoader4Line }))), +}); + +export const AlertCircleIcon = createIcon({ + lucide: Lucide.AlertCircleIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiErrorWarningFill }))), +}); + +export const AlertTriangleIcon = createIcon({ + lucide: Lucide.AlertTriangleIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiAlertLine }))), +}); + +export const CheckCircle2Icon = createIcon({ + lucide: Lucide.CheckCircle2Icon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiCheckboxCircleLine }))), +}); + +export const InfoIcon = createIcon({ + lucide: Lucide.InfoIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiInformationLine }))), +}); + +export const ChevronRightIcon = createIcon({ + lucide: Lucide.ChevronRightIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiArrowRightSLine }))), +}); + +export const ChevronLeftIcon = createIcon({ + lucide: Lucide.ChevronLeftIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiArrowLeftSLine }))), +}); + +export const ChevronDownIcon = createIcon({ + lucide: Lucide.ChevronDownIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiArrowDownSLine }))), +}); + +export const ChevronUpIcon = createIcon({ + lucide: Lucide.ChevronUpIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiArrowUpSLine }))), +}); + +export const SearchIcon = createIcon({ + lucide: Lucide.SearchIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiSearchLine }))), +}); + +export const HelpCircleIcon = createIcon({ + lucide: Lucide.HelpCircleIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiQuestionLine }))), +}); + +export const CalendarIcon = createIcon({ + lucide: Lucide.CalendarIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiCalendarLine }))), +}); + +export const XIcon = createIcon({ + lucide: Lucide.XIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiCloseLine }))), +}); + +export const AsteriskIcon = createIcon({ + lucide: Lucide.AsteriskIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiAsterisk }))), +}); + +export const CheckIcon = createIcon({ + lucide: Lucide.CheckIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiCheckLine }))), +}); + +export const MinusIcon = createIcon({ + lucide: Lucide.MinusIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiSubtractLine }))), +}); + +export const PlusIcon = createIcon({ + lucide: Lucide.PlusIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiAddLine }))), +}); + +export const WalletIcon = createIcon({ + lucide: Lucide.WalletIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiWalletLine }))), +}); + +export const GlobeIcon = createIcon({ + lucide: Lucide.GlobeIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiGlobalLine }))), +}); + +export const User2Icon = createIcon({ + lucide: Lucide.User2Icon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiUserLine }))), +}); + +export const ShieldIcon = createIcon({ + lucide: Lucide.ShieldIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiShieldLine }))), +}); + +export const ArrowRightCircleIcon = createIcon({ + lucide: Lucide.ArrowRightCircleIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiArrowRightCircleLine }))), +}); + +export const HomeIcon = createIcon({ + lucide: Lucide.HomeIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiHomeLine }))), +}); + +export const LogInIcon = createIcon({ + lucide: Lucide.LogInIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiLoginBoxLine }))), +}); + +export const UploadIcon = createIcon({ + lucide: Lucide.UploadIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiUpload2Line }))), +}); + +export const PaletteIcon = createIcon({ + lucide: Lucide.PaletteIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiPaletteLine }))), +}); + +export const UsersIcon = createIcon({ + lucide: Lucide.UsersIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiGroupLine }))), +}); + +export const PlaneIcon = createIcon({ + lucide: Lucide.PlaneIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiPlaneLine }))), +}); + +export const CameraIcon = createIcon({ + lucide: Lucide.CameraIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiCameraLine }))), +}); + +export const ExternalLinkIcon = createIcon({ + lucide: Lucide.ExternalLinkIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiExternalLinkLine }))), +}); + +export const MenuIcon = createIcon({ + lucide: Lucide.MenuIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiMenuLine }))), +}); + +export const CopyIcon = createIcon({ + lucide: Lucide.CopyIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiFileCopyLine }))), +}); + +export const PlusSquareIcon = createIcon({ + lucide: Lucide.PlusSquareIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiAddBoxLine }))), +}); + +export const SquarePenIcon = createIcon({ + lucide: Lucide.SquarePenIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiEditLine }))), +}); + +export const RotateCwIcon = createIcon({ + lucide: Lucide.RotateCwIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiRestartLine }))), +}); + +export const XCircleIcon = createIcon({ + lucide: Lucide.XCircleIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiCloseCircleLine }))), +}); + +export const BoldIcon = createIcon({ + lucide: Lucide.BoldIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiBold }))), +}); + +export const ItalicIcon = createIcon({ + lucide: Lucide.ItalicIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiItalic }))), +}); + +export const TimerIcon = createIcon({ + lucide: Lucide.TimerIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiTimerLine }))), +}); + +export const PinIcon = createIcon({ + lucide: Lucide.PinIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiPushpinLine }))), +}); + +export const ALargeSmallIcon = createIcon({ + lucide: Lucide.ALargeSmallIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiFontSize }))), +}); + +export const Volume1Icon = createIcon({ + lucide: Lucide.Volume1Icon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiVolumeDownLine }))), +}); + +export const Volume2Icon = createIcon({ + lucide: Lucide.Volume2Icon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiVolumeUpLine }))), +}); + +export const ChevronsUpDownIcon = createIcon({ + lucide: Lucide.ChevronsUpDownIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiExpandUpDownLine }))), +}); + +export const PenSquareIcon = createIcon({ + lucide: Lucide.PenSquareIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiEditBoxLine }))), +}); + +export const GripVerticalIcon = createIcon({ + lucide: Lucide.GripVerticalIcon, + remix: React.lazy(() => import("@remixicon/react").then(mod => ({ default: mod.RiDraggable }))), +}); diff --git a/packages/registry/src/base/base.css b/packages/registry/src/base/base.css new file mode 100644 index 000000000..7d932f37d --- /dev/null +++ b/packages/registry/src/base/base.css @@ -0,0 +1,25 @@ +@import "tw-animate-css"; + +@plugin "tailwindcss-react-aria-components"; +@plugin "tailwindcss-autocontrast"; +@plugin "tailwindcss-with"; + +@custom-variant dark { + &:where(.dark, .dark *) { + @slot; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-bg text-fg; + } +} + +::selection { + background-color: var(--accent-800); + color: var(--on-accent-800); +} diff --git a/packages/registry/src/base/base.ts b/packages/registry/src/base/base.ts new file mode 100644 index 000000000..6b8f0372f --- /dev/null +++ b/packages/registry/src/base/base.ts @@ -0,0 +1,25 @@ +import type { RegistryItem } from "@dotui/registry/types"; + +export const registryBase = [ + { + name: "base", + type: "registry:style", + extends: "none", + dependencies: [ + "tailwind-variants", + "clsx", + "tailwind-merge", + "react-aria-components", + "tailwindcss-react-aria-components", + "tw-animate-css", + "tailwindcss-autocontrast", + ], + css: { + "@plugin tailwindcss-react-aria-components": {}, + "@plugin tailwindcss-autocontrast": {}, + }, + + registryDependencies: ["utils", "focus-styles", "theme"], + files: [], + }, +] as const satisfies RegistryItem[]; diff --git a/packages/registry/src/base/bg-patterns.css b/packages/registry/src/base/bg-patterns.css new file mode 100644 index 000000000..e69de29bb diff --git a/packages/registry/src/background-patterns/registry.ts b/packages/registry/src/base/bg-patterns.ts similarity index 100% rename from packages/registry/src/background-patterns/registry.ts rename to packages/registry/src/base/bg-patterns.ts diff --git a/packages/registry/src/base/colors.css b/packages/registry/src/base/colors.css new file mode 100644 index 000000000..0f9945f38 --- /dev/null +++ b/packages/registry/src/base/colors.css @@ -0,0 +1,147 @@ +:root { + --neutral-50: hsl(0, 0%, 98%); + --neutral-100: hsl(0, 0%, 96%); + --neutral-200: hsl(0, 0%, 94%); + --neutral-300: hsl(0, 0%, 90%); + --neutral-400: hsl(0, 0%, 85%); + --neutral-500: hsl(0, 0%, 80%); + --neutral-600: hsl(0, 0%, 75%); + --neutral-700: hsl(0, 0%, 70%); + --neutral-800: hsl(0, 0%, 28%); + --neutral-900: hsl(0, 0%, 16%); + --neutral-950: hsl(0, 0%, 12%); + + --accent-50: hsl(210, 100%, 88%); + --accent-100: hsl(210, 100%, 85%); + --accent-200: hsl(210, 100%, 81%); + --accent-300: hsl(210, 100%, 74%); + --accent-400: hsl(210, 100%, 67%); + --accent-500: hsl(210, 64%, 55%); + --accent-600: hsl(210, 51%, 44%); + --accent-700: hsl(210, 51%, 36%); + --accent-800: hsl(210, 52%, 29%); + --accent-900: hsl(211, 52%, 17%); + --accent-950: hsl(211, 52%, 12%); + + --success-50: hsl(130, 34%, 83%); + --success-100: hsl(130, 34%, 80%); + --success-200: hsl(131, 35%, 75%); + --success-300: hsl(131, 35%, 66%); + --success-400: hsl(132, 35%, 56%); + --success-500: hsl(131, 41%, 43%); + --success-600: hsl(132, 41%, 34%); + --success-700: hsl(132, 41%, 28%); + --success-800: hsl(131, 41%, 23%); + --success-900: hsl(131, 40%, 13%); + --success-950: hsl(132, 42%, 9%); + + --warning-50: hsl(35, 100%, 80%); + --warning-100: hsl(35, 100%, 75%); + --warning-200: hsl(35, 100%, 69%); + --warning-300: hsl(35, 100%, 58%); + --warning-400: hsl(35, 93%, 49%); + --warning-500: hsl(35, 92%, 41%); + --warning-600: hsl(35, 93%, 32%); + --warning-700: hsl(35, 93%, 26%); + --warning-800: hsl(35, 93%, 22%); + --warning-900: hsl(35, 94%, 12%); + --warning-950: hsl(36, 91%, 9%); + + --danger-50: hsl(358, 69%, 90%); + --danger-100: hsl(358, 69%, 88%); + --danger-200: hsl(358, 69%, 85%); + --danger-300: hsl(358, 70%, 79%); + --danger-400: hsl(358, 69%, 73%); + --danger-500: hsl(358, 69%, 63%); + --danger-600: hsl(358, 64%, 49%); + --danger-700: hsl(358, 63%, 41%); + --danger-800: hsl(358, 64%, 33%); + --danger-900: hsl(357, 64%, 20%); + --danger-950: hsl(358, 65%, 15%); + + --info-50: hsl(210, 100%, 88%); + --info-100: hsl(210, 100%, 84%); + --info-200: hsl(210, 100%, 81%); + --info-300: hsl(210, 100%, 74%); + --info-400: hsl(210, 100%, 67%); + --info-500: hsl(210, 64%, 55%); + --info-600: hsl(210, 51%, 44%); + --info-700: hsl(210, 51%, 36%); + --info-800: hsl(210, 52%, 29%); + --info-900: hsl(211, 52%, 17%); + --info-950: hsl(211, 52%, 12%); +} + +.dark { + --neutral-50: hsl(0, 6%, 4%); + --neutral-100: hsl(240, 6%, 8%); + --neutral-200: hsl(240, 6%, 10%); + --neutral-300: hsl(240, 6%, 17%); + --neutral-400: hsl(240, 6%, 23%); + --neutral-500: hsl(240, 6%, 33%); + --neutral-600: hsl(240, 6%, 47%); + --neutral-700: hsl(240, 6%, 55%); + --neutral-800: hsl(240, 6%, 70%); + --neutral-900: hsl(240, 6%, 85%); + --neutral-950: hsl(240, 10%, 98%); + + --accent-50: hsl(211, 51%, 12%); + --accent-100: hsl(211, 51%, 15%); + --accent-200: hsl(211, 52%, 18%); + --accent-300: hsl(210, 52%, 29%); + --accent-400: hsl(209, 51%, 40%); + --accent-500: hsl(210, 52%, 48%); + --accent-600: hsl(210, 51%, 55%); + --accent-700: hsl(210, 68%, 62%); + --accent-800: hsl(210, 97%, 71%); + --accent-900: hsl(210, 100%, 82%); + --accent-950: hsl(210, 100%, 87%); + + --success-50: hsl(131, 44%, 8%); + --success-100: hsl(131, 44%, 11%); + --success-200: hsl(130, 41%, 14%); + --success-300: hsl(131, 41%, 18%); + --success-400: hsl(132, 41%, 22%); + --success-500: hsl(131, 41%, 44%); + --success-600: hsl(131, 41%, 48%); + --success-700: hsl(131, 41%, 54%); + --success-800: hsl(131, 34%, 70%); + --success-900: hsl(131, 34%, 76%); + --success-950: hsl(130, 35%, 83%); + + --warning-50: hsl(35, 100%, 7%); + --warning-100: hsl(35, 100%, 18%); + --warning-200: hsl(34, 100%, 21%); + --warning-300: hsl(35, 96%, 22%); + --warning-400: hsl(35, 100%, 38%); + --warning-500: hsl(35, 100%, 48%); + --warning-600: hsl(35, 100%, 58%); + --warning-700: hsl(35, 100%, 66%); + --warning-800: hsl(35, 93%, 65%); + --warning-900: hsl(35, 100%, 72%); + --warning-950: hsl(35, 100%, 80%); + + --danger-50: hsl(357, 64%, 14%); + --danger-100: hsl(357, 64%, 18%); + --danger-200: hsl(358, 64%, 21%); + --danger-300: hsl(358, 64%, 26%); + --danger-400: hsl(358, 64%, 32%); + --danger-500: hsl(358, 64%, 42%); + --danger-600: hsl(358, 69%, 51%); + --danger-700: hsl(358, 69%, 60%); + --danger-800: hsl(358, 69%, 70%); + --danger-900: hsl(359, 70%, 86%); + --danger-950: hsl(358, 69%, 90%); + + --info-50: hsl(211, 51%, 12%); + --info-100: hsl(211, 51%, 15%); + --info-200: hsl(211, 52%, 18%); + --info-300: hsl(210, 52%, 29%); + --info-400: hsl(209, 51%, 40%); + --info-500: hsl(210, 52%, 48%); + --info-600: hsl(210, 51%, 55%); + --info-700: hsl(210, 68%, 62%); + --info-800: hsl(210, 97%, 71%); + --info-900: hsl(210, 100%, 82%); + --info-950: hsl(210, 100%, 87%); +} diff --git a/packages/registry/src/base/fonts.css b/packages/registry/src/base/fonts.css new file mode 100644 index 000000000..b216023ad --- /dev/null +++ b/packages/registry/src/base/fonts.css @@ -0,0 +1,4 @@ +@theme { + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} \ No newline at end of file diff --git a/packages/registry/src/fonts/registry.ts b/packages/registry/src/base/fonts.ts similarity index 100% rename from packages/registry/src/fonts/registry.ts rename to packages/registry/src/base/fonts.ts diff --git a/packages/registry/src/textures/registry.ts b/packages/registry/src/base/textures.ts similarity index 100% rename from packages/registry/src/textures/registry.ts rename to packages/registry/src/base/textures.ts diff --git a/packages/registry/src/base/theme.css b/packages/registry/src/base/theme.css new file mode 100644 index 000000000..4d52a0573 --- /dev/null +++ b/packages/registry/src/base/theme.css @@ -0,0 +1,126 @@ +/* -------------- Fonts -------------- */ +@theme { + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +/* -------------- Radius -------------- */ +@theme inline { + --radius-xs: calc(0.125rem * var(--radius-factor)); + --radius-sm: calc(0.25rem * var(--radius-factor)); + --radius-md: calc(0.375rem * var(--radius-factor)); + --radius-lg: calc(0.5rem * var(--radius-factor)); + --radius-xl: calc(0.75rem * var(--radius-factor)); + --radius-2xl: calc(1rem * var(--radius-factor)); + --radius-3xl: calc(1.5rem * var(--radius-factor)); + --radius-4xl: calc(2rem * var(--radius-factor)); +} + +/* -------------- Semantic Colors -------------- */ +@theme { + --color-bg: var(--neutral-50); + --color-muted: var(--neutral-200); + --color-inverse: var(--neutral-950); + --color-disabled: var(--neutral-200); + + --color-selected: var(--neutral-300); + --color-selected-hover: var(--neutral-400); + --color-selected-active: var(--neutral-500); + --color-fg-on-selected: var(--on-neutral-300); + + --color-neutral: var(--neutral-200); + --color-neutral-hover: var(--neutral-300); + --color-neutral-active: var(--neutral-400); + + --color-primary: var(--neutral-950); + --color-primary-hover: var(--neutral-900); + --color-primary-active: var(--neutral-800); + --color-primary-muted: var(--neutral-200); + + --color-success: var(--success-500); + --color-success-hover: var(--success-600); + --color-success-active: var(--success-700); + --color-success-muted: var(--success-100); + --color-success-muted-hover: var(--success-200); + + --color-danger: var(--danger-500); + --color-danger-hover: var(--danger-600); + --color-danger-active: var(--danger-700); + --color-danger-muted: var(--danger-100); + --color-danger-muted-hover: var(--danger-200); + + --color-warning: var(--warning-500); + --color-warning-hover: var(--warning-600); + --color-warning-active: var(--warning-700); + --color-warning-muted: var(--warning-100); + --color-warning-muted-hover: var(--warning-200); + + --color-info: var(--info-500); + --color-info-hover: var(--info-600); + --color-info-active: var(--info-700); + --color-info-muted: var(--info-50); + + --color-accent: var(--accent-500); + --color-accent-hover: var(--accent-600); + --color-accent-active: var(--accent-700); + --color-accent-muted: var(--accent-50); + --color-accent-muted-hover: var(--accent-100); + + --color-fg: var(--neutral-950); + --color-fg-muted: var(--neutral-800); + --color-fg-inverse: var(--neutral-50); + --color-fg-disabled: var(--neutral-500); + --color-fg-primary-disabled: var(--neutral-300); + --color-fg-danger: var(--danger-800); + --color-fg-warning: var(--warning-800); + --color-fg-success: var(--success-800); + --color-fg-info: var(--info-800); + --color-fg-accent: var(--accent-800); + + --color-fg-on-neutral: var(--on-neutral-200); + --color-fg-on-primary: var(--on-neutral-950); + --color-fg-on-accent: var(--on-accent-500); + --color-fg-on-success: var(--on-success-500); + --color-fg-on-danger: var(--on-danger-500); + --color-fg-on-warning: var(--on-warning-500); + --color-fg-on-info: var(--on-info-500); + + --color-border: var(--neutral-300); + --color-border-hover: var(--neutral-400); + --color-border-active: var(--neutral-500); + --color-border-field: var(--neutral-400); + --color-border-control: var(--neutral-700); + --color-border-disabled: var(--neutral-300); + --color-border-focus: var(--accent-500); + --color-border-focus-muted: var(--accent-300); + + --color-border-success: var(--success-300); + --color-border-success-hover: var(--success-400); + --color-border-accent: var(--accent-300); + --color-border-accent-hover: var(--accent-400); + --color-border-danger: var(--danger-300); + --color-border-danger-hover: var(--danger-400); + --color-border-warning: var(--warning-300); + --color-border-warning-hover: var(--warning-400); + --color-border-info: var(--info-300); + --color-border-info-hover: var(--info-400); +} + +/* -------------- Component specific colors -------------- */ +@theme { + --color-tooltip: var(--neutral-950); + --color-fg-on-tooltip: var(--on-neutral-950); + --color-card: var(--neutral-100); + --color-popover: var(--neutral-100); + + --color-sidebar: var(--neutral-100); + --color-border-sidebar: var(--neutral-300); +} + +/* -------------- Variant specific colors -------------- */ + +@theme inline { + --shadow-shine: + inset 0px 0px 0px 1px color-mix(in oklab, var(--color-shine) 15%, transparent), inset 0px 1px 0px + color-mix(in oklab, var(--color-shine) 30%, transparent); +} diff --git a/packages/registry/src/tokens/registry.ts b/packages/registry/src/base/tokens.ts similarity index 100% rename from packages/registry/src/tokens/registry.ts rename to packages/registry/src/base/tokens.ts diff --git a/packages/registry/src/blocks/__blocks__.tsx b/packages/registry/src/blocks/__blocks__.tsx deleted file mode 100644 index 738a952fc..000000000 --- a/packages/registry/src/blocks/__blocks__.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -// @ts-nocheck -// This file is autogenerated by scripts/build-registry.ts -// Do not edit this file directly. -import * as React from "react"; - -export const Index: Record< - string, - { - files: string[]; - component: React.LazyExoticComponent>; - } -> = { - login: { - files: [ - "blocks/auth/login/page.tsx", - "blocks/auth/login/components/login-form.tsx", - ], - component: React.lazy(async () => { - const mod = await import("@dotui/registry/blocks/auth/login/page"); - const exportName = - Object.keys(mod).find( - (key) => - typeof mod[key] === "function" || typeof mod[key] === "object", - ) || item.name; - return { default: mod.default || mod[exportName] }; - }), - }, - cards: { - files: [ - "blocks/showcase/cards/components/cards.tsx", - "blocks/showcase/cards/components/account-menu.tsx", - "blocks/showcase/cards/components/backlog.tsx", - "blocks/showcase/cards/components/booking.tsx", - "blocks/showcase/cards/components/color-editor.tsx", - "blocks/showcase/cards/components/filters.tsx", - "blocks/showcase/cards/components/invite-members.tsx", - "blocks/showcase/cards/components/login-form.tsx", - "blocks/showcase/cards/components/notifications.tsx", - "blocks/showcase/cards/components/team-name.tsx", - ], - component: React.lazy(async () => { - const mod = await import( - "@dotui/registry/blocks/showcase/cards/components/cards" - ); - const exportName = - Object.keys(mod).find( - (key) => - typeof mod[key] === "function" || typeof mod[key] === "object", - ) || item.name; - return { default: mod.default || mod[exportName] }; - }), - }, - animation: { - files: ["blocks/showcase/animation/components/animation.tsx"], - component: React.lazy(async () => { - const mod = await import( - "@dotui/registry/blocks/showcase/animation/components/animation" - ); - const exportName = - Object.keys(mod).find( - (key) => - typeof mod[key] === "function" || typeof mod[key] === "object", - ) || item.name; - return { default: mod.default || mod[exportName] }; - }), - }, -}; diff --git a/packages/registry/src/blocks/index.ts b/packages/registry/src/blocks/index.ts index e3cb17348..336e4eeb0 100644 --- a/packages/registry/src/blocks/index.ts +++ b/packages/registry/src/blocks/index.ts @@ -1 +1 @@ -export { Index } from "./__blocks__"; +export { BlocksIndex as Index } from "../__generated__/blocks"; diff --git a/packages/registry/src/blocks/showcase/animation/components/animation.tsx b/packages/registry/src/blocks/showcase/animation/components/animation.tsx index 571f362d8..7ed494c46 100644 --- a/packages/registry/src/blocks/showcase/animation/components/animation.tsx +++ b/packages/registry/src/blocks/showcase/animation/components/animation.tsx @@ -31,3 +31,5 @@ export function Animation({
); } + +export default Animation; diff --git a/packages/registry/src/blocks/showcase/cards/components/cards.tsx b/packages/registry/src/blocks/showcase/cards/components/cards.tsx index 13c881edb..dc28e1312 100644 --- a/packages/registry/src/blocks/showcase/cards/components/cards.tsx +++ b/packages/registry/src/blocks/showcase/cards/components/cards.tsx @@ -35,3 +35,5 @@ export function Cards(props: React.ComponentProps<"div">) {
); } + +export default Cards; diff --git a/packages/registry/src/constants.ts b/packages/registry/src/constants.ts deleted file mode 100644 index e407edc34..000000000 --- a/packages/registry/src/constants.ts +++ /dev/null @@ -1,222 +0,0 @@ -import type { - ColorTokens, - Fonts, - IconLibrary, - ModeDefinition, - StyleDefinition, - ThemeDefinition, - Variants, - VariantsDefinition, -} from "@dotui/style-system/types"; - -import { registry } from "./index"; -import { COLOR_TOKENS } from "./tokens/registry"; - -export const DEFAULT_RADIUS_FACTOR = 1; - -export const DEFAULT_SPACING = 0.25; - -export const DEFAULT_LETTER_SPACING = 0; - -export const DEFAULT_BACKGROUND_PATTERN = "none"; - -export const DEFAULT_TEXTURE = "none"; - -export const DEFAULT_SHADOWS = "default"; - -export const DEFAULT_ICON_LIBRARY: IconLibrary = "lucide"; -export const DEFAULT_ICON_STROKE_WIDTH = 2; - -export const DEFAULT_ACCENT_EMPHASIS_LEVEL = 1; - -export const DEFAULT_FONTS: Fonts = { - heading: "Inter", - body: "Inter", -}; - -export const DEFAULT_VARIANTS_DEFINITION: VariantsDefinition = { - alert: "basic", - buttons: "basic", - loader: "ring", - inputs: "basic", - pickers: "basic", - selection: "basic", - calendars: "basic", - "list-box-and-menu": "basic", - overlays: "basic", - checkboxes: "basic", - radios: "basic", - switch: "basic", - slider: "basic", - "badge-and-tag-group": "basic", - tooltip: "basic", - link: "basic", - card: "basic", - skeleton: "basic", - - "focus-style": "basic", -}; - -export const DEFAULT_VARIANTS = Object.fromEntries( - registry - .filter((item) => item.defaultVariant !== undefined) - .map((item) => [item.name, item.defaultVariant]), -) as unknown as Variants; - -export const DEFAULT_CSS = { - "@layer base": { - "*": { - "border-color": "var(--color-border)", - }, - body: { - "background-color": "var(--color-bg)", - color: "var(--color-fg)", - }, - }, -}; - -export const SCALE_STEPS = [ - 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950, -] as const; - -export const DEFAULT_LIGHT_MODE: ModeDefinition = { - lightness: 97, - saturation: 100, - contrast: 100, - scales: { - neutral: { - name: "neutral", - colorKeys: ["#ffffff"], - ratios: [1.05, 1.15, 1.25, 1.7, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - accent: { - name: "accent", - colorKeys: ["#0091FF"], - ratios: [1.25, 1.35, 1.5, 1.8, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - success: { - name: "success", - colorKeys: ["#1A9338"], - ratios: [1.25, 1.35, 1.5, 1.8, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - warning: { - name: "warning", - colorKeys: ["#E79D13"], - ratios: [1.25, 1.35, 1.5, 1.8, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - danger: { - name: "danger", - colorKeys: ["#D93036"], - ratios: [1.25, 1.35, 1.5, 1.8, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - info: { - name: "info", - colorKeys: ["#0091FF"], - ratios: [1.25, 1.35, 1.5, 1.8, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - }, -}; - -export const DEFAULT_DARK_MODE: ModeDefinition = { - lightness: 3, - saturation: 100, - contrast: 100, - scales: { - neutral: { - name: "neutral", - colorKeys: ["#000000"], - ratios: [1, 1.15, 1.25, 1.7, 2.23, 3.16, 4.78, 6.36, 8.28, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - accent: { - name: "accent", - colorKeys: ["#0091FF"], - ratios: [1.25, 1.35, 1.5, 1.8, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - success: { - name: "success", - colorKeys: ["#1A9338"], - ratios: [1.25, 1.35, 1.5, 1.8, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - warning: { - name: "warning", - colorKeys: ["#E79D13"], - ratios: [1.25, 1.35, 1.5, 1.8, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - danger: { - name: "danger", - colorKeys: ["#D93036"], - ratios: [1.25, 1.35, 1.5, 1.8, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - info: { - name: "info", - colorKeys: ["#0091FF"], - ratios: [1.25, 1.35, 1.5, 1.8, 2.25, 3.15, 4.8, 6.35, 8.3, 13.2, 15.2], - overrides: {}, - smooth: false, - }, - }, -}; - -export const DEFAULT_TOKENS: ColorTokens = Object.fromEntries( - Object.entries(COLOR_TOKENS).map(([key, value]) => [ - key, - { - name: key, - value: value.defaultValue, - }, - ]), -); - -export const DEFAULT_THEME: ThemeDefinition = { - colors: { - activeModes: ["light", "dark"], - modes: { - light: DEFAULT_LIGHT_MODE, - dark: DEFAULT_DARK_MODE, - }, - tokens: DEFAULT_TOKENS, - accentEmphasisLevel: DEFAULT_ACCENT_EMPHASIS_LEVEL, - }, - fonts: { - heading: DEFAULT_FONTS.heading, - body: DEFAULT_FONTS.body, - }, - spacing: DEFAULT_SPACING, - - shadows: DEFAULT_SHADOWS, - radius: DEFAULT_RADIUS_FACTOR, - texture: DEFAULT_TEXTURE, - backgroundPattern: DEFAULT_BACKGROUND_PATTERN, - letterSpacing: DEFAULT_LETTER_SPACING, -}; - -export const DEFAULT_STYLE: StyleDefinition = { - theme: DEFAULT_THEME, - icons: { - library: DEFAULT_ICON_LIBRARY, - strokeWidth: DEFAULT_ICON_STROKE_WIDTH, - }, - variants: DEFAULT_VARIANTS_DEFINITION, -}; diff --git a/packages/registry/src/icons/__icons__.tsx b/packages/registry/src/icons/__icons__.tsx deleted file mode 100644 index e46092394..000000000 --- a/packages/registry/src/icons/__icons__.tsx +++ /dev/null @@ -1,352 +0,0 @@ -// This file is autogenerated by scripts/build-registry.ts -// Do not edit this file directly. -"use client"; - -import * as React from "react"; -import * as Lucide from "lucide-react"; - -import { createIcon } from "./create-icon"; - -export const Loader2Icon = createIcon({ - lucide: Lucide.Loader2Icon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiLoader4Line })), - ), -}); - -export const AlertCircleIcon = createIcon({ - lucide: Lucide.AlertCircleIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiErrorWarningFill, - })), - ), -}); - -export const AlertTriangleIcon = createIcon({ - lucide: Lucide.AlertTriangleIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiAlertLine })), - ), -}); - -export const CheckCircle2Icon = createIcon({ - lucide: Lucide.CheckCircle2Icon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiCheckboxCircleLine, - })), - ), -}); - -export const InfoIcon = createIcon({ - lucide: Lucide.InfoIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiInformationLine, - })), - ), -}); - -export const ChevronRightIcon = createIcon({ - lucide: Lucide.ChevronRightIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiArrowRightSLine, - })), - ), -}); - -export const ChevronLeftIcon = createIcon({ - lucide: Lucide.ChevronLeftIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiArrowLeftSLine, - })), - ), -}); - -export const ChevronDownIcon = createIcon({ - lucide: Lucide.ChevronDownIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiArrowDownSLine, - })), - ), -}); - -export const ChevronUpIcon = createIcon({ - lucide: Lucide.ChevronUpIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiArrowUpSLine })), - ), -}); - -export const SearchIcon = createIcon({ - lucide: Lucide.SearchIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiSearchLine })), - ), -}); - -export const HelpCircleIcon = createIcon({ - lucide: Lucide.HelpCircleIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiQuestionLine })), - ), -}); - -export const CalendarIcon = createIcon({ - lucide: Lucide.CalendarIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiCalendarLine })), - ), -}); - -export const XIcon = createIcon({ - lucide: Lucide.XIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiCloseLine })), - ), -}); - -export const AsteriskIcon = createIcon({ - lucide: Lucide.AsteriskIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiAsterisk })), - ), -}); - -export const CheckIcon = createIcon({ - lucide: Lucide.CheckIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiCheckLine })), - ), -}); - -export const MinusIcon = createIcon({ - lucide: Lucide.MinusIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiSubtractLine })), - ), -}); - -export const PlusIcon = createIcon({ - lucide: Lucide.PlusIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiAddLine })), - ), -}); - -export const WalletIcon = createIcon({ - lucide: Lucide.WalletIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiWalletLine })), - ), -}); - -export const GlobeIcon = createIcon({ - lucide: Lucide.GlobeIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiGlobalLine })), - ), -}); - -export const User2Icon = createIcon({ - lucide: Lucide.User2Icon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiUserLine })), - ), -}); - -export const ShieldIcon = createIcon({ - lucide: Lucide.ShieldIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiShieldLine })), - ), -}); - -export const ArrowRightCircleIcon = createIcon({ - lucide: Lucide.ArrowRightCircleIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiArrowRightCircleLine, - })), - ), -}); - -export const HomeIcon = createIcon({ - lucide: Lucide.HomeIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiHomeLine })), - ), -}); - -export const LogInIcon = createIcon({ - lucide: Lucide.LogInIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiLoginBoxLine })), - ), -}); - -export const UploadIcon = createIcon({ - lucide: Lucide.UploadIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiUpload2Line })), - ), -}); - -export const PaletteIcon = createIcon({ - lucide: Lucide.PaletteIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiPaletteLine })), - ), -}); - -export const UsersIcon = createIcon({ - lucide: Lucide.UsersIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiGroupLine })), - ), -}); - -export const PlaneIcon = createIcon({ - lucide: Lucide.PlaneIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiPlaneLine })), - ), -}); - -export const CameraIcon = createIcon({ - lucide: Lucide.CameraIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiCameraLine })), - ), -}); - -export const ExternalLinkIcon = createIcon({ - lucide: Lucide.ExternalLinkIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiExternalLinkLine, - })), - ), -}); - -export const MenuIcon = createIcon({ - lucide: Lucide.MenuIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiMenuLine })), - ), -}); - -export const CopyIcon = createIcon({ - lucide: Lucide.CopyIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiFileCopyLine })), - ), -}); - -export const PlusSquareIcon = createIcon({ - lucide: Lucide.PlusSquareIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiAddBoxLine })), - ), -}); - -export const SquarePenIcon = createIcon({ - lucide: Lucide.SquarePenIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiEditLine })), - ), -}); - -export const RotateCwIcon = createIcon({ - lucide: Lucide.RotateCwIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiRestartLine })), - ), -}); - -export const XCircleIcon = createIcon({ - lucide: Lucide.XCircleIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiCloseCircleLine, - })), - ), -}); - -export const BoldIcon = createIcon({ - lucide: Lucide.BoldIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiBold })), - ), -}); - -export const ItalicIcon = createIcon({ - lucide: Lucide.ItalicIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiItalic })), - ), -}); - -export const TimerIcon = createIcon({ - lucide: Lucide.TimerIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiTimerLine })), - ), -}); - -export const PinIcon = createIcon({ - lucide: Lucide.PinIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiPushpinLine })), - ), -}); - -export const ALargeSmallIcon = createIcon({ - lucide: Lucide.ALargeSmallIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiFontSize })), - ), -}); - -export const Volume1Icon = createIcon({ - lucide: Lucide.Volume1Icon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiVolumeDownLine, - })), - ), -}); - -export const Volume2Icon = createIcon({ - lucide: Lucide.Volume2Icon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiVolumeUpLine })), - ), -}); - -export const ChevronsUpDownIcon = createIcon({ - lucide: Lucide.ChevronsUpDownIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ - default: mod.RiExpandUpDownLine, - })), - ), -}); - -export const PenSquareIcon = createIcon({ - lucide: Lucide.PenSquareIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiEditBoxLine })), - ), -}); - -export const GripVerticalIcon = createIcon({ - lucide: Lucide.GripVerticalIcon, - remix: React.lazy(() => - import("@remixicon/react").then((mod) => ({ default: mod.RiDraggable })), - ), -}); diff --git a/packages/registry/src/icons/index.ts b/packages/registry/src/icons/index.ts index d2ef07957..e1cfbfa49 100644 --- a/packages/registry/src/icons/index.ts +++ b/packages/registry/src/icons/index.ts @@ -1 +1 @@ -export * from "./__icons__"; +export * from "../__generated__/icons"; diff --git a/packages/registry/src/index.ts b/packages/registry/src/index.ts deleted file mode 100644 index e40badc47..000000000 --- a/packages/registry/src/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { RegistryItem } from "@dotui/registry/types"; - -import { registryBase } from "./base/registry"; -import { registryBlocks } from "./blocks/registry"; -import { registryHooks } from "./hooks/registry"; -import { registryLib } from "./lib/registry"; -import { registryUi } from "./ui/registry"; - -export const registry: RegistryItem[] = [ - ...registryBase, - ...registryUi, - ...registryBlocks, - ...registryHooks, - ...registryLib, -]; diff --git a/packages/registry/src/lib/focus-styles/basic.css b/packages/registry/src/lib/focus-styles/basic.css new file mode 100644 index 000000000..e69de29bb diff --git a/packages/registry/src/providers.tsx b/packages/registry/src/providers.tsx deleted file mode 100644 index 86ed79d9b..000000000 --- a/packages/registry/src/providers.tsx +++ /dev/null @@ -1,60 +0,0 @@ -"use client"; - -import { - StyleProvider as BaseStyleProvider, - ThemeProvider as BaseThemeProvider, -} from "@dotui/style-system/providers"; -import type { - StyleProviderProps as BaseStyleProviderProps, - ThemeProviderProps as BaseThemeProviderProps, -} from "@dotui/style-system/providers"; - -import { registryBackgroundPatterns } from "./background-patterns/registry"; -import { cn } from "./lib/utils"; -import { registryTextures } from "./textures/registry"; - -// Re-export types -export type StyleProviderProps = Omit< - BaseStyleProviderProps, - "textures" | "backgroundPatterns" | "cn" ->; -export type ThemeProviderProps = Omit< - BaseThemeProviderProps, - "textures" | "backgroundPatterns" | "cn" ->; - -/** - * StyleProvider with registry dependencies injected - */ -export const StyleProvider = (props: StyleProviderProps) => { - return ( - - ); -}; - -/** - * ThemeProvider with registry dependencies injected - */ -export const ThemeProvider = (props: ThemeProviderProps) => { - return ( - - ); -}; - -// Re-export other providers and hooks from style-system -export { - FontLoader, - useCurrentStyle, - useVariant, - VariantsProvider, -} from "@dotui/style-system/providers"; diff --git a/packages/registry/src/schemas.test.ts b/packages/registry/src/schemas.test.ts deleted file mode 100644 index 2a4db5da1..000000000 --- a/packages/registry/src/schemas.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { registry } from "./index"; -import { variantsSchema } from "./schemas"; - -describe("variantsSchema completeness", () => { - it("includes all registry components with variants", () => { - const registryComponents = registry - .filter((item) => item.variants && Object.keys(item.variants).length > 0) - .map((item) => item.name) - .sort(); - - const schemaKeys = Object.keys(variantsSchema.shape).sort(); - const missing = registryComponents.filter((n) => !schemaKeys.includes(n)); - const extra = schemaKeys.filter((n) => !registryComponents.includes(n)); - - if (missing.length > 0 || extra.length > 0) { - let errorMsg = "\n"; - if (missing.length > 0) { - errorMsg += `āŒ Missing in variantsSchema:\n${missing.map((n) => ` - ${n}`).join("\n")}\n\n`; - errorMsg += `Add to schemas.ts:\n${missing.map((n) => ` "${n}": z.enum(${n}Variants),`).join("\n")}\n`; - } - if (extra.length > 0) { - errorMsg += `āŒ Extra in variantsSchema (not in registry):\n${extra.map((n) => ` - ${n}`).join("\n")}\n`; - } - throw new Error(errorMsg); - } - - expect(schemaKeys).toEqual(registryComponents); - }); -}); diff --git a/packages/registry/src/schemas.ts b/packages/registry/src/schemas.ts deleted file mode 100644 index dd427f4b9..000000000 --- a/packages/registry/src/schemas.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { z } from "zod"; - -import { registryBackgroundPatterns } from "@dotui/registry/background-patterns/registry"; -import { iconLibraries } from "@dotui/registry/icons/registry"; -import { focusStylesVariants } from "@dotui/registry/lib/focus-styles/meta"; -import { registryTextures } from "@dotui/registry/textures/registry"; -import { accordionVariants } from "@dotui/registry/ui/accordion/meta"; -import { alertVariants } from "@dotui/registry/ui/alert/meta"; -import { avatarVariants } from "@dotui/registry/ui/avatar/meta"; -import { badgeVariants } from "@dotui/registry/ui/badge/meta"; -import { breadcrumbsVariants } from "@dotui/registry/ui/breadcrumbs/meta"; -import { buttonVariants } from "@dotui/registry/ui/button/meta"; -import { calendarVariants } from "@dotui/registry/ui/calendar/meta"; -import { cardVariants } from "@dotui/registry/ui/card/meta"; -import { checkboxVariants } from "@dotui/registry/ui/checkbox/meta"; -import { checkboxGroupVariants } from "@dotui/registry/ui/checkbox-group/meta"; -import { colorAreaVariants } from "@dotui/registry/ui/color-area/meta"; -import { colorEditorVariants } from "@dotui/registry/ui/color-editor/meta"; -import { colorFieldVariants } from "@dotui/registry/ui/color-field/meta"; -import { colorPickerVariants } from "@dotui/registry/ui/color-picker/meta"; -import { colorSliderVariants } from "@dotui/registry/ui/color-slider/meta"; -import { colorSwatchVariants } from "@dotui/registry/ui/color-swatch/meta"; -import { colorSwatchPickerVariants } from "@dotui/registry/ui/color-swatch-picker/meta"; -import { colorThumbVariants } from "@dotui/registry/ui/color-thumb/meta"; -import { comboboxVariants } from "@dotui/registry/ui/combobox/meta"; -import { commandVariants } from "@dotui/registry/ui/command/meta"; -import { dateFieldVariants } from "@dotui/registry/ui/date-field/meta"; -import { datePickerVariants } from "@dotui/registry/ui/date-picker/meta"; -import { dialogVariants } from "@dotui/registry/ui/dialog/meta"; -import { disclosureVariants } from "@dotui/registry/ui/disclosure/meta"; -import { drawerVariants } from "@dotui/registry/ui/drawer/meta"; -import { dropZoneVariants } from "@dotui/registry/ui/drop-zone/meta"; -import { emptyVariants } from "@dotui/registry/ui/empty/meta"; -import { fieldVariants } from "@dotui/registry/ui/field/meta"; -import { fileTriggerVariants } from "@dotui/registry/ui/file-trigger/meta"; -import { groupVariants } from "@dotui/registry/ui/group/meta"; -import { inputVariants } from "@dotui/registry/ui/input/meta"; -import { kbdVariants } from "@dotui/registry/ui/kbd/meta"; -import { linkVariants } from "@dotui/registry/ui/link/meta"; -import { listBoxVariants } from "@dotui/registry/ui/list-box/meta"; -import { loaderVariants } from "@dotui/registry/ui/loader/meta"; -import { menuVariants } from "@dotui/registry/ui/menu/meta"; -import { modalVariants } from "@dotui/registry/ui/modal/meta"; -import { numberFieldVariants } from "@dotui/registry/ui/number-field/meta"; -import { overlayVariants } from "@dotui/registry/ui/overlay/meta"; -import { popoverVariants } from "@dotui/registry/ui/popover/meta"; -import { progressBarVariants } from "@dotui/registry/ui/progress-bar/meta"; -import { radioGroupVariants } from "@dotui/registry/ui/radio-group/meta"; -import { searchFieldVariants } from "@dotui/registry/ui/search-field/meta"; -import { selectVariants } from "@dotui/registry/ui/select/meta"; -import { separatorVariants } from "@dotui/registry/ui/separator/meta"; -import { skeletonVariants } from "@dotui/registry/ui/skeleton/meta"; -import { sliderVariants } from "@dotui/registry/ui/slider/meta"; -import { switchVariants } from "@dotui/registry/ui/switch/meta"; -import { tableVariants } from "@dotui/registry/ui/table/meta"; -import { tabsVariants } from "@dotui/registry/ui/tabs/meta"; -import { tagGroupVariants } from "@dotui/registry/ui/tag-group/meta"; -import { textVariants } from "@dotui/registry/ui/text/meta"; -import { textFieldVariants } from "@dotui/registry/ui/text-field/meta"; -import { timeFieldVariants } from "@dotui/registry/ui/time-field/meta"; -import { toastVariants } from "@dotui/registry/ui/toast/meta"; -import { toggleButtonVariants } from "@dotui/registry/ui/toggle-button/meta"; -import { toggleButtonGroupVariants } from "@dotui/registry/ui/toggle-button-group/meta"; -import { tooltipVariants } from "@dotui/registry/ui/tooltip/meta"; - -// --------------------------------- Definitions ----------------------------------- // - -// Icons -export const iconLibrarySchema = z.enum(iconLibraries.map((lib) => lib.name)); -export const iconsDefinitionSchema = z.object({ - library: iconLibrarySchema, - strokeWidth: z.number().min(0.5).max(3), -}); - -// Colors -export const colorScaleSchema = z.object({ - name: z.string().min(1), - colorKeys: z.array(z.string()).min(1), - ratios: z.array(z.number().min(0)), - smooth: z.boolean(), - overrides: z.record(z.string(), z.string()), -}); - -export const modeDefinitionSchema = z.object({ - lightness: z.number().min(0).max(100), - saturation: z.number().min(0).max(100), - contrast: z.number().min(0).max(500), - scales: z.object({ - neutral: colorScaleSchema, - accent: colorScaleSchema, - success: colorScaleSchema, - warning: colorScaleSchema, - danger: colorScaleSchema, - info: colorScaleSchema, - }), -}); - -export const colorTokenSchema = z.object({ - name: z.string(), - value: z.string(), -}); - -export const colorTokensSchema = z.record(z.string(), colorTokenSchema); - -// layout -export const radiusSchema = z.number().min(0).max(2); -export const spacingSchema = z.number().min(0.1).max(0.35); - -// typography -export const fontsSchema = z.object({ - heading: z.string().min(2), - body: z.string().min(2), -}); -export const letterSpacingSchema = z.number().min(-0.05).max(0.1); - -// effects -export const backgroundPatternSchema = z.enum([ - "none", - ...registryBackgroundPatterns.map((bgPattern) => bgPattern.slug), -]); -export const textureSchema = z.enum([ - "none", - ...registryTextures.map((texture) => texture.slug), -]); -export const shadowPresetSchema = z.enum(["default"]); -export const shadowsSchema = z.union([ - shadowPresetSchema, - z.object({ - color: z.string(), - opacity: z.number().min(0).max(1), - blurRadius: z.number().min(0).max(100), - offsetX: z.number().min(0).max(100), - offsetY: z.number().min(0).max(100), - spread: z.number().min(0).max(100), - }), -]); - -export const activeModesSchema = z - .array(z.enum(["light", "dark"]).and(z.string())) - .refine((modes) => modes.length > 0, { - message: "At least one mode must be defined", - }); - -// theme -export const themeDefinitionSchema = z.object({ - colors: z.object({ - activeModes: activeModesSchema, - modes: z.object({ - light: modeDefinitionSchema, - dark: modeDefinitionSchema, - }), - tokens: colorTokensSchema, - accentEmphasisLevel: z.number().min(0).max(3), - }), - radius: radiusSchema, - spacing: spacingSchema, - fonts: fontsSchema, - letterSpacing: letterSpacingSchema, - backgroundPattern: backgroundPatternSchema, - texture: textureSchema, - shadows: shadowsSchema, -}); - -// Variants -export const variantsDefinitionSchema = z.object({ - alert: z.enum(alertVariants), - buttons: z.enum(buttonVariants), - calendars: z.enum(calendarVariants), - card: z.enum(cardVariants), - checkboxes: z.enum(checkboxVariants), - "focus-style": z.enum(focusStylesVariants), - inputs: z.enum(inputVariants), - link: z.enum(linkVariants), - "list-box-and-menu": z.enum(listBoxVariants), - loader: z.enum(loaderVariants), - overlays: z.enum(overlayVariants), - pickers: z.enum(selectVariants), - radios: z.enum(radioGroupVariants), - selection: z.enum(selectVariants), - skeleton: z.enum(skeletonVariants), - slider: z.enum(sliderVariants), - switch: z.enum(switchVariants), - "badge-and-tag-group": z.enum(badgeVariants), - tooltip: z.enum(tooltipVariants), -}); - -export const styleDefinitionSchema = z.object({ - theme: themeDefinitionSchema, - icons: iconsDefinitionSchema, - variants: variantsDefinitionSchema, -}); - -// --------------------------------- Minimized definitions ----------------------------------- // - -export const minimizedColorTokensSchema = colorTokensSchema.optional(); - -export const minimizedColorScaleSchema = z.object({ - name: z.string().min(1).optional(), - colorKeys: z.array(z.string()).min(1).optional(), - ratios: z.array(z.number().min(0)).optional(), - smooth: z.boolean().optional(), - overrides: z.record(z.string(), z.string()).optional(), -}); - -export const minimizedModeDefinitionSchema = z.object({ - lightness: z.number().min(0).max(100).optional(), - saturation: z.number().min(0).max(100).optional(), - contrast: z.number().min(0).max(500).optional(), - scales: z - .object({ - neutral: minimizedColorScaleSchema.optional(), - accent: minimizedColorScaleSchema.optional(), - success: minimizedColorScaleSchema.optional(), - warning: minimizedColorScaleSchema.optional(), - danger: minimizedColorScaleSchema.optional(), - info: minimizedColorScaleSchema.optional(), - }) - .and(z.record(z.string(), minimizedColorScaleSchema)) - .optional(), -}); - -export const minimizedThemeDefinitionSchema = z.object({ - colors: z.object({ - activeModes: activeModesSchema.optional(), - modes: z - .object({ - light: minimizedModeDefinitionSchema.optional(), - dark: minimizedModeDefinitionSchema.optional(), - }) - .optional(), - tokens: colorTokensSchema.optional(), - accentEmphasisLevel: z.number().min(0).max(3).optional(), - }), - radius: radiusSchema.optional(), - spacing: spacingSchema.optional(), - fonts: fontsSchema.partial().optional(), - letterSpacing: letterSpacingSchema.optional(), - backgroundPattern: backgroundPatternSchema.optional(), - texture: textureSchema.optional(), - shadows: shadowsSchema.optional(), -}); - -export const minimizedVariantsDefinitionSchema = - variantsDefinitionSchema.partial(); - -export const minimizedIconsDefinitionSchema = iconsDefinitionSchema - .partial() - .optional(); - -export const minimizedStyleDefinitionSchema = z.object({ - theme: minimizedThemeDefinitionSchema, - icons: minimizedIconsDefinitionSchema.optional().nullable(), - variants: minimizedVariantsDefinitionSchema.optional().nullable(), -}); - -// --------------------------------- Processed ----------------------------------- // - -export const CssSchema = z.record( - z.string(), - z.union([ - z.string(), - z.record( - z.string(), - z.union([z.string(), z.record(z.string(), z.string())]), - ), - ]), -); - -// Theme will include all the cssVars and css need for colors, radius, spacing, fonts, backgroundPattern, texture and shadows. -export const themeSchema = z.object({ - css: CssSchema.optional(), - cssVars: z.object({ - light: z.record(z.string(), z.string()), - dark: z.record(z.string(), z.string()).optional(), - theme: z.record(z.string(), z.string()), - }), -}); - -/** - * Variants schema built statically from imported variant arrays - * This approach preserves full type information for perfect TypeScript inference - * - * Each UI component from the registry gets its own entry here, enabling - * proper autocomplete and type safety for all variants - */ -export const variantsSchema = z.object({ - accordion: z.enum(accordionVariants), - alert: z.enum(alertVariants), - avatar: z.enum(avatarVariants), - badge: z.enum(badgeVariants), - breadcrumbs: z.enum(breadcrumbsVariants), - button: z.enum(buttonVariants), - calendar: z.enum(calendarVariants), - card: z.enum(cardVariants), - checkbox: z.enum(checkboxVariants), - "checkbox-group": z.enum(checkboxGroupVariants), - "color-area": z.enum(colorAreaVariants), - "color-editor": z.enum(colorEditorVariants), - "color-field": z.enum(colorFieldVariants), - "color-picker": z.enum(colorPickerVariants), - "color-slider": z.enum(colorSliderVariants), - "color-swatch": z.enum(colorSwatchVariants), - "color-swatch-picker": z.enum(colorSwatchPickerVariants), - "color-thumb": z.enum(colorThumbVariants), - combobox: z.enum(comboboxVariants), - command: z.enum(commandVariants), - "date-field": z.enum(dateFieldVariants), - "date-picker": z.enum(datePickerVariants), - dialog: z.enum(dialogVariants), - disclosure: z.enum(disclosureVariants), - drawer: z.enum(drawerVariants), - "drop-zone": z.enum(dropZoneVariants), - empty: z.enum(emptyVariants), - field: z.enum(fieldVariants), - "file-trigger": z.enum(fileTriggerVariants), - "focus-styles": z.enum(focusStylesVariants), - group: z.enum(groupVariants), - input: z.enum(inputVariants), - kbd: z.enum(kbdVariants), - link: z.enum(linkVariants), - "list-box": z.enum(listBoxVariants), - loader: z.enum(loaderVariants), - menu: z.enum(menuVariants), - modal: z.enum(modalVariants), - "number-field": z.enum(numberFieldVariants), - overlay: z.enum(overlayVariants), - popover: z.enum(popoverVariants), - "progress-bar": z.enum(progressBarVariants), - "radio-group": z.enum(radioGroupVariants), - "search-field": z.enum(searchFieldVariants), - select: z.enum(selectVariants), - separator: z.enum(separatorVariants), - skeleton: z.enum(skeletonVariants), - slider: z.enum(sliderVariants), - switch: z.enum(switchVariants), - table: z.enum(tableVariants), - tabs: z.enum(tabsVariants), - "tag-group": z.enum(tagGroupVariants), - text: z.enum(textVariants), - "text-field": z.enum(textFieldVariants), - "time-field": z.enum(timeFieldVariants), - toast: z.enum(toastVariants), - "toggle-button": z.enum(toggleButtonVariants), - "toggle-button-group": z.enum(toggleButtonGroupVariants), - tooltip: z.enum(tooltipVariants), -}); - -export const styleSchema = z.object({ - theme: themeSchema, - icons: iconsDefinitionSchema, - variants: variantsSchema, -}); diff --git a/packages/registry/src/styles.css b/packages/registry/src/styles.css new file mode 100644 index 000000000..551db0edc --- /dev/null +++ b/packages/registry/src/styles.css @@ -0,0 +1,4 @@ +@import "@dotui/registry/base.css"; +@import "@dotui/registry/colors.css"; +@import "@dotui/registry/theme.css"; +@import "@dotui/registry/fonts.css"; diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index 4b6df1ff3..d1fe693dd 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -1,13 +1,10 @@ -import type { - Registry as ShadcnRegistry, - RegistryItem as ShadcnRegistryItem, -} from "shadcn/schema"; +import type { Registry as ShadcnRegistry, RegistryItem as ShadcnRegistryItem } from "shadcn/schema"; export interface RegistryItem extends ShadcnRegistryItem { - variants?: Record>; - defaultVariant?: string; + variants?: Record>; + defaultVariant?: string; } export interface Registry extends Omit { - items: RegistryItem[]; + items: RegistryItem[]; } diff --git a/packages/registry/src/ui/__demos__.tsx b/packages/registry/src/ui/__demos__.tsx deleted file mode 100644 index a4f92423d..000000000 --- a/packages/registry/src/ui/__demos__.tsx +++ /dev/null @@ -1,2449 +0,0 @@ -// This file is autogenerated by scripts/build-registry.ts -// Do not edit this file directly. -import * as React from "react"; - -export const Index: Record< - string, - { - files: string[]; - component: React.LazyExoticComponent>; - } -> = { - "accordion/demos/allows-multiple": { - files: ["ui/accordion/demos/allows-multiple.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/accordion/demos/allows-multiple"), - ), - }, - "accordion/demos/basic": { - files: ["ui/accordion/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/accordion/demos/basic"), - ), - }, - "accordion/demos/controlled": { - files: ["ui/accordion/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/accordion/demos/controlled"), - ), - }, - "accordion/demos/default-expanded": { - files: ["ui/accordion/demos/default-expanded.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/accordion/demos/default-expanded"), - ), - }, - "accordion/demos/disabled": { - files: ["ui/accordion/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/accordion/demos/disabled"), - ), - }, - "alert/demos/action": { - files: ["ui/alert/demos/action.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/alert/demos/action"), - ), - }, - "alert/demos/custom-icon": { - files: ["ui/alert/demos/custom-icon.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/alert/demos/custom-icon"), - ), - }, - "alert/demos/danger": { - files: ["ui/alert/demos/danger.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/alert/demos/danger"), - ), - }, - "alert/demos/default": { - files: ["ui/alert/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/alert/demos/default"), - ), - }, - "alert/demos/success": { - files: ["ui/alert/demos/success.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/alert/demos/success"), - ), - }, - "alert/demos/warning": { - files: ["ui/alert/demos/warning.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/alert/demos/warning"), - ), - }, - "avatar/demos/composition": { - files: ["ui/avatar/demos/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/avatar/demos/composition"), - ), - }, - "avatar/demos/default": { - files: ["ui/avatar/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/avatar/demos/default"), - ), - }, - "avatar/demos/shape": { - files: ["ui/avatar/demos/shape.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/avatar/demos/shape"), - ), - }, - "avatar/demos/sizes": { - files: ["ui/avatar/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/avatar/demos/sizes"), - ), - }, - "badge/demos/default": { - files: ["ui/badge/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/badge/demos/default"), - ), - }, - "badge/demos/icon": { - files: ["ui/badge/demos/icon.tsx"], - component: React.lazy(() => import("@dotui/registry/ui/badge/demos/icon")), - }, - "badge/demos/sizes": { - files: ["ui/badge/demos/sizes.tsx"], - component: React.lazy(() => import("@dotui/registry/ui/badge/demos/sizes")), - }, - "badge/demos/variants": { - files: ["ui/badge/demos/variants.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/badge/demos/variants"), - ), - }, - "breadcrumbs/demos/basic": { - files: ["ui/breadcrumbs/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/breadcrumbs/demos/basic"), - ), - }, - "breadcrumbs/demos/composition": { - files: ["ui/breadcrumbs/demos/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/breadcrumbs/demos/composition"), - ), - }, - "breadcrumbs/demos/disabled": { - files: ["ui/breadcrumbs/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/breadcrumbs/demos/disabled"), - ), - }, - "breadcrumbs/demos/icon": { - files: ["ui/breadcrumbs/demos/icon.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/breadcrumbs/demos/icon"), - ), - }, - "breadcrumbs/demos/router-integration": { - files: ["ui/breadcrumbs/demos/router-integration.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/breadcrumbs/demos/router-integration"), - ), - }, - "button/demos/default": { - files: ["ui/button/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/button/demos/default"), - ), - }, - "button/demos/disabled": { - files: ["ui/button/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/button/demos/disabled"), - ), - }, - "button/demos/link-button": { - files: ["ui/button/demos/link-button.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/button/demos/link-button"), - ), - }, - "button/demos/loading": { - files: ["ui/button/demos/loading.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/button/demos/loading"), - ), - }, - "button/demos/prefix-and-suffix": { - files: ["ui/button/demos/prefix-and-suffix.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/button/demos/prefix-and-suffix"), - ), - }, - "button/demos/shapes": { - files: ["ui/button/demos/shapes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/button/demos/shapes"), - ), - }, - "button/demos/sizes": { - files: ["ui/button/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/button/demos/sizes"), - ), - }, - "button/demos/variants": { - files: ["ui/button/demos/variants.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/button/demos/variants"), - ), - }, - "calendar/demos/calendar/composition": { - files: ["ui/calendar/demos/calendar/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/composition"), - ), - }, - "calendar/demos/calendar/controlled": { - files: ["ui/calendar/demos/calendar/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/controlled"), - ), - }, - "calendar/demos/calendar/default": { - files: ["ui/calendar/demos/calendar/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/default"), - ), - }, - "calendar/demos/calendar/disabled": { - files: ["ui/calendar/demos/calendar/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/disabled"), - ), - }, - "calendar/demos/calendar/error-message": { - files: ["ui/calendar/demos/calendar/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/error-message"), - ), - }, - "calendar/demos/calendar/page-behaviour": { - files: ["ui/calendar/demos/calendar/page-behaviour.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/page-behaviour"), - ), - }, - "calendar/demos/calendar/read-only": { - files: ["ui/calendar/demos/calendar/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/read-only"), - ), - }, - "calendar/demos/calendar/uncontrolled": { - files: ["ui/calendar/demos/calendar/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/uncontrolled"), - ), - }, - "calendar/demos/calendar/unvailable-dates": { - files: ["ui/calendar/demos/calendar/unvailable-dates.tsx"], - component: React.lazy( - () => - import("@dotui/registry/ui/calendar/demos/calendar/unvailable-dates"), - ), - }, - "calendar/demos/calendar/validation": { - files: ["ui/calendar/demos/calendar/validation.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/validation"), - ), - }, - "calendar/demos/calendar/variant": { - files: ["ui/calendar/demos/calendar/variant.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/variant"), - ), - }, - "calendar/demos/calendar/visible-months": { - files: ["ui/calendar/demos/calendar/visible-months.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/calendar/visible-months"), - ), - }, - "calendar/demos/range-calendar/composition": { - files: ["ui/calendar/demos/range-calendar/composition.tsx"], - component: React.lazy( - () => - import("@dotui/registry/ui/calendar/demos/range-calendar/composition"), - ), - }, - "calendar/demos/range-calendar/controlled": { - files: ["ui/calendar/demos/range-calendar/controlled.tsx"], - component: React.lazy( - () => - import("@dotui/registry/ui/calendar/demos/range-calendar/controlled"), - ), - }, - "calendar/demos/range-calendar/default": { - files: ["ui/calendar/demos/range-calendar/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/range-calendar/default"), - ), - }, - "calendar/demos/range-calendar/disabled": { - files: ["ui/calendar/demos/range-calendar/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/range-calendar/disabled"), - ), - }, - "calendar/demos/range-calendar/error-message": { - files: ["ui/calendar/demos/range-calendar/error-message.tsx"], - component: React.lazy( - () => - import( - "@dotui/registry/ui/calendar/demos/range-calendar/error-message" - ), - ), - }, - "calendar/demos/range-calendar/non-contiguous-ranges": { - files: ["ui/calendar/demos/range-calendar/non-contiguous-ranges.tsx"], - component: React.lazy( - () => - import( - "@dotui/registry/ui/calendar/demos/range-calendar/non-contiguous-ranges" - ), - ), - }, - "calendar/demos/range-calendar/page-behaviour": { - files: ["ui/calendar/demos/range-calendar/page-behaviour.tsx"], - component: React.lazy( - () => - import( - "@dotui/registry/ui/calendar/demos/range-calendar/page-behaviour" - ), - ), - }, - "calendar/demos/range-calendar/read-only": { - files: ["ui/calendar/demos/range-calendar/read-only.tsx"], - component: React.lazy( - () => - import("@dotui/registry/ui/calendar/demos/range-calendar/read-only"), - ), - }, - "calendar/demos/range-calendar/uncontrolled": { - files: ["ui/calendar/demos/range-calendar/uncontrolled.tsx"], - component: React.lazy( - () => - import("@dotui/registry/ui/calendar/demos/range-calendar/uncontrolled"), - ), - }, - "calendar/demos/range-calendar/unvailable-dates": { - files: ["ui/calendar/demos/range-calendar/unvailable-dates.tsx"], - component: React.lazy( - () => - import( - "@dotui/registry/ui/calendar/demos/range-calendar/unvailable-dates" - ), - ), - }, - "calendar/demos/range-calendar/validation": { - files: ["ui/calendar/demos/range-calendar/validation.tsx"], - component: React.lazy( - () => - import("@dotui/registry/ui/calendar/demos/range-calendar/validation"), - ), - }, - "calendar/demos/range-calendar/variant": { - files: ["ui/calendar/demos/range-calendar/variant.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/calendar/demos/range-calendar/variant"), - ), - }, - "calendar/demos/range-calendar/visible-months": { - files: ["ui/calendar/demos/range-calendar/visible-months.tsx"], - component: React.lazy( - () => - import( - "@dotui/registry/ui/calendar/demos/range-calendar/visible-months" - ), - ), - }, - "checkbox/demos/card": { - files: ["ui/checkbox/demos/card.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox/demos/card"), - ), - }, - "checkbox/demos/composition": { - files: ["ui/checkbox/demos/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox/demos/composition"), - ), - }, - "checkbox/demos/controlled": { - files: ["ui/checkbox/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox/demos/controlled"), - ), - }, - "checkbox/demos/default": { - files: ["ui/checkbox/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox/demos/default"), - ), - }, - "checkbox/demos/disabled": { - files: ["ui/checkbox/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox/demos/disabled"), - ), - }, - "checkbox/demos/indeterminate": { - files: ["ui/checkbox/demos/indeterminate.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox/demos/indeterminate"), - ), - }, - "checkbox/demos/read-only": { - files: ["ui/checkbox/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox/demos/read-only"), - ), - }, - "checkbox/demos/uncontrolled": { - files: ["ui/checkbox/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox/demos/uncontrolled"), - ), - }, - "checkbox-group/demos/cards": { - files: ["ui/checkbox-group/demos/cards.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox-group/demos/cards"), - ), - }, - "checkbox-group/demos/controlled": { - files: ["ui/checkbox-group/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox-group/demos/controlled"), - ), - }, - "checkbox-group/demos/default": { - files: ["ui/checkbox-group/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox-group/demos/default"), - ), - }, - "checkbox-group/demos/description": { - files: ["ui/checkbox-group/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox-group/demos/description"), - ), - }, - "checkbox-group/demos/disabled": { - files: ["ui/checkbox-group/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox-group/demos/disabled"), - ), - }, - "checkbox-group/demos/error-message": { - files: ["ui/checkbox-group/demos/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox-group/demos/error-message"), - ), - }, - "checkbox-group/demos/label": { - files: ["ui/checkbox-group/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox-group/demos/label"), - ), - }, - "checkbox-group/demos/read-only": { - files: ["ui/checkbox-group/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox-group/demos/read-only"), - ), - }, - "checkbox-group/demos/required": { - files: ["ui/checkbox-group/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox-group/demos/required"), - ), - }, - "checkbox-group/demos/uncontrolled": { - files: ["ui/checkbox-group/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/checkbox-group/demos/uncontrolled"), - ), - }, - "color-area/demos/channels": { - files: ["ui/color-area/demos/channels.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-area/demos/channels"), - ), - }, - "color-area/demos/controlled": { - files: ["ui/color-area/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-area/demos/controlled"), - ), - }, - "color-area/demos/default": { - files: ["ui/color-area/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-area/demos/default"), - ), - }, - "color-area/demos/disabled": { - files: ["ui/color-area/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-area/demos/disabled"), - ), - }, - "color-area/demos/uncontrolled": { - files: ["ui/color-area/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-area/demos/uncontrolled"), - ), - }, - "color-editor/demos/default": { - files: ["ui/color-editor/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-editor/demos/default"), - ), - }, - "color-field/demos/color-channel": { - files: ["ui/color-field/demos/color-channel.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/color-channel"), - ), - }, - "color-field/demos/controlled": { - files: ["ui/color-field/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/controlled"), - ), - }, - "color-field/demos/default": { - files: ["ui/color-field/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/default"), - ), - }, - "color-field/demos/description": { - files: ["ui/color-field/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/description"), - ), - }, - "color-field/demos/disabled": { - files: ["ui/color-field/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/disabled"), - ), - }, - "color-field/demos/error-message": { - files: ["ui/color-field/demos/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/error-message"), - ), - }, - "color-field/demos/label": { - files: ["ui/color-field/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/label"), - ), - }, - "color-field/demos/prefix-and-suffix": { - files: ["ui/color-field/demos/prefix-and-suffix.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/prefix-and-suffix"), - ), - }, - "color-field/demos/read-only": { - files: ["ui/color-field/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/read-only"), - ), - }, - "color-field/demos/required": { - files: ["ui/color-field/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/required"), - ), - }, - "color-field/demos/sizes": { - files: ["ui/color-field/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/sizes"), - ), - }, - "color-field/demos/uncontrolled": { - files: ["ui/color-field/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-field/demos/uncontrolled"), - ), - }, - "color-picker/demos/channel-sliders": { - files: ["ui/color-picker/demos/channel-sliders.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-picker/demos/channel-sliders"), - ), - }, - "color-picker/demos/controlled": { - files: ["ui/color-picker/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-picker/demos/controlled"), - ), - }, - "color-picker/demos/default": { - files: ["ui/color-picker/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-picker/demos/default"), - ), - }, - "color-picker/demos/swatches": { - files: ["ui/color-picker/demos/swatches.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-picker/demos/swatches"), - ), - }, - "color-picker/demos/uncontrolled": { - files: ["ui/color-picker/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-picker/demos/uncontrolled"), - ), - }, - "color-slider/demos/channel": { - files: ["ui/color-slider/demos/channel.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-slider/demos/channel"), - ), - }, - "color-slider/demos/controlled": { - files: ["ui/color-slider/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-slider/demos/controlled"), - ), - }, - "color-slider/demos/default": { - files: ["ui/color-slider/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-slider/demos/default"), - ), - }, - "color-slider/demos/disabled": { - files: ["ui/color-slider/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-slider/demos/disabled"), - ), - }, - "color-slider/demos/label": { - files: ["ui/color-slider/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-slider/demos/label"), - ), - }, - "color-slider/demos/uncontrolled": { - files: ["ui/color-slider/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-slider/demos/uncontrolled"), - ), - }, - "color-slider/demos/vertical": { - files: ["ui/color-slider/demos/vertical.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-slider/demos/vertical"), - ), - }, - "color-swatch/demos/default": { - files: ["ui/color-swatch/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-swatch/demos/default"), - ), - }, - "color-swatch-picker/demos/basic": { - files: ["ui/color-swatch-picker/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-swatch-picker/demos/basic"), - ), - }, - "color-swatch-picker/demos/controlled": { - files: ["ui/color-swatch-picker/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-swatch-picker/demos/controlled"), - ), - }, - "color-swatch-picker/demos/disabled": { - files: ["ui/color-swatch-picker/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/color-swatch-picker/demos/disabled"), - ), - }, - "combobox/demos/async-loading": { - files: ["ui/combobox/demos/async-loading.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/async-loading"), - ), - }, - "combobox/demos/basic": { - files: ["ui/combobox/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/basic"), - ), - }, - "combobox/demos/controlled": { - files: ["ui/combobox/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/controlled"), - ), - }, - "combobox/demos/custom-value": { - files: ["ui/combobox/demos/custom-value.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/custom-value"), - ), - }, - "combobox/demos/description": { - files: ["ui/combobox/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/description"), - ), - }, - "combobox/demos/disabled": { - files: ["ui/combobox/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/disabled"), - ), - }, - "combobox/demos/label": { - files: ["ui/combobox/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/label"), - ), - }, - "combobox/demos/loading": { - files: ["ui/combobox/demos/loading.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/loading"), - ), - }, - "combobox/demos/required": { - files: ["ui/combobox/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/required"), - ), - }, - "combobox/demos/sections": { - files: ["ui/combobox/demos/sections.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/sections"), - ), - }, - "combobox/demos/uncontrolled": { - files: ["ui/combobox/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/uncontrolled"), - ), - }, - "combobox/demos/validation": { - files: ["ui/combobox/demos/validation.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/combobox/demos/validation"), - ), - }, - "command/demos/basic": { - files: ["ui/command/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/command/demos/basic"), - ), - }, - "command/demos/dialog": { - files: ["ui/command/demos/dialog.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/command/demos/dialog"), - ), - }, - "date-field/demos/controlled": { - files: ["ui/date-field/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/controlled"), - ), - }, - "date-field/demos/default": { - files: ["ui/date-field/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/default"), - ), - }, - "date-field/demos/description": { - files: ["ui/date-field/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/description"), - ), - }, - "date-field/demos/disabled": { - files: ["ui/date-field/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/disabled"), - ), - }, - "date-field/demos/error-message": { - files: ["ui/date-field/demos/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/error-message"), - ), - }, - "date-field/demos/granularity": { - files: ["ui/date-field/demos/granularity.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/granularity"), - ), - }, - "date-field/demos/hide-time-zone": { - files: ["ui/date-field/demos/hide-time-zone.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/hide-time-zone"), - ), - }, - "date-field/demos/hour-cycle": { - files: ["ui/date-field/demos/hour-cycle.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/hour-cycle"), - ), - }, - "date-field/demos/label": { - files: ["ui/date-field/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/label"), - ), - }, - "date-field/demos/placeholder": { - files: ["ui/date-field/demos/placeholder.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/placeholder"), - ), - }, - "date-field/demos/prefix-and-suffix": { - files: ["ui/date-field/demos/prefix-and-suffix.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/prefix-and-suffix"), - ), - }, - "date-field/demos/read-only": { - files: ["ui/date-field/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/read-only"), - ), - }, - "date-field/demos/required": { - files: ["ui/date-field/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/required"), - ), - }, - "date-field/demos/sizes": { - files: ["ui/date-field/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/sizes"), - ), - }, - "date-field/demos/time-zones": { - files: ["ui/date-field/demos/time-zones.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/time-zones"), - ), - }, - "date-field/demos/uncontrolled": { - files: ["ui/date-field/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-field/demos/uncontrolled"), - ), - }, - "date-picker/demos/composition": { - files: ["ui/date-picker/demos/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/composition"), - ), - }, - "date-picker/demos/controlled": { - files: ["ui/date-picker/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/controlled"), - ), - }, - "date-picker/demos/default": { - files: ["ui/date-picker/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/default"), - ), - }, - "date-picker/demos/description": { - files: ["ui/date-picker/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/description"), - ), - }, - "date-picker/demos/disabled": { - files: ["ui/date-picker/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/disabled"), - ), - }, - "date-picker/demos/error-message": { - files: ["ui/date-picker/demos/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/error-message"), - ), - }, - "date-picker/demos/granularity": { - files: ["ui/date-picker/demos/granularity.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/granularity"), - ), - }, - "date-picker/demos/hide-time-zone": { - files: ["ui/date-picker/demos/hide-time-zone.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/hide-time-zone"), - ), - }, - "date-picker/demos/hour-cycle": { - files: ["ui/date-picker/demos/hour-cycle.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/hour-cycle"), - ), - }, - "date-picker/demos/label": { - files: ["ui/date-picker/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/label"), - ), - }, - "date-picker/demos/placeholder": { - files: ["ui/date-picker/demos/placeholder.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/placeholder"), - ), - }, - "date-picker/demos/range/composition": { - files: ["ui/date-picker/demos/range/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/composition"), - ), - }, - "date-picker/demos/range/controlled": { - files: ["ui/date-picker/demos/range/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/controlled"), - ), - }, - "date-picker/demos/range/default": { - files: ["ui/date-picker/demos/range/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/default"), - ), - }, - "date-picker/demos/range/description": { - files: ["ui/date-picker/demos/range/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/description"), - ), - }, - "date-picker/demos/range/disabled": { - files: ["ui/date-picker/demos/range/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/disabled"), - ), - }, - "date-picker/demos/range/error-message": { - files: ["ui/date-picker/demos/range/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/error-message"), - ), - }, - "date-picker/demos/range/granularity": { - files: ["ui/date-picker/demos/range/granularity.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/granularity"), - ), - }, - "date-picker/demos/range/hide-time-zone": { - files: ["ui/date-picker/demos/range/hide-time-zone.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/hide-time-zone"), - ), - }, - "date-picker/demos/range/hour-cycle": { - files: ["ui/date-picker/demos/range/hour-cycle.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/hour-cycle"), - ), - }, - "date-picker/demos/range/label": { - files: ["ui/date-picker/demos/range/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/label"), - ), - }, - "date-picker/demos/range/placeholder": { - files: ["ui/date-picker/demos/range/placeholder.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/placeholder"), - ), - }, - "date-picker/demos/range/read-only": { - files: ["ui/date-picker/demos/range/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/read-only"), - ), - }, - "date-picker/demos/range/required": { - files: ["ui/date-picker/demos/range/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/required"), - ), - }, - "date-picker/demos/range/time-zones": { - files: ["ui/date-picker/demos/range/time-zones.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/time-zones"), - ), - }, - "date-picker/demos/range/uncontrolled": { - files: ["ui/date-picker/demos/range/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/range/uncontrolled"), - ), - }, - "date-picker/demos/read-only": { - files: ["ui/date-picker/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/read-only"), - ), - }, - "date-picker/demos/required": { - files: ["ui/date-picker/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/required"), - ), - }, - "date-picker/demos/time-zones": { - files: ["ui/date-picker/demos/time-zones.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/time-zones"), - ), - }, - "date-picker/demos/uncontrolled": { - files: ["ui/date-picker/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/date-picker/demos/uncontrolled"), - ), - }, - "dialog/demos/alert-dialog": { - files: ["ui/dialog/demos/alert-dialog.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/alert-dialog"), - ), - }, - "dialog/demos/async-form-submission": { - files: ["ui/dialog/demos/async-form-submission.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/async-form-submission"), - ), - }, - "dialog/demos/basic": { - files: ["ui/dialog/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/basic"), - ), - }, - "dialog/demos/composition": { - files: ["ui/dialog/demos/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/composition"), - ), - }, - "dialog/demos/controlled": { - files: ["ui/dialog/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/controlled"), - ), - }, - "dialog/demos/description": { - files: ["ui/dialog/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/description"), - ), - }, - "dialog/demos/dismissable": { - files: ["ui/dialog/demos/dismissable.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/dismissable"), - ), - }, - "dialog/demos/drawer": { - files: ["ui/dialog/demos/drawer.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/drawer"), - ), - }, - "dialog/demos/inset-content": { - files: ["ui/dialog/demos/inset-content.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/inset-content"), - ), - }, - "dialog/demos/nested": { - files: ["ui/dialog/demos/nested.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/nested"), - ), - }, - "dialog/demos/popover": { - files: ["ui/dialog/demos/popover.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/popover"), - ), - }, - "dialog/demos/title": { - files: ["ui/dialog/demos/title.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/title"), - ), - }, - "dialog/demos/types": { - files: ["ui/dialog/demos/types.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/dialog/demos/types"), - ), - }, - "disclosure/demos/advanced-composition": { - files: ["ui/disclosure/demos/advanced-composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/disclosure/demos/advanced-composition"), - ), - }, - "disclosure/demos/basic": { - files: ["ui/disclosure/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/disclosure/demos/basic"), - ), - }, - "disclosure/demos/controlled": { - files: ["ui/disclosure/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/disclosure/demos/controlled"), - ), - }, - "disclosure/demos/default-expanded": { - files: ["ui/disclosure/demos/default-expanded.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/disclosure/demos/default-expanded"), - ), - }, - "disclosure/demos/disabled": { - files: ["ui/disclosure/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/disclosure/demos/disabled"), - ), - }, - "drawer/demos/basic": { - files: ["ui/drawer/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/drawer/demos/basic"), - ), - }, - "drawer/demos/placement": { - files: ["ui/drawer/demos/placement.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/drawer/demos/placement"), - ), - }, - "drop-zone/demos/basic": { - files: ["ui/drop-zone/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/drop-zone/demos/basic"), - ), - }, - "drop-zone/demos/disabled": { - files: ["ui/drop-zone/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/drop-zone/demos/disabled"), - ), - }, - "drop-zone/demos/events": { - files: ["ui/drop-zone/demos/events.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/drop-zone/demos/events"), - ), - }, - "drop-zone/demos/file-trigger": { - files: ["ui/drop-zone/demos/file-trigger.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/drop-zone/demos/file-trigger"), - ), - }, - "drop-zone/demos/label": { - files: ["ui/drop-zone/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/drop-zone/demos/label"), - ), - }, - "drop-zone/demos/visual-feedback": { - files: ["ui/drop-zone/demos/visual-feedback.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/drop-zone/demos/visual-feedback"), - ), - }, - "empty/demos/empty-projects": { - files: ["ui/empty/demos/empty-projects.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/empty/demos/empty-projects"), - ), - }, - "file-trigger/demos/default": { - files: ["ui/file-trigger/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/file-trigger/demos/default"), - ), - }, - "file-trigger/demos/directory-selection": { - files: ["ui/file-trigger/demos/directory-selection.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/file-trigger/demos/directory-selection"), - ), - }, - "file-trigger/demos/file-types": { - files: ["ui/file-trigger/demos/file-types.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/file-trigger/demos/file-types"), - ), - }, - "file-trigger/demos/media-capture": { - files: ["ui/file-trigger/demos/media-capture.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/file-trigger/demos/media-capture"), - ), - }, - "file-trigger/demos/multiple-files": { - files: ["ui/file-trigger/demos/multiple-files.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/file-trigger/demos/multiple-files"), - ), - }, - "form/demos/basic": { - files: ["ui/form/demos/basic.tsx"], - component: React.lazy(() => import("@dotui/registry/ui/form/demos/basic")), - }, - "form/demos/react-aria": { - files: ["ui/form/demos/react-aria.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/form/demos/react-aria"), - ), - }, - "group/demos/default": { - files: ["ui/group/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/group/demos/default"), - ), - }, - "list-box/demos/async-loading": { - files: ["ui/list-box/demos/async-loading.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/async-loading"), - ), - }, - "list-box/demos/basic": { - files: ["ui/list-box/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/basic"), - ), - }, - "list-box/demos/composition": { - files: ["ui/list-box/demos/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/composition"), - ), - }, - "list-box/demos/controlled": { - files: ["ui/list-box/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/controlled"), - ), - }, - "list-box/demos/disabled-items": { - files: ["ui/list-box/demos/disabled-items.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/disabled-items"), - ), - }, - "list-box/demos/empty-state": { - files: ["ui/list-box/demos/empty-state.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/empty-state"), - ), - }, - "list-box/demos/grid": { - files: ["ui/list-box/demos/grid.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/grid"), - ), - }, - "list-box/demos/horizontal": { - files: ["ui/list-box/demos/horizontal.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/horizontal"), - ), - }, - "list-box/demos/item-variant": { - files: ["ui/list-box/demos/item-variant.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/item-variant"), - ), - }, - "list-box/demos/label-and-description": { - files: ["ui/list-box/demos/label-and-description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/label-and-description"), - ), - }, - "list-box/demos/links": { - files: ["ui/list-box/demos/links.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/links"), - ), - }, - "list-box/demos/loading": { - files: ["ui/list-box/demos/loading.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/loading"), - ), - }, - "list-box/demos/prefix-and-suffix": { - files: ["ui/list-box/demos/prefix-and-suffix.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/prefix-and-suffix"), - ), - }, - "list-box/demos/sections": { - files: ["ui/list-box/demos/sections.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/sections"), - ), - }, - "list-box/demos/selection-behavior": { - files: ["ui/list-box/demos/selection-behavior.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/selection-behavior"), - ), - }, - "list-box/demos/selection-mode": { - files: ["ui/list-box/demos/selection-mode.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/selection-mode"), - ), - }, - "list-box/demos/separator": { - files: ["ui/list-box/demos/separator.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/separator"), - ), - }, - "list-box/demos/uncontrolled": { - files: ["ui/list-box/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/list-box/demos/uncontrolled"), - ), - }, - "menu/demos/basic": { - files: ["ui/menu/demos/basic.tsx"], - component: React.lazy(() => import("@dotui/registry/ui/menu/demos/basic")), - }, - "menu/demos/controlled": { - files: ["ui/menu/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/controlled"), - ), - }, - "menu/demos/disabled-items": { - files: ["ui/menu/demos/disabled-items.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/disabled-items"), - ), - }, - "menu/demos/item-variant": { - files: ["ui/menu/demos/item-variant.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/item-variant"), - ), - }, - "menu/demos/label-and-description": { - files: ["ui/menu/demos/label-and-description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/label-and-description"), - ), - }, - "menu/demos/link-items": { - files: ["ui/menu/demos/link-items.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/link-items"), - ), - }, - "menu/demos/long-press": { - files: ["ui/menu/demos/long-press.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/long-press"), - ), - }, - "menu/demos/multiple-selection": { - files: ["ui/menu/demos/multiple-selection.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/multiple-selection"), - ), - }, - "menu/demos/overlay-type": { - files: ["ui/menu/demos/overlay-type.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/overlay-type"), - ), - }, - "menu/demos/placement": { - files: ["ui/menu/demos/placement.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/placement"), - ), - }, - "menu/demos/prefix-and-suffix": { - files: ["ui/menu/demos/prefix-and-suffix.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/prefix-and-suffix"), - ), - }, - "menu/demos/section": { - files: ["ui/menu/demos/section.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/section"), - ), - }, - "menu/demos/separator": { - files: ["ui/menu/demos/separator.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/separator"), - ), - }, - "menu/demos/shortcut": { - files: ["ui/menu/demos/shortcut.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/shortcut"), - ), - }, - "menu/demos/single-selection": { - files: ["ui/menu/demos/single-selection.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/single-selection"), - ), - }, - "menu/demos/submenus": { - files: ["ui/menu/demos/submenus.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/menu/demos/submenus"), - ), - }, - "modal/demos/basic": { - files: ["ui/modal/demos/basic.tsx"], - component: React.lazy(() => import("@dotui/registry/ui/modal/demos/basic")), - }, - "number-field/demos/controlled": { - files: ["ui/number-field/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/controlled"), - ), - }, - "number-field/demos/default": { - files: ["ui/number-field/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/default"), - ), - }, - "number-field/demos/description": { - files: ["ui/number-field/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/description"), - ), - }, - "number-field/demos/disabled": { - files: ["ui/number-field/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/disabled"), - ), - }, - "number-field/demos/error-message": { - files: ["ui/number-field/demos/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/error-message"), - ), - }, - "number-field/demos/format-options": { - files: ["ui/number-field/demos/format-options.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/format-options"), - ), - }, - "number-field/demos/label": { - files: ["ui/number-field/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/label"), - ), - }, - "number-field/demos/read-only": { - files: ["ui/number-field/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/read-only"), - ), - }, - "number-field/demos/required": { - files: ["ui/number-field/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/required"), - ), - }, - "number-field/demos/sizes": { - files: ["ui/number-field/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/sizes"), - ), - }, - "number-field/demos/uncontrolled": { - files: ["ui/number-field/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/number-field/demos/uncontrolled"), - ), - }, - "overlay/demos/basic": { - files: ["ui/overlay/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/overlay/demos/basic"), - ), - }, - "overlay/demos/type": { - files: ["ui/overlay/demos/type.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/overlay/demos/type"), - ), - }, - "popover/demos/basic": { - files: ["ui/popover/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/popover/demos/basic"), - ), - }, - "progress-bar/demos/custom-value-label": { - files: ["ui/progress-bar/demos/custom-value-label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/custom-value-label"), - ), - }, - "progress-bar/demos/default": { - files: ["ui/progress-bar/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/default"), - ), - }, - "progress-bar/demos/duration": { - files: ["ui/progress-bar/demos/duration.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/duration"), - ), - }, - "progress-bar/demos/format-options": { - files: ["ui/progress-bar/demos/format-options.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/format-options"), - ), - }, - "progress-bar/demos/indeterminate": { - files: ["ui/progress-bar/demos/indeterminate.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/indeterminate"), - ), - }, - "progress-bar/demos/label": { - files: ["ui/progress-bar/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/label"), - ), - }, - "progress-bar/demos/min-max-values": { - files: ["ui/progress-bar/demos/min-max-values.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/min-max-values"), - ), - }, - "progress-bar/demos/shape": { - files: ["ui/progress-bar/demos/shape.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/shape"), - ), - }, - "progress-bar/demos/sizes": { - files: ["ui/progress-bar/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/sizes"), - ), - }, - "progress-bar/demos/value-label": { - files: ["ui/progress-bar/demos/value-label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/value-label"), - ), - }, - "progress-bar/demos/variants": { - files: ["ui/progress-bar/demos/variants.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/progress-bar/demos/variants"), - ), - }, - "radio-group/demos/cards": { - files: ["ui/radio-group/demos/cards.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/cards"), - ), - }, - "radio-group/demos/controlled": { - files: ["ui/radio-group/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/controlled"), - ), - }, - "radio-group/demos/default": { - files: ["ui/radio-group/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/default"), - ), - }, - "radio-group/demos/description": { - files: ["ui/radio-group/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/description"), - ), - }, - "radio-group/demos/disabled": { - files: ["ui/radio-group/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/disabled"), - ), - }, - "radio-group/demos/error-message": { - files: ["ui/radio-group/demos/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/error-message"), - ), - }, - "radio-group/demos/label": { - files: ["ui/radio-group/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/label"), - ), - }, - "radio-group/demos/orientation": { - files: ["ui/radio-group/demos/orientation.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/orientation"), - ), - }, - "radio-group/demos/read-only": { - files: ["ui/radio-group/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/read-only"), - ), - }, - "radio-group/demos/required": { - files: ["ui/radio-group/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/required"), - ), - }, - "radio-group/demos/uncontrolled": { - files: ["ui/radio-group/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/radio-group/demos/uncontrolled"), - ), - }, - "react-hook-form/demos/register": { - files: ["ui/react-hook-form/demos/register.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/react-hook-form/demos/register"), - ), - }, - "search-field/demos/controlled": { - files: ["ui/search-field/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/search-field/demos/controlled"), - ), - }, - "search-field/demos/default": { - files: ["ui/search-field/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/search-field/demos/default"), - ), - }, - "search-field/demos/description": { - files: ["ui/search-field/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/search-field/demos/description"), - ), - }, - "search-field/demos/disabled": { - files: ["ui/search-field/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/search-field/demos/disabled"), - ), - }, - "search-field/demos/error-message": { - files: ["ui/search-field/demos/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/search-field/demos/error-message"), - ), - }, - "search-field/demos/label": { - files: ["ui/search-field/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/search-field/demos/label"), - ), - }, - "search-field/demos/read-only": { - files: ["ui/search-field/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/search-field/demos/read-only"), - ), - }, - "search-field/demos/required": { - files: ["ui/search-field/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/search-field/demos/required"), - ), - }, - "search-field/demos/sizes": { - files: ["ui/search-field/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/search-field/demos/sizes"), - ), - }, - "search-field/demos/uncontrolled": { - files: ["ui/search-field/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/search-field/demos/uncontrolled"), - ), - }, - "select/demos/async-loading": { - files: ["ui/select/demos/async-loading.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/async-loading"), - ), - }, - "select/demos/basic": { - files: ["ui/select/demos/basic.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/basic"), - ), - }, - "select/demos/composition": { - files: ["ui/select/demos/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/composition"), - ), - }, - "select/demos/controlled": { - files: ["ui/select/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/controlled"), - ), - }, - "select/demos/description": { - files: ["ui/select/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/description"), - ), - }, - "select/demos/disabled": { - files: ["ui/select/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/disabled"), - ), - }, - "select/demos/label": { - files: ["ui/select/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/label"), - ), - }, - "select/demos/links": { - files: ["ui/select/demos/links.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/links"), - ), - }, - "select/demos/loading": { - files: ["ui/select/demos/loading.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/loading"), - ), - }, - "select/demos/placeholder": { - files: ["ui/select/demos/placeholder.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/placeholder"), - ), - }, - "select/demos/required": { - files: ["ui/select/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/required"), - ), - }, - "select/demos/sections": { - files: ["ui/select/demos/sections.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/sections"), - ), - }, - "select/demos/uncontrolled": { - files: ["ui/select/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/uncontrolled"), - ), - }, - "select/demos/validation": { - files: ["ui/select/demos/validation.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/select/demos/validation"), - ), - }, - "separator/demos/card": { - files: ["ui/separator/demos/card.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/separator/demos/card"), - ), - }, - "separator/demos/orientation": { - files: ["ui/separator/demos/orientation.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/separator/demos/orientation"), - ), - }, - "skeleton/demos/api-simulation": { - files: ["ui/skeleton/demos/api-simulation.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/skeleton/demos/api-simulation"), - ), - }, - "skeleton/demos/card": { - files: ["ui/skeleton/demos/card.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/skeleton/demos/card"), - ), - }, - "skeleton/demos/children": { - files: ["ui/skeleton/demos/children.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/skeleton/demos/children"), - ), - }, - "skeleton/demos/fixed-size-children": { - files: ["ui/skeleton/demos/fixed-size-children.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/skeleton/demos/fixed-size-children"), - ), - }, - "skeleton/demos/show": { - files: ["ui/skeleton/demos/show.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/skeleton/demos/show"), - ), - }, - "slider/demos/composition": { - files: ["ui/slider/demos/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/composition"), - ), - }, - "slider/demos/controlled": { - files: ["ui/slider/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/controlled"), - ), - }, - "slider/demos/default": { - files: ["ui/slider/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/default"), - ), - }, - "slider/demos/description": { - files: ["ui/slider/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/description"), - ), - }, - "slider/demos/disabled": { - files: ["ui/slider/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/disabled"), - ), - }, - "slider/demos/format-options": { - files: ["ui/slider/demos/format-options.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/format-options"), - ), - }, - "slider/demos/label": { - files: ["ui/slider/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/label"), - ), - }, - "slider/demos/range": { - files: ["ui/slider/demos/range.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/range"), - ), - }, - "slider/demos/sizes": { - files: ["ui/slider/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/sizes"), - ), - }, - "slider/demos/step": { - files: ["ui/slider/demos/step.tsx"], - component: React.lazy(() => import("@dotui/registry/ui/slider/demos/step")), - }, - "slider/demos/uncontrolled": { - files: ["ui/slider/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/uncontrolled"), - ), - }, - "slider/demos/value-label": { - files: ["ui/slider/demos/value-label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/value-label"), - ), - }, - "slider/demos/value-scale": { - files: ["ui/slider/demos/value-scale.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/value-scale"), - ), - }, - "slider/demos/vertical": { - files: ["ui/slider/demos/vertical.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/slider/demos/vertical"), - ), - }, - "switch/demos/card": { - files: ["ui/switch/demos/card.tsx"], - component: React.lazy(() => import("@dotui/registry/ui/switch/demos/card")), - }, - "switch/demos/composition": { - files: ["ui/switch/demos/composition.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/switch/demos/composition"), - ), - }, - "switch/demos/controlled": { - files: ["ui/switch/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/switch/demos/controlled"), - ), - }, - "switch/demos/default": { - files: ["ui/switch/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/switch/demos/default"), - ), - }, - "switch/demos/disabled": { - files: ["ui/switch/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/switch/demos/disabled"), - ), - }, - "switch/demos/label": { - files: ["ui/switch/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/switch/demos/label"), - ), - }, - "switch/demos/sizes": { - files: ["ui/switch/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/switch/demos/sizes"), - ), - }, - "switch/demos/uncontrolled": { - files: ["ui/switch/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/switch/demos/uncontrolled"), - ), - }, - "table/demos/basic": { - files: ["ui/table/demos/basic.tsx"], - component: React.lazy(() => import("@dotui/registry/ui/table/demos/basic")), - }, - "table/demos/column-resizing": { - files: ["ui/table/demos/column-resizing.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/column-resizing"), - ), - }, - "table/demos/controlled": { - files: ["ui/table/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/controlled"), - ), - }, - "table/demos/disabled-rows": { - files: ["ui/table/demos/disabled-rows.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/disabled-rows"), - ), - }, - "table/demos/disallow-empty-selection": { - files: ["ui/table/demos/disallow-empty-selection.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/disallow-empty-selection"), - ), - }, - "table/demos/dynamic-collection": { - files: ["ui/table/demos/dynamic-collection.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/dynamic-collection"), - ), - }, - "table/demos/empty-state": { - files: ["ui/table/demos/empty-state.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/empty-state"), - ), - }, - "table/demos/links": { - files: ["ui/table/demos/links.tsx"], - component: React.lazy(() => import("@dotui/registry/ui/table/demos/links")), - }, - "table/demos/reordable": { - files: ["ui/table/demos/reordable.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/reordable"), - ), - }, - "table/demos/row-action": { - files: ["ui/table/demos/row-action.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/row-action"), - ), - }, - "table/demos/selection-behavior": { - files: ["ui/table/demos/selection-behavior.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/selection-behavior"), - ), - }, - "table/demos/selection-mode": { - files: ["ui/table/demos/selection-mode.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/selection-mode"), - ), - }, - "table/demos/selection": { - files: ["ui/table/demos/selection.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/selection"), - ), - }, - "table/demos/sorting": { - files: ["ui/table/demos/sorting.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/sorting"), - ), - }, - "table/demos/static-row-action": { - files: ["ui/table/demos/static-row-action.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/static-row-action"), - ), - }, - "table/demos/uncontrolled": { - files: ["ui/table/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/table/demos/uncontrolled"), - ), - }, - "tabs/demos/basic": { - files: ["ui/tabs/demos/basic.tsx"], - component: React.lazy(() => import("@dotui/registry/ui/tabs/demos/basic")), - }, - "tabs/demos/controlled": { - files: ["ui/tabs/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tabs/demos/controlled"), - ), - }, - "tabs/demos/disabled": { - files: ["ui/tabs/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tabs/demos/disabled"), - ), - }, - "tabs/demos/keyboard-activation": { - files: ["ui/tabs/demos/keyboard-activation.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tabs/demos/keyboard-activation"), - ), - }, - "tabs/demos/variant": { - files: ["ui/tabs/demos/variant.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tabs/demos/variant"), - ), - }, - "tabs/demos/vertical": { - files: ["ui/tabs/demos/vertical.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tabs/demos/vertical"), - ), - }, - "tag-group/demos/default": { - files: ["ui/tag-group/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tag-group/demos/default"), - ), - }, - "text-area/demos/controlled": { - files: ["ui/text-area/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-area/demos/controlled"), - ), - }, - "text-area/demos/default": { - files: ["ui/text-area/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-area/demos/default"), - ), - }, - "text-area/demos/description": { - files: ["ui/text-area/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-area/demos/description"), - ), - }, - "text-area/demos/disabled": { - files: ["ui/text-area/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-area/demos/disabled"), - ), - }, - "text-area/demos/error-message": { - files: ["ui/text-area/demos/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-area/demos/error-message"), - ), - }, - "text-area/demos/label": { - files: ["ui/text-area/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-area/demos/label"), - ), - }, - "text-area/demos/prefix-and-suffix": { - files: ["ui/text-area/demos/prefix-and-suffix.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-area/demos/prefix-and-suffix"), - ), - }, - "text-area/demos/read-only": { - files: ["ui/text-area/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-area/demos/read-only"), - ), - }, - "text-area/demos/required": { - files: ["ui/text-area/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-area/demos/required"), - ), - }, - "text-area/demos/uncontrolled": { - files: ["ui/text-area/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-area/demos/uncontrolled"), - ), - }, - "text-field/demos/controlled": { - files: ["ui/text-field/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/controlled"), - ), - }, - "text-field/demos/default": { - files: ["ui/text-field/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/default"), - ), - }, - "text-field/demos/description": { - files: ["ui/text-field/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/description"), - ), - }, - "text-field/demos/disabled": { - files: ["ui/text-field/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/disabled"), - ), - }, - "text-field/demos/error-message": { - files: ["ui/text-field/demos/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/error-message"), - ), - }, - "text-field/demos/label": { - files: ["ui/text-field/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/label"), - ), - }, - "text-field/demos/prefix-and-suffix": { - files: ["ui/text-field/demos/prefix-and-suffix.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/prefix-and-suffix"), - ), - }, - "text-field/demos/read-only": { - files: ["ui/text-field/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/read-only"), - ), - }, - "text-field/demos/required": { - files: ["ui/text-field/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/required"), - ), - }, - "text-field/demos/sizes": { - files: ["ui/text-field/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/sizes"), - ), - }, - "text-field/demos/uncontrolled": { - files: ["ui/text-field/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/text-field/demos/uncontrolled"), - ), - }, - "time-field/demos/controlled": { - files: ["ui/time-field/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/controlled"), - ), - }, - "time-field/demos/default": { - files: ["ui/time-field/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/default"), - ), - }, - "time-field/demos/description": { - files: ["ui/time-field/demos/description.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/description"), - ), - }, - "time-field/demos/disabled": { - files: ["ui/time-field/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/disabled"), - ), - }, - "time-field/demos/error-message": { - files: ["ui/time-field/demos/error-message.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/error-message"), - ), - }, - "time-field/demos/granularity": { - files: ["ui/time-field/demos/granularity.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/granularity"), - ), - }, - "time-field/demos/hide-time-zone": { - files: ["ui/time-field/demos/hide-time-zone.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/hide-time-zone"), - ), - }, - "time-field/demos/hour-cycle": { - files: ["ui/time-field/demos/hour-cycle.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/hour-cycle"), - ), - }, - "time-field/demos/label": { - files: ["ui/time-field/demos/label.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/label"), - ), - }, - "time-field/demos/placeholder": { - files: ["ui/time-field/demos/placeholder.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/placeholder"), - ), - }, - "time-field/demos/prefix-and-suffix": { - files: ["ui/time-field/demos/prefix-and-suffix.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/prefix-and-suffix"), - ), - }, - "time-field/demos/read-only": { - files: ["ui/time-field/demos/read-only.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/read-only"), - ), - }, - "time-field/demos/required": { - files: ["ui/time-field/demos/required.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/required"), - ), - }, - "time-field/demos/sizes": { - files: ["ui/time-field/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/sizes"), - ), - }, - "time-field/demos/time-zones": { - files: ["ui/time-field/demos/time-zones.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/time-zones"), - ), - }, - "time-field/demos/uncontrolled": { - files: ["ui/time-field/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/time-field/demos/uncontrolled"), - ), - }, - "toggle-button/demos/controlled": { - files: ["ui/toggle-button/demos/controlled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/toggle-button/demos/controlled"), - ), - }, - "toggle-button/demos/default": { - files: ["ui/toggle-button/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/toggle-button/demos/default"), - ), - }, - "toggle-button/demos/disabled": { - files: ["ui/toggle-button/demos/disabled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/toggle-button/demos/disabled"), - ), - }, - "toggle-button/demos/shapes": { - files: ["ui/toggle-button/demos/shapes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/toggle-button/demos/shapes"), - ), - }, - "toggle-button/demos/sizes": { - files: ["ui/toggle-button/demos/sizes.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/toggle-button/demos/sizes"), - ), - }, - "toggle-button/demos/uncontrolled": { - files: ["ui/toggle-button/demos/uncontrolled.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/toggle-button/demos/uncontrolled"), - ), - }, - "toggle-button/demos/variants": { - files: ["ui/toggle-button/demos/variants.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/toggle-button/demos/variants"), - ), - }, - "toggle-button-group/demos/default": { - files: ["ui/toggle-button-group/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/toggle-button-group/demos/default"), - ), - }, - "tooltip/demos/arrow": { - files: ["ui/tooltip/demos/arrow.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tooltip/demos/arrow"), - ), - }, - "tooltip/demos/container-padding": { - files: ["ui/tooltip/demos/container-padding.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tooltip/demos/container-padding"), - ), - }, - "tooltip/demos/default": { - files: ["ui/tooltip/demos/default.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tooltip/demos/default"), - ), - }, - "tooltip/demos/delay": { - files: ["ui/tooltip/demos/delay.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tooltip/demos/delay"), - ), - }, - "tooltip/demos/flip": { - files: ["ui/tooltip/demos/flip.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tooltip/demos/flip"), - ), - }, - "tooltip/demos/offset": { - files: ["ui/tooltip/demos/offset.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tooltip/demos/offset"), - ), - }, - "tooltip/demos/placement": { - files: ["ui/tooltip/demos/placement.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tooltip/demos/placement"), - ), - }, - "tooltip/demos/variant": { - files: ["ui/tooltip/demos/variant.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tooltip/demos/variant"), - ), - }, - "tooltip/demos/with-arrow": { - files: ["ui/tooltip/demos/with-arrow.tsx"], - component: React.lazy( - () => import("@dotui/registry/ui/tooltip/demos/with-arrow"), - ), - }, -}; diff --git a/packages/registry/src/ui/accordion/index.tsx b/packages/registry/src/ui/accordion/index.tsx index 94c15c355..4eab10c12 100644 --- a/packages/registry/src/ui/accordion/index.tsx +++ b/packages/registry/src/ui/accordion/index.tsx @@ -1,13 +1,16 @@ "use client"; -import { createDynamicComponent } from "../create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; + import * as Default from "./basic"; import type { AccordionVariant } from "./meta"; import type { AccordionProps } from "./types"; -export const Accordion = createDynamicComponent< - AccordionProps, - AccordionVariant ->("accordion", "Accordion", Default.Accordion, {}); +export const Accordion = createDynamicComponent( + "accordion", + "Accordion", + Default.Accordion, + {}, +); export type { AccordionProps }; diff --git a/packages/registry/src/ui/alert/index.tsx b/packages/registry/src/ui/alert/index.tsx index 2c9eb8ca6..d2418e656 100644 --- a/packages/registry/src/ui/alert/index.tsx +++ b/packages/registry/src/ui/alert/index.tsx @@ -1,42 +1,32 @@ "use client"; -import { createDynamicComponent } from "../create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; + import * as Default from "./basic"; import type { AlertVariant } from "./meta"; -import type { - AlertActionProps, - AlertDescriptionProps, - AlertProps, - AlertTitleProps, -} from "./types"; +import type { AlertActionProps, AlertDescriptionProps, AlertProps, AlertTitleProps } from "./types"; -export const Alert = createDynamicComponent( - "alert", - "Alert", - Default.Alert, - {}, -); +export const Alert = createDynamicComponent("alert", "Alert", Default.Alert, {}); export const AlertTitle = createDynamicComponent( - "alert", - "AlertTitle", - Default.AlertTitle, - {}, + "alert", + "AlertTitle", + Default.AlertTitle, + {}, ); -export const AlertDescription = createDynamicComponent< - AlertDescriptionProps, - AlertVariant ->("alert", "AlertDescription", Default.AlertDescription, {}); +export const AlertDescription = createDynamicComponent( + "alert", + "AlertDescription", + Default.AlertDescription, + {}, +); -export const AlertAction = createDynamicComponent< - AlertActionProps, - AlertVariant ->("alert", "AlertAction", Default.AlertAction, {}); +export const AlertAction = createDynamicComponent( + "alert", + "AlertAction", + Default.AlertAction, + {}, +); -export type { - AlertProps, - AlertDescriptionProps, - AlertTitleProps, - AlertActionProps, -}; +export type { AlertProps, AlertDescriptionProps, AlertTitleProps, AlertActionProps }; diff --git a/packages/registry/src/ui/avatar/index.tsx b/packages/registry/src/ui/avatar/index.tsx index 5e7a5527a..ee7775dbd 100644 --- a/packages/registry/src/ui/avatar/index.tsx +++ b/packages/registry/src/ui/avatar/index.tsx @@ -1,64 +1,44 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { - AvatarFallbackProps, - AvatarGroupProps, - AvatarImageProps, - AvatarPlaceholderProps, - AvatarProps, - AvatarRootProps, + AvatarFallbackProps, + AvatarGroupProps, + AvatarImageProps, + AvatarPlaceholderProps, + AvatarProps, + AvatarRootProps, } from "./types"; -export const AvatarGroup = createDynamicComponent( - "avatar", - "AvatarGroup", - Default.AvatarGroup, - {}, -); +export const AvatarGroup = createDynamicComponent("avatar", "AvatarGroup", Default.AvatarGroup, {}); -export const Avatar = createDynamicComponent( - "avatar", - "Avatar", - Default.Avatar, - {}, -); +export const Avatar = createDynamicComponent("avatar", "Avatar", Default.Avatar, {}); -export const AvatarRoot = createDynamicComponent( - "avatar", - "AvatarRoot", - Default.AvatarRoot, - {}, -); +export const AvatarRoot = createDynamicComponent("avatar", "AvatarRoot", Default.AvatarRoot, {}); -export const AvatarImage = createDynamicComponent( - "avatar", - "AvatarImage", - Default.AvatarImage, - {}, -); +export const AvatarImage = createDynamicComponent("avatar", "AvatarImage", Default.AvatarImage, {}); export const AvatarFallback = createDynamicComponent( - "avatar", - "AvatarFallback", - Default.AvatarFallback, - {}, + "avatar", + "AvatarFallback", + Default.AvatarFallback, + {}, ); export const AvatarPlaceholder = createDynamicComponent( - "avatar", - "AvatarPlaceholder", - Default.AvatarPlaceholder, - {}, + "avatar", + "AvatarPlaceholder", + Default.AvatarPlaceholder, + {}, ); export type { - AvatarProps, - AvatarRootProps, - AvatarImageProps, - AvatarFallbackProps, - AvatarGroupProps, - AvatarPlaceholderProps, + AvatarProps, + AvatarRootProps, + AvatarImageProps, + AvatarFallbackProps, + AvatarGroupProps, + AvatarPlaceholderProps, }; diff --git a/packages/registry/src/ui/badge/index.tsx b/packages/registry/src/ui/badge/index.tsx index b209bf385..567a75152 100644 --- a/packages/registry/src/ui/badge/index.tsx +++ b/packages/registry/src/ui/badge/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { BadgeProps } from "./types"; -export const Badge = createDynamicComponent( - "badge", - "Badge", - Default.Badge, - {}, -); +export const Badge = createDynamicComponent("badge", "Badge", Default.Badge, {}); export type { BadgeProps }; diff --git a/packages/registry/src/ui/breadcrumbs/index.tsx b/packages/registry/src/ui/breadcrumbs/index.tsx index 5100e3966..d2ee8e4f0 100644 --- a/packages/registry/src/ui/breadcrumbs/index.tsx +++ b/packages/registry/src/ui/breadcrumbs/index.tsx @@ -1,46 +1,31 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; -import type { - BreadcrumbItemProps, - BreadcrumbLinkProps, - BreadcrumbProps, - BreadcrumbsProps, -} from "./types"; +import type { BreadcrumbItemProps, BreadcrumbLinkProps, BreadcrumbProps, BreadcrumbsProps } from "./types"; export const Breadcrumbs = createDynamicComponent>( - "breadcrumbs", - "Breadcrumbs", - Default.Breadcrumbs, - {}, + "breadcrumbs", + "Breadcrumbs", + Default.Breadcrumbs, + {}, ); -export const Breadcrumb = createDynamicComponent( - "breadcrumbs", - "Breadcrumb", - Default.Breadcrumb, - {}, -); +export const Breadcrumb = createDynamicComponent("breadcrumbs", "Breadcrumb", Default.Breadcrumb, {}); export const BreadcrumbItem = createDynamicComponent( - "breadcrumbs", - "BreadcrumbItem", - Default.BreadcrumbItem, - {}, + "breadcrumbs", + "BreadcrumbItem", + Default.BreadcrumbItem, + {}, ); export const BreadcrumbLink = createDynamicComponent( - "breadcrumbs", - "BreadcrumbLink", - Default.BreadcrumbLink, - {}, + "breadcrumbs", + "BreadcrumbLink", + Default.BreadcrumbLink, + {}, ); -export type { - BreadcrumbProps, - BreadcrumbItemProps, - BreadcrumbLinkProps, - BreadcrumbsProps, -}; +export type { BreadcrumbProps, BreadcrumbItemProps, BreadcrumbLinkProps, BreadcrumbsProps }; diff --git a/packages/registry/src/ui/button/index.tsx b/packages/registry/src/ui/button/index.tsx index a1c966402..4da57704d 100644 --- a/packages/registry/src/ui/button/index.tsx +++ b/packages/registry/src/ui/button/index.tsx @@ -2,31 +2,17 @@ import React from "react"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { ButtonProps, LinkButtonProps } from "./types"; -export const Button = createDynamicComponent( - "button", - "Button", - Default.Button, - { - ripple: React.lazy(() => - import("./ripple").then((mod) => ({ default: mod.Button })), - ), - }, -); +export const Button = createDynamicComponent("button", "Button", Default.Button, { + ripple: React.lazy(() => import("./ripple").then((mod) => ({ default: mod.Button }))), +}); -export const LinkButton = createDynamicComponent( - "link", - "LinkButton", - Default.LinkButton, - { - ripple: React.lazy(() => - import("./ripple").then((mod) => ({ default: mod.LinkButton })), - ), - }, -); +export const LinkButton = createDynamicComponent("link", "LinkButton", Default.LinkButton, { + ripple: React.lazy(() => import("./ripple").then((mod) => ({ default: mod.LinkButton }))), +}); export type { ButtonProps, LinkButtonProps }; diff --git a/packages/registry/src/ui/calendar/index.tsx b/packages/registry/src/ui/calendar/index.tsx index 775d61d65..128a3e301 100644 --- a/packages/registry/src/ui/calendar/index.tsx +++ b/packages/registry/src/ui/calendar/index.tsx @@ -2,66 +2,59 @@ import type { DateValue } from "react-aria-components"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { - CalendarCellProps, - CalendarGridBodyProps, - CalendarGridHeaderProps, - CalendarGridProps, - CalendarHeaderCellProps, - CalendarHeaderProps, - CalendarProps, + CalendarCellProps, + CalendarGridBodyProps, + CalendarGridHeaderProps, + CalendarGridProps, + CalendarHeaderCellProps, + CalendarHeaderProps, + CalendarProps, } from "./types"; -export const Calendar = createDynamicComponent>( - "calendar", - "Calendar", - Default.Calendar, - {}, -); +export const Calendar = createDynamicComponent>("calendar", "Calendar", Default.Calendar, {}); export const CalendarHeader = createDynamicComponent( - "calendar", - "CalendarHeader", - Default.CalendarHeader, - {}, + "calendar", + "CalendarHeader", + Default.CalendarHeader, + {}, ); export const CalendarGrid = createDynamicComponent( - "calendar", - "CalendarGrid", - Default.CalendarGrid, - {}, + "calendar", + "CalendarGrid", + Default.CalendarGrid, + {}, ); -export const CalendarGridHeader = - createDynamicComponent( - "calendar", - "CalendarGridHeader", - Default.CalendarGridHeader, - {}, - ); +export const CalendarGridHeader = createDynamicComponent( + "calendar", + "CalendarGridHeader", + Default.CalendarGridHeader, + {}, +); -export const CalendarHeaderCell = - createDynamicComponent( - "calendar", - "CalendarHeaderCell", - Default.CalendarHeaderCell, - {}, - ); +export const CalendarHeaderCell = createDynamicComponent( + "calendar", + "CalendarHeaderCell", + Default.CalendarHeaderCell, + {}, +); export const CalendarGridBody = createDynamicComponent( - "calendar", - "CalendarGridBody", - Default.CalendarGridBody, - {}, + "calendar", + "CalendarGridBody", + Default.CalendarGridBody, + {}, ); export const CalendarCell = createDynamicComponent( - "calendar", - "CalendarCell", - Default.CalendarCell, - {}, + "calendar", + "CalendarCell", + Default.CalendarCell, + {}, ); diff --git a/packages/registry/src/ui/card/index.tsx b/packages/registry/src/ui/card/index.tsx index c35ab38ff..4058e76bc 100644 --- a/packages/registry/src/ui/card/index.tsx +++ b/packages/registry/src/ui/card/index.tsx @@ -1,73 +1,43 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { - CardActionProps, - CardContentProps, - CardDescriptionProps, - CardFooterProps, - CardHeaderProps, - CardProps, - CardTitleProps, + CardActionProps, + CardContentProps, + CardDescriptionProps, + CardFooterProps, + CardHeaderProps, + CardProps, + CardTitleProps, } from "./types"; -export const Card = createDynamicComponent( - "card", - "Card", - Default.Card, - {}, -); +export const Card = createDynamicComponent("card", "Card", Default.Card, {}); -export const CardHeader = createDynamicComponent( - "card", - "CardHeader", - Default.CardHeader, - {}, -); +export const CardHeader = createDynamicComponent("card", "CardHeader", Default.CardHeader, {}); -export const CardTitle = createDynamicComponent( - "card", - "CardTitle", - Default.CardTitle, - {}, -); +export const CardTitle = createDynamicComponent("card", "CardTitle", Default.CardTitle, {}); export const CardDescription = createDynamicComponent( - "card", - "CardDescription", - Default.CardDescription, - {}, + "card", + "CardDescription", + Default.CardDescription, + {}, ); -export const CardContent = createDynamicComponent( - "card", - "CardContent", - Default.CardContent, - {}, -); +export const CardContent = createDynamicComponent("card", "CardContent", Default.CardContent, {}); -export const CardFooter = createDynamicComponent( - "card", - "CardFooter", - Default.CardFooter, - {}, -); +export const CardFooter = createDynamicComponent("card", "CardFooter", Default.CardFooter, {}); -export const CardAction = createDynamicComponent( - "card", - "CardAction", - Default.CardAction, - {}, -); +export const CardAction = createDynamicComponent("card", "CardAction", Default.CardAction, {}); export type { - CardProps, - CardHeaderProps, - CardTitleProps, - CardDescriptionProps, - CardActionProps, - CardContentProps, - CardFooterProps, + CardProps, + CardHeaderProps, + CardTitleProps, + CardDescriptionProps, + CardActionProps, + CardContentProps, + CardFooterProps, }; diff --git a/packages/registry/src/ui/checkbox-group/index.tsx b/packages/registry/src/ui/checkbox-group/index.tsx index bbf293885..630769a2a 100644 --- a/packages/registry/src/ui/checkbox-group/index.tsx +++ b/packages/registry/src/ui/checkbox-group/index.tsx @@ -1,15 +1,15 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { CheckboxGroupProps } from "./types"; export const CheckboxGroup = createDynamicComponent( - "checkbox-group", - "CheckboxGroup", - Default.CheckboxGroup, - {}, + "checkbox-group", + "CheckboxGroup", + Default.CheckboxGroup, + {}, ); export type { CheckboxGroupProps }; diff --git a/packages/registry/src/ui/checkbox/index.tsx b/packages/registry/src/ui/checkbox/index.tsx index 94e021034..3783c08c7 100644 --- a/packages/registry/src/ui/checkbox/index.tsx +++ b/packages/registry/src/ui/checkbox/index.tsx @@ -1,22 +1,17 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { CheckboxIndicatorProps, CheckboxProps } from "./types"; -export const Checkbox = createDynamicComponent( - "checkbox", - "Checkbox", - Default.Checkbox, - {}, -); +export const Checkbox = createDynamicComponent("checkbox", "Checkbox", Default.Checkbox, {}); export const CheckboxIndicator = createDynamicComponent( - "checkbox", - "CheckboxIndicator", - Default.CheckboxIndicator, - {}, + "checkbox", + "CheckboxIndicator", + Default.CheckboxIndicator, + {}, ); export type { CheckboxProps, CheckboxIndicatorProps }; diff --git a/packages/registry/src/ui/color-area/index.tsx b/packages/registry/src/ui/color-area/index.tsx index ea72729e9..7b23c8e8e 100644 --- a/packages/registry/src/ui/color-area/index.tsx +++ b/packages/registry/src/ui/color-area/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { ColorAreaProps } from "./types"; -export const ColorArea = createDynamicComponent( - "color-area", - "ColorArea", - Default.ColorArea, - {}, -); +export const ColorArea = createDynamicComponent("color-area", "ColorArea", Default.ColorArea, {}); export type { ColorAreaProps }; diff --git a/packages/registry/src/ui/color-editor/index.tsx b/packages/registry/src/ui/color-editor/index.tsx index dbf57a5cc..f8d841bb1 100644 --- a/packages/registry/src/ui/color-editor/index.tsx +++ b/packages/registry/src/ui/color-editor/index.tsx @@ -1,15 +1,15 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { ColorEditorProps } from "./types"; export const ColorEditor = createDynamicComponent( - "color-editor", - "ColorEditor", - Default.ColorEditor, - {}, + "color-editor", + "ColorEditor", + Default.ColorEditor, + {}, ); export type { ColorEditorProps }; diff --git a/packages/registry/src/ui/color-field/index.tsx b/packages/registry/src/ui/color-field/index.tsx index 9717873ea..39f3be7aa 100644 --- a/packages/registry/src/ui/color-field/index.tsx +++ b/packages/registry/src/ui/color-field/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { ColorFieldProps } from "./types"; -export const ColorField = createDynamicComponent( - "color-field", - "ColorField", - Default.ColorField, - {}, -); +export const ColorField = createDynamicComponent("color-field", "ColorField", Default.ColorField, {}); export type { ColorFieldProps }; diff --git a/packages/registry/src/ui/color-picker/index.tsx b/packages/registry/src/ui/color-picker/index.tsx index 7bcae9799..725963a24 100644 --- a/packages/registry/src/ui/color-picker/index.tsx +++ b/packages/registry/src/ui/color-picker/index.tsx @@ -1,35 +1,29 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; -import type { - ColorPickerContentProps, - ColorPickerProps, - ColorPickerTriggerProps, -} from "./types"; +import type { ColorPickerContentProps, ColorPickerProps, ColorPickerTriggerProps } from "./types"; export const ColorPicker = createDynamicComponent( - "color-picker", - "ColorPicker", - Default.ColorPicker, - {}, + "color-picker", + "ColorPicker", + Default.ColorPicker, + {}, ); -export const ColorPickerTrigger = - createDynamicComponent( - "color-picker", - "ColorPickerTrigger", - Default.ColorPickerTrigger, - {}, - ); +export const ColorPickerTrigger = createDynamicComponent( + "color-picker", + "ColorPickerTrigger", + Default.ColorPickerTrigger, + {}, +); -export const ColorPickerContent = - createDynamicComponent( - "color-picker", - "ColorPickerContent", - Default.ColorPickerContent, - {}, - ); +export const ColorPickerContent = createDynamicComponent( + "color-picker", + "ColorPickerContent", + Default.ColorPickerContent, + {}, +); export type { ColorPickerProps }; diff --git a/packages/registry/src/ui/color-slider/index.tsx b/packages/registry/src/ui/color-slider/index.tsx index 1f10b6ba1..fa16a581a 100644 --- a/packages/registry/src/ui/color-slider/index.tsx +++ b/packages/registry/src/ui/color-slider/index.tsx @@ -1,38 +1,29 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; -import type { - ColorSliderControlProps, - ColorSliderOutputProps, - ColorSliderProps, -} from "./types"; +import type { ColorSliderControlProps, ColorSliderOutputProps, ColorSliderProps } from "./types"; export const ColorSlider = createDynamicComponent( - "color-slider", - "ColorSlider", - Default.ColorSlider, - {}, + "color-slider", + "ColorSlider", + Default.ColorSlider, + {}, ); export const ColorSliderOutput = createDynamicComponent( - "color-slider", - "ColorSliderOutput", - Default.ColorSliderOutput, - {}, + "color-slider", + "ColorSliderOutput", + Default.ColorSliderOutput, + {}, ); -export const ColorSliderControl = - createDynamicComponent( - "color-slider", - "ColorSliderControl", - Default.ColorSliderControl, - {}, - ); +export const ColorSliderControl = createDynamicComponent( + "color-slider", + "ColorSliderControl", + Default.ColorSliderControl, + {}, +); -export type { - ColorSliderProps, - ColorSliderControlProps, - ColorSliderOutputProps, -}; +export type { ColorSliderProps, ColorSliderControlProps, ColorSliderOutputProps }; diff --git a/packages/registry/src/ui/color-swatch-picker/index.tsx b/packages/registry/src/ui/color-swatch-picker/index.tsx index 07e01f66e..a4e5cdc9b 100644 --- a/packages/registry/src/ui/color-swatch-picker/index.tsx +++ b/packages/registry/src/ui/color-swatch-picker/index.tsx @@ -1,26 +1,22 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; -import type { - ColorSwatchPickerItemProps, - ColorSwatchPickerProps, -} from "./types"; +import type { ColorSwatchPickerItemProps, ColorSwatchPickerProps } from "./types"; export const ColorSwatchPicker = createDynamicComponent( - "color-swatch-picker", - "ColorSwatchPicker", - Default.ColorSwatchPicker, - {}, + "color-swatch-picker", + "ColorSwatchPicker", + Default.ColorSwatchPicker, + {}, ); -export const ColorSwatchPickerItem = - createDynamicComponent( - "color-swatch-picker", - "ColorSwatchPickerItem", - Default.ColorSwatchPickerItem, - {}, - ); +export const ColorSwatchPickerItem = createDynamicComponent( + "color-swatch-picker", + "ColorSwatchPickerItem", + Default.ColorSwatchPickerItem, + {}, +); export type { ColorSwatchPickerProps, ColorSwatchPickerItemProps }; diff --git a/packages/registry/src/ui/color-swatch/index.tsx b/packages/registry/src/ui/color-swatch/index.tsx index 2b18506fe..50d496737 100644 --- a/packages/registry/src/ui/color-swatch/index.tsx +++ b/packages/registry/src/ui/color-swatch/index.tsx @@ -1,15 +1,15 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { ColorSwatchProps } from "./types"; export const ColorSwatch = createDynamicComponent( - "color-swatch", - "ColorSwatch", - Default.ColorSwatch, - {}, + "color-swatch", + "ColorSwatch", + Default.ColorSwatch, + {}, ); export type { ColorSwatchProps }; diff --git a/packages/registry/src/ui/color-thumb/index.tsx b/packages/registry/src/ui/color-thumb/index.tsx index dec0581ee..f8ebbe88b 100644 --- a/packages/registry/src/ui/color-thumb/index.tsx +++ b/packages/registry/src/ui/color-thumb/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { ColorThumbProps } from "./types"; -export const ColorThumb = createDynamicComponent( - "color-thumb", - "ColorThumb", - Default.ColorThumb, - {}, -); +export const ColorThumb = createDynamicComponent("color-thumb", "ColorThumb", Default.ColorThumb, {}); export type { ColorThumbProps }; diff --git a/packages/registry/src/ui/combobox/index.tsx b/packages/registry/src/ui/combobox/index.tsx index 4f481500d..6e183ab6b 100644 --- a/packages/registry/src/ui/combobox/index.tsx +++ b/packages/registry/src/ui/combobox/index.tsx @@ -1,56 +1,39 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; -import { - ListBoxItem, - ListBoxSection, - ListBoxSectionHeader, -} from "@dotui/registry/ui/list-box"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; +import { ListBoxItem, ListBoxSection, ListBoxSectionHeader } from "@dotui/registry/ui/list-box"; import * as Default from "./basic"; -import type { - ComboboxContentProps, - ComboboxInputProps, - ComboboxProps, -} from "./types"; - -export const Combobox = ( - props: ComboboxProps, -) => { - const Component = createDynamicComponent>( - "combobox", - "Combobox", - Default.Combobox, - {}, - ); - - return ; +import type { ComboboxContentProps, ComboboxInputProps, ComboboxProps } from "./types"; + +export const Combobox = (props: ComboboxProps) => { + const Component = createDynamicComponent>("combobox", "Combobox", Default.Combobox, {}); + + return ; }; export const ComboboxInput = createDynamicComponent( - "combobox", - "ComboboxInput", - Default.ComboboxInput, - {}, + "combobox", + "ComboboxInput", + Default.ComboboxInput, + {}, ); -export const ComboboxContent = ( - props: ComboboxContentProps, -) => { - const Component = createDynamicComponent>( - "combobox", - "ComboboxContent", - Default.ComboboxContent, - {}, - ); - - return ; +export const ComboboxContent = (props: ComboboxContentProps) => { + const Component = createDynamicComponent>( + "combobox", + "ComboboxContent", + Default.ComboboxContent, + {}, + ); + + return ; }; export { - ListBoxItem as ComboboxItem, - ListBoxSection as ComboboxSection, - ListBoxSectionHeader as ComboboxSectionHeader, + ListBoxItem as ComboboxItem, + ListBoxSection as ComboboxSection, + ListBoxSectionHeader as ComboboxSectionHeader, }; export type { ComboboxProps, ComboboxInputProps, ComboboxContentProps }; diff --git a/packages/registry/src/ui/command/index.tsx b/packages/registry/src/ui/command/index.tsx index 29add5a10..f91e2ecd4 100644 --- a/packages/registry/src/ui/command/index.tsx +++ b/packages/registry/src/ui/command/index.tsx @@ -1,52 +1,28 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; -import { - ListBoxItem, - ListBoxSection, - ListBoxSectionHeader, -} from "@dotui/registry/ui/list-box"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; +import { ListBoxItem, ListBoxSection, ListBoxSectionHeader } from "@dotui/registry/ui/list-box"; import * as Default from "./basic"; -import type { - CommandContentProps, - CommandInputProps, - CommandProps, -} from "./types"; +import type { CommandContentProps, CommandInputProps, CommandProps } from "./types"; -export const Command = createDynamicComponent( - "command", - "Command", - Default.Command, - {}, -); +export const Command = createDynamicComponent("command", "Command", Default.Command, {}); -export const CommandContent = ( - props: CommandContentProps, -) => { - const Component = createDynamicComponent>( - "command", - "CommandContent", - Default.CommandContent, - {}, - ); - return ; +export const CommandContent = (props: CommandContentProps) => { + const Component = createDynamicComponent>( + "command", + "CommandContent", + Default.CommandContent, + {}, + ); + return ; }; export const CommandInput = (props: CommandInputProps) => { - const Component = createDynamicComponent( - "command", - "CommandInput", - Default.CommandInput, - {}, - ); - return ; + const Component = createDynamicComponent("command", "CommandInput", Default.CommandInput, {}); + return ; }; -export { - ListBoxItem as CommandItem, - ListBoxSection as CommandSection, - ListBoxSectionHeader as CommandSectionHeader, -}; +export { ListBoxItem as CommandItem, ListBoxSection as CommandSection, ListBoxSectionHeader as CommandSectionHeader }; export type { CommandContentProps, CommandInputProps, CommandProps }; diff --git a/packages/registry/src/ui/create-dynamic-component.tsx b/packages/registry/src/ui/create-dynamic-component.tsx deleted file mode 100644 index 54fbca7f2..000000000 --- a/packages/registry/src/ui/create-dynamic-component.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import React from "react"; -import { AlertCircleIcon } from "lucide-react"; -import { ErrorBoundary } from "react-error-boundary"; - -import { useVariant, VariantsProvider } from "@dotui/style-system/providers"; -import { DEFAULT_VARIANTS_DEFINITION } from "@dotui/style-system/utils"; -import type { Variants } from "@dotui/style-system/types"; - -import { cn } from "../lib/utils"; - -// Type helper to exclude the default variant from the variants map keys -type OmitDefault = T extends Default - ? never - : T; - -// Utility to create a properly typed variants object -export type VariantsMap< - Props, - VariantKey extends string, - DefaultVariant extends string = "basic", -> = Record< - OmitDefault, - React.LazyExoticComponent> ->; - -/** - * Helper to safely extract className from props - * Returns undefined if className doesn't exist or isn't a string - */ -function getClassName(props: Record): string | undefined { - if ("className" in props && typeof props.className === "string") { - return props.className; - } - return undefined; -} - -export interface CreateDynamicComponentOptions { - defaultVariant?: string; - disableSkeleton?: boolean; -} - -export const createDynamicComponent = < - Props extends Record = Record, - VariantKey extends string = string, - DefaultVariant extends string = "basic", ->( - componentName: keyof Variants, - slotName: string, - DefaultComponent: React.FC, - variants: VariantsMap, - options?: CreateDynamicComponentOptions, -): React.FC => { - const { defaultVariant = "basic", disableSkeleton } = options ?? {}; - const Component = (props: Props) => { - const rawVariantName = useVariant(componentName); - const disableSuspense = useDisableSuspense(); - - // If the variant is not defined, we are not in a preview - if (!rawVariantName) { - return ; - } - - // After null check, variantName is guaranteed to be a string enum value - // TypeScript infers complex union types from Variants, so we need to help it understand - // that this is just a string value at runtime - const variantName = rawVariantName as string; - - // Extract className safely without type assertions - const className = getClassName(props); - - // Type-safe variant lookup - we need to cast to OmitDefault - // since the variants map excludes the default variant - const variantKey = variantName as OmitDefault; - const LazyComponent = variants[variantKey]; - - // If LazyComponent is not defined, it's because the requested variant is the default variant - // or the variant doesn't exist in the map (fallback to default) - if (!LazyComponent || variantName === defaultVariant) { - if (disableSuspense) { - return ; - } - - return ( - - } - > - - - - - - } - > - - - - - - ); - } - - // Render the lazy component - // LazyExoticComponent> is compatible with ComponentType - // but TypeScript needs explicit casting to understand the relationship - const renderLazy = () => { - const Component = LazyComponent as React.ComponentType; - return ; - }; - - if (disableSuspense) { - return renderLazy(); - } - - return ( - - } - > - - - - - - } - > - {renderLazy()} - - - ); - }; - Component.displayName = slotName; - - return Component; -}; - -const DisableSuspenseContext = React.createContext(false); - -export const DisableSuspense = ({ - children, -}: { - children?: React.ReactNode; -}) => { - return {children}; -}; - -const useDisableSuspense = () => { - return React.useContext(DisableSuspenseContext); -}; - -export type SkeletonProps = React.HTMLAttributes & { - show?: boolean; -}; - -function Skeleton({ className, show = true, ...props }: SkeletonProps) { - if (!show) return props.children; - return ( -
- ); -} - -function ErrorFallback({ - componentName, - slotName, - variantName, -}: { - componentName: string; - slotName: string; - variantName: string; -}) { - return ( -
-
- -
- Error rendering dynamic component: -
    -
  • - component: {componentName} -
  • -
  • - slot: {slotName} -
  • -
  • - variant: {variantName} -
  • -
-
-
-
- ); -} diff --git a/packages/registry/src/ui/date-field/index.tsx b/packages/registry/src/ui/date-field/index.tsx index 2d8149f4c..ff920cfcc 100644 --- a/packages/registry/src/ui/date-field/index.tsx +++ b/packages/registry/src/ui/date-field/index.tsx @@ -2,20 +2,15 @@ import type { DateValue } from "react-aria-components"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { DateFieldProps } from "./types"; export const DateField = (props: DateFieldProps) => { - const Component = createDynamicComponent>( - "date-field", - "DateField", - Default.DateField, - {}, - ); + const Component = createDynamicComponent>("date-field", "DateField", Default.DateField, {}); - return ; + return ; }; export type { DateFieldProps }; diff --git a/packages/registry/src/ui/date-picker/index.tsx b/packages/registry/src/ui/date-picker/index.tsx index 15bb9ab84..ed5f2d8e6 100644 --- a/packages/registry/src/ui/date-picker/index.tsx +++ b/packages/registry/src/ui/date-picker/index.tsx @@ -2,38 +2,29 @@ import type { DateValue } from "react-aria"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; -import type { - DatePickerContentProps, - DatePickerInputProps, - DatePickerProps, -} from "./types"; +import type { DatePickerContentProps, DatePickerInputProps, DatePickerProps } from "./types"; export const DatePicker = (props: DatePickerProps) => { - const Component = createDynamicComponent>( - "date-picker", - "DatePicker", - Default.DatePicker, - {}, - ); - - return ; + const Component = createDynamicComponent>("date-picker", "DatePicker", Default.DatePicker, {}); + + return ; }; export const DatePickerContent = createDynamicComponent( - "date-picker", - "DatePickerContent", - Default.DatePickerContent, - {}, + "date-picker", + "DatePickerContent", + Default.DatePickerContent, + {}, ); export const DatePickerInput = createDynamicComponent( - "date-picker", - "DatePickerInput", - Default.DatePickerInput, - {}, + "date-picker", + "DatePickerInput", + Default.DatePickerInput, + {}, ); export type { DatePickerProps, DatePickerContentProps, DatePickerInputProps }; diff --git a/packages/registry/src/ui/dialog/index.tsx b/packages/registry/src/ui/dialog/index.tsx index 33ea3610b..8752657ad 100644 --- a/packages/registry/src/ui/dialog/index.tsx +++ b/packages/registry/src/ui/dialog/index.tsx @@ -1,81 +1,66 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { - DialogBodyProps, - DialogContentProps, - DialogDescriptionProps, - DialogFooterProps, - DialogHeaderProps, - DialogHeadingProps, - DialogInsetProps, - DialogProps, + DialogBodyProps, + DialogContentProps, + DialogDescriptionProps, + DialogFooterProps, + DialogHeaderProps, + DialogHeadingProps, + DialogInsetProps, + DialogProps, } from "./types"; -export const Dialog = createDynamicComponent( - "dialog", - "Dialog", - Default.Dialog, - {}, -); +export const Dialog = createDynamicComponent("dialog", "Dialog", Default.Dialog, {}); export const DialogHeader = createDynamicComponent( - "dialog", - "DialogHeader", - Default.DialogHeader, - {}, + "dialog", + "DialogHeader", + Default.DialogHeader, + {}, ); export const DialogHeading = createDynamicComponent( - "dialog", - "DialogHeading", - Default.DialogHeading, - {}, + "dialog", + "DialogHeading", + Default.DialogHeading, + {}, ); export const DialogDescription = createDynamicComponent( - "dialog", - "DialogDescription", - Default.DialogDescription, - {}, + "dialog", + "DialogDescription", + Default.DialogDescription, + {}, ); export const DialogContent = createDynamicComponent( - "dialog", - "DialogContent", - Default.DialogContent, - {}, + "dialog", + "DialogContent", + Default.DialogContent, + {}, ); -export const DialogBody = createDynamicComponent( - "dialog", - "DialogBody", - Default.DialogBody, - {}, -); +export const DialogBody = createDynamicComponent("dialog", "DialogBody", Default.DialogBody, {}); export const DialogFooter = createDynamicComponent( - "dialog", - "DialogFooter", - Default.DialogFooter, - {}, + "dialog", + "DialogFooter", + Default.DialogFooter, + {}, ); -export const DialogInset = createDynamicComponent( - "dialog", - "DialogInset", - Default.DialogInset, - {}, -); +export const DialogInset = createDynamicComponent("dialog", "DialogInset", Default.DialogInset, {}); export type { - DialogProps, - DialogHeaderProps, - DialogHeadingProps, - DialogDescriptionProps, - DialogContentProps, - DialogFooterProps, - DialogInsetProps, + DialogProps, + DialogHeaderProps, + DialogHeadingProps, + DialogDescriptionProps, + DialogContentProps, + DialogFooterProps, + DialogInsetProps, }; diff --git a/packages/registry/src/ui/disclosure/basic.css b/packages/registry/src/ui/disclosure/basic.css new file mode 100644 index 000000000..eced193f3 --- /dev/null +++ b/packages/registry/src/ui/disclosure/basic.css @@ -0,0 +1,3 @@ +@theme { + --ease-fluid-out: cubic-bezier(0.32, 0.72, 0, 1); +} \ No newline at end of file diff --git a/packages/registry/src/ui/disclosure/index.tsx b/packages/registry/src/ui/disclosure/index.tsx index 6f7ccf270..3a9e01ef9 100644 --- a/packages/registry/src/ui/disclosure/index.tsx +++ b/packages/registry/src/ui/disclosure/index.tsx @@ -1,27 +1,30 @@ "use client"; -import { createDynamicComponent } from "../create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; + import * as Default from "./basic"; import type { DisclosureVariant } from "./meta"; -import type { - DisclosurePanelProps, - DisclosureProps, - DisclosureTriggerProps, -} from "./types"; +import type { DisclosurePanelProps, DisclosureProps, DisclosureTriggerProps } from "./types"; -export const Disclosure = createDynamicComponent< - DisclosureProps, - DisclosureVariant ->("disclosure", "Disclosure", Default.Disclosure, {}); +export const Disclosure = createDynamicComponent( + "disclosure", + "Disclosure", + Default.Disclosure, + {}, +); -export const DisclosurePanel = createDynamicComponent< - DisclosurePanelProps, - DisclosureVariant ->("disclosure", "DisclosurePanel", Default.DisclosurePanel, {}); +export const DisclosurePanel = createDynamicComponent( + "disclosure", + "DisclosurePanel", + Default.DisclosurePanel, + {}, +); -export const DisclosureTrigger = createDynamicComponent< - DisclosureTriggerProps, - DisclosureVariant ->("disclosure", "DisclosureTrigger", Default.DisclosureTrigger, {}); +export const DisclosureTrigger = createDynamicComponent( + "disclosure", + "DisclosureTrigger", + Default.DisclosureTrigger, + {}, +); export type { DisclosureProps, DisclosureTriggerProps, DisclosurePanelProps }; diff --git a/packages/registry/src/ui/drawer/basic.css b/packages/registry/src/ui/drawer/basic.css new file mode 100644 index 000000000..eced193f3 --- /dev/null +++ b/packages/registry/src/ui/drawer/basic.css @@ -0,0 +1,3 @@ +@theme { + --ease-fluid-out: cubic-bezier(0.32, 0.72, 0, 1); +} \ No newline at end of file diff --git a/packages/registry/src/ui/drawer/index.tsx b/packages/registry/src/ui/drawer/index.tsx index 3e5130743..32948e9ed 100644 --- a/packages/registry/src/ui/drawer/index.tsx +++ b/packages/registry/src/ui/drawer/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { DrawerProps } from "./types"; -export const Drawer = createDynamicComponent( - "drawer", - "Drawer", - Default.Drawer, - {}, -); +export const Drawer = createDynamicComponent("drawer", "Drawer", Default.Drawer, {}); export type { DrawerProps }; diff --git a/packages/registry/src/ui/drop-zone/index.tsx b/packages/registry/src/ui/drop-zone/index.tsx index 2a6b3ac09..1de3a1502 100644 --- a/packages/registry/src/ui/drop-zone/index.tsx +++ b/packages/registry/src/ui/drop-zone/index.tsx @@ -1,22 +1,17 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { DropZoneLabelProps, DropZoneProps } from "./types"; -export const DropZone = createDynamicComponent( - "drop-zone", - "DropZone", - Default.DropZone, - {}, -); +export const DropZone = createDynamicComponent("drop-zone", "DropZone", Default.DropZone, {}); export const DropZoneLabel = createDynamicComponent( - "drop-zone", - "DropZoneLabel", - Default.DropZoneLabel, - {}, + "drop-zone", + "DropZoneLabel", + Default.DropZoneLabel, + {}, ); export type { DropZoneProps, DropZoneLabelProps }; diff --git a/packages/registry/src/ui/empty/index.tsx b/packages/registry/src/ui/empty/index.tsx index b2f1eae7e..037c6aa6f 100644 --- a/packages/registry/src/ui/empty/index.tsx +++ b/packages/registry/src/ui/empty/index.tsx @@ -1,62 +1,42 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { - EmptyContentProps, - EmptyDescriptionProps, - EmptyHeaderProps, - EmptyMediaProps, - EmptyProps, - EmptyTitleProps, + EmptyContentProps, + EmptyDescriptionProps, + EmptyHeaderProps, + EmptyMediaProps, + EmptyProps, + EmptyTitleProps, } from "./types"; -export const Empty = createDynamicComponent( - "empty", - "Empty", - Default.Empty, - {}, -); +export const Empty = createDynamicComponent("empty", "Empty", Default.Empty, {}); -export const EmptyHeader = createDynamicComponent( - "empty", - "EmptyHeader", - Default.EmptyHeader, - {}, -); +export const EmptyHeader = createDynamicComponent("empty", "EmptyHeader", Default.EmptyHeader, {}); -export const EmptyTitle = createDynamicComponent( - "empty", - "EmptyTitle", - Default.EmptyTitle, - {}, -); +export const EmptyTitle = createDynamicComponent("empty", "EmptyTitle", Default.EmptyTitle, {}); export const EmptyDescription = createDynamicComponent( - "empty", - "EmptyDescription", - Default.EmptyDescription, - {}, + "empty", + "EmptyDescription", + Default.EmptyDescription, + {}, ); export const EmptyContent = createDynamicComponent( - "empty", - "EmptyContent", - Default.EmptyContent, - {}, -); -export const EmptyMedia = createDynamicComponent( - "empty", - "EmptyMedia", - Default.EmptyMedia, - {}, + "empty", + "EmptyContent", + Default.EmptyContent, + {}, ); +export const EmptyMedia = createDynamicComponent("empty", "EmptyMedia", Default.EmptyMedia, {}); export type { - EmptyProps, - EmptyHeaderProps, - EmptyTitleProps, - EmptyDescriptionProps, - EmptyContentProps, - EmptyMediaProps, + EmptyProps, + EmptyHeaderProps, + EmptyTitleProps, + EmptyDescriptionProps, + EmptyContentProps, + EmptyMediaProps, }; diff --git a/packages/registry/src/ui/field/index.tsx b/packages/registry/src/ui/field/index.tsx index 1c748fb72..36cdf382d 100644 --- a/packages/registry/src/ui/field/index.tsx +++ b/packages/registry/src/ui/field/index.tsx @@ -1,84 +1,49 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { - DescriptionProps, - FieldContentProps, - FieldErrorProps, - FieldGroupProps, - FieldProps, - FieldsetProps, - LabelProps, - LegendProps, + DescriptionProps, + FieldContentProps, + FieldErrorProps, + FieldGroupProps, + FieldProps, + FieldsetProps, + LabelProps, + LegendProps, } from "./types"; -export const Fieldset = createDynamicComponent( - "field", - "Fieldset", - Default.Fieldset, - {}, -); +export const Fieldset = createDynamicComponent("field", "Fieldset", Default.Fieldset, {}); -export const Legend = createDynamicComponent( - "field", - "Legend", - Default.Legend, - {}, -); +export const Legend = createDynamicComponent("field", "Legend", Default.Legend, {}); -export const FieldGroup = createDynamicComponent( - "field", - "FieldGroup", - Default.FieldGroup, - {}, -); +export const FieldGroup = createDynamicComponent("field", "FieldGroup", Default.FieldGroup, {}); -export const Field = createDynamicComponent( - "field", - "Field", - Default.Field, - {}, -); +export const Field = createDynamicComponent("field", "Field", Default.Field, {}); export const FieldContent = createDynamicComponent( - "field", - "FieldContent", - Default.FieldContent, - {}, + "field", + "FieldContent", + Default.FieldContent, + {}, ); -export const Label = createDynamicComponent( - "field", - "Label", - Default.Label, - {}, -); +export const Label = createDynamicComponent("field", "Label", Default.Label, {}); -export const Description = createDynamicComponent( - "field", - "Description", - Default.Description, - {}, -); +export const Description = createDynamicComponent("field", "Description", Default.Description, {}); -export const FieldError = createDynamicComponent( - "field", - "FieldError", - Default.FieldError, - {}, -); +export const FieldError = createDynamicComponent("field", "FieldError", Default.FieldError, {}); export { fieldStyles } from "./basic"; export type { - LabelProps, - DescriptionProps, - FieldErrorProps, - FieldGroupProps, - FieldContentProps, - FieldsetProps, - LegendProps, - FieldProps, + LabelProps, + DescriptionProps, + FieldErrorProps, + FieldGroupProps, + FieldContentProps, + FieldsetProps, + LegendProps, + FieldProps, }; diff --git a/packages/registry/src/ui/file-trigger/index.tsx b/packages/registry/src/ui/file-trigger/index.tsx index c869e169f..150dbb8b1 100644 --- a/packages/registry/src/ui/file-trigger/index.tsx +++ b/packages/registry/src/ui/file-trigger/index.tsx @@ -1,15 +1,15 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { FileTriggerProps } from "./types"; export const FileTrigger = createDynamicComponent( - "file-trigger", - "FileTrigger", - Default.FileTrigger, - {}, + "file-trigger", + "FileTrigger", + Default.FileTrigger, + {}, ); export type { FileTriggerProps }; diff --git a/packages/registry/src/ui/group/index.tsx b/packages/registry/src/ui/group/index.tsx index c9cac8c64..5fff87e6c 100644 --- a/packages/registry/src/ui/group/index.tsx +++ b/packages/registry/src/ui/group/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { GroupProps } from "./types"; -export const Group = createDynamicComponent( - "group", - "Group", - Default.Group, - {}, -); +export const Group = createDynamicComponent("group", "Group", Default.Group, {}); export type { GroupProps }; diff --git a/packages/registry/src/ui/input/index.tsx b/packages/registry/src/ui/input/index.tsx index 8a58fa81b..463b1dd45 100644 --- a/packages/registry/src/ui/input/index.tsx +++ b/packages/registry/src/ui/input/index.tsx @@ -1,64 +1,27 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { - DateInputProps, - DateSegmentProps, - InputAddonProps, - InputGroupProps, - InputProps, - TextAreaProps, + DateInputProps, + DateSegmentProps, + InputAddonProps, + InputGroupProps, + InputProps, + TextAreaProps, } from "./types"; -export const Input = createDynamicComponent( - "input", - "Input", - Default.Input, - {}, -); +export const Input = createDynamicComponent("input", "Input", Default.Input, {}); -export const TextArea = createDynamicComponent( - "input", - "TextAreaInput", - Default.TextArea, - {}, -); +export const TextArea = createDynamicComponent("input", "TextAreaInput", Default.TextArea, {}); -export const InputGroup = createDynamicComponent( - "input", - "InputGroup", - Default.InputGroup, - {}, -); +export const InputGroup = createDynamicComponent("input", "InputGroup", Default.InputGroup, {}); -export const InputAddon = createDynamicComponent( - "input", - "InputAddon", - Default.InputAddon, - {}, -); +export const InputAddon = createDynamicComponent("input", "InputAddon", Default.InputAddon, {}); -export const DateInput = createDynamicComponent( - "input", - "DateInput", - Default.DateInput, - {}, -); +export const DateInput = createDynamicComponent("input", "DateInput", Default.DateInput, {}); -export const DateSegment = createDynamicComponent( - "input", - "DateSegment", - Default.DateSegment, - {}, -); +export const DateSegment = createDynamicComponent("input", "DateSegment", Default.DateSegment, {}); -export type { - DateInputProps, - DateSegmentProps, - InputAddonProps, - InputGroupProps, - InputProps, - TextAreaProps, -}; +export type { DateInputProps, DateSegmentProps, InputAddonProps, InputGroupProps, InputProps, TextAreaProps }; diff --git a/packages/registry/src/ui/kbd/index.tsx b/packages/registry/src/ui/kbd/index.tsx index 23ca515f1..7e5d96631 100644 --- a/packages/registry/src/ui/kbd/index.tsx +++ b/packages/registry/src/ui/kbd/index.tsx @@ -1,22 +1,12 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { KbdGroupProps, KbdProps } from "./types"; -export const Kbd = createDynamicComponent( - "kbd", - "Kbd", - Default.Kbd, - {}, -); +export const Kbd = createDynamicComponent("kbd", "Kbd", Default.Kbd, {}); -export const KbdGroup = createDynamicComponent( - "kbd", - "KbdGroup", - Default.KbdGroup, - {}, -); +export const KbdGroup = createDynamicComponent("kbd", "KbdGroup", Default.KbdGroup, {}); export type { KbdProps, KbdGroupProps }; diff --git a/packages/registry/src/ui/link/index.tsx b/packages/registry/src/ui/link/index.tsx index bc5f054f6..403727899 100644 --- a/packages/registry/src/ui/link/index.tsx +++ b/packages/registry/src/ui/link/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { LinkProps } from "./types"; -export const Link = createDynamicComponent( - "link", - "Link", - Default.Link, - {}, -); +export const Link = createDynamicComponent("link", "Link", Default.Link, {}); export type { LinkProps }; diff --git a/packages/registry/src/ui/list-box/index.tsx b/packages/registry/src/ui/list-box/index.tsx index 9f3e12596..2b5395c48 100644 --- a/packages/registry/src/ui/list-box/index.tsx +++ b/packages/registry/src/ui/list-box/index.tsx @@ -1,78 +1,55 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { - ListBoxItemProps, - ListBoxProps, - ListBoxSectionHeaderProps, - ListBoxSectionProps, - ListBoxVirtualizerProps, + ListBoxItemProps, + ListBoxProps, + ListBoxSectionHeaderProps, + ListBoxSectionProps, + ListBoxVirtualizerProps, } from "./types"; export const ListBox = (props: ListBoxProps) => { - const Component = createDynamicComponent>( - "list-box", - "ListBox", - Default.ListBox, - {}, - ); + const Component = createDynamicComponent>("list-box", "ListBox", Default.ListBox, {}); - return ; + return ; }; -export const ListBoxItem = ( - props: ListBoxItemProps, -) => { - const Component = createDynamicComponent>( - "list-box", - "ListBoxItem", - Default.ListBoxItem, - {}, - ); +export const ListBoxItem = (props: ListBoxItemProps) => { + const Component = createDynamicComponent>("list-box", "ListBoxItem", Default.ListBoxItem, {}); - return ; + return ; }; -export const ListBoxSection = ( - props: ListBoxSectionProps, -) => { - const Component = createDynamicComponent>( - "list-box", - "ListBoxSection", - Default.ListBoxSection, - {}, - ); +export const ListBoxSection = (props: ListBoxSectionProps) => { + const Component = createDynamicComponent>( + "list-box", + "ListBoxSection", + Default.ListBoxSection, + {}, + ); - return ; + return ; }; -export const ListBoxSectionHeader = - createDynamicComponent( - "list-box", - "ListBoxSectionHeader", - Default.ListBoxSectionHeader, - {}, - ); - -export const ListBoxVirtualizer = ( - props: ListBoxVirtualizerProps, -) => { - const Component = createDynamicComponent>( - "list-box", - "ListBoxVirtualizer", - Default.ListBoxVirtualizer, - {}, - ); - - return ; +export const ListBoxSectionHeader = createDynamicComponent( + "list-box", + "ListBoxSectionHeader", + Default.ListBoxSectionHeader, + {}, +); + +export const ListBoxVirtualizer = (props: ListBoxVirtualizerProps) => { + const Component = createDynamicComponent>( + "list-box", + "ListBoxVirtualizer", + Default.ListBoxVirtualizer, + {}, + ); + + return ; }; -export type { - ListBoxProps, - ListBoxItemProps, - ListBoxSectionProps, - ListBoxSectionHeaderProps, - ListBoxVirtualizerProps, -}; +export type { ListBoxProps, ListBoxItemProps, ListBoxSectionProps, ListBoxSectionHeaderProps, ListBoxVirtualizerProps }; diff --git a/packages/registry/src/ui/loader/index.tsx b/packages/registry/src/ui/loader/index.tsx index 89ca18c25..263a7af3a 100644 --- a/packages/registry/src/ui/loader/index.tsx +++ b/packages/registry/src/ui/loader/index.tsx @@ -2,20 +2,13 @@ import React from "react"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { LoaderProps } from "./types"; -export const Loader = createDynamicComponent( - "loader", - "Loader", - Default.Loader, - { - ring: React.lazy(() => - import("./ring").then((mod) => ({ default: mod.Loader })), - ), - }, -); +export const Loader = createDynamicComponent("loader", "Loader", Default.Loader, { + ring: React.lazy(() => import("./ring").then((mod) => ({ default: mod.Loader }))), +}); export type { LoaderProps }; diff --git a/packages/registry/src/ui/menu/index.tsx b/packages/registry/src/ui/menu/index.tsx index ef34a5dd4..779dea329 100644 --- a/packages/registry/src/ui/menu/index.tsx +++ b/packages/registry/src/ui/menu/index.tsx @@ -1,73 +1,42 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { - MenuContentProps, - MenuItemProps, - MenuProps, - MenuSectionHeaderProps, - MenuSectionProps, - MenuSubProps, + MenuContentProps, + MenuItemProps, + MenuProps, + MenuSectionHeaderProps, + MenuSectionProps, + MenuSubProps, } from "./types"; -export const Menu = createDynamicComponent( - "menu", - "Menu", - Default.Menu, - {}, -); +export const Menu = createDynamicComponent("menu", "Menu", Default.Menu, {}); -export const MenuContent = ( - props: MenuContentProps, -) => { - const Component = createDynamicComponent>( - "menu", - "MenuContent", - Default.MenuContent, - {}, - ); +export const MenuContent = (props: MenuContentProps) => { + const Component = createDynamicComponent>("menu", "MenuContent", Default.MenuContent, {}); - return ; + return ; }; -export const MenuItem = ( - props: MenuItemProps, -) => { - const Component = createDynamicComponent>( - "menu", - "MenuItem", - Default.MenuItem, - {}, - ); +export const MenuItem = (props: MenuItemProps) => { + const Component = createDynamicComponent>("menu", "MenuItem", Default.MenuItem, {}); - return ; + return ; }; -export const MenuSection = ( - props: MenuSectionProps, -) => { - const Component = createDynamicComponent>( - "menu", - "MenuSection", - Default.MenuSection, - {}, - ); +export const MenuSection = (props: MenuSectionProps) => { + const Component = createDynamicComponent>("menu", "MenuSection", Default.MenuSection, {}); - return ; + return ; }; export const MenuSectionHeader = createDynamicComponent( - "menu", - "MenuSectionHeader", - Default.MenuSectionHeader, - {}, + "menu", + "MenuSectionHeader", + Default.MenuSectionHeader, + {}, ); -export const MenuSub = createDynamicComponent( - "menu", - "MenuSub", - Default.MenuSub, - {}, -); +export const MenuSub = createDynamicComponent("menu", "MenuSub", Default.MenuSub, {}); diff --git a/packages/registry/src/ui/modal/index.tsx b/packages/registry/src/ui/modal/index.tsx index 4736614d3..0e236b219 100644 --- a/packages/registry/src/ui/modal/index.tsx +++ b/packages/registry/src/ui/modal/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { ModalProps } from "./types"; -export const Modal = createDynamicComponent( - "modal", - "Modal", - Default.Modal, - {}, -); +export const Modal = createDynamicComponent("modal", "Modal", Default.Modal, {}); export type { ModalProps }; diff --git a/packages/registry/src/ui/number-field/index.tsx b/packages/registry/src/ui/number-field/index.tsx index 24179c488..fc168f64f 100644 --- a/packages/registry/src/ui/number-field/index.tsx +++ b/packages/registry/src/ui/number-field/index.tsx @@ -1,15 +1,15 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { NumberFieldProps } from "./types"; export const NumberField = createDynamicComponent( - "number-field", - "NumberField", - Default.NumberField, - {}, + "number-field", + "NumberField", + Default.NumberField, + {}, ); export type { NumberFieldProps }; diff --git a/packages/registry/src/ui/overlay/index.tsx b/packages/registry/src/ui/overlay/index.tsx index 25743edb9..ff3605e14 100644 --- a/packages/registry/src/ui/overlay/index.tsx +++ b/packages/registry/src/ui/overlay/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { OverlayProps } from "./types"; -export const Overlay = createDynamicComponent( - "overlay", - "Overlay", - Default.Overlay, - {}, -); +export const Overlay = createDynamicComponent("overlay", "Overlay", Default.Overlay, {}); export type { OverlayProps }; diff --git a/packages/registry/src/ui/popover/index.tsx b/packages/registry/src/ui/popover/index.tsx index 177574021..f7ca16de4 100644 --- a/packages/registry/src/ui/popover/index.tsx +++ b/packages/registry/src/ui/popover/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { PopoverProps } from "./types"; -export const Popover = createDynamicComponent( - "popover", - "Popover", - Default.Popover, - {}, -); +export const Popover = createDynamicComponent("popover", "Popover", Default.Popover, {}); export type { PopoverProps }; diff --git a/packages/registry/src/ui/progress-bar/basic.css b/packages/registry/src/ui/progress-bar/basic.css new file mode 100644 index 000000000..339361db6 --- /dev/null +++ b/packages/registry/src/ui/progress-bar/basic.css @@ -0,0 +1,25 @@ +@theme inline { + /* progress-bar */ + --animate-progress-indeterminate: + progress-grow var(--progress-duration) 1 both normal, progress-pulse 1s ease var(--progress-duration) infinite + normal none running; +} + +@keyframes progress-grow { + 0% { + transform: scaleX(0.01); + } + 20% { + transform: scaleX(0.1); + } + 30% { + transform: scaleX(0.6); + } + 40%, + 50% { + transform: scaleX(0.9); + } + 100% { + transform: scaleX(1); + } +} diff --git a/packages/registry/src/ui/progress-bar/index.tsx b/packages/registry/src/ui/progress-bar/index.tsx index 97d75746f..e77256c19 100644 --- a/packages/registry/src/ui/progress-bar/index.tsx +++ b/packages/registry/src/ui/progress-bar/index.tsx @@ -1,39 +1,29 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; -import type { - ProgressBarControlProps, - ProgressBarProps, - ProgressBarValueLabelProps, -} from "./types"; +import type { ProgressBarControlProps, ProgressBarProps, ProgressBarValueLabelProps } from "./types"; export const ProgressBar = createDynamicComponent( - "progress-bar", - "ProgressBar", - Default.ProgressBar, - {}, + "progress-bar", + "ProgressBar", + Default.ProgressBar, + {}, ); -export const ProgressBarControl = - createDynamicComponent( - "progress-bar", - "ProgressBarControl", - Default.ProgressBarControl, - {}, - ); +export const ProgressBarControl = createDynamicComponent( + "progress-bar", + "ProgressBarControl", + Default.ProgressBarControl, + {}, +); -export const ProgressBarValueLabel = - createDynamicComponent( - "progress-bar", - "ProgressBarValueLabel", - Default.ProgressBarValueLabel, - {}, - ); +export const ProgressBarValueLabel = createDynamicComponent( + "progress-bar", + "ProgressBarValueLabel", + Default.ProgressBarValueLabel, + {}, +); -export type { - ProgressBarProps, - ProgressBarControlProps, - ProgressBarValueLabelProps, -}; +export type { ProgressBarProps, ProgressBarControlProps, ProgressBarValueLabelProps }; diff --git a/packages/registry/src/ui/radio-group/index.tsx b/packages/registry/src/ui/radio-group/index.tsx index 0ccb34be6..ce73a4669 100644 --- a/packages/registry/src/ui/radio-group/index.tsx +++ b/packages/registry/src/ui/radio-group/index.tsx @@ -1,29 +1,19 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { RadioGroupProps, RadioIndicatorProps, RadioProps } from "./types"; -export const Radio = createDynamicComponent( - "radio-group", - "Radio", - Default.Radio, - {}, -); +export const Radio = createDynamicComponent("radio-group", "Radio", Default.Radio, {}); export const RadioIndicator = createDynamicComponent( - "radio-group", - "RadioIndicator", - Default.RadioIndicator, - {}, + "radio-group", + "RadioIndicator", + Default.RadioIndicator, + {}, ); -export const RadioGroup = createDynamicComponent( - "radio-group", - "RadioGroup", - Default.RadioGroup, - {}, -); +export const RadioGroup = createDynamicComponent("radio-group", "RadioGroup", Default.RadioGroup, {}); export type { RadioProps, RadioIndicatorProps, RadioGroupProps }; diff --git a/packages/registry/src/ui/search-field/index.tsx b/packages/registry/src/ui/search-field/index.tsx index 93f95a0c5..ab4a844c5 100644 --- a/packages/registry/src/ui/search-field/index.tsx +++ b/packages/registry/src/ui/search-field/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { SearchFieldProps } from "./types"; -export const SearchField = createDynamicComponent( - "search-field", - "SearchField", - Default.SearchField, - {}, -); +export const SearchField = createDynamicComponent("search-field", "SearchField", Default.SearchField, {}); export type { SearchFieldProps }; diff --git a/packages/registry/src/ui/select/index.tsx b/packages/registry/src/ui/select/index.tsx index 47844fe72..5603b39c9 100644 --- a/packages/registry/src/ui/select/index.tsx +++ b/packages/registry/src/ui/select/index.tsx @@ -1,95 +1,58 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import type { ButtonProps } from "@dotui/registry/ui/button"; -import type { - ListBoxItemProps, - ListBoxSectionHeaderProps, - ListBoxSectionProps, -} from "@dotui/registry/ui/list-box"; +import type { ListBoxItemProps, ListBoxSectionHeaderProps, ListBoxSectionProps } from "@dotui/registry/ui/list-box"; import * as Default from "./basic"; -import type { - SelectContentProps, - SelectProps, - SelectValueProps, -} from "./types"; +import type { SelectContentProps, SelectProps, SelectValueProps } from "./types"; export const Select = (props: SelectProps) => { - const Component = createDynamicComponent>( - "select", - "Select", - Default.Select, - {}, - ); + const Component = createDynamicComponent>("select", "Select", Default.Select, {}); - return ; + return ; }; export const SelectTrigger = createDynamicComponent( - "select", - "SelectTrigger", - Default.SelectTrigger, - {}, - { disableSkeleton: true }, + "select", + "SelectTrigger", + Default.SelectTrigger, + {}, + { disableSkeleton: true }, ); -export const SelectValue = ( - props: SelectValueProps, -) => { - const Component = createDynamicComponent>( - "select", - "SelectValue", - Default.SelectValue, - {}, - ); +export const SelectValue = (props: SelectValueProps) => { + const Component = createDynamicComponent>("select", "SelectValue", Default.SelectValue, {}); - return ; + return ; }; -export const SelectContent = ( - props: SelectContentProps, -) => { - const Component = createDynamicComponent>( - "select", - "SelectContent", - Default.SelectContent, - {}, - ); - return ; +export const SelectContent = (props: SelectContentProps) => { + const Component = createDynamicComponent>("select", "SelectContent", Default.SelectContent, {}); + return ; }; -export const SelectItem = ( - props: ListBoxItemProps, -) => { - const Component = createDynamicComponent>( - "select", - "SelectItem", - Default.SelectItem, - {}, - ); +export const SelectItem = (props: ListBoxItemProps) => { + const Component = createDynamicComponent>("select", "SelectItem", Default.SelectItem, {}); - return ; + return ; }; -export const SelectSection = ( - props: ListBoxSectionProps, -) => { - const Component = createDynamicComponent>( - "select", - "SelectSection", - Default.SelectSection, - {}, - ); - return ; +export const SelectSection = (props: ListBoxSectionProps) => { + const Component = createDynamicComponent>( + "select", + "SelectSection", + Default.SelectSection, + {}, + ); + return ; }; -export const SelectSectionHeader = - createDynamicComponent( - "select", - "SelectSectionHeader", - Default.SelectSectionHeader, - {}, - ); +export const SelectSectionHeader = createDynamicComponent( + "select", + "SelectSectionHeader", + Default.SelectSectionHeader, + {}, +); export type { SelectProps, SelectContentProps, SelectValueProps }; diff --git a/packages/registry/src/ui/separator/index.tsx b/packages/registry/src/ui/separator/index.tsx index b3ce42e52..63dd5eca2 100644 --- a/packages/registry/src/ui/separator/index.tsx +++ b/packages/registry/src/ui/separator/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { SeparatorProps } from "./types"; -export const Separator = createDynamicComponent( - "separator", - "Separator", - Default.Separator, - {}, -); +export const Separator = createDynamicComponent("separator", "Separator", Default.Separator, {}); export type { SeparatorProps }; diff --git a/packages/registry/src/ui/slider/index.tsx b/packages/registry/src/ui/slider/index.tsx index 99811ce4d..093e57985 100644 --- a/packages/registry/src/ui/slider/index.tsx +++ b/packages/registry/src/ui/slider/index.tsx @@ -1,55 +1,33 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; -import type { - SliderControlProps, - SliderFillerProps, - SliderOutputProps, - SliderProps, - SliderThumbProps, -} from "./types"; - -export const Slider = createDynamicComponent( - "slider", - "Slider", - Default.Slider, - {}, -); +import type { SliderControlProps, SliderFillerProps, SliderOutputProps, SliderProps, SliderThumbProps } from "./types"; + +export const Slider = createDynamicComponent("slider", "Slider", Default.Slider, {}); export const SliderControl = createDynamicComponent( - "slider", - "SliderControl", - Default.SliderControl, - {}, + "slider", + "SliderControl", + Default.SliderControl, + {}, ); export const SliderFiller = createDynamicComponent( - "slider", - "SliderFiller", - Default.SliderFiller, - {}, + "slider", + "SliderFiller", + Default.SliderFiller, + {}, ); -export const SliderThumb = createDynamicComponent( - "slider", - "SliderThumb", - Default.SliderThumb, - {}, -); +export const SliderThumb = createDynamicComponent("slider", "SliderThumb", Default.SliderThumb, {}); export const SliderOutput = createDynamicComponent( - "slider", - "SliderOutput", - Default.SliderOutput, - {}, + "slider", + "SliderOutput", + Default.SliderOutput, + {}, ); -export type { - SliderProps, - SliderControlProps, - SliderFillerProps, - SliderThumbProps, - SliderOutputProps, -}; +export type { SliderProps, SliderControlProps, SliderFillerProps, SliderThumbProps, SliderOutputProps }; diff --git a/packages/registry/src/ui/switch/index.tsx b/packages/registry/src/ui/switch/index.tsx index de077b11e..9158dec91 100644 --- a/packages/registry/src/ui/switch/index.tsx +++ b/packages/registry/src/ui/switch/index.tsx @@ -1,33 +1,19 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; -import type { - SwitchIndicatorProps, - SwitchProps, - SwitchThumbProps, -} from "./types"; +import type { SwitchIndicatorProps, SwitchProps, SwitchThumbProps } from "./types"; -export const Switch = createDynamicComponent( - "switch", - "Switch", - Default.Switch, - {}, -); +export const Switch = createDynamicComponent("switch", "Switch", Default.Switch, {}); export const SwitchIndicator = createDynamicComponent( - "switch", - "SwitchIndicator", - Default.SwitchIndicator, - {}, + "switch", + "SwitchIndicator", + Default.SwitchIndicator, + {}, ); -export const SwitchThumb = createDynamicComponent( - "switch", - "SwitchThumb", - Default.SwitchThumb, - {}, -); +export const SwitchThumb = createDynamicComponent("switch", "SwitchThumb", Default.SwitchThumb, {}); export type { SwitchProps, SwitchIndicatorProps, SwitchThumbProps }; diff --git a/packages/registry/src/ui/table/index.tsx b/packages/registry/src/ui/table/index.tsx index 8cfa61335..49ae55728 100644 --- a/packages/registry/src/ui/table/index.tsx +++ b/packages/registry/src/ui/table/index.tsx @@ -1,91 +1,55 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { - TableBodyProps, - TableCellProps, - TableColumnProps, - TableHeaderProps, - TableLoadMoreProps, - TableProps, - TableRowProps, + TableBodyProps, + TableCellProps, + TableColumnProps, + TableHeaderProps, + TableLoadMoreProps, + TableProps, + TableRowProps, } from "./types"; -export const Table = createDynamicComponent( - "table", - "Table", - Default.Table, - {}, -); +export const Table = createDynamicComponent("table", "Table", Default.Table, {}); -export const TableHeader = ( - props: TableHeaderProps, -) => { - const Component = createDynamicComponent>( - "table", - "TableHeader", - Default.TableHeader, - {}, - ); +export const TableHeader = (props: TableHeaderProps) => { + const Component = createDynamicComponent>("table", "TableHeader", Default.TableHeader, {}); - return ; + return ; }; -export const TableColumn = createDynamicComponent( - "table", - "TableColumn", - Default.TableColumn, - {}, -); +export const TableColumn = createDynamicComponent("table", "TableColumn", Default.TableColumn, {}); -export const TableBody = ( - props: TableBodyProps, -) => { - const Component = createDynamicComponent>( - "table", - "TableBody", - Default.TableBody, - {}, - ); +export const TableBody = (props: TableBodyProps) => { + const Component = createDynamicComponent>("table", "TableBody", Default.TableBody, {}); - return ; + return ; }; -export const TableRow = ( - props: TableRowProps, -) => { - const Component = createDynamicComponent>( - "table", - "TableRow", - Default.TableRow, - {}, - ); +export const TableRow = (props: TableRowProps) => { + const Component = createDynamicComponent>("table", "TableRow", Default.TableRow, {}); - return ; + return ; }; -export const TableCell = createDynamicComponent( - "table", - "TableCell", - Default.TableCell, - {}, -); +export const TableCell = createDynamicComponent("table", "TableCell", Default.TableCell, {}); export const TableLoadMore = createDynamicComponent( - "table", - "TableLoadMore", - Default.TableLoadMore, - {}, + "table", + "TableLoadMore", + Default.TableLoadMore, + {}, ); export type { - TableProps, - TableHeaderProps, - TableBodyProps, - TableColumnProps, - TableRowProps, - TableCellProps, - TableLoadMoreProps, + TableProps, + TableHeaderProps, + TableBodyProps, + TableColumnProps, + TableRowProps, + TableCellProps, + TableLoadMoreProps, }; diff --git a/packages/registry/src/ui/tabs/index.tsx b/packages/registry/src/ui/tabs/index.tsx index 975ef7ea2..a1e09d317 100644 --- a/packages/registry/src/ui/tabs/index.tsx +++ b/packages/registry/src/ui/tabs/index.tsx @@ -1,44 +1,24 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { TabListProps, TabPanelProps, TabProps, TabsProps } from "./types"; -export const Tabs = createDynamicComponent( - "tabs", - "Tabs", - Default.Tabs, - { - // Future variants will be added here - }, -); +export const Tabs = createDynamicComponent("tabs", "Tabs", Default.Tabs, { + // Future variants will be added here +}); -export const TabList = createDynamicComponent( - "tabs", - "TabList", - Default.TabList, - { - // Future variants will be added here - }, -); +export const TabList = createDynamicComponent("tabs", "TabList", Default.TabList, { + // Future variants will be added here +}); -export const Tab = createDynamicComponent( - "tabs", - "Tab", - Default.Tab, - { - // Future variants will be added here - }, -); +export const Tab = createDynamicComponent("tabs", "Tab", Default.Tab, { + // Future variants will be added here +}); -export const TabPanel = createDynamicComponent( - "tabs", - "TabPanel", - Default.TabPanel, - { - // Future variants will be added here - }, -); +export const TabPanel = createDynamicComponent("tabs", "TabPanel", Default.TabPanel, { + // Future variants will be added here +}); export type { TabsProps, TabListProps, TabProps, TabPanelProps }; diff --git a/packages/registry/src/ui/tag-group/index.tsx b/packages/registry/src/ui/tag-group/index.tsx index f43206034..3de9b30e5 100644 --- a/packages/registry/src/ui/tag-group/index.tsx +++ b/packages/registry/src/ui/tag-group/index.tsx @@ -1,33 +1,18 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { TagGroupProps, TagListProps, TagProps } from "./types"; -export const TagGroup = createDynamicComponent( - "tag-group", - "TagGroup", - Default.TagGroup, - {}, -); +export const TagGroup = createDynamicComponent("tag-group", "TagGroup", Default.TagGroup, {}); export const TagList = (props: TagListProps) => { - const Component = createDynamicComponent>( - "tag-group", - "TagList", - Default.TagList, - {}, - ); + const Component = createDynamicComponent>("tag-group", "TagList", Default.TagList, {}); - return ; + return ; }; -export const Tag = createDynamicComponent( - "tag-group", - "Tag", - Default.Tag, - {}, -); +export const Tag = createDynamicComponent("tag-group", "Tag", Default.Tag, {}); export type { TagGroupProps, TagProps }; diff --git a/packages/registry/src/ui/text-field/index.tsx b/packages/registry/src/ui/text-field/index.tsx index 31bf7fa16..4c1202018 100644 --- a/packages/registry/src/ui/text-field/index.tsx +++ b/packages/registry/src/ui/text-field/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { TextFieldProps } from "./types"; -export const TextField = createDynamicComponent( - "text-field", - "TextField", - Default.TextField, - {}, -); +export const TextField = createDynamicComponent("text-field", "TextField", Default.TextField, {}); export type { TextFieldProps }; diff --git a/packages/registry/src/ui/text/index.tsx b/packages/registry/src/ui/text/index.tsx index 438659af9..47a2e2abc 100644 --- a/packages/registry/src/ui/text/index.tsx +++ b/packages/registry/src/ui/text/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { TextProps } from "./types"; -export const Text = createDynamicComponent( - "text", - "Text", - Default.Text, - {}, -); +export const Text = createDynamicComponent("text", "Text", Default.Text, {}); export type { TextProps }; diff --git a/packages/registry/src/ui/time-field/index.tsx b/packages/registry/src/ui/time-field/index.tsx index 903137465..375dd0784 100644 --- a/packages/registry/src/ui/time-field/index.tsx +++ b/packages/registry/src/ui/time-field/index.tsx @@ -1,15 +1,10 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { TimeFieldProps } from "./types"; -export const TimeField = createDynamicComponent>( - "time-field", - "TimeField", - Default.TimeField, - {}, -); +export const TimeField = createDynamicComponent>("time-field", "TimeField", Default.TimeField, {}); export type { TimeFieldProps }; diff --git a/packages/registry/src/ui/toast/index.tsx b/packages/registry/src/ui/toast/index.tsx index 241efc612..90873acd7 100644 --- a/packages/registry/src/ui/toast/index.tsx +++ b/packages/registry/src/ui/toast/index.tsx @@ -1,14 +1,9 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; -export const Toaster = createDynamicComponent( - "toast", - "ToastProvider", - Default.Toaster, - {}, -); +export const Toaster = createDynamicComponent("toast", "ToastProvider", Default.Toaster, {}); export { toast } from "./basic"; diff --git a/packages/registry/src/ui/toggle-button-group/index.tsx b/packages/registry/src/ui/toggle-button-group/index.tsx index 5b42d9ba8..8f130d98b 100644 --- a/packages/registry/src/ui/toggle-button-group/index.tsx +++ b/packages/registry/src/ui/toggle-button-group/index.tsx @@ -1,15 +1,15 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { ToggleButtonGroupProps } from "./types"; export const ToggleButtonGroup = createDynamicComponent( - "toggle-button-group", - "ToggleButtonGroup", - Default.ToggleButtonGroup, - {}, + "toggle-button-group", + "ToggleButtonGroup", + Default.ToggleButtonGroup, + {}, ); export type { ToggleButtonGroupProps }; diff --git a/packages/registry/src/ui/toggle-button/index.tsx b/packages/registry/src/ui/toggle-button/index.tsx index 717f66218..6bd422252 100644 --- a/packages/registry/src/ui/toggle-button/index.tsx +++ b/packages/registry/src/ui/toggle-button/index.tsx @@ -1,22 +1,22 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { ToggleButtonProps } from "./types"; export const ToggleButton = createDynamicComponent( - "toggle-button", - "ToggleButton", - Default.ToggleButton, - {}, + "toggle-button", + "ToggleButton", + Default.ToggleButton, + {}, ); export const ToggleButtonProvider = createDynamicComponent( - "toggle-button", - "ToggleButtonProvider", - Default.ToggleButtonProvider, - {}, + "toggle-button", + "ToggleButtonProvider", + Default.ToggleButtonProvider, + {}, ); export { toggleButtonStyles } from "./basic"; diff --git a/packages/registry/src/ui/tooltip/index.tsx b/packages/registry/src/ui/tooltip/index.tsx index 1d7b7de8e..b727c0d5d 100644 --- a/packages/registry/src/ui/tooltip/index.tsx +++ b/packages/registry/src/ui/tooltip/index.tsx @@ -1,22 +1,17 @@ "use client"; -import { createDynamicComponent } from "@dotui/registry/ui/create-dynamic-component"; +import { createDynamicComponent } from "@dotui/core/utils/create-dynamic-component"; import * as Default from "./basic"; import type { TooltipContentProps, TooltipProps } from "./types"; -export const Tooltip = createDynamicComponent( - "tooltip", - "Tooltip", - Default.Tooltip, - {}, -); +export const Tooltip = createDynamicComponent("tooltip", "Tooltip", Default.Tooltip, {}); export const TooltipContent = createDynamicComponent( - "tooltip", - "TooltipContent", - Default.TooltipContent, - {}, + "tooltip", + "TooltipContent", + Default.TooltipContent, + {}, ); export type { TooltipProps, TooltipContentProps }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ad6bf654..b82e6a6ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,22 @@ importers: specifier: ^4.0.1 version: 4.0.15(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@22.19.2)(typescript@5.9.3))(tsx@4.21.0) + packages/core: + dependencies: + react: + specifier: ^19.2.1 + version: 19.2.1 + react-dom: + specifier: ^19.2.1 + version: 19.2.1(react@19.2.1) + devDependencies: + '@dotui/ts-config': + specifier: workspace:* + version: link:../../config/ts-config + typescript: + specifier: ^5.8.3 + version: 5.9.3 + packages/db: dependencies: '@dotui/registry': @@ -169,9 +185,9 @@ importers: packages/registry: dependencies: - '@dotui/style-system': + '@dotui/core': specifier: workspace:* - version: link:../style-system + version: link:../core '@hookform/resolvers': specifier: ^5.2.2 version: 5.2.2(react-hook-form@7.68.0(react@19.2.1)) @@ -233,6 +249,9 @@ importers: specifier: ^4.0.5 version: 4.1.13 devDependencies: + '@dotui/biome-config': + specifier: workspace:* + version: link:../../config/biome-config '@dotui/ts-config': specifier: workspace:* version: link:../../config/ts-config From 0b82c4f010c3a2ec3ab918b720be864f9e9e378e Mon Sep 17 00:00:00 2001 From: mehdibha Date: Mon, 15 Dec 2025 14:59:00 +0100 Subject: [PATCH 08/48] wip --- .../src/utils/create-dynamic-component.tsx | 24 +-- packages/registry/package.json | 2 +- packages/registry/src/base/base.ts | 1 + pnpm-lock.yaml | 151 +++++++++++++++++- 4 files changed, 166 insertions(+), 12 deletions(-) diff --git a/packages/core/src/utils/create-dynamic-component.tsx b/packages/core/src/utils/create-dynamic-component.tsx index 8f5f0c469..03b9d2552 100644 --- a/packages/core/src/utils/create-dynamic-component.tsx +++ b/packages/core/src/utils/create-dynamic-component.tsx @@ -1,10 +1,16 @@ -export const createDynamicComponent = >( - componentName: string, - slotName: string, - DefaultComponent: React.FC, - variants: Record>, -) => { - return (props: Props) => { - return ; - }; +import type React from "react"; + +export const createDynamicComponent = < + Props extends object, + _Variant extends string = string, +>( + _componentKey: string, + _componentName: string, + DefaultComponent: React.ComponentType, + _variants: object, + _options?: object, +): React.ComponentType => { + // TODO: Implement dynamic component resolution based on user config + // For now, always return the default component + return DefaultComponent; }; diff --git a/packages/registry/package.json b/packages/registry/package.json index b17d22464..44a16ec9a 100644 --- a/packages/registry/package.json +++ b/packages/registry/package.json @@ -69,7 +69,7 @@ "@types/react-dom": "^19.2.1", "next": "^16.0.5", "rimraf": "^6.0.1", - "shadcn": "^3.2.1", + "shadcn": "^3.6.1", "tsx": "^4.19.2", "typescript": "^5.8.3" }, diff --git a/packages/registry/src/base/base.ts b/packages/registry/src/base/base.ts index 6b8f0372f..c1e6b8661 100644 --- a/packages/registry/src/base/base.ts +++ b/packages/registry/src/base/base.ts @@ -1,4 +1,5 @@ import type { RegistryItem } from "@dotui/registry/types"; +import { } from "shadcn" export const registryBase = [ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b82e6a6ce..a13b65c37 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -271,8 +271,8 @@ importers: specifier: ^6.0.1 version: 6.1.2 shadcn: - specifier: ^3.2.1 - version: 3.5.1(@types/node@22.19.2)(babel-plugin-macros@3.1.0)(typescript@5.9.3) + specifier: ^3.6.1 + version: 3.6.1(@types/node@22.19.2)(babel-plugin-macros@3.1.0)(typescript@5.9.3) tsx: specifier: ^4.19.2 version: 4.21.0 @@ -3158,6 +3158,10 @@ packages: resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==} engines: {node: '>=6.14.2'} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -3386,6 +3390,18 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.4.0: + resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} @@ -4112,6 +4128,11 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extendable@0.1.1: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} engines: {node: '>=0.10.0'} @@ -4131,6 +4152,15 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} @@ -4185,6 +4215,10 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -4841,6 +4875,10 @@ packages: oniguruma-to-es@4.3.4: resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + ora@8.2.0: resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} engines: {node: '>=18'} @@ -5014,6 +5052,10 @@ packages: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -5060,6 +5102,10 @@ packages: postgres-range@1.1.4: resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} @@ -5274,6 +5320,10 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -5317,6 +5367,10 @@ packages: resolution: {integrity: sha512-yLbIDouYv8Xz25BxV/GAGC/46R7/oNwoXIs/IFIYXK47+fKcFIYzThtBqJwFEZTzkkvqJCo+MBg0K9QLTmhFmQ==} hasBin: true + shadcn@3.6.1: + resolution: {integrity: sha512-ClAtlvtyWVVzM87NCQyivSNSEPQEPLOAVCvey8rr6CukNyK2JeLNzJTdo9+4kpS/BSpx2MvQT+g+/V6ymZWmoQ==} + hasBin: true + sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -5898,6 +5952,10 @@ packages: utf-8-validate: optional: true + wsl-utils@0.3.0: + resolution: {integrity: sha512-3sFIGLiaDP7rTO4xh3g+b3AzhYDIUGGywE/WsmqzJWDxus5aJXVnPTNC/6L+r2WzrwXqVOdD262OaO+cEyPMSQ==} + engines: {node: '>=20'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -8800,6 +8858,10 @@ snapshots: dependencies: node-gyp-build: 4.8.4 + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + bytes@3.1.2: {} call-bind-apply-helpers@1.0.2: @@ -8975,6 +9037,15 @@ snapshots: deepmerge@4.3.1: {} + default-browser-id@5.0.1: {} + + default-browser@5.4.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + defu@6.1.4: {} degenerator@5.0.1: @@ -9771,6 +9842,8 @@ snapshots: is-decimal@2.0.1: {} + is-docker@3.0.0: {} + is-extendable@0.1.1: {} is-extglob@2.1.1: {} @@ -9783,6 +9856,12 @@ snapshots: is-hexadecimal@2.0.1: {} + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-interactive@2.0.0: {} is-node-process@1.2.0: {} @@ -9813,6 +9892,10 @@ snapshots: is-windows@1.0.2: {} + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + isexe@2.0.0: {} isexe@3.1.1: {} @@ -10620,6 +10703,15 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 + open@11.0.0: + dependencies: + default-browser: 5.4.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.0 + ora@8.2.0: dependencies: chalk: 5.6.2 @@ -10792,6 +10884,11 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-value-parser@4.2.0: {} postcss@8.4.31: @@ -10828,6 +10925,8 @@ snapshots: postgres-range@1.1.4: {} + powershell-utils@0.1.0: {} + prettier@2.8.8: {} pretty-ms@9.3.0: @@ -11236,6 +11335,8 @@ snapshots: transitivePeerDependencies: - supports-color + run-applescript@7.1.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -11325,6 +11426,47 @@ snapshots: - supports-color - typescript + shadcn@3.6.1(@types/node@22.19.2)(babel-plugin-macros@3.1.0)(typescript@5.9.3): + dependencies: + '@antfu/ni': 25.0.0 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) + '@dotenvx/dotenvx': 1.51.1 + '@modelcontextprotocol/sdk': 1.24.3(zod@3.25.76) + browserslist: 4.28.1 + commander: 14.0.2 + cosmiconfig: 9.0.0(typescript@5.9.3) + dedent: 1.7.0(babel-plugin-macros@3.1.0) + deepmerge: 4.3.1 + diff: 8.0.2 + execa: 9.6.1 + fast-glob: 3.3.3 + fs-extra: 11.3.2 + fuzzysort: 3.1.0 + https-proxy-agent: 7.0.6 + kleur: 4.1.5 + msw: 2.12.4(@types/node@22.19.2)(typescript@5.9.3) + node-fetch: 3.3.2 + open: 11.0.0 + ora: 8.2.0 + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + prompts: 2.4.2 + recast: 0.23.11 + stringify-object: 5.0.0 + ts-morph: 26.0.0 + tsconfig-paths: 4.2.0 + zod: 3.25.76 + zod-to-json-schema: 3.25.0(zod@3.25.76) + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@types/node' + - babel-plugin-macros + - supports-color + - typescript + sharp@0.34.5: dependencies: '@img/colour': 1.0.0 @@ -11894,6 +12036,11 @@ snapshots: optionalDependencies: bufferutil: 4.0.9 + wsl-utils@0.3.0: + dependencies: + is-wsl: 3.1.0 + powershell-utils: 0.1.0 + xtend@4.0.2: {} y18n@5.0.8: {} From b97b2300d4d67cb0cecb7357161fbb6318adb799 Mon Sep 17 00:00:00 2001 From: mehdibha Date: Mon, 15 Dec 2025 15:27:59 +0100 Subject: [PATCH 09/48] new core package wip --- packages/core/src/__generated__/base.ts | 29 + packages/core/src/__generated__/blocks.ts | 1378 ++++ packages/core/src/__generated__/hooks.ts | 38 + packages/core/src/__generated__/icons.ts | 206 + packages/core/src/__generated__/lib.ts | 54 + packages/core/src/__generated__/types.ts | 42 + packages/core/src/__generated__/ui.ts | 7435 +++++++++++++++++++++ packages/registry/package.json | 1 + packages/registry/scripts/build-core.ts | 278 + packages/registry/src/base/base.ts | 1 - packages/registry/src/types.ts | 8 +- packages/shadcn-adapter/package.json | 2 +- pnpm-lock.yaml | 51 +- www/package.json | 2 +- 14 files changed, 9471 insertions(+), 54 deletions(-) create mode 100644 packages/core/src/__generated__/base.ts create mode 100644 packages/core/src/__generated__/blocks.ts create mode 100644 packages/core/src/__generated__/hooks.ts create mode 100644 packages/core/src/__generated__/icons.ts create mode 100644 packages/core/src/__generated__/lib.ts create mode 100644 packages/core/src/__generated__/types.ts create mode 100644 packages/core/src/__generated__/ui.ts create mode 100644 packages/registry/scripts/build-core.ts diff --git a/packages/core/src/__generated__/base.ts b/packages/core/src/__generated__/base.ts new file mode 100644 index 000000000..92a9a654b --- /dev/null +++ b/packages/core/src/__generated__/base.ts @@ -0,0 +1,29 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build:core" to regenerate + +export const base = [ + { + "name": "base", + "type": "registry:style", + "dependencies": [ + "tailwind-variants", + "clsx", + "tailwind-merge", + "react-aria-components", + "tailwindcss-react-aria-components", + "tw-animate-css", + "tailwindcss-autocontrast" + ], + "registryDependencies": [ + "utils", + "focus-styles", + "theme" + ], + "extends": "none", + "css": { + "@plugin tailwindcss-react-aria-components": {}, + "@plugin tailwindcss-autocontrast": {} + }, + "files": [] + } +] as const; diff --git a/packages/core/src/__generated__/blocks.ts b/packages/core/src/__generated__/blocks.ts new file mode 100644 index 000000000..214786c34 --- /dev/null +++ b/packages/core/src/__generated__/blocks.ts @@ -0,0 +1,1378 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build:core" to regenerate + +export const blocksCategories = [ + { + "name": "Featured", + "slug": "featured" + } +] as const; + +export const blocks = [ + { + "name": "login", + "type": "registry:block", + "dependencies": [ + "@internationalized/date" + ], + "registryDependencies": [ + "button", + "text-field", + "card", + "link" + ], + "description": "A simple login form.", + "categories": [ + "featured", + "authentication" + ], + "meta": { + "containerHeight": 600 + }, + "files": [ + { + "type": "registry:page", + "path": "blocks/auth/login/page.tsx", + "target": "app/login/page.tsx", + "content": `import { LoginForm } from "@dotui/registry/blocks/auth/login/components/login-form"; + +export default function Page() { + return ( +
+ +
+ ); +} +` + }, + { + "type": "registry:component", + "path": "blocks/auth/login/components/login-form.tsx", + "target": "blocks/auth/login/components/login-form.tsx", + "content": `"use client"; + +import { cn } from "@dotui/registry/lib/utils"; +import { Button } from "@dotui/registry/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@dotui/registry/ui/card"; +import { Label } from "@dotui/registry/ui/field"; +import { Input } from "@dotui/registry/ui/input"; +import { Link } from "@dotui/registry/ui/link"; +import { TextField } from "@dotui/registry/ui/text-field"; + +export function LoginForm(props: React.ComponentProps<"div">) { + return ( + + + Login to your account + + Enter your email below to login to your account + + + +
+ + + +
+
+
+ +
+
+ Or +
+
+ + + + + +

+ Don't have an account?{" "} + + register + +

+
+
+ ); +} +` + } + ] + }, + { + "name": "cards", + "type": "registry:block", + "registryDependencies": [ + "all" + ], + "description": "A set of cards.", + "categories": [ + "featured", + "showcase" + ], + "meta": { + "containerHeight": 600 + }, + "files": [ + { + "type": "registry:component", + "path": "blocks/showcase/cards/components/cards.tsx", + "target": "blocks/showcase/cards/components/cards.tsx", + "content": `import { AccountMenu } from "@dotui/registry/blocks/showcase/cards/components/account-menu"; +import { Backlog } from "@dotui/registry/blocks/showcase/cards/components/backlog"; +import { Booking } from "@dotui/registry/blocks/showcase/cards/components/booking"; +import { ColorEditorCard } from "@dotui/registry/blocks/showcase/cards/components/color-editor"; +import { Filters } from "@dotui/registry/blocks/showcase/cards/components/filters"; +import { InviteMembers } from "@dotui/registry/blocks/showcase/cards/components/invite-members"; +import { LoginForm } from "@dotui/registry/blocks/showcase/cards/components/login-form"; +import { Notifications } from "@dotui/registry/blocks/showcase/cards/components/notifications"; +import { TeamName } from "@dotui/registry/blocks/showcase/cards/components/team-name"; +import { cn } from "@dotui/registry/lib/utils"; + +export function Cards(props: React.ComponentProps<"div">) { + return ( +
+
+ + +
+ + + + +
+ + +
+ + +
+ +
+
+ ); +} + +export default Cards; +` + }, + { + "type": "registry:component", + "path": "blocks/showcase/cards/components/account-menu.tsx", + "target": "blocks/showcase/cards/components/account-menu.tsx", + "content": `"use client"; + +import { + BookIcon, + ContrastIcon, + LanguagesIcon, + LogOutIcon, + SettingsIcon, + User2Icon, + Users2Icon, +} from "lucide-react"; + +import { cn } from "@dotui/registry/lib/utils"; +import { Avatar } from "@dotui/registry/ui/avatar"; +import { Card, CardContent, CardHeader } from "@dotui/registry/ui/card"; +import { + ListBox, + ListBoxItem, + ListBoxSection, + ListBoxSectionHeader, +} from "@dotui/registry/ui/list-box"; +import { Separator } from "@dotui/registry/ui/separator"; + +export function AccountMenu({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( + + + +
+

mehdibha

+

+ hello@mehdibha.com +

+
+
+ + + + + Profile + + + + Settings + + + + Documentation + + + + Community + + + + Preferences + + + Theme + + + + Language + + + + + + Log out + + + +
+ ); +} +` + }, + { + "type": "registry:component", + "path": "blocks/showcase/cards/components/backlog.tsx", + "target": "blocks/showcase/cards/components/backlog.tsx", + "content": `"use client"; + +import { + RiCheckboxCircleFill, + RiProgress4Line, + RiProgress6Line, +} from "@remixicon/react"; +import { + AlertTriangle, + Circle, + CircleDashedIcon, + MoreHorizontal, + Settings2Icon, + Zap, +} from "lucide-react"; + +import { Badge } from "@dotui/registry/ui/badge"; +import { Button } from "@dotui/registry/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@dotui/registry/ui/card"; +import { Input } from "@dotui/registry/ui/input"; +import { Menu, MenuContent, MenuItem } from "@dotui/registry/ui/menu"; +import { Overlay } from "@dotui/registry/ui/overlay"; +import { SearchField } from "@dotui/registry/ui/search-field"; +import { + Table, + TableBody, + TableCell, + TableColumn, + TableHeader, + TableRow, +} from "@dotui/registry/ui/table"; + +export const statuses = [ + { + value: "draft", + label: "Draft", + variant: "default", + icon: CircleDashedIcon, + }, + { + value: "in progress", + label: "In progress", + variant: "info", + icon: RiProgress4Line, + }, + { + value: "in review", + label: "In review", + variant: "warning", + icon: RiProgress6Line, + }, + { + value: "done", + label: "Done", + variant: "success", + icon: RiCheckboxCircleFill, + }, +] as const; + +export const priorities = [ + { + value: "P0", + label: "P0", + variant: "danger", + icon: AlertTriangle, + }, + { + value: "P1", + label: "P1", + variant: "warning", + icon: Zap, + }, + { + value: "P2", + label: "P2", + variant: "info", + icon: Circle, + }, + { + value: "P3", + label: "P3", + variant: "default", + icon: Circle, + }, +] as const; + +export const types = [ + { + value: "feature", + label: "Feature", + variant: "info", + }, + { + value: "bug", + label: "Bug", + variant: "danger", + }, + { + value: "tech debt", + label: "Tech Debt", + variant: "warning", + }, + { + value: "spike", + label: "Spike", + variant: "info", + }, + { + value: "chore", + label: "Chore", + variant: "neutral", + }, + { + value: "performance", + label: "Performance", + variant: "success", + }, +] as const; + +interface Column { + id: keyof Omit | "actions"; + name: string; + isRowHeader?: boolean; +} + +const columns: Column[] = [ + { name: "Title", id: "title", isRowHeader: true }, + { name: "Priority", id: "priority" }, + { name: "Status", id: "status" }, + { name: "Assignee", id: "assignee" }, + { name: "Story Points", id: "storyPoints" }, + { name: "", id: "actions" }, +]; + +interface Item { + id: number; + title: string; + priority: string; + status: string; + assignee: string; + storyPoints: string; + type: string; +} + +interface User { + username: string; + name: string; + avatar: string; +} + +export const users: User[] = [ + { + username: "shadcn", + name: "shadcn", + avatar: "https://github.com/shadcn.png", + }, + { + username: "tannerlinsley", + name: "Tanner Linsley", + avatar: "https://github.com/tannerlinsley.png", + }, + { + username: "t3dotgg", + name: "Theo Browne", + avatar: "https://github.com/t3dotgg.png", + }, + { + username: "rauchg", + name: "Guillermo Rauch", + avatar: "https://github.com/rauchg.png", + }, + { + username: "leerob", + name: "Lee Robinson", + avatar: "https://github.com/leerob.png", + }, + { + username: "steventey", + name: "Steven Tey", + avatar: "https://github.com/steventey.png", + }, +] as const; + +const data: Item[] = [ + { + id: 1, + title: "Refactor AuthProvider to support SSO + 2FA", + priority: "P0", + status: "in progress", + assignee: "shadcn", + storyPoints: "13", + type: "feature", + }, + { + id: 2, + title: "Fix race condition in payment webhooks", + priority: "P1", + status: "in review", + assignee: "tannerlinsley", + storyPoints: "5", + type: "bug", + }, + { + id: 3, + title: "Migrate legacy API from REST to GraphQL", + priority: "P2", + status: "draft", + assignee: "t3dotgg", + storyPoints: "21", + type: "tech debt", + }, + { + id: 4, + title: "Add Storybook stories for Button variants", + priority: "P3", + status: "done", + assignee: "rauchg", + storyPoints: "3", + type: "chore", + }, + { + id: 5, + title: "Spike: Evaluate Redis vs Kafka for event streaming", + priority: "P2", + status: "in progress", + assignee: "leerob", + storyPoints: "8", + type: "spike", + }, + { + id: 6, + title: "Implement lazy loading for ProductGrid component", + priority: "P1", + status: "draft", + assignee: "steventey", + storyPoints: "5", + type: "performance", + }, +]; + +export function Backlog(props: React.ComponentProps<"div">) { + return ( + + + Backlog + + Here's a list of your tasks for this month. + + + +
+
+ + + +
+
+ + +
+
+ + + {(column) => ( + + {column.name} + + )} + + + {(item) => ( + + + {() => { + const type = types.find((t) => t.value === item.type); + if (!type) return null; + return ( +
+ + {type?.label || item.type} + + {item.title} +
+ ); + }} +
+ + {(() => { + const priority = priorities.find( + (p) => p.value === item.priority, + ); + if (!priority) return null; + return ( + {priority.label} + ); + })()} + + + {(() => { + const status = statuses.find( + (s) => s.value === item.status, + ); + if (!status) return null; + const StatusIcon = status.icon || Circle; + return ( + + + {status?.label || item.status} + + ); + })()} + + + {(() => { + const user = users.find( + (u) => u.username === item.assignee, + ); + if (!user) return null; + return ( +
+ {user.name} + {user.name} +
+ ); + })()} +
+ + + {item.storyPoints} + + + + + + + + Edit + Duplicate + Archive + Delete + + + + +
+ )} +
+
+
+
+ ); +} +` + }, + { + "type": "registry:component", + "path": "blocks/showcase/cards/components/booking.tsx", + "target": "blocks/showcase/cards/components/booking.tsx", + "content": `"use client"; + +import { parseDate } from "@internationalized/date"; + +import { cn } from "@dotui/registry/lib/utils"; +import { Button } from "@dotui/registry/ui/button"; +import { Calendar } from "@dotui/registry/ui/calendar"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@dotui/registry/ui/card"; +import { Label } from "@dotui/registry/ui/field"; +import { Input } from "@dotui/registry/ui/input"; +import { TimeField } from "@dotui/registry/ui/time-field"; + +export function Booking({ className, ...props }: React.ComponentProps<"div">) { + return ( + + + Booking + Pick a time for your meeting. + + + +
+ + + + + + + + +
+
+ + + + +
+ ); +} +` + }, + { + "type": "registry:component", + "path": "blocks/showcase/cards/components/color-editor.tsx", + "target": "blocks/showcase/cards/components/color-editor.tsx", + "content": `"use client"; + +import { cn } from "@dotui/registry/lib/utils"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@dotui/registry/ui/card"; +import { ColorEditor } from "@dotui/registry/ui/color-editor"; + +export function ColorEditorCard({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( + + + Accent color + Edit the accent color of the app. + + + + + + ); +} +` + }, + { + "type": "registry:component", + "path": "blocks/showcase/cards/components/filters.tsx", + "target": "blocks/showcase/cards/components/filters.tsx", + "content": `"use client"; + +// import { ZapIcon } from "lucide-react"; +import { cn } from "@dotui/registry/lib/utils"; +import { Button } from "@dotui/registry/ui/button"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@dotui/registry/ui/card"; +import { Description, Label } from "@dotui/registry/ui/field"; +import { Slider, SliderControl, SliderOutput } from "@dotui/registry/ui/slider"; +import { Switch } from "@dotui/registry/ui/switch"; +import { Tag, TagGroup, TagList } from "@dotui/registry/ui/tag-group"; +import { ToggleButton } from "@dotui/registry/ui/toggle-button"; +import { ToggleButtonGroup } from "@dotui/registry/ui/toggle-button-group"; + +export function Filters({ className, ...props }: React.ComponentProps<"div">) { + return ( + + + Filters + + +
+ + + Any type + Room + Entire home + +
+ +
+ + +
+ + Trip price, includes all fees +
+ + + + Wifi + TV + Kitchen + Pool + Washer + Dryer + Heating + Hair dryer + EV charger + Gym + BBQ grill + Breakfast + + + + + {/* */} + Instant booking + + +
+ + + + +
+ ); +} +` + }, + { + "type": "registry:component", + "path": "blocks/showcase/cards/components/invite-members.tsx", + "target": "blocks/showcase/cards/components/invite-members.tsx", + "content": `"use client"; + +import { PlusCircleIcon } from "lucide-react"; + +import { ExternalLinkIcon } from "@dotui/registry/icons"; +import { Avatar } from "@dotui/registry/ui/avatar"; +import { Button } from "@dotui/registry/ui/button"; +import { + Card, + CardAction, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@dotui/registry/ui/card"; +import { Label } from "@dotui/registry/ui/field"; +import { Input } from "@dotui/registry/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, +} from "@dotui/registry/ui/select"; +import { Separator } from "@dotui/registry/ui/separator"; +import { TextField } from "@dotui/registry/ui/text-field"; + +const teamMembers = [ + { + name: "shadcn", + email: "shadcn@vercel.com", + avatar: "https://github.com/shadcn.png", + role: "owner", + }, + { + name: "rauchg", + email: "rauchg@vercel.com", + avatar: "https://github.com/rauchg.png", + role: "member", + }, + { + name: "Lee Robinson", + email: "lee@cursor.com", + avatar: "https://github.com/leerob.png", + role: "member", + }, +]; + +export function InviteMembers(props: React.ComponentProps<"div">) { + return ( + + + Invite Members + + Collaborate with members on this project. + + + + + + + +
+
+ + + + + +
+ +
+

Team members

+
+ {teamMembers.map((member) => ( +
+
+ +
+

{member.name}

+

{member.role}

+
+
+ +
+ ))} +
+
+
+
+ + +

+ Learn more about{" "} + + inviting members + + . +

+ +
+
+ ); +} +` + }, + { + "type": "registry:component", + "path": "blocks/showcase/cards/components/login-form.tsx", + "target": "blocks/showcase/cards/components/login-form.tsx", + "content": `"use client"; + +import { cn } from "@dotui/registry/lib/utils"; +import { Button } from "@dotui/registry/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@dotui/registry/ui/card"; +import { Label } from "@dotui/registry/ui/field"; +import { Input } from "@dotui/registry/ui/input"; +import { Link } from "@dotui/registry/ui/link"; +import { TextField } from "@dotui/registry/ui/text-field"; + +export function LoginForm(props: React.ComponentProps<"div">) { + return ( + + + Login to your account + + Enter your email below to login to your account + + + +
+ + + +
+
+
+ +
+
+ Or +
+
+ + + + + +

+ {/* TODO */} + Don't have an account?{" "} + + register + +

+
+
+ ); +} +` + }, + { + "type": "registry:component", + "path": "blocks/showcase/cards/components/notifications.tsx", + "target": "blocks/showcase/cards/components/notifications.tsx", + "content": `import React from "react"; + +import { cn } from "@dotui/registry/lib/utils"; +import { Avatar } from "@dotui/registry/ui/avatar"; +import { Badge } from "@dotui/registry/ui/badge"; +import { Button } from "@dotui/registry/ui/button"; +import { + Card, + CardAction, + CardContent, + CardHeader, + CardTitle, +} from "@dotui/registry/ui/card"; +import { ListBox, ListBoxItem } from "@dotui/registry/ui/list-box"; +import { Separator } from "@dotui/registry/ui/separator"; +import { Tab, TabList, TabPanel, Tabs } from "@dotui/registry/ui/tabs"; + +export function Notifications({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( + + + + Notifications + 12 + + + + + + + + + All + Unread + Read + + {["all", "unread", "read"].map((tab) => ( + + + {notifications + .filter((notification) => { + if (tab === "all") return true; + if (tab === "unread") return !notification.read; + if (tab === "read") return notification.read; + return false; + }) + .map((notification, index) => ( + + + +
+ n[0]) + .join("")} + size="md" + /> +
+

+ + {notification.user.name} + {" "} + {notification.content ? ( + notification.content + ) : ( + {notification.text} + )} +

+
+

+ {notification.timestamp} +

+ {notification.action && ( +
+ +
+ )} +
+
+
+
+
+ ))} +
+
+ ))} +
+
+
+ ); +} + +const notifications = [ + { + user: { + name: "Guillermo Rauch", + avatar: "https://avatars.githubusercontent.com/rauchg", + }, + text: "starred your repository dotUI.", + content: ( + <> + starred mehdibha/dotUI. + + ), + read: false, + timestamp: "2 hours ago", + }, + { + user: { + name: "Lee Robinson", + avatar: "https://avatars.githubusercontent.com/leerob", + }, + text: "invited you to the Vercel GitHub organization.", + content: ( + <> + invited you to join Cursor on + GitHub. + + ), + read: false, + action: { label: "View invite" }, + timestamp: "7 hours ago", + }, + { + user: { + name: "Tim Neutkens", + avatar: "https://avatars.githubusercontent.com/timneutkens", + }, + text: "published a new release v14.2.0-canary on vercel/next.js.", + content: ( + <> + published v14.2.0-canary on + vercel/next.js. + + ), + read: false, + action: { label: "See release" }, + timestamp: "Yesterday", + }, + { + user: { + name: "Steven Tey", + avatar: "https://avatars.githubusercontent.com/steven-tey", + }, + text: "opened a pull request: Improve docs.", + content: ( + <> + opened PR: + Improve docs in + mehdibha/dotUI. + + ), + read: true, + action: { label: "Review PR" }, + timestamp: "Yesterday", + }, + { + user: { + name: "Shu Ding", + avatar: "https://avatars.githubusercontent.com/shuding", + }, + text: "starred your repository dotUI.", + content: ( + <> + starred mehdibha/dotUI. + + ), + read: true, + timestamp: "2 days ago", + }, + { + user: { + name: "Delba de Oliveira", + avatar: "https://avatars.githubusercontent.com/delbaoliveira", + }, + text: "commented on issue: Add theme presets.", + content: ( + <> + commented on #128: + Add theme presets. + + ), + read: false, + action: { label: "Reply" }, + timestamp: "3 days ago", + }, +]; +` + }, + { + "type": "registry:component", + "path": "blocks/showcase/cards/components/team-name.tsx", + "target": "blocks/showcase/cards/components/team-name.tsx", + "content": `"use client"; + +import { cn } from "@dotui/registry/lib/utils"; +import { Button } from "@dotui/registry/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@dotui/registry/ui/card"; +import { Input } from "@dotui/registry/ui/input"; +import { TextField } from "@dotui/registry/ui/text-field"; + +export function TeamName({ className, ...props }: React.ComponentProps<"div">) { + return ( + + + Team Name + + This is your team's visible name within the platform. For example, the + name of your company or department. + + + + + + + + +

+ Please use 32 characters at maximum. +

+ +
+
+ ); +} +` + } + ] + }, + { + "name": "animation", + "type": "registry:block", + "registryDependencies": [ + "all" + ], + "categories": [ + "featured", + "showcase" + ], + "meta": { + "containerHeight": 600 + }, + "files": [ + { + "type": "registry:component", + "path": "blocks/showcase/animation/components/animation.tsx", + "target": "blocks/showcase/animation/components/animation.tsx", + "content": `"use client"; + +import React from "react"; + +import { Select, SelectItem } from "@dotui/registry/ui/select"; + +export function Animation({ + className: _className, +}: React.ComponentProps<"div">) { + const [isOpen, setIsOpen] = React.useState(false); + + React.useEffect(() => { + const interval = setInterval(() => { + setIsOpen((prev) => !prev); + }, 1000); + return () => clearInterval(interval); + }, []); + + return ( +
+ +
+ ); +} + +export default Animation; +` + } + ] + } +] as const; diff --git a/packages/core/src/__generated__/hooks.ts b/packages/core/src/__generated__/hooks.ts new file mode 100644 index 000000000..f935fe2b7 --- /dev/null +++ b/packages/core/src/__generated__/hooks.ts @@ -0,0 +1,38 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build:core" to regenerate + +export const hooks = [ + { + "name": "use-mobile", + "type": "registry:hook", + "files": [ + { + "type": "registry:hook", + "path": "hooks/use-mobile.ts", + "target": "hooks/use-mobile.ts", + "content": `import * as React from "react"; + +const MOBILE_BREAKPOINT = 768; + +export function useIsMobile() { + const [isMobile, setIsMobile] = React.useState( + undefined, + ); + + React.useEffect(() => { + const mql = window.matchMedia(\`(max-width: \${MOBILE_BREAKPOINT - 1}px)\`); + const onChange = () => { + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + }; + mql.addEventListener("change", onChange); + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + return () => mql.removeEventListener("change", onChange); + }, []); + + return !!isMobile; +} +` + } + ] + } +] as const; diff --git a/packages/core/src/__generated__/icons.ts b/packages/core/src/__generated__/icons.ts new file mode 100644 index 000000000..c51d75d76 --- /dev/null +++ b/packages/core/src/__generated__/icons.ts @@ -0,0 +1,206 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build:core" to regenerate + +export const iconLibraries = [ + { + "name": "lucide", + "label": "Lucide icons", + "package": "lucide-react", + "import": "lucide-react" + }, + { + "name": "remix", + "label": "Remix icons", + "package": "@remixicon/react", + "import": "@remixicon/react" + } +] as const; + +export type IconLibraryName = (typeof iconLibraries)[number]["name"]; + +export const icons = { + "Loader2Icon": { + "lucide": "Loader2Icon", + "remix": "RiLoader4Line" + }, + "AlertCircleIcon": { + "lucide": "AlertCircleIcon", + "remix": "RiErrorWarningFill" + }, + "AlertTriangleIcon": { + "lucide": "AlertTriangleIcon", + "remix": "RiAlertLine" + }, + "CheckCircle2Icon": { + "lucide": "CheckCircle2Icon", + "remix": "RiCheckboxCircleLine" + }, + "InfoIcon": { + "lucide": "InfoIcon", + "remix": "RiInformationLine" + }, + "ChevronRightIcon": { + "lucide": "ChevronRightIcon", + "remix": "RiArrowRightSLine" + }, + "ChevronLeftIcon": { + "lucide": "ChevronLeftIcon", + "remix": "RiArrowLeftSLine" + }, + "ChevronDownIcon": { + "lucide": "ChevronDownIcon", + "remix": "RiArrowDownSLine" + }, + "ChevronUpIcon": { + "lucide": "ChevronUpIcon", + "remix": "RiArrowUpSLine" + }, + "SearchIcon": { + "lucide": "SearchIcon", + "remix": "RiSearchLine" + }, + "HelpCircleIcon": { + "lucide": "HelpCircleIcon", + "remix": "RiQuestionLine" + }, + "CalendarIcon": { + "lucide": "CalendarIcon", + "remix": "RiCalendarLine" + }, + "XIcon": { + "lucide": "XIcon", + "remix": "RiCloseLine" + }, + "AsteriskIcon": { + "lucide": "AsteriskIcon", + "remix": "RiAsterisk" + }, + "CheckIcon": { + "lucide": "CheckIcon", + "remix": "RiCheckLine" + }, + "MinusIcon": { + "lucide": "MinusIcon", + "remix": "RiSubtractLine" + }, + "PlusIcon": { + "lucide": "PlusIcon", + "remix": "RiAddLine" + }, + "WalletIcon": { + "lucide": "WalletIcon", + "remix": "RiWalletLine" + }, + "GlobeIcon": { + "lucide": "GlobeIcon", + "remix": "RiGlobalLine" + }, + "User2Icon": { + "lucide": "User2Icon", + "remix": "RiUserLine" + }, + "ShieldIcon": { + "lucide": "ShieldIcon", + "remix": "RiShieldLine" + }, + "ArrowRightCircleIcon": { + "lucide": "ArrowRightCircleIcon", + "remix": "RiArrowRightCircleLine" + }, + "HomeIcon": { + "lucide": "HomeIcon", + "remix": "RiHomeLine" + }, + "LogInIcon": { + "lucide": "LogInIcon", + "remix": "RiLoginBoxLine" + }, + "UploadIcon": { + "lucide": "UploadIcon", + "remix": "RiUpload2Line" + }, + "PaletteIcon": { + "lucide": "PaletteIcon", + "remix": "RiPaletteLine" + }, + "UsersIcon": { + "lucide": "UsersIcon", + "remix": "RiGroupLine" + }, + "PlaneIcon": { + "lucide": "PlaneIcon", + "remix": "RiPlaneLine" + }, + "CameraIcon": { + "lucide": "CameraIcon", + "remix": "RiCameraLine" + }, + "ExternalLinkIcon": { + "lucide": "ExternalLinkIcon", + "remix": "RiExternalLinkLine" + }, + "MenuIcon": { + "lucide": "MenuIcon", + "remix": "RiMenuLine" + }, + "CopyIcon": { + "lucide": "CopyIcon", + "remix": "RiFileCopyLine" + }, + "PlusSquareIcon": { + "lucide": "PlusSquareIcon", + "remix": "RiAddBoxLine" + }, + "SquarePenIcon": { + "lucide": "SquarePenIcon", + "remix": "RiEditLine" + }, + "RotateCwIcon": { + "lucide": "RotateCwIcon", + "remix": "RiRestartLine" + }, + "XCircleIcon": { + "lucide": "XCircleIcon", + "remix": "RiCloseCircleLine" + }, + "BoldIcon": { + "lucide": "BoldIcon", + "remix": "RiBold" + }, + "ItalicIcon": { + "lucide": "ItalicIcon", + "remix": "RiItalic" + }, + "TimerIcon": { + "lucide": "TimerIcon", + "remix": "RiTimerLine" + }, + "PinIcon": { + "lucide": "PinIcon", + "remix": "RiPushpinLine" + }, + "ALargeSmallIcon": { + "lucide": "ALargeSmallIcon", + "remix": "RiFontSize" + }, + "Volume1Icon": { + "lucide": "Volume1Icon", + "remix": "RiVolumeDownLine" + }, + "Volume2Icon": { + "lucide": "Volume2Icon", + "remix": "RiVolumeUpLine" + }, + "ChevronsUpDownIcon": { + "lucide": "ChevronsUpDownIcon", + "remix": "RiExpandUpDownLine" + }, + "PenSquareIcon": { + "lucide": "PenSquareIcon", + "remix": "RiEditBoxLine" + }, + "GripVerticalIcon": { + "lucide": "GripVerticalIcon", + "remix": "RiDraggable" + } +} as const; diff --git a/packages/core/src/__generated__/lib.ts b/packages/core/src/__generated__/lib.ts new file mode 100644 index 000000000..0e0187c91 --- /dev/null +++ b/packages/core/src/__generated__/lib.ts @@ -0,0 +1,54 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build:core" to regenerate + +export const lib = [ + { + "name": "utils", + "type": "registry:lib", + "files": [ + { + "type": "registry:lib", + "path": "lib/utils/index.ts", + "target": "lib/utils.ts", + "content": `import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; +import type { ClassValue } from "clsx"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} +` + } + ] + }, + { + "name": "focus-styles", + "type": "registry:lib", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:lib", + "path": "lib/focus-styles/basic.ts", + "target": "lib/focus-styles.ts", + "content": `import { tv } from "tailwind-variants"; + +export const focusRing = tv({ + base: "outline-hidden ring-0 ring-border-focus focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-bg", +}); + +export const focusInput = tv({ + base: "ring-0 focus-within:ring-2 focus-within:ring-border-focus", +}); + +export const focusRingGroup = tv({ + base: "outline-hidden ring-0 ring-border-focus group-focus-visible:ring-2 group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-bg", +}); +` + } + ] + } + } + } +] as const; diff --git a/packages/core/src/__generated__/types.ts b/packages/core/src/__generated__/types.ts new file mode 100644 index 000000000..633cac51e --- /dev/null +++ b/packages/core/src/__generated__/types.ts @@ -0,0 +1,42 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build:core" to regenerate + +export interface FileEntry { + type?: string; + path: string; + target: string; + content: string; +} + +export interface VariantData { + files: FileEntry[]; + registryDependencies?: string[]; + dependencies?: string[]; +} + +export interface RegistryItemData { + name: string; + type?: string; + defaultVariant?: string; + variants?: Record; + files?: FileEntry[]; + registryDependencies?: string[]; + dependencies?: string[]; + description?: string; + categories?: string[]; + meta?: Record; + extends?: string; + css?: Record; +} + +export interface IconLibrary { + name: string; + label: string; + package: string; + import: string; +} + +export interface BlockCategory { + name: string; + slug: string; +} diff --git a/packages/core/src/__generated__/ui.ts b/packages/core/src/__generated__/ui.ts new file mode 100644 index 000000000..f3944e387 --- /dev/null +++ b/packages/core/src/__generated__/ui.ts @@ -0,0 +1,7435 @@ +// AUTO-GENERATED - DO NOT EDIT +// Run "pnpm build:core" to regenerate + +export const ui = [ + { + "name": "accordion", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/accordion/basic.tsx", + "target": "ui/accordion.tsx", + "content": `"use client"; + +import { + DisclosureGroup as AriaDisclosureGroup, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; + +const accordionStyles = tv({ + base: "**:data-disclosure:not-last:border-b", +}); + +interface AccordionProps + extends React.ComponentProps {} +function Accordion({ className, ...props }: AccordionProps) { + return ( + + accordionStyles({ className: c }), + )} + {...props} + /> + ); +} + +export { Accordion }; + +export type { AccordionProps }; +` + } + ] + } + } + }, + { + "name": "alert", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/alert/basic.tsx", + "target": "ui/alert.tsx", + "content": `import { tv } from "tailwind-variants"; +import type * as React from "react"; +import type { VariantProps } from "tailwind-variants"; + +const alertVariants = tv({ + slots: { + base: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border bg-card px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + title: "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", + description: + "col-start-2 grid justify-items-start gap-1 text-muted-foreground text-sm [&_p]:leading-relaxed", + }, + variants: { + variant: { + neutral: { + base: "text-fg", + }, + danger: { + base: "bg-danger-muted text-fg-danger *:data-[slot=alert-description]:text-fg-danger/90 [&>svg]:text-current", + }, + warning: { + base: "text-fg-warning *:data-[slot=alert-description]:text-fg-warning/90 [&>svg]:text-current", + }, + info: { + base: "text-fg-info *:data-[slot=alert-description]:text-fg-info/90 [&>svg]:text-current", + }, + success: { + base: "text-fg-success *:data-[slot=alert-description]:text-fg-success/90 [&>svg]:text-current", + }, + }, + }, + defaultVariants: { + variant: "neutral", + }, +}); + +const { base, title, description } = alertVariants(); + +/* -----------------------------------------------------------------------------------------------*/ + +interface AlertProps + extends React.ComponentProps<"div">, + VariantProps {} + +function Alert({ className, variant, ...props }: AlertProps) { + return ( +
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface AlertTitleProps extends React.ComponentProps<"div"> {} + +function AlertTitle({ className, ...props }: AlertTitleProps) { + return ( +
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface AlertDescriptionProps extends React.ComponentProps<"div"> {} + +function AlertDescription({ className, ...props }: AlertDescriptionProps) { + return ( +
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface AlertActionProps extends React.ComponentProps<"div"> {} + +function AlertAction({ className, ...props }: AlertActionProps) { + return
; +} + +/* -----------------------------------------------------------------------------------------------*/ + +export { Alert, AlertTitle, AlertDescription, AlertAction }; + +export type { + AlertProps, + AlertTitleProps, + AlertDescriptionProps, + AlertActionProps, +}; +` + } + ] + } + } + }, + { + "name": "avatar", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/avatar/basic.tsx", + "target": "ui/avatar.tsx", + "content": `"use client"; + +import * as React from "react"; +import { tv } from "tailwind-variants"; +import type { VariantProps } from "tailwind-variants"; + +import { useImageLoadingStatus } from "@dotui/registry/hooks/use-image-loading-status"; +import { createContext } from "@dotui/registry/lib/context"; +import type { ImageLoadingStatus } from "@dotui/registry/hooks/use-image-loading-status"; + +const avatarStyles = tv({ + slots: { + group: + "-space-x-2 flex flex-wrap *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-bg", + root: "relative inline-flex shrink-0 overflow-hidden rounded-full bg-bg align-middle", + image: "aspect-square size-full", + fallback: "flex size-full select-none items-center justify-center bg-muted", + placeholder: + "flex size-full h-full animate-pulse items-center justify-center bg-muted", + }, + variants: { + size: { + sm: { group: "*:data-[slot=avatar]:size-8", root: "size-8" }, + md: { group: "*:data-[slot=avatar]:size-10", root: "size-10" }, + lg: { group: "*:data-[slot=avatar]:size-12", root: "size-12" }, + }, + }, + defaultVariants: { + size: "md", + }, +}); + +const { group, root, image, fallback, placeholder } = avatarStyles(); + +/* -----------------------------------------------------------------------------------------------*/ + +interface AvatarGroupProps + extends React.ComponentProps<"div">, + VariantProps {} + +const AvatarGroup = ({ className, size, ...props }: AvatarGroupProps) => { + return
; +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface AvatarProps + extends AvatarImageProps, + VariantProps { + fallback?: React.ReactNode; +} +const Avatar = ({ + className, + style, + fallback, + size, + ...props +}: AvatarProps) => { + return ( + + + {fallback} + + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +const [AvatarInternalContext, useAvatarInternalContext] = createContext<{ + status: ImageLoadingStatus; + setStatus: (status: ImageLoadingStatus) => void; +}>({ + name: "AvatarRoot", + strict: true, +}); + +interface AvatarRootProps + extends React.ComponentProps<"span">, + VariantProps {} +function AvatarRoot({ className, size, ...props }: AvatarRootProps) { + const [status, setStatus] = React.useState("idle"); + + return ( + + + + ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface AvatarImageProps extends Omit, "src"> { + src?: string; +} + +function AvatarImage({ + src, + alt, + className, + referrerPolicy, + crossOrigin, + ...props +}: AvatarImageProps) { + const status = useImageLoadingStatus(src, { referrerPolicy, crossOrigin }); + const { setStatus } = useAvatarInternalContext("AvatarImage"); + + React.useLayoutEffect(() => { + if (status !== "idle") { + setStatus(status); + } + }, [status, setStatus]); + + if (status === "loaded") + return ( + {alt} + ); + + return null; +} + +/* -----------------------------------------------------------------------------------------------*/ + +type AvatarFallbackProps = React.HTMLAttributes; + +const AvatarFallback = ({ className, ...props }: AvatarFallbackProps) => { + const { status } = useAvatarInternalContext("AvatarFallback"); + if (status === "error") + return ( + + ); + return null; +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface AvatarPlaceholderProps extends React.ComponentProps<"span"> {} + +const AvatarPlaceholder = ({ className, ...props }: AvatarPlaceholderProps) => { + const { status } = useAvatarInternalContext("AvatarPlaceholder"); + if (["idle", "loading"].includes(status)) + return ; + return null; +}; + +/* -----------------------------------------------------------------------------------------------*/ + +const CompoundAvatar = Object.assign(Avatar, { + Group: AvatarGroup, + Root: AvatarRoot, + Image: AvatarImage, + Fallback: AvatarFallback, + Placeholder: AvatarPlaceholder, +}); + +export { + CompoundAvatar as Avatar, + AvatarGroup, + AvatarRoot, + AvatarImage, + AvatarFallback, + AvatarPlaceholder, +}; + +export type { + AvatarGroupProps, + AvatarProps, + AvatarRootProps, + AvatarImageProps, + AvatarFallbackProps, + AvatarPlaceholderProps, +}; +` + } + ] + } + } + }, + { + "name": "badge", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/badge/basic.tsx", + "target": "ui/badge.tsx", + "content": `import { tv } from "tailwind-variants"; +import type * as React from "react"; +import type { VariantProps } from "tailwind-variants"; + +const badgeStyles = tv({ + base: "inline-flex w-fit shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-md px-2 py-0.5 font-medium text-xs [&>svg]:pointer-events-none [&>svg]:size-3", + variants: { + variant: { + default: "bg-neutral text-fg-on-neutral", + danger: "bg-danger text-fg-on-danger", + success: "bg-success text-fg-on-success", + warning: "bg-warning text-fg-on-warning", + info: "bg-info text-fg-on-info", + }, + }, + defaultVariants: { + variant: "default", + }, +}); + +interface BadgeProps + extends React.ComponentProps<"span">, + VariantProps {} +const Badge = ({ className, variant, ...props }: BadgeProps) => { + return ( + + ); +}; + +export type { BadgeProps }; +export { Badge }; +` + } + ] + } + } + }, + { + "name": "breadcrumbs", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/breadcrumbs/basic.tsx", + "target": "ui/breadcrumbs.tsx", + "content": `"use client"; + +import { ChevronRightIcon } from "lucide-react"; +import { + Breadcrumb as AriaBreadcrumb, + Breadcrumbs as AriaBreadcrumbs, + Link as AriaLink, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type { BreadcrumbsProps as AriaBreadcrumbsProps } from "react-aria-components"; + +const breadcrumbsStyles = tv({ + slots: { + root: "wrap-break-word flex flex-wrap items-center gap-1.5 text-fg-muted text-sm [&_svg]:size-4", + item: "inline-flex items-center gap-1", + link: [ + "focus-reset focus-visible:focus-ring", + "inline-flex items-center gap-1 rounded px-0.5 current:text-fg leading-none transition-colors disabled:cursor-default disabled:not-current:text-fg-disabled hover:[a]:text-fg", + ], + }, +}); + +const { root, item, link } = breadcrumbsStyles(); + +interface BreadcrumbsProps extends AriaBreadcrumbsProps { + ref?: React.RefObject; +} +const Breadcrumbs = ({ + className, + ...props +}: BreadcrumbsProps) => { + return ; +}; + +type BreadcrumbProps = BreadcrumbItemProps & + Omit; +const Breadcrumb = ({ ref, children, ...props }: BreadcrumbProps) => { + return ( + + {composeRenderProps(children, (children, { isCurrent }) => ( + <> + {children} + {!isCurrent && } + + ))} + + ); +}; + +interface BreadcrumbItemProps + extends React.ComponentProps {} +const BreadcrumbItem = ({ className, ...props }: BreadcrumbItemProps) => ( + + item({ className }), + )} + {...props} + /> +); + +interface BreadcrumbLinkProps extends React.ComponentProps {} +const BreadcrumbLink = ({ className, ...props }: BreadcrumbLinkProps) => ( + + link({ className }), + )} + {...props} + /> +); + +export { Breadcrumbs, Breadcrumb, BreadcrumbItem, BreadcrumbLink }; + +export type { + BreadcrumbsProps, + BreadcrumbProps, + BreadcrumbItemProps, + BreadcrumbLinkProps, +}; +` + } + ], + "registryDependencies": [ + "focus-styles" + ] + } + } + }, + { + "name": "button", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/button/basic.tsx", + "target": "ui/button.tsx", + "content": `"use client"; + +import { + Button as AriaButton, + ButtonContext as AriaButtonContext, + Link as AriaLink, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type * as React from "react"; +import type { VariantProps } from "tailwind-variants"; + +import { useButtonAspect } from "@dotui/registry/hooks/use-button-aspect"; +import { createVariantsContext } from "@dotui/registry/lib/context"; +import { Loader } from "@dotui/registry/ui/loader"; + +const buttonStyles = tv({ + base: [ + "relative box-border inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm leading-normal transition-[background-color,border-color,color,box-shadow] data-icon-only:px-0", + "*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2", + // svg + "[&_svg]:pointer-events-none [&_svg]:not-with-[size]:size-4 [&_svg]:shrink-0", + // focus state + "focus-reset focus-visible:focus-ring", + // disabled state + "disabled:cursor-default disabled:border-border-disabled disabled:bg-disabled disabled:text-fg-disabled", + // pending state + "pending:cursor-default pending:border-border-disabled pending:bg-disabled pending:text-transparent pending:**:not-data-[slot=spinner]:not-in-data-[slot=spinner]:opacity-0 pending:**:data-[slot=spinner]:text-fg-muted", + ], + variants: { + variant: { + default: + "border pressed:border-border-active bg-neutral pressed:bg-neutral-active text-fg-on-neutral hover:border-border-hover hover:bg-neutral-hover", + primary: + "pending:border-0 bg-primary pressed:bg-primary-active text-fg-on-primary [--color-disabled:var(--neutral-500)] [--color-fg-disabled:var(--neutral-300)] hover:bg-primary-hover disabled:border-0", + quiet: "bg-transparent pressed:bg-inverse/20 text-fg hover:bg-inverse/10", + link: "text-fg underline-offset-4 hover:underline", + warning: + "bg-warning pressed:bg-warning-active text-fg-on-warning hover:bg-warning-hover", + danger: + "bg-danger pressed:bg-danger-active text-fg-on-danger hover:bg-danger-hover", + }, + size: { + sm: "h-8 px-3 has-[>svg]:px-2.5 data-icon-only:not-with-[size]:not-with-[w]:w-8", + md: "h-9 px-4 has-[>svg]:px-3 data-icon-only:not-with-[size]:not-with-[w]:w-9", + lg: "h-10 px-5 has-[>svg]:px-4 data-icon-only:not-with-[size]:not-with-[w]:w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "md", + }, +}); + +type ButtonVariants = VariantProps; + +const [ButtonProvider, useContextProps] = createVariantsContext< + ButtonVariants, + React.ComponentProps +>(AriaButtonContext); + +/* -----------------------------------------------------------------------------------------------*/ + +interface ButtonProps + extends React.ComponentProps, + ButtonVariants { + aspect?: "default" | "square" | "auto"; +} + +const Button = (localProps: ButtonProps) => { + const { + variant, + size, + aspect = "auto", + className, + slot, + style, + children, + ...props + } = useContextProps(localProps); + + const isIconOnly = useButtonAspect(children, aspect); + + return ( + + buttonStyles({ variant, size, className: cn }), + )} + slot={slot} + style={style} + {...props} + > + {composeRenderProps(children, (children, { isPending }) => ( + <> + {isPending && ( + + )} + {typeof children === "string" ? ( + {children} + ) : ( + children + )} + + ))} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface LinkButtonProps + extends React.ComponentProps, + VariantProps { + aspect?: "default" | "square" | "auto"; +} + +const LinkButton = (localProps: LinkButtonProps) => { + const { + variant, + size, + aspect = "auto", + className, + slot, + style, + children, + ...props + } = useContextProps(localProps); + + const isIconOnly = useButtonAspect(children, aspect); + + return ( + + buttonStyles({ variant, size, className: cn }), + )} + slot={slot} + style={style} + {...props} + > + {composeRenderProps(children, (children) => ( + <> + {typeof children === "string" ? ( + {children} + ) : ( + children + )} + + ))} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +export type { ButtonProps, LinkButtonProps }; + +export { Button, LinkButton, ButtonProvider, buttonStyles }; +` + } + ], + "registryDependencies": [ + "loader", + "focus-styles" + ] + }, + "ripple": { + "files": [ + { + "type": "registry:ui", + "path": "ui/button/ripple.tsx", + "target": "ui/button.tsx", + "content": `"use client"; + +import { + Button as AriaButton, + ButtonContext as AriaButtonContext, + Link as AriaLink, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type * as React from "react"; +import type { VariantProps } from "tailwind-variants"; + +import { useButtonAspect } from "@dotui/registry/hooks/use-button-aspect"; +import { createVariantsContext } from "@dotui/registry/lib/context"; +import { Loader } from "@dotui/registry/ui/loader"; + +const buttonStyles = tv({ + base: [ + "ripple relative box-border inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm leading-normal transition-[background-color,border-color,color,box-shadow] data-icon-only:px-0", + "*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2", + // focus state + "focus-reset focus-visible:focus-ring", + // disabled state + "disabled:cursor-default disabled:border-border-disabled disabled:bg-disabled disabled:text-fg-disabled", + // pending state + "pending:cursor-default pending:border-border-disabled pending:bg-disabled pending:text-transparent pending:**:not-data-[slot=spinner]:not-in-data-[slot=spinner]:opacity-0 pending:**:data-[slot=spinner]:text-fg-muted", + ], + variants: { + variant: { + default: + "border pressed:border-border-active bg-neutral pressed:bg-neutral-active text-fg-on-neutral hover:border-border-hover hover:bg-neutral-hover", + primary: + "pending:border-0 bg-primary pressed:bg-primary-active text-fg-on-primary [--color-disabled:var(--neutral-500)] [--color-fg-disabled:var(--neutral-300)] hover:bg-primary-hover disabled:border-0", + quiet: "bg-transparent pressed:bg-inverse/20 text-fg hover:bg-inverse/10", + link: "text-fg underline-offset-4 hover:underline", + warning: + "bg-warning pressed:bg-warning-active text-fg-on-warning hover:bg-warning-hover", + danger: + "bg-danger pressed:bg-danger-active text-fg-on-danger hover:bg-danger-hover", + }, + size: { + sm: "h-8 px-3 data-icon-only:not-with-[size]:not-with-[w]:w-8 [&_svg]:size-4", + md: "h-9 px-4 data-icon-only:not-with-[size]:not-with-[w]:w-9 [&_svg]:size-4", + lg: "h-10 px-5 data-icon-only:not-with-[size]:not-with-[w]:w-10 [&_svg]:size-5", + }, + }, + defaultVariants: { + variant: "default", + size: "md", + }, +}); + +type ButtonVariants = VariantProps; + +const [ButtonProvider, useContextProps] = createVariantsContext< + ButtonVariants, + React.ComponentProps +>(AriaButtonContext); + +/* -----------------------------------------------------------------------------------------------*/ + +interface ButtonProps + extends React.ComponentProps, + ButtonVariants { + aspect?: "default" | "square" | "auto"; +} + +const Button = (localProps: ButtonProps) => { + const { + variant, + size, + aspect = "auto", + className, + slot, + style, + children, + ...props + } = useContextProps(localProps); + + const isIconOnly = useButtonAspect(children, aspect); + + return ( + + buttonStyles({ variant, size, className: cn }), + )} + slot={slot} + style={style} + {...props} + > + {composeRenderProps(children, (children, { isPending }) => ( + <> + {isPending && ( + + )} + {typeof children === "string" ? ( + {children} + ) : ( + children + )} + + ))} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface LinkButtonProps + extends React.ComponentProps, + VariantProps { + aspect?: "default" | "square" | "auto"; +} + +const LinkButton = (localProps: LinkButtonProps) => { + const { + variant, + size, + aspect = "auto", + className, + slot, + style, + children, + ...props + } = useContextProps(localProps); + + const isIconOnly = useButtonAspect(children, aspect); + + return ( + + buttonStyles({ variant, size, className: cn }), + )} + slot={slot} + style={style} + {...props} + > + {composeRenderProps(children, (children) => ( + <> + {typeof children === "string" ? ( + {children} + ) : ( + children + )} + + ))} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +export type { ButtonProps, LinkButtonProps }; + +export { Button, LinkButton, ButtonProvider, buttonStyles }; +` + } + ] + } + } + }, + { + "name": "calendar", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/calendar/basic.tsx", + "target": "ui/calendar.tsx", + "content": `"use client"; + +import React from "react"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; +import { + Calendar as AriaCalendar, + CalendarCell as AriaCalendarCell, + CalendarContext as AriaCalendarContext, + CalendarGrid as AriaCalendarGrid, + CalendarGridBody as AriaCalendarGridBody, + CalendarGridHeader as AriaCalendarGridHeader, + CalendarHeaderCell as AriaCalendarHeaderCell, + Heading as AriaHeading, + RangeCalendar as AriaRangeCalendar, + RangeCalendarContext as AriaRangeCalendarContext, + RangeCalendarStateContext as AriaRangeCalendarStateContext, + composeRenderProps, + useSlottedContext, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type { + CalendarProps as AriaCalendarProps, + RangeCalendarProps as AriaRangeCalendarProps, + DateValue, +} from "react-aria-components"; +import type { VariantProps } from "tailwind-variants"; + +import { Button } from "@dotui/registry/ui/button"; + +const calendarStyles = tv({ + slots: { + root: "flex flex-col gap-4", + header: "flex items-center justify-between gap-2", + grid: "w-full border-collapse", + gridHeader: "", + gridHeaderCell: "font-normal text-fg-muted text-xs", + gridBody: "", + }, + variants: { + standalone: { + true: { + root: "rounded-md border bg-bg p-3", + }, + }, + }, +}); + +const calendarCellStyles = tv({ + slots: { + cellRoot: + "flex outside-month:hidden items-center justify-center outline-none selection-end:rounded-r-md selection-start:rounded-l-md", + cell: [ + "focus-reset focus-visible:focus-ring", + "my-1 flex size-8 cursor-pointer unavailable:cursor-default items-center justify-center rounded-md pressed:bg-inverse/20 text-sm unavailable:text-fg-disabled unavailable:not-data-disabled:line-through transition-colors read-only:cursor-default hover:bg-inverse/10 hover:unavailable:bg-transparent hover:read-only:bg-transparent disabled:cursor-default disabled:bg-transparent disabled:text-fg-disabled", + ], + }, + variants: { + variant: { + primary: {}, + accent: {}, + }, + range: { + true: { + cellRoot: + "selected: selected:bg-inverse/10 selected:invalid:bg-danger-muted selected:invalid:text-fg-danger", + cell: "selection-end:invalid:bg-danger selection-start:invalid:bg-danger selection-end:invalid:text-fg-on-danger selection-start:invalid:text-fg-on-danger", + }, + false: { + cell: "selected:invalid:bg-danger selected:invalid:text-fg-on-danger", + }, + }, + }, + compoundVariants: [ + { + variant: "primary", + range: false, + className: { + cell: "selected:bg-primary selected:text-fg-on-primary", + }, + }, + { + variant: "accent", + range: false, + className: { + cell: "selected:bg-accent selected:text-fg-on-accent", + }, + }, + { + variant: "primary", + range: true, + className: { + cell: "selection-end:bg-primary selection-start:bg-primary selection-end:text-fg-on-primary selection-start:text-fg-on-primary", + }, + }, + { + variant: "accent", + range: true, + className: { + cell: "selection-end:bg-accent selection-start:bg-accent selection-end:text-fg-on-accent selection-start:text-fg-on-accent", + }, + }, + ], + defaultVariants: { + variant: "accent", + }, +}); + +const { root, header, grid, gridHeader, gridHeaderCell, gridBody } = + calendarStyles(); + +const { cellRoot, cell } = calendarCellStyles(); + +/* -----------------------------------------------------------------------------------------------*/ + +type CalendarProps = + | ({ + mode?: "single"; + } & AriaCalendarProps) + | ({ + mode: "range"; + } & AriaRangeCalendarProps); + +const Calendar = ({ + mode, + className, + ...props +}: CalendarProps) => { + const rangeCalendarContext = useSlottedContext(AriaRangeCalendarContext); + const calendarContext = useSlottedContext(AriaCalendarContext); + + if (mode === "range" || rangeCalendarContext) { + const standalone = Object.keys(rangeCalendarContext ?? {}).length === 0; + return ( + ["className"], + (className) => root({ standalone, className }), + )} + {...(props as AriaRangeCalendarProps)} + > + {composeRenderProps( + props.children as AriaRangeCalendarProps["children"], + (children) => + children ?? ( + <> + + + + ), + )} + + ); + } + + const standalone = !!calendarContext; + return ( + ["className"], + (className) => root({ standalone, className }), + )} + {...(props as AriaCalendarProps)} + > + {composeRenderProps( + props.children as AriaCalendarProps["children"], + (children) => + children ?? ( + <> + + + + ), + )} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface CalendarHeaderProps extends React.ComponentProps<"header"> {} + +const CalendarHeader = ({ className, ...props }: CalendarHeaderProps) => { + return ( +
+ {props.children ?? ( + <> + + + + + )} +
+ ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface CalendarGridProps + extends React.ComponentProps {} + +const CalendarGrid = ({ className, ...props }: CalendarGridProps) => { + return ( + + {props.children ?? ( + <> + + {(day) => {day}} + + + {(date) => } + + + )} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface CalendarGridHeaderProps + extends React.ComponentProps {} +const CalendarGridHeader = ({ + className, + ...props +}: CalendarGridHeaderProps) => { + return ( + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface CalendarHeaderCellProps + extends React.ComponentProps {} +const CalendarHeaderCell = ({ + className, + ...props +}: CalendarHeaderCellProps) => { + return ( + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface CalendarGridBodyProps + extends React.ComponentProps {} +const CalendarGridBody = ({ className, ...props }: CalendarGridBodyProps) => { + return ( + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface CalendarCellProps + extends React.ComponentProps, + Omit, "range"> {} +const CalendarCell = ({ + variant = "accent", + children, + className, + ...props +}: CalendarCellProps) => { + const rangeCalendarState = React.use(AriaRangeCalendarStateContext); + const range = !!rangeCalendarState; + + return ( + + cellRoot({ + range, + variant, + className, + }), + )} + > + {composeRenderProps( + children, + ( + _, + { + isSelected, + isFocused, + isHovered, + isPressed, + isUnavailable, + isDisabled, + isFocusVisible, + isInvalid, + isOutsideMonth, + isOutsideVisibleRange, + isSelectionEnd, + isSelectionStart, + formattedDate, + }, + ) => ( + + {formattedDate} + + ), + )} + + ); +}; + +export { + Calendar, + CalendarHeader, + CalendarGrid, + CalendarGridHeader, + CalendarHeaderCell, + CalendarGridBody, + CalendarCell, + calendarStyles, +}; + +export type { + CalendarProps, + CalendarHeaderProps, + CalendarGridProps, + CalendarGridHeaderProps, + CalendarHeaderCellProps, + CalendarGridBodyProps, + CalendarCellProps, +}; +` + } + ], + "registryDependencies": [ + "button", + "text", + "focus-styles" + ] + } + } + }, + { + "name": "card", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/card/basic.tsx", + "target": "ui/card.tsx", + "content": `import { tv } from "tailwind-variants"; +import type * as React from "react"; + +const cardStyles = tv({ + slots: { + root: "flex flex-col gap-6 rounded-xl border bg-card py-6 text-fg shadow-sm", + header: + "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", + title: "font-semibold leading-none", + description: "text-fg-muted text-sm", + action: "col-start-2 row-span-2 row-start-1 self-start justify-self-end", + content: "flex-1 px-6", + footer: "flex items-center px-6 [.border-t]:pt-6", + }, +}); + +const { root, header, title, description, action, content, footer } = + cardStyles(); + +/* -----------------------------------------------------------------------------------------------*/ + +interface CardProps extends React.ComponentProps<"div"> {} + +function Card({ className, ...props }: CardProps) { + return
; +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface CardHeaderProps extends React.ComponentProps<"div"> {} + +function CardHeader({ className, ...props }: CardHeaderProps) { + return ( +
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface CardTitleProps extends React.ComponentProps<"div"> {} + +function CardTitle({ className, ...props }: CardTitleProps) { + return ( +
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface CardDescriptionProps extends React.ComponentProps<"div"> {} + +function CardDescription({ className, ...props }: CardDescriptionProps) { + return ( +
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface CardActionProps extends React.ComponentProps<"div"> {} + +function CardAction({ className, ...props }: CardActionProps) { + return ( +
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface CardContentProps extends React.ComponentProps<"div"> {} + +function CardContent({ className, ...props }: CardContentProps) { + return ( +
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface CardFooterProps extends React.ComponentProps<"div"> {} + +function CardFooter({ className, ...props }: CardFooterProps) { + return ( +
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +}; + +export type { + CardProps, + CardHeaderProps, + CardTitleProps, + CardDescriptionProps, + CardActionProps, + CardContentProps, + CardFooterProps, +}; +` + } + ], + "registryDependencies": [ + "button", + "text", + "focus-styles" + ] + } + } + }, + { + "name": "checkbox-group", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/checkbox-group/basic.tsx", + "target": "ui/checkbox-group.tsx", + "content": `"use client"; + +import { + CheckboxGroup as AriaCheckboxGroup, + composeRenderProps, +} from "react-aria-components"; +import type { CheckboxGroupProps } from "react-aria-components"; + +import { fieldStyles } from "@dotui/registry/ui/field"; + +const { field } = fieldStyles(); + +const CheckboxGroup = ({ className, ...props }: CheckboxGroupProps) => { + return ( + + field({ className }), + )} + {...props} + /> + ); +}; + +export type { CheckboxGroupProps }; +export { CheckboxGroup }; +` + } + ], + "registryDependencies": [ + "field", + "checkbox" + ] + } + } + }, + { + "name": "checkbox", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/checkbox/basic.tsx", + "target": "ui/checkbox.tsx", + "content": `"use client"; + +import { CheckIcon, MinusIcon } from "lucide-react"; +import { + Checkbox as AriaCheckbox, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type * as React from "react"; +import type { CheckboxRenderProps } from "react-aria-components"; + +import { createContext } from "@dotui/registry/lib/context"; +import { cn } from "@dotui/registry/lib/utils"; + +const checkboxStyles = tv({ + slots: { + root: [ + "focus-reset focus-visible:focus-ring", + "flex items-center gap-2 text-sm leading-none has-data-[slot=description]:items-start", + "disabled:cursor-not-allowed disabled:text-fg-disabled", + ], + indicator: [ + "flex size-4 shrink-0 items-center justify-center rounded-sm border border-border-control bg-transparent text-transparent", + "transition-[background-color,border-color,box-shadow,color] duration-75", + // selected state + "selected:border-transparent selected:bg-primary selected:text-fg-on-primary", + // read-only state + "read-only:cursor-default", + // disabled state + "disabled:cursor-not-allowed disabled:border-border-disabled selected:disabled:bg-disabled selected:disabled:text-fg-disabled indeterminate:disabled:bg-disabled", + // invalid state + "invalid:border-border-danger invalid:selected:bg-danger-muted invalid:selected:text-fg-onMutedDanger", + // indeterminate state + "indeterminate:border-transparent indeterminate:bg-primary indeterminate:text-fg-on-primary", + ], + }, +}); + +const { root, indicator } = checkboxStyles(); + +const [InternalCheckboxProvider, useInternalCheckbox] = + createContext({ + strict: true, + }); + +/* -----------------------------------------------------------------------------------------------*/ + +interface CheckboxProps extends React.ComponentProps {} + +const Checkbox = ({ className, ...props }: CheckboxProps) => { + return ( + + props.children + ? root({ className }) + : indicator({ + className: cn(className, "focus-reset focus-visible:focus-ring"), + }), + )} + {...props} + > + {composeRenderProps(props.children, (children, renderProps) => { + return children ? ( + + {children} + + ) : renderProps.isIndeterminate ? ( + + ) : ( + + ); + })} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface CheckboxIndicatorProps extends React.ComponentProps<"div"> {} + +const CheckboxIndicator = ({ className, ...props }: CheckboxIndicatorProps) => { + const ctx = useInternalCheckbox("CheckboxIndicator"); + return ( +
+ {ctx.isIndeterminate ? ( + + ) : ( + + )} +
+ ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +export { Checkbox, CheckboxIndicator }; + +export type { CheckboxProps, CheckboxIndicatorProps }; +` + } + ], + "registryDependencies": [ + "focus-styles" + ] + } + } + }, + { + "name": "color-area", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/color-area/basic.tsx", + "target": "ui/color-area.tsx", + "content": `"use client"; + +import { + ColorArea as AriaColorArea, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; + +import { ColorThumb } from "@dotui/registry/ui/color-thumb"; + +const colorAreaStyles = tv({ + base: "block size-48 min-w-20 rounded-md disabled:[background:var(--color-disabled)]!", +}); + +/* -----------------------------------------------------------------------------------------------*/ + +type ColorAreaProps = React.ComponentProps; + +const ColorArea = ({ className, ...props }: ColorAreaProps) => { + return ( + + colorAreaStyles({ className }), + )} + {...props} + > + {props.children || } + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +export { ColorArea }; + +export type { ColorAreaProps }; +` + } + ], + "registryDependencies": [ + "color-thumb" + ] + } + } + }, + { + "name": "color-editor", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/color-editor/basic.tsx", + "target": "ui/color-editor.tsx", + "content": `import React from "react"; +import { getColorChannels } from "react-aria-components"; + +import { cn } from "@dotui/registry/lib/utils"; +import { ColorArea } from "@dotui/registry/ui/color-area"; +import { ColorField } from "@dotui/registry/ui/color-field"; +import { ColorSlider } from "@dotui/registry/ui/color-slider"; +import { Input } from "@dotui/registry/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, +} from "@dotui/registry/ui/select"; + +type ColorFormat = "hex" | "rgb" | "hsl" | "hsb"; + +interface ColorEditorProps extends React.ComponentProps<"div"> { + colorFormat?: ColorFormat; + showAlphaChannel?: boolean; + showFormatSelector?: boolean; +} + +const ColorEditor = ({ + colorFormat: ColorFormatProp = "hex", + showAlphaChannel = false, + showFormatSelector = true, + className, + ...props +}: ColorEditorProps) => { + const [colorFormat, setColorFormat] = + React.useState(ColorFormatProp); + + return ( +
+
+ + + {showAlphaChannel && ( + + )} +
+
+ {showFormatSelector && ( + + )} +
+ {colorFormat === "hex" ? ( + + + + ) : ( + getColorChannels(colorFormat).map((channel) => ( + + + + )) + )} +
+
+
+ ); +}; + +export { ColorEditor }; +export type { ColorEditorProps }; +` + } + ], + "registryDependencies": [ + "color-area", + "color-slider", + "select" + ] + } + } + }, + { + "name": "color-field", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/color-field/basic.tsx", + "target": "ui/color-field.tsx", + "content": `"use client"; + +import { + ColorField as AriaColorField, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type * as React from "react"; + +import { fieldStyles } from "@dotui/registry/ui/field/basic"; + +const colorFieldStyles = tv({ + base: [fieldStyles().field({ orientation: "vertical" }), ""], +}); + +interface ColorFieldProps extends React.ComponentProps {} + +const ColorField = ({ className, ...props }: ColorFieldProps) => { + return ( + + colorFieldStyles({ className }), + )} + {...props} + /> + ); +}; + +export { ColorField }; +export type { ColorFieldProps }; +` + } + ], + "registryDependencies": [ + "field", + "input" + ] + } + } + }, + { + "name": "color-picker", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/color-picker/basic.tsx", + "target": "ui/color-picker.tsx", + "content": `"use client"; + +import { useContext } from "react"; +import { + ColorPicker as AriaColorPicker, + ColorPickerStateContext as AriaColorPickerStateContext, + composeRenderProps, +} from "react-aria-components"; +import type { + ColorPickerProps as AriaColorPickerProps, + ColorPickerState, +} from "react-aria-components"; + +import { Button } from "@dotui/registry/ui/button"; +import { ColorSwatch } from "@dotui/registry/ui/color-swatch"; +import { Dialog, DialogContent } from "@dotui/registry/ui/dialog"; +import { Overlay } from "@dotui/registry/ui/overlay"; +import type { ButtonProps } from "@dotui/registry/ui/button"; +import type { + DialogContentProps, + DialogProps, +} from "@dotui/registry/ui/dialog"; + +interface ColorPickerProps + extends AriaColorPickerProps, + Omit {} + +const ColorPicker = ({ + defaultOpen, + isOpen, + onOpenChange, + ...props +}: ColorPickerProps) => { + return ( + + {composeRenderProps(props.children, (children) => ( + + {children} + + ))} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface ColorPickerTriggerProps extends Omit { + children?: React.ReactNode | ((props: ColorPickerState) => React.ReactNode); +} + +const ColorPickerTrigger = ({ + children, + ...props +}: ColorPickerTriggerProps) => { + const state = useContext(AriaColorPickerStateContext)!; + return ( + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface ColorPickerContentProps extends DialogContentProps {} +const ColorPickerContent = ({ + children, + ...props +}: ColorPickerContentProps) => { + return ( + + {children} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +export { ColorPicker, ColorPickerTrigger, ColorPickerContent }; +export type { + ColorPickerProps, + ColorPickerTriggerProps, + ColorPickerContentProps, +}; +` + } + ], + "registryDependencies": [ + "button", + "color-area", + "color-field", + "color-slider", + "color-swatch", + "dialog", + "select" + ] + } + } + }, + { + "name": "color-slider", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/color-slider/basic.tsx", + "target": "ui/color-slider.tsx", + "content": `"use client"; + +import { useSlotId } from "@react-aria/utils"; +import { + ColorSlider as AriaColorSlider, + SliderOutput as AriaSliderOutput, + SliderTrack as AriaSliderTrack, + composeRenderProps, + Provider, + TextContext, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; + +import { ColorThumb } from "@dotui/registry/ui/color-thumb"; + +const colorSliderStyles = tv({ + slots: { + root: "flex flex-col gap-2", + output: "text-fg-muted text-sm", + track: + "relative rounded-md before:absolute before:inset-0 before:z-[-1] before:rounded-[inherit] before:bg-[repeating-conic-gradient(#e6e6e6_0%_25%,#fff_0%_50%)] before:bg-center before:bg-size-[16px_16px] before:content-[''] orientation-horizontal:**:data-[slot=color-thumb]:top-1/2 orientation-vertical:**:data-[slot=color-thumb]:left-1/2 disabled:[background:var(--color-disabled)]!", + }, + variants: { + orientation: { + horizontal: { + root: "w-48", + track: "h-6 w-full", + }, + vertical: { + root: "h-48 items-center", + track: "w-6 flex-1", + }, + }, + }, + defaultVariants: { + orientation: "horizontal", + }, +}); + +const { root, track, output } = colorSliderStyles(); + +/* -----------------------------------------------------------------------------------------------*/ + +interface ColorSliderProps + extends React.ComponentProps {} + +const ColorSlider = ({ className, ...props }: ColorSliderProps) => { + const descriptionId = useSlotId(); + return ( + + + root({ orientation, className: cn }), + )} + aria-describedby={descriptionId} + {...props} + > + {props.children ?? } + + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface ColorSliderControlProps + extends React.ComponentProps {} + +const ColorSliderControl = ({ + className, + ...props +}: ColorSliderControlProps) => { + return ( + + track({ orientation, className: cn }), + )} + {...props} + > + {props.children ?? } + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface ColorSliderOutputProps + extends React.ComponentProps {} + +const ColorSliderOutput = ({ className, ...props }: ColorSliderOutputProps) => { + return ( + + output({ className }), + )} + {...props} + /> + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +export { ColorSlider, ColorSliderControl, ColorSliderOutput }; + +export type { + ColorSliderProps, + ColorSliderControlProps, + ColorSliderOutputProps, +}; +` + } + ], + "registryDependencies": [ + "field", + "color-thumb" + ] + } + } + }, + { + "name": "color-swatch-picker", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/color-swatch-picker/basic.tsx", + "target": "ui/color-swatch-picker.tsx", + "content": `"use client"; + +import { + ColorSwatchPicker as AriaColorSwatchPicker, + ColorSwatchPickerItem as AriaColorSwatchPickerItem, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type React from "react"; + +import { ColorSwatch } from "@dotui/registry/ui/color-swatch"; + +const colorSwatchPickerStyles = tv({ + slots: { + root: "flex flex-wrap gap-1", + item: [ + "relative size-8 rounded-md transition-shadow focus:z-10 *:data-[slot=color-swatch]:size-full *:data-[slot=color-swatch]:rounded-[inherit]", + // focus state + "focus-reset focus-visible:focus-ring", + // disabled state + "disabled:cursor-not-allowed disabled:*:data-[slot=color-swatch]:[background:color-mix(in_oklab,var(--color-disabled)_90%,var(--color))]!", + // selected state + "before:absolute before:inset-0 before:scale-90 selected:before:scale-100 before:rounded-[inherit] before:bg-bg before:opacity-0 selected:before:opacity-100 before:outline-2 before:outline-inverse before:transition-[opacity,scale] before:duration-100 before:content-['']", + ], + }, +}); + +const { root, item } = colorSwatchPickerStyles(); + +/* -----------------------------------------------------------------------------------------------*/ + +interface ColorSwatchPickerProps + extends React.ComponentProps {} + +const ColorSwatchPicker = ({ className, ...props }: ColorSwatchPickerProps) => { + return ( + + root({ className }), + )} + {...props} + /> + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface ColorSwatchPickerItemProps + extends React.ComponentProps {} +const ColorSwatchPickerItem = ({ + className, + style, + ...props +}: ColorSwatchPickerItemProps) => { + return ( + + item({ className }), + )} + style={composeRenderProps( + style, + (style, { color }) => + ({ + "--color": color.toString(), + ...style, + }) as React.CSSProperties, + )} + {...props} + > + + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +const CompoundColorSwatchPicker = Object.assign(ColorSwatchPicker, { + Item: ColorSwatchPickerItem, +}); + +export type { ColorSwatchPickerProps, ColorSwatchPickerItemProps }; +export { + CompoundColorSwatchPicker as ColorSwatchPicker, + ColorSwatchPickerItem, +}; +` + } + ], + "registryDependencies": [ + "focus-styles", + "color-swatch" + ] + } + } + }, + { + "name": "color-swatch", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/color-swatch/basic.tsx", + "target": "ui/color-swatch.tsx", + "content": `"use client"; + +import { + ColorSwatch as AriaColorSwatch, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; + +const colorSwatchStyles = tv({ + base: "relative size-5 rounded-sm border", +}); + +interface ColorSwatchProps + extends React.ComponentProps {} +const ColorSwatch = ({ className, style, ...props }: ColorSwatchProps) => { + return ( + + colorSwatchStyles({ className }), + )} + style={composeRenderProps(style, (style, { color }) => ({ + ...style, + background: \`linear-gradient(\${color}, \${color}), + repeating-conic-gradient(#CCC 0% 25%, white 0% 50%) 50% / 16px 16px\`, + }))} + {...props} + /> + ); +}; + +export type { ColorSwatchProps }; +export { ColorSwatch }; +` + } + ] + } + } + }, + { + "name": "color-thumb", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/color-thumb/basic.tsx", + "target": "ui/color-thumb.tsx", + "content": `"use client"; + +import { ColorThumb as AriaColorThumb } from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type { ColorThumbProps as AriaColorThumbProps } from "react-aria-components"; + +const colorThumbStyles = tv({ + base: [ + "focus-reset focus-visible:focus-ring", + "z-30 size-6 rounded-full border-2 border-white ring-1 ring-black/40 disabled:border-border-disabled disabled:bg-disabled!", + "group-orientation-horizontal/color-slider:top-1/2 group-orientation-vertical/color-slider:left-1/2", + ], +}); + +interface ColorThumbProps extends Omit { + className?: string; +} +const ColorThumb = ({ className, ...props }: ColorThumbProps) => { + return ( + + ); +}; + +export type { ColorThumbProps }; +export { ColorThumb }; +` + } + ], + "registryDependencies": [ + "focus-styles" + ] + } + } + }, + { + "name": "combobox", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/combobox/basic.tsx", + "target": "ui/combobox.tsx", + "content": `"use client"; + +import React from "react"; +import { useResizeObserver } from "@react-aria/utils"; +import { ChevronDownIcon } from "lucide-react"; +import { mergeProps } from "react-aria"; +import { + ComboBox as AriaCombobox, + GroupContext as AriaGroupContext, + PopoverContext as AriaPopoverContext, + composeRenderProps, + Provider, +} from "react-aria-components"; +import type { ComboBoxProps as AriaComboboxProps } from "react-aria-components"; + +import { cn } from "@dotui/registry/lib/utils"; +import { Button } from "@dotui/registry/ui/button"; +import { fieldStyles } from "@dotui/registry/ui/field"; +import { Input, InputAddon, InputGroup } from "@dotui/registry/ui/input"; +import { + ListBox, + ListBoxItem, + ListBoxSection, + ListBoxSectionHeader, + ListBoxVirtualizer, +} from "@dotui/registry/ui/list-box"; +import { Popover } from "@dotui/registry/ui/popover"; +import type { InputGroupProps } from "@dotui/registry/ui/input"; +import type { ListBoxProps } from "@dotui/registry/ui/list-box"; +import type { PopoverProps } from "@dotui/registry/ui/popover"; + +/* -----------------------------------------------------------------------------------------------*/ + +interface ComboboxProps + extends Omit, "className"> { + className?: string; +} +const Combobox = ({ + menuTrigger = "focus", + className, + ...props +}: ComboboxProps) => { + return ( + + {composeRenderProps(props.children, (children) => ( + {children} + ))} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +/** + * This abstraction allows the Combobox to work with InputGroup and + * sync the trigger width with the popover dropdown. + */ + +const ComboboxInner = ({ children }: { children: React.ReactNode }) => { + const [menuWidth, setMenuWidth] = React.useState( + undefined, + ); + + const groupProps = React.use(AriaGroupContext); + const popoverProps = React.use(AriaPopoverContext); + const triggerRef = React.useRef(null); + + const onResize = React.useCallback(() => { + if (triggerRef.current) { + const triggerWidth = triggerRef.current.getBoundingClientRect().width; + setMenuWidth(\`\${triggerWidth}px\`); + } + }, []); + + useResizeObserver({ + ref: triggerRef, + onResize: onResize, + }); + + return ( + + {children} + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface ComboboxInputProps extends InputGroupProps { + placeholder?: string; +} + +const ComboboxInput = ({ placeholder, ...props }: ComboboxInputProps) => { + return ( + + + + + + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface ComboboxContentProps + extends ListBoxProps, + Pick< + PopoverProps, + "placement" | "defaultOpen" | "isOpen" | "onOpenChange" + > { + virtulized?: boolean; +} + +const ComboboxContent = ({ + virtulized, + placement, + defaultOpen, + isOpen, + onOpenChange, + ...props +}: ComboboxContentProps) => { + if (virtulized) { + return ( + + + + + + ); + } + + return ( + + + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +export { + Combobox, + ComboboxInput, + ComboboxContent, + ListBoxItem as ComboboxItem, + ListBoxSection as ComboboxSection, + ListBoxSectionHeader as ComboboxSectionHeader, +}; + +export type { ComboboxProps, ComboboxInputProps, ComboboxContentProps }; +` + } + ], + "registryDependencies": [ + "field", + "button", + "input", + "list-box", + "overlay" + ] + } + } + }, + { + "name": "command", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/command/basic.tsx", + "target": "ui/command.tsx", + "content": `"use client"; + +import { SearchIcon } from "lucide-react"; +import { + Autocomplete as AriaAutocomplete, + useFilter, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; + +import { Input, InputAddon, InputGroup } from "@dotui/registry/ui/input"; +import { + ListBox, + ListBoxItem, + ListBoxSection, + ListBoxSectionHeader, + ListBoxVirtualizer, +} from "@dotui/registry/ui/list-box"; +import { SearchField } from "@dotui/registry/ui/search-field"; +import type { ListBoxProps } from "@dotui/registry/ui/list-box"; +import type { PopoverProps } from "@dotui/registry/ui/popover"; +import type { SearchFieldProps } from "@dotui/registry/ui/search-field"; + +const commandStyles = tv({ + slots: { + base: [ + "in-drawer:rounded-[inherit] in-modal:rounded-[inherit] in-popover:rounded-[inherit] rounded-lg not-in-popover:not-in-modal:not-in-drawer:border not-in-popover:not-in-modal:not-in-drawer:bg-card", + "**:data-[slot=list-box]:w-full **:data-[slot=list-box]:border-0 **:data-[slot=list-box]:bg-transparent", + "**:data-[slot=search-field]:w-full **:data-[slot=search-field]:outline-none [&_[data-slot=search-field]_[data-slot=input-group]]:rounded-b-none [&_[data-slot=search-field]_[data-slot=input-group]]:border-0 [&_[data-slot=search-field]_[data-slot=input-group]]:border-b [&_[data-slot=search-field]_[data-slot=input-group]]:bg-transparent", + "in-modal:w-full", + ], + }, +}); + +const { base } = commandStyles(); + +/* -----------------------------------------------------------------------------------------------*/ + +interface CommandProps extends React.ComponentProps<"div"> {} + +function Command({ className, ...props }: CommandProps) { + const { contains } = useFilter({ + sensitivity: "base", + ignorePunctuation: true, + }); + + return ( + +
+
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +interface CommandInputProps extends SearchFieldProps { + placeholder?: string; +} + +const CommandInput = ({ placeholder, ...props }: CommandInputProps) => { + return ( + + {/* TODO: Remove this */} + + + + + + + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface CommandContentProps extends ListBoxProps { + placement?: PopoverProps["placement"]; + virtulized?: boolean; +} + +const CommandContent = ({ + virtulized, + placement, + ...props +}: CommandContentProps) => { + if (virtulized) { + return ( + + + + ); + } + + return ; +}; + +/* -----------------------------------------------------------------------------------------------*/ + +export { + Command, + CommandInput, + CommandContent, + ListBoxItem as CommandItem, + ListBoxSection as CommandSection, + ListBoxSectionHeader as CommandSectionHeader, +}; + +export type { CommandProps, CommandContentProps, CommandInputProps }; +` + } + ] + } + } + }, + { + "name": "date-field", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/date-field/basic.tsx", + "target": "ui/date-field.tsx", + "content": `"use client"; + +import { + DateField as AriaDateField, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type { + DateFieldProps as AriaDateFieldProps, + DateValue, +} from "react-aria-components"; + +import { fieldStyles } from "@dotui/registry/ui/field"; + +const dateFieldStyles = tv({ + base: [fieldStyles().field({ orientation: "vertical" })], +}); + +/* -----------------------------------------------------------------------------------------------*/ + +interface DateFieldProps extends AriaDateFieldProps {} + +const DateField = ({ + className, + ...props +}: DateFieldProps) => { + return ( + + dateFieldStyles({ className }), + )} + {...props} + /> + ); +}; + +export type { DateFieldProps }; +export { DateField }; +` + } + ], + "registryDependencies": [ + "field", + "input" + ] + } + } + }, + { + "name": "date-picker", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/date-picker/basic.tsx", + "target": "ui/date-picker.tsx", + "content": `"use client"; + +import { useContext } from "react"; +import { CalendarIcon } from "lucide-react"; +import { + DateRangePicker as AriaDataRangePicker, + DatePicker as AriaDatePicker, + RangeCalendarContext as AriaRangeCalendarContext, + composeRenderProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type { + DateRangePickerProps as AriaDataRangePickerProps, + DatePickerProps as AriaDatePickerProps, + DateValue, +} from "react-aria-components"; + +import { Button } from "@dotui/registry/ui/button"; +import { + DialogContent, + type DialogContentProps, +} from "@dotui/registry/ui/dialog"; +import { DateInput, InputAddon, InputGroup } from "@dotui/registry/ui/input"; +import { Overlay, type OverlayProps } from "@dotui/registry/ui/overlay"; +import type { InputGroupProps } from "@dotui/registry/ui/input"; + +const datePickerStyles = tv({ + base: "flex flex-col items-start gap-2", +}); + +type DatePickerProps = + | ({ + mode?: "single"; + } & AriaDatePickerProps) + | ({ + mode: "range"; + } & AriaDataRangePickerProps); + +const DatePicker = ({ + mode = "single", + className, + ...props +}: DatePickerProps) => { + if (mode === "range") { + return ( + ["className"], + (className) => datePickerStyles({ className }), + )} + {...(props as AriaDataRangePickerProps)} + /> + ); + } + + return ( + ["className"], + (className) => datePickerStyles({ className }), + )} + {...(props as AriaDatePickerProps)} + /> + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface DatePickerInputProps extends InputGroupProps {} + +const DatePickerInput = (props: DatePickerInputProps) => { + const rangeCalendarContext = useContext(AriaRangeCalendarContext); + const mode = rangeCalendarContext ? "range" : "single"; + + return ( + + {mode === "single" ? ( + + ) : ( + <> + + – + + + )} + + + + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface DatePickerContentProps + extends DialogContentProps, + Pick {} + +const DatePickerContent = ({ + children, + type = "popover", + mobileType, + popoverProps, + ...props +}: DatePickerContentProps) => { + return ( + + {children} + + ); +}; + +export type { DatePickerProps, DatePickerContentProps, DatePickerInputProps }; +export { DatePicker, DatePickerContent, DatePickerInput }; +` + } + ], + "registryDependencies": [ + "button", + "calendar", + "field", + "input", + "dialog" + ] + } + } + }, + { + "name": "dialog", + "type": "registry:ui", + "defaultVariant": "basic", + "variants": { + "basic": { + "files": [ + { + "type": "registry:ui", + "path": "ui/dialog/basic.tsx", + "target": "ui/dialog.tsx", + "content": `"use client"; + +import { + Dialog as AriaDialog, + DialogTrigger as AriaDialogTrigger, + Heading as AriaHeading, + Text as AriaText, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; +import type * as React from "react"; + +const dialogStyles = tv({ + slots: { + content: + "relative flex flex-col gap-4 in-data-popover:p-4 p-6 outline-none", + header: "flex flex-col gap-2 text-left", + heading: + "font-semibold in-popover:font-medium in-popover:text-base text-lg leading-none", + description: "text-fg-muted text-sm", + body: "flex flex-1 flex-col gap-2", + inset: "-mx-6 in-popover:-mx-4 border bg-muted in-popover:px-4 px-6 py-4", + footer: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", + }, +}); + +const { content, header, heading, description, body, footer, inset } = + dialogStyles(); + +/* -----------------------------------------------------------------------------------------------*/ + +interface DialogProps extends React.ComponentProps {} + +const Dialog = (props: DialogProps) => { + return ; +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface DialogContentProps extends React.ComponentProps {} + +const DialogContent = ({ className, ...props }: DialogContentProps) => { + return ( + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface DialogHeaderProps extends React.ComponentProps<"header"> {} + +const DialogHeader = ({ className, ...props }: DialogHeaderProps) => { + return
; +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface DialogHeadingProps extends React.ComponentProps {} + +const DialogHeading = ({ className, ...props }: DialogHeadingProps) => { + return ; +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface DialogDescriptionProps + extends Omit, "slot"> {} + +const DialogDescription = ({ className, ...props }: DialogDescriptionProps) => { + return ( + + ); +}; + +/* -----------------------------------------------------------------------------------------------*/ + +interface DialogBodyProps extends React.ComponentProps<"div"> {} + +const DialogBody = ({ className, ...props }: DialogBodyProps) => { + return
; +}; + +/* -----------------------------------------------------------------------------------------------*/ + +type DialogFooterProps = React.ComponentProps<"footer">; + +const DialogFooter = ({ className, ...props }: DialogFooterProps) => { + return