diff --git a/bun.lock b/bun.lock index b9c96631a..ef02b7190 100644 --- a/bun.lock +++ b/bun.lock @@ -18,6 +18,7 @@ "expo-font": "^14.0.10", "next": "^16.1.1", "node-emoji": "^2.2.0", + "postcss": "^8.5.6", "react": "19.2.3", "react-content-loader": "^7.1.1", "react-dom": "19.2.3", @@ -31,6 +32,8 @@ "rehype-sanitize": "^6.0.0", "remark-emoji": "^5.0.2", "remark-gfm": "^4.0.1", + "tailwindcss": "^3.4.19", + "twrnc": "^4.16.0", "use-debounce": "^10.0.6", }, "devDependencies": { @@ -67,6 +70,8 @@ "packages": { "@0no-co/graphql.web": ["@0no-co/graphql.web@1.2.0", "", { "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "optionalPeers": ["graphql"] }, "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw=="], + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + "@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], @@ -895,6 +900,8 @@ "big.js": ["big.js@5.2.2", "", {}, "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="], + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], "bplist-creator": ["bplist-creator@0.1.0", "", { "dependencies": { "stream-buffers": "2.2.x" } }, "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg=="], @@ -927,6 +934,8 @@ "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], + "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], + "caniuse-lite": ["caniuse-lite@1.0.30001759", "", {}, "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -947,6 +956,8 @@ "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], + "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="], @@ -1011,6 +1022,8 @@ "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], @@ -1051,6 +1064,10 @@ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], + + "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], + "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], @@ -1213,7 +1230,7 @@ "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], - "fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], "fast-json-patch": ["fast-json-patch@2.2.1", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig=="], @@ -1401,6 +1418,8 @@ "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], + "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], "is-buffer": ["is-buffer@2.0.5", "", {}, "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="], @@ -1495,6 +1514,8 @@ "jimp-compact": ["jimp-compact@0.16.1", "", {}, "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww=="], + "jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], + "jquery": ["jquery@3.7.1", "", {}, "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="], "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], @@ -1559,6 +1580,8 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], "linkifyjs": ["linkifyjs@2.1.9", "", { "peerDependencies": { "jquery": ">= 1.11.0", "react": ">= 0.14.0", "react-dom": ">= 0.14.0" } }, "sha512-74ivurkK6WHvHFozVaGtQWV38FzBwSTGNmJolEgFp7QgR2bl6ArUWlvT4GcHKbPe1z3nWYi+VUdDZk16zDOVug=="], @@ -1793,6 +1816,8 @@ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], @@ -1867,6 +1892,8 @@ "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], + "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], @@ -1875,7 +1902,17 @@ "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], - "postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="], + + "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], + + "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], @@ -1939,6 +1976,10 @@ "react-shiki": ["react-shiki@0.9.1", "", { "dependencies": { "clsx": "^2.1.1", "dequal": "^2.0.3", "hast-util-to-jsx-runtime": "^2.3.6", "shiki": "^3.11.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "@types/react": ">=16.8.0", "@types/react-dom": ">=16.8.0", "react": ">= 16.8.0", "react-dom": ">= 16.8.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ln1PnISi7WaSlheSBRdxVruVbU1zMUkCmxe+vmbIvZSsHdfvOF5NBOgf1h4cCr6OjdR0dLAxmPKcx3tobdyxVA=="], + "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], + + "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="], @@ -2147,6 +2188,8 @@ "synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], + "tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], "tar": ["tar@7.5.2", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg=="], @@ -2195,6 +2238,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "twrnc": ["twrnc@4.16.0", "", { "dependencies": { "tailwindcss": ">=2.0.0 <4.0.0" }, "peerDependencies": { "react-native": ">=0.62.2" } }, "sha512-sPSnSlT2CmOd2ITUm9M8ltsEjTyJti/9HpYfewAfhqRT4gMQtOWkK1tiIev0vVhPJ9IcTGtk2TC33OsSLhsFfA=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="], @@ -2259,6 +2304,8 @@ "user-agent-data-types": ["user-agent-data-types@0.4.2", "", {}, "sha512-jXep3kO/dGNmDOkbDa8ccp4QArgxR4I76m3QVcJ1aOF0B9toc+YtSXtX5gLdDTZXyWlpQYQrABr6L1L2GZOghw=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], "uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="], @@ -2419,6 +2466,8 @@ "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + "@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], + "@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.81.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.29.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g=="], "@react-native/babel-preset/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.29.1", "", { "dependencies": { "hermes-parser": "0.29.1" } }, "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA=="], @@ -2445,6 +2494,8 @@ "cheerio/undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], + "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "cli-truncate/string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="], "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -2543,6 +2594,8 @@ "metro-file-map/jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="], + "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], "ora/cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="], @@ -2649,6 +2702,8 @@ "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + "@next/eslint-plugin-next/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], "@react-native/community-cli-plugin/@react-native/dev-middleware/@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.83.1", "", {}, "sha512-01Rn3goubFvPjHXONooLmsW0FLxJDKIUJNOlOS0cPtmmTIx9YIjxhe/DxwHXGk7OnULd7yl3aYy7WlBsEd5Xmg=="], diff --git a/common/styleguide.tsx b/common/styleguide.tsx index 6c0afb8f6..52b70a76a 100644 --- a/common/styleguide.tsx +++ b/common/styleguide.tsx @@ -1,13 +1,7 @@ import * as HtmlElements from '@expo/html-elements'; import { type TextProps } from '@expo/html-elements/build/primitives/Text'; import Link from 'next/link'; -import { - type ComponentType, - type CSSProperties, - type PropsWithChildren, - useContext, - useState, -} from 'react'; +import { type ComponentType, type CSSProperties, type PropsWithChildren, useState } from 'react'; import { StyleSheet, type TextStyle, @@ -17,93 +11,40 @@ import { type ViewStyle, } from 'react-native'; -import CustomAppearanceContext from '../context/CustomAppearanceContext'; - -export const layout = { - maxWidth: 1200, -}; +import tw from '~/util/tailwind'; export function useLayout() { const { width } = useWindowDimensions(); return { isSmallScreen: width < 800, - isBelowMaxWidth: width < layout.maxWidth, + isBelowMaxWidth: width < 1200, }; } -export const colors = { - primary: '#61DAFB', - primaryLight: '#c1f4ff', - primaryDark: '#39BEE2', - primaryHover: '#61dafb16', - sky: '#C6EEFB', - powder: '#EEFAFE', - pewter: '#BEC8CB', - gray1: '#f9f9f9', - gray2: '#ececec', - gray3: '#CFCFD5', - gray4: '#828898', - gray5: '#505461', - gray6: '#24262e', - gray7: '#21232A', - black: '#242424', - white: '#ffffff', - secondary: '#afb1af', - warning: '#FBE679', - warningLight: '#FEF7D6', - warningDark: '#995e00', - error: '#ff5555', - success: '#4caf50', -}; - -export const darkColors = { - black: '#000', - background: '#19191f', - subHeader: '#14141a', - border: '#2a2e36', - veryDark: '#111114', - dark: '#14141a', - darkBright: '#1c1c21', - powder: '#262a36', - pewter: '#767C8E', - secondary: '#a2a7ab', - warningLight: '#2f2704', - warning: '#9a810c', - primaryDark: '#2e9ab8', -}; - -const baseTextStyles = { - color: colors.black, - marginVertical: 0, - fontWeight: '400' as const, - fontFamily: 'inherit', -}; - const textStyles = StyleSheet.create({ - h1: { ...baseTextStyles, fontSize: 57.25, fontWeight: '600' as const }, - h2: { ...baseTextStyles, fontSize: 35.5, fontWeight: '600' as const }, - h3: { ...baseTextStyles, fontSize: 26.5, fontWeight: '600' as const }, - h4: { ...baseTextStyles, fontSize: 22 }, - h5: { ...baseTextStyles, fontSize: 20 }, - h6: { ...baseTextStyles, fontSize: 18 }, - headline: { ...baseTextStyles, fontSize: 16, fontWeight: '500' as const }, - p: { ...baseTextStyles, fontSize: 16 }, - caption: { ...baseTextStyles, fontSize: 15, lineHeight: 22 }, - label: { ...baseTextStyles, fontSize: 12, fontWeight: '500' as const }, + h1: tw`text-[57.25px] font-semibold`, + h2: tw`text-[35.5px] font-semibold`, + h3: tw`text-[26.5px] font-semibold`, + h4: tw`text-[22px]`, + h5: tw`text-[20px]`, + h6: tw`text-[18px]`, + headline: tw`text-[16px] font-medium`, + p: tw`text-[16px]`, + caption: tw`text-[15px] leading-[22px]`, + label: tw`text-[12px] font-medium`, }); -type TextStyles = TextStyle | TextStyle[]; - type CustomTextProps = TextProps & PropsWithChildren<{ id?: string; numberOfLines?: number; }>; -function createTextComponent(Element: ComponentType, textStyle?: TextStyles) { +export function createTextComponent( + Element: ComponentType, + textStyle?: StyleProp +) { function TextComponent({ children, style, id, numberOfLines }: CustomTextProps) { - const { isDark } = useContext(CustomAppearanceContext); - const elementStyle = Element?.displayName ? StyleSheet.flatten(textStyles[Element.displayName as keyof typeof textStyles]) : undefined; @@ -112,7 +53,12 @@ function createTextComponent(Element: ComponentType, textStyle?: Text + style={[ + tw`font-sans font-normal my-0 text-black dark:text-white`, + elementStyle as StyleProp, + textStyle, + style, + ]}> {children} ); @@ -139,15 +85,14 @@ type AProps = PropsWithChildren<{ target?: string; href: string; hoverStyle?: StyleProp; - containerStyle?: CSSProperties | undefined; + containerStyle?: CSSProperties; }>; export function A({ href, target, children, style, hoverStyle, containerStyle, ...rest }: AProps) { - const { isDark } = useContext(CustomAppearanceContext); const [isHovered, setIsHovered] = useState(false); - const linkStyles = getLinkStyles(isDark); - const linkHoverStyles = getLinkHoverStyles(); + const linkStyles = tw`font-sans text-black underline decoration-pewter dark:text-white dark:decoration-palette-gray5`; + const linkHoverStyles = tw`decoration-primary-dark`; if ((target === '_self' && !href.startsWith('#')) || href.startsWith('/')) { const passedStyle = StyleSheet.flatten(style); @@ -171,7 +116,7 @@ export function A({ href, target, children, style, hoverStyle, containerStyle, . setIsHovered(true)} onPointerLeave={() => setIsHovered(false)} - style={{ display: 'contents', ...containerStyle }}> + style={{ ...tw`contents`, ...containerStyle }}> }>; export function HoverEffect({ children, style }: HoverEffectProps) { @@ -209,10 +139,9 @@ export function HoverEffect({ children, style }: HoverEffectProps) { return ( setIsHovered(true)} diff --git a/components/Button.tsx b/components/Button.tsx index 45a8776dd..7e866df7d 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -1,14 +1,9 @@ import { A } from '@expo/html-elements'; import { type PropsWithChildren } from 'react'; -import { - StyleSheet, - type TextStyle, - Pressable, - type StyleProp, - type ViewStyle, -} from 'react-native'; +import { type TextStyle, Pressable, type StyleProp, type ViewStyle } from 'react-native'; -import { darkColors, HoverEffect, P } from '~/common/styleguide'; +import { HoverEffect, P } from '~/common/styleguide'; +import tw from '~/util/tailwind'; type Props = PropsWithChildren & { href?: string; @@ -29,10 +24,7 @@ export function Button({ }: Props) { const isLink = !!href; const buttonStyle = [ - styles.container, - { - backgroundColor: darkColors.primaryDark, - }, + tw`justify-center items-center rounded outline-offset-1 select-none bg-primary-darker dark:bg-primary-dark`, style, ]; @@ -43,7 +35,7 @@ export function Button({ {isLink ? ( @@ -58,12 +50,3 @@ export function Button({ ); } - -const styles = StyleSheet.create({ - container: { - justifyContent: 'center', - alignItems: 'center', - borderRadius: 4, - outlineOffset: 1, - }, -}); diff --git a/components/CheckBox.tsx b/components/CheckBox.tsx index f654466e0..c15061193 100644 --- a/components/CheckBox.tsx +++ b/components/CheckBox.tsx @@ -1,46 +1,26 @@ -import { useContext } from 'react'; -import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native'; +import { type StyleProp, View, type ViewStyle } from 'react-native'; -import { colors, darkColors } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import tw from '~/util/tailwind'; import { Check } from './Icons'; type Props = { style?: StyleProp; value?: boolean; - color?: string; }; -export default function CheckBox({ style, value, color }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - +export default function CheckBox({ style, value }: Props) { return ( - {value ? ( - - ) : null} + {value ? : null} ); } - -const styles = StyleSheet.create({ - container: { - height: 18, - width: 18, - justifyContent: 'center', - alignItems: 'center', - borderWidth: 2, - borderStyle: 'solid', - borderRadius: 2, - marginRight: 8, - }, -}); diff --git a/components/CompatibilityTags.tsx b/components/CompatibilityTags.tsx index 3c2efcb68..678a2a92a 100644 --- a/components/CompatibilityTags.tsx +++ b/components/CompatibilityTags.tsx @@ -1,9 +1,8 @@ -import { useContext } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; -import { A, colors, darkColors } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { A } from '~/common/styleguide'; import { type LibraryType } from '~/types'; +import tw from '~/util/tailwind'; import { Info } from './Icons'; import { NewArchitectureTag } from './Library/NewArchitectureTag'; @@ -15,8 +14,6 @@ type Props = { }; export default function CompatibilityTags({ library }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - const platforms = [ library.android ? 'Android' : null, library.ios ? 'iOS' : null, @@ -25,29 +22,21 @@ export default function CompatibilityTags({ library }: Props) { library.visionos ? 'visionOS' : null, library.web ? 'Web' : null, library.windows ? 'Windows' : null, - ] - .map(platform => platform) - .filter(Boolean); + ].filter(Boolean); return ( - + {library.dev ? ( ) : null} {library.template ? ( ) : null} @@ -57,10 +46,7 @@ export default function CompatibilityTags({ library }: Props) { ) : null )} @@ -68,13 +54,13 @@ export default function CompatibilityTags({ library }: Props) { - + + }> Additional information
-
    +
      {library.expoGo &&
    • Works with Expo Go
    • } {library.fireos &&
    • Works with Fire OS
    • } {library.horizon &&
    • Works with Meta Horizon OS
    • } @@ -85,7 +71,7 @@ export default function CompatibilityTags({ library }: Props) {
      + style={tw`text-xs text-white font-light`}> (via dedicated support package) @@ -96,27 +82,3 @@ export default function CompatibilityTags({ library }: Props) { ); } - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - alignItems: 'center', - flexWrap: 'wrap', - gap: 6, - }, - infoTrigger: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - cursor: 'pointer', - minHeight: 25, - }, - compatibilityList: { - margin: 0, - paddingLeft: 14, - }, - tooltipLink: { - fontSize: 12, - fontWeight: 300, - }, -}); diff --git a/components/ContentContainer.tsx b/components/ContentContainer.tsx index 1a1f9d238..89265f5ab 100644 --- a/components/ContentContainer.tsx +++ b/components/ContentContainer.tsx @@ -1,17 +1,8 @@ import { type PropsWithChildren } from 'react'; -import { View, type ViewProps, StyleSheet } from 'react-native'; +import { View, type ViewProps } from 'react-native'; -import { layout } from '~/common/styleguide'; +import tw from '~/util/tailwind'; export default function ContentContainer({ children, style }: PropsWithChildren) { - return {children}; + return {children}; } - -const styles = StyleSheet.create({ - container: { - flex: 1, - width: '100%', - maxWidth: layout.maxWidth, - margin: 'auto', - }, -}); diff --git a/components/Explore/ExploreSection.tsx b/components/Explore/ExploreSection.tsx index ddc556997..2a0219bde 100644 --- a/components/Explore/ExploreSection.tsx +++ b/components/Explore/ExploreSection.tsx @@ -1,28 +1,19 @@ import dynamic from 'next/dynamic'; -import { createElement, type FunctionComponent, useContext } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { createElement, type FunctionComponent } from 'react'; +import { View } from 'react-native'; -import { A, colors, darkColors, H3, P } from '~/common/styleguide'; +import { A, H3, P } from '~/common/styleguide'; import { type IconProps } from '~/components/Icons'; import LoadingContent from '~/components/Library/LoadingContent'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; import { type LibraryType, type Query } from '~/types'; +import tw from '~/util/tailwind'; import urlWithQuery from '~/util/urlWithQuery'; const LibraryWithLoading = dynamic(() => import('~/components/Library'), { - loading: () => ( - - ), + loading: () => , }); -type ExploreSectionProps = { +type Props = { data: LibraryType[]; title: string; filter: (library: LibraryType) => boolean; @@ -32,32 +23,12 @@ type ExploreSectionProps = { }; const UPDATED_IN = 1000 * 60 * 60 * 24 * 90; // 90 days - const DEFAULT_PARAMS: Query = { wasRecentlyUpdated: 'true', isMaintained: 'true', order: 'popularity', }; -function renderLibs(list: LibraryType[], count = 4) { - const now = new Date().getTime(); - return ( - - {list - .filter(lib => now - new Date(lib.github.stats.updatedAt).getTime() < UPDATED_IN) - .splice(0, count) - .map((item: LibraryType, index: number) => ( - - ))} - - ); -} - export default function ExploreSection({ data, title, @@ -65,26 +36,25 @@ export default function ExploreSection({ icon, count = 4, queryParams = { [title.toLowerCase()]: true }, -}: ExploreSectionProps) { - const { isDark } = useContext(CustomAppearanceContext); - const color = isDark ? darkColors.pewter : colors.gray5; - const hoverColor = isDark ? colors.gray5 : colors.gray4; +}: Props) { const hashLink = title.replace(/\s/g, '').toLowerCase(); return ( <> -

      - {icon && createElement(icon, { fill: color, width: 30, height: 30 })} +

      + {icon && createElement(icon, { style: tw`size-7.5 text-icon mt-px` })} + style={tw`no-underline text-icon`} + hoverStyle={tw`text-palette-gray4 dark:text-palette-gray5`}> {title}

      - {renderLibs(data.filter(filter), count)} -

      + + {renderLibs(data.filter(filter), count)} + +

      Want to see more? Check out other{' '} {title} libraries @@ -95,34 +65,17 @@ export default function ExploreSection({ ); } -const styles = StyleSheet.create({ - container: { - paddingHorizontal: 16, - paddingBottom: 12, - }, - librariesContainer: { - paddingVertical: 4, - flex: 1, - flexDirection: 'row', - flexWrap: 'wrap', - }, - subHeader: { - display: 'flex', - gap: 16, - marginHorizontal: 8, - marginVertical: 16, - alignItems: 'center', - }, - subHeaderTitle: { - fontSize: 26, - fontWeight: 700, - textDecorationLine: 'none', - backgroundColor: 'transparent', - }, - note: { - paddingHorizontal: 24, - paddingTop: 8, - paddingBottom: 24, - fontSize: 14, - }, -}); +function renderLibs(list: LibraryType[], count = 4) { + const now = new Date().getTime(); + return list + .filter(({ github }) => now - new Date(github.stats.updatedAt).getTime() < UPDATED_IN) + .splice(0, count) + .map((item: LibraryType, index: number) => ( + + )); +} diff --git a/components/Filters/ClearButton.tsx b/components/Filters/ClearButton.tsx index c542f32ff..ea6151cdf 100644 --- a/components/Filters/ClearButton.tsx +++ b/components/Filters/ClearButton.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; -import { Pressable, type PressableProps, StyleSheet } from 'react-native'; +import { Pressable, type PressableProps } from 'react-native'; -import { colors } from '~/common/styleguide'; +import tw from '~/util/tailwind'; import { XIcon } from '../Icons'; import Tooltip from '../Tooltip'; @@ -18,21 +18,12 @@ export function ClearButton({ onPress }: ClearButtonProps) { onHoverIn={() => setIsXIconHovered(true)} onHoverOut={() => setIsXIconHovered(false)} onPress={onPress} - style={styles.container} + style={tw`size-6 items-center justify-center`} aria-label="Clear all"> - + }> Clear all ); } - -const styles = StyleSheet.create({ - container: { - width: 24, - height: 24, - alignItems: 'center', - justifyContent: 'center', - }, -}); diff --git a/components/Filters/FilterButton.tsx b/components/Filters/FilterButton.tsx index 3d51b61b4..dc23a544a 100644 --- a/components/Filters/FilterButton.tsx +++ b/components/Filters/FilterButton.tsx @@ -1,9 +1,8 @@ -import { useContext } from 'react'; -import { StyleSheet, View, type ViewStyle } from 'react-native'; +import { type StyleProp, View, type ViewStyle } from 'react-native'; -import { colors, darkColors, P } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { P } from '~/common/styleguide'; import { type Query } from '~/types'; +import tw from '~/util/tailwind'; import { ClearButton } from './ClearButton'; import { @@ -17,25 +16,15 @@ import { import { Button } from '../Button'; import { Filter as FilterIcon } from '../Icons'; -type FilterButtonProps = { +type Props = { query: Query; onPress: () => void; onClearAllPress: () => void; isFilterVisible: boolean; - containerStyle?: ViewStyle; - style?: ViewStyle; + style?: StyleProp; }; -export function FilterButton({ - isFilterVisible, - query, - onPress, - onClearAllPress, - containerStyle, - style, -}: FilterButtonProps) { - const { isDark } = useContext(CustomAppearanceContext); - +export function FilterButton({ isFilterVisible, query, onPress, onClearAllPress, style }: Props) { const params = [ ...FILTER_PLATFORMS.map(platform => platform.param), ...FILTER_REQUIRES_MAIN_SEARCH.map(filter => filter.param), @@ -51,77 +40,35 @@ export function FilterButton({ ); const isFilterCount = !!filterCount; - const backgroundColor = isDark ? darkColors.border : colors.gray5; - const borderLeftColor = isDark ? darkColors.dark : colors.gray6; - return ( - + {filterCount > 0 && ( - + )} ); } - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - alignItems: 'center', - borderRadius: 4, - }, - button: { - height: '100%', - paddingHorizontal: 8, - }, - activeButton: { - backgroundColor: darkColors.primaryDark, - }, - leftBorderRadiusOnly: { - borderTopRightRadius: 0, - borderBottomRightRadius: 0, - }, - buttonText: { - fontSize: 14, - color: colors.white, - marginLeft: 6, - fontWeight: '500', - userSelect: 'none', - }, - iconContainer: { - top: 1, - }, - displayHorizontal: { - flexDirection: 'row', - alignItems: 'center', - }, - clearButtonContainer: { - height: '100%', - justifyContent: 'center', - alignItems: 'center', - borderTopRightRadius: 4, - borderBottomRightRadius: 4, - borderLeftWidth: 1, - }, -}); diff --git a/components/Filters/FiltersSection.tsx b/components/Filters/FiltersSection.tsx new file mode 100644 index 000000000..64ffc1fe5 --- /dev/null +++ b/components/Filters/FiltersSection.tsx @@ -0,0 +1,24 @@ +import { type PropsWithChildren, type ReactElement } from 'react'; +import { View } from 'react-native'; + +import { Headline, useLayout } from '~/common/styleguide'; +import tw from '~/util/tailwind'; + +type Props = PropsWithChildren<{ + title: string; + rightSlot?: ReactElement; +}>; + +export default function FiltersSection({ title, children, rightSlot }: Props) { + const { isSmallScreen } = useLayout(); + + return ( + + + {title} + {rightSlot} + + {children} + + ); +} diff --git a/components/Filters/ToggleLink.tsx b/components/Filters/ToggleLink.tsx index 2084ca769..c45f4c6f4 100644 --- a/components/Filters/ToggleLink.tsx +++ b/components/Filters/ToggleLink.tsx @@ -1,65 +1,41 @@ import Link from 'next/link'; -import { useContext, useState } from 'react'; -import { Platform, StyleSheet, View } from 'react-native'; +import { useState } from 'react'; +import { View } from 'react-native'; -import { P, colors } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; -import { type Query } from '~/types'; +import { P } from '~/common/styleguide'; +import { type FilterParamsType, type Query } from '~/types'; +import tw from '~/util/tailwind'; import urlWithQuery from '~/util/urlWithQuery'; import CheckBox from '../CheckBox'; type Props = { query: Query; - paramName: keyof Query; - title: string; + filterParam: FilterParamsType; basePath?: string; }; -export function ToggleLink({ query, paramName, title, basePath = '/' }: Props) { - const { isDark } = useContext(CustomAppearanceContext); +export function ToggleLink({ query, filterParam, basePath = '/' }: Props) { const [isHovered, setHovered] = useState(false); - const isSelected = !!query[paramName]; + const isSelected = !!query[filterParam.param]; return ( + style={tw`no-underline mr-4 my-1`}> setHovered(true)} onPointerLeave={() => setHovered(false)}> - -

      - {title} + +

      + {filterParam.title}

      ); } - -const styles = StyleSheet.create({ - link: { - ...Platform.select({ - web: { - cursor: 'pointer', - }, - }), - marginRight: 16, - marginVertical: 4, - alignItems: 'center', - flexDirection: 'row', - }, - text: { - fontSize: 14, - fontWeight: 300, - }, -}); diff --git a/components/Filters/index.tsx b/components/Filters/index.tsx index 5d7d5d9e7..d9055c6b1 100644 --- a/components/Filters/index.tsx +++ b/components/Filters/index.tsx @@ -1,11 +1,10 @@ -import { useContext } from 'react'; -import { StyleSheet, View, type ViewStyle } from 'react-native'; +import { type StyleProp, View, type ViewStyle } from 'react-native'; -import { colors, Headline, layout, darkColors, useLayout } from '~/common/styleguide'; +import FiltersSection from '~/components/Filters/FiltersSection'; import { Tag } from '~/components/Tag'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; import { type Query } from '~/types'; import { getPageQuery } from '~/util/search'; +import tw from '~/util/tailwind'; import { ToggleLink } from './ToggleLink'; import { @@ -19,191 +18,87 @@ import { type FiltersProps = { query: Query; - style?: ViewStyle | ViewStyle[]; + style?: StyleProp; basePath?: string; }; export function Filters({ query, style, basePath = '/' }: FiltersProps) { - const { isDark } = useContext(CustomAppearanceContext); - const { isSmallScreen } = useLayout(); const pageQuery = getPageQuery(basePath, query); - const isMainSearch = basePath === '/'; - const titleColor = { - color: isDark ? darkColors.secondary : colors.gray5, - }; - return ( - - - - Platform - - {FILTER_PLATFORMS.map(platform => ( - - ))} - - - - Type - - {FILTER_TYPE.map(entryType => ( - - ))} - - + + + + {FILTER_PLATFORMS.map(platform => ( + + ))} + + + {FILTER_TYPE.map(entryType => ( + + ))} + - - Status - - {isMainSearch && - FILTER_REQUIRES_MAIN_SEARCH.map(status => ( - - ))} - {FILTER_STATUS.map(status => ( + + {isMainSearch && + FILTER_REQUIRES_MAIN_SEARCH.map(status => ( ))} - - - - - Compatibility - - {FILTER_COMPATIBILITY.map(compatibility => ( - - ))} - - - - - Module type + {FILTER_STATUS.map(status => ( + + ))} + + + + {FILTER_COMPATIBILITY.map(compatibility => ( + + ))} + + - - - {FILTER_MODULE_TYPE.map(moduleType => ( - - ))} - - + }> + {FILTER_MODULE_TYPE.map(moduleType => ( + + ))} + ); } - -const styles = StyleSheet.create({ - wrapper: { - paddingVertical: 8, - flex: 1, - flexGrow: 0, - alignItems: 'center', - }, - container: { - width: '100%', - paddingHorizontal: 16, - paddingVertical: 8, - maxWidth: layout.maxWidth, - }, - wrappableContainer: { - paddingHorizontal: 16, - paddingVertical: 8, - maxWidth: layout.maxWidth, - }, - wrappableContainerSmallScreen: { - maxWidth: '100%', - }, - optionsContainer: { - flexWrap: 'wrap', - flexDirection: 'row', - }, - title: { - marginBottom: 8, - fontWeight: 600, - }, - titleWithTag: { - marginBottom: 6, - fontWeight: 600, - }, - titleTag: { - marginLeft: 8, - top: -1, - paddingVertical: 0, - minHeight: 22, - }, - twoColumns: { - width: '100%', - maxWidth: layout.maxWidth, - alignContent: 'flex-start', - flexDirection: 'row', - flexWrap: 'wrap', - }, -}); diff --git a/components/Footer/PlatformTile.tsx b/components/Footer/PlatformTile.tsx index c047c1862..3b17deb0d 100644 --- a/components/Footer/PlatformTile.tsx +++ b/components/Footer/PlatformTile.tsx @@ -1,63 +1,27 @@ -import { createElement, useContext, type ComponentType } from 'react'; -import { StyleSheet, View, type ViewStyle } from 'react-native'; +import { createElement, type ComponentType } from 'react'; +import { View } from 'react-native'; -import { A, colors, darkColors, P } from '~/common/styleguide'; +import { A, P } from '~/common/styleguide'; import { type IconProps } from '~/components/Icons'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import tw from '~/util/tailwind'; -type PlatformProps = { +type Props = { name: string; pkgName: string; url: string; Icon: ComponentType; - style?: ViewStyle; }; -export default function Platform({ name, pkgName, url, Icon, style }: PlatformProps) { - const { isDark } = useContext(CustomAppearanceContext); - - const packageHoverStyle = { - backgroundColor: isDark ? darkColors.background : colors.gray2, - borderRadius: 8, - }; - +export default function Platform({ name, pkgName, url, Icon }: Props) { return ( - - + + {createElement(Icon, { - fill: isDark ? darkColors.pewter : colors.gray5, - width: 32, - height: 32, + style: tw`size-8 text-icon`, })} -

      {name}

      -

      {pkgName}

      +

      {name}

      +

      {pkgName}

      ); } - -const styles = StyleSheet.create({ - platformItem: { - minWidth: 160, - paddingHorizontal: 8, - paddingVertical: 16, - borderRadius: 8, - alignItems: 'center', - }, - platformName: { - marginTop: 12, - }, - platformPackageName: { - fontSize: 12, - fontFamily: 'monospace', - borderRadius: 4, - paddingHorizontal: 8, - lineHeight: 22, - marginTop: 2, - }, - itemLink: { - backgroundColor: 'none', - borderWidth: 1, - borderColor: 'transparent', - }, -}); diff --git a/components/Footer/index.tsx b/components/Footer/index.tsx index d7b293f30..b0ad3cd9e 100644 --- a/components/Footer/index.tsx +++ b/components/Footer/index.tsx @@ -1,7 +1,6 @@ -import { useContext } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; -import { A, P, colors, darkColors, useLayout } from '~/common/styleguide'; +import { A, P, useLayout } from '~/common/styleguide'; import ContentContainer from '~/components/ContentContainer'; import { Logo, @@ -13,31 +12,23 @@ import { PlatformWeb, PlatformWindows, } from '~/components/Icons'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import tw from '~/util/tailwind'; import PlatformTile from './PlatformTile'; import VercelBanner from './VercelBanner'; export default function Footer() { - const { isDark } = useContext(CustomAppearanceContext); const { isSmallScreen } = useLayout(); return ( - + - + - - -

      + + +

      Missing a library?{' '} Add it to the directory .

      -

      +

      Want to learn more? Check out the{' '} official React Native docs, and Expo.

      - + - - + +
      ); } - -const styles = StyleSheet.create({ - container: { - width: '100%', - paddingTop: 32, - paddingBottom: 28, - marginTop: 4, - }, - footerTextWrapper: { - flexDirection: 'row', - paddingHorizontal: 16, - marginTop: 12, - }, - footerTextContainer: { - flex: 1, - flexDirection: 'column', - }, - footerText: { - paddingVertical: 7, - fontSize: 13, - fontWeight: 300, - }, - platformsWrapper: { - flexDirection: 'row', - marginTop: 4, - justifyContent: 'center', - flexWrap: 'wrap', - gap: 14, - marginBottom: 28, - maxWidth: 960, - marginHorizontal: 'auto', - }, - bannerContainer: { - alignSelf: 'center', - }, - bannerContainerSmall: { - marginTop: 24, - alignItems: 'center', - }, - bannerLink: { - backgroundColor: 'transparent', - }, - logoContainer: { - flex: 1, - alignItems: 'center', - paddingTop: 48, - paddingBottom: 32, - }, -}); diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index da4dc4092..ce57fbab1 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -1,545 +1,553 @@ -import { type ViewStyle } from 'react-native'; +import { type StyleProp, type ViewStyle } from 'react-native'; import { Circle, Line, Path, Polyline, Rect, Svg } from 'react-native-svg'; -import { colors } from '~/common/styleguide'; +import tw from '~/util/tailwind'; export type IconProps = { - fill?: string; width?: number; height?: number; - style?: ViewStyle; + style?: StyleProp; }; -export function Search({ width, height, fill = colors.black }: IconProps) { +export function Search({ width, height, style }: IconProps) { return ( - + ); } -export function Star({ width, height, fill = colors.black }: IconProps) { +export function Star({ width, height, style }: IconProps) { return ( - + ); } -export function Web({ width, height, fill = colors.black }: IconProps) { +export function Web({ width, height, style }: IconProps) { return ( - + ); } -export function Arrow({ width, height, fill = colors.black }: IconProps) { +export function Arrow({ width, height, style }: IconProps) { return ( - + ); } -export function Badge({ width, height, fill = colors.black }: IconProps) { +export function Calendar({ width, height, style }: IconProps) { return ( - - - - - ); -} - -export function Calendar({ width, height, fill = colors.black }: IconProps) { - return ( - + ); } -export function Check({ width, height, fill = colors.black }: IconProps) { +export function Check({ width, height, style }: IconProps) { return ( - + ); } -export function Download({ width, height, fill = colors.black }: IconProps) { +export function Download({ width, height, style }: IconProps) { return ( - + ); } -export function Filter({ width, height, fill = colors.black }: IconProps) { +export function Filter({ width, height, style }: IconProps) { return ( - + ); } -export function Issue({ width, height, fill = colors.black }: IconProps) { +export function Issue({ width, height, style }: IconProps) { return ( - + ); } -export function Eye({ width = 22, height = 19, fill = colors.black }: IconProps) { +export function Eye({ width = 22, height = 19, style }: IconProps) { return ( - + ); } -export function Logo({ width, height, style, fill = colors.black }: IconProps) { +export function Logo({ width, height, style }: IconProps) { return ( - + ); } -export function GitHub({ width, height, fill = colors.black }: IconProps) { +export function GitHub({ width, height, style }: IconProps) { return ( - + ); } -export function Plus({ width, height, style, fill = colors.black }: IconProps) { +export function Plus({ width, height, style }: IconProps) { return ( - - - + + + ); } -export function XIcon({ width, height, fill = colors.black }: IconProps) { +export function XIcon({ width, height, style }: IconProps) { return ( - - + style={[style, tw`rotate-45`]}> + + ); } -export function Question({ width, height, fill = colors.black }: IconProps) { +export function Question({ width, height, style }: IconProps) { return ( - + ); } -export function Sort({ width, height, fill = colors.black }: IconProps) { +export function Sort({ width, height, style }: IconProps) { return ( - + ); } -export function Thumbnail({ width = 16, height = 16, fill = colors.gray3 }: IconProps) { +export function Thumbnail({ width = 16, height = 16, style }: IconProps) { return ( - - + + - + ); } -export function License({ width = 16, height = 18, fill = colors.black }: IconProps) { +export function License({ width = 16, height = 18, style }: IconProps) { return ( - + ); } -export function Fork({ width = 15, height = 16, fill = colors.black }: IconProps) { +export function Fork({ width = 15, height = 16, style }: IconProps) { return ( - + ); } -export function Code({ width = 16, height = 16, fill = colors.black }: IconProps) { +export function Code({ width = 16, height = 16, style }: IconProps) { return ( - - - - + + + + ); } -export function Warning({ width = 17, height = 17, fill = colors.warningDark }: IconProps) { +export function Warning({ width = 17, height = 17, style }: IconProps) { return ( - + ); } -export function TypeScript({ width = 16, height = 16, fill = colors.black }: IconProps) { +export function TypeScript({ width = 16, height = 16, style }: IconProps) { return ( - + ); } -export function PlatformTvOS({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function PlatformTvOS({ width = 18, height = 18, style }: IconProps) { return ( - - + + ); } -export function PlatformMacOS({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function PlatformMacOS({ width = 18, height = 18, style }: IconProps) { return ( - - + + ); } -export function PlatformIOS({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function PlatformIOS({ width = 18, height = 18, style }: IconProps) { return ( - + - - + + ); } -export function PlatformAndroid({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function PlatformAndroid({ width = 18, height = 18, style }: IconProps) { return ( - + ); } -export function PlatformWeb({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function PlatformWeb({ width = 18, height = 18, style }: IconProps) { return ( - - + + ); } -export function PlatformWindows({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function PlatformWindows({ width = 18, height = 18, style }: IconProps) { return ( - + ); } -export function PlatformExpo({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function PlatformExpo({ width = 18, height = 18, style }: IconProps) { return ( - + ); } -export function PlatformVisionOS({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function PlatformVisionOS({ width = 18, height = 18, style }: IconProps) { return ( - + ); } -export function ReactLogo({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function ReactLogo({ width = 18, height = 18, style }: IconProps) { return ( - + ); } -export function Info({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function Info({ width = 18, height = 18, style }: IconProps) { return ( - + - + ); } -export function NativeCode({ width = 18, height = 22, fill = colors.black }: IconProps) { +export function NativeCode({ width = 18, height = 22, style }: IconProps) { return ( - + ); } -export function ConfigPlugin({ width = 18, height = 22, fill = colors.black }: IconProps) { +export function ConfigPlugin({ width = 18, height = 22, style }: IconProps) { return ( - + ); } -export function PackageSize({ width = 18, height = 18, fill = colors.black }: IconProps) { +export function PackageSize({ width = 18, height = 18, style }: IconProps) { return ( - - + + ); } -export function Dependency({ width = 18, height = 20, fill = colors.black }: IconProps) { +export function Dependency({ width = 18, height = 20, style }: IconProps) { return ( - + ); } -export function ThemeLight({ width = 24, height = 24, fill = colors.black }: IconProps) { +export function ThemeLight({ width = 24, height = 24, style }: IconProps) { return ( - + ); } -export function ThemeDark({ width = 24, height = 24, fill = colors.black }: IconProps) { +export function ThemeDark({ width = 24, height = 24, style }: IconProps) { return ( - + ); } -export function Tools({ width = 24, height = 24, fill = colors.black }: IconProps) { +export function Tools({ width = 24, height = 24, style }: IconProps) { return ( - + ); } -export function ReadmeFile({ width = 24, height = 24, fill = colors.black }: IconProps) { +export function ReadmeFile({ width = 24, height = 24, style }: IconProps) { return ( - - + + ); } -export function CodeBrackets({ width = 24, height = 24, fill = colors.black }: IconProps) { +export function CodeBrackets({ width = 24, height = 24, style }: IconProps) { return ( - + + @@ -632,7 +641,7 @@ export function Copy({ width = 24, height = 24, fill = colors.black }: IconProps width="128" height="128" fill="none" - stroke={fill} + stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="16" @@ -641,13 +650,13 @@ export function Copy({ width = 24, height = 24, fill = colors.black }: IconProps ); } -export function Verified({ width = 24, height = 24, fill = colors.black }: IconProps) { +export function Verified({ width = 24, height = 24, style }: IconProps) { return ( - + + + - + + {libraries.map((item, index) => ( ))} ); } - -const styles = StyleSheet.create({ - librariesContainer: { - paddingTop: 12, - }, -}); diff --git a/components/Library/DirectoryScore.tsx b/components/Library/DirectoryScore.tsx index 6a379192f..46ec6a7ab 100644 --- a/components/Library/DirectoryScore.tsx +++ b/components/Library/DirectoryScore.tsx @@ -1,11 +1,9 @@ -import { useContext } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { type ColorValue, View } from 'react-native'; import { Svg, Path, Circle } from 'react-native-svg'; -import { colors, darkColors } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; import { type LibraryType } from '~/types'; import { MAX_SCORE } from '~/util/scoring'; +import tw from '~/util/tailwind'; import Tooltip from '../Tooltip'; @@ -16,16 +14,12 @@ type Props = { }; export function DirectoryScore({ score, matchingScoreModifiers, sizeMultiplier = 1 }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - const chunk = (MAX_SCORE - 5) / 5; function getFill(chunkNumber = 1) { return score >= chunk * chunkNumber - ? colors.primaryDark - : isDark - ? darkColors.pewter - : colors.gray3; + ? (tw`text-primary-dark`.color as ColorValue) + : 'currentColor'; } const scoreSymbol = ( @@ -33,11 +27,11 @@ export function DirectoryScore({ score, matchingScoreModifiers, sizeMultiplier = width={24 * sizeMultiplier} height={22 * sizeMultiplier} viewBox="0 0 22 20" - fill="none" + style={tw`text-palette-gray3 dark:text-pewter`} aria-label={`Score: ${score} out of 100`}> 0 ? colors.primaryDark : isDark ? darkColors.pewter : colors.gray3} + fill={score > 0 ? (tw`text-primary-dark`.color as ColorValue) : 'currentColor'} /> - + ); @@ -68,13 +67,13 @@ export function DirectoryScore({ score, matchingScoreModifiers, sizeMultiplier = } return ( - {scoreSymbol}
      }> + {scoreSymbol}}> Score:{' '} {score} / {MAX_SCORE}
      -
        +
          {matchingScoreModifiers.map((mod, index) => (
        • {mod}
        • ))} @@ -82,11 +81,3 @@ export function DirectoryScore({ score, matchingScoreModifiers, sizeMultiplier = ); } - -const styles = StyleSheet.create({ - scoreModsList: { - margin: 0, - paddingLeft: 14, - fontSize: 12, - }, -}); diff --git a/components/Library/LibraryDescription.tsx b/components/Library/LibraryDescription.tsx index 27f96c265..bd469b18f 100644 --- a/components/Library/LibraryDescription.tsx +++ b/components/Library/LibraryDescription.tsx @@ -1,11 +1,9 @@ import * as emoji from 'node-emoji'; -import { useContext } from 'react'; import { Linkify } from 'react-easy-linkify'; -import { StyleSheet } from 'react-native'; -import { A, Caption, colors, darkColors, Headline } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { A, Caption, Headline } from '~/common/styleguide'; import { type LibraryType } from '~/types'; +import tw from '~/util/tailwind'; type Props = { github: LibraryType['github']; @@ -13,10 +11,8 @@ type Props = { }; export default function LibraryDescription({ github, maxLines }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - return github.description && github.description.length > 0 ? ( - + {children}, @@ -25,15 +21,8 @@ export default function LibraryDescription({ github, maxLines }: Props) { ) : ( - + The package does not have a description defined. ); } - -const styles = StyleSheet.create({ - description: { - fontWeight: 300, - lineHeight: 23, - }, -}); diff --git a/components/Library/LoadingContent.tsx b/components/Library/LoadingContent.tsx index 9a8766699..47aa5b5a1 100644 --- a/components/Library/LoadingContent.tsx +++ b/components/Library/LoadingContent.tsx @@ -1,8 +1,8 @@ -import { type SVGAttributes, useContext } from 'react'; +import { type SVGAttributes } from 'react'; import ContentLoader from 'react-content-loader'; -import { colors, darkColors, useLayout } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { useLayout } from '~/common/styleguide'; +import tw from '~/util/tailwind'; type Props = { width?: string | number; @@ -11,22 +11,16 @@ type Props = { }; export default function LoadingContent({ width = '100%', height = 204, wrapperStyle = {} }: Props) { - const { isDark } = useContext(CustomAppearanceContext); const { isSmallScreen } = useLayout(); return ( diff --git a/components/Library/MetaData.tsx b/components/Library/MetaData.tsx index 4a07e7883..67b6eaac7 100644 --- a/components/Library/MetaData.tsx +++ b/components/Library/MetaData.tsx @@ -1,15 +1,14 @@ import { partition } from 'es-toolkit'; -import { useContext } from 'react'; -import { Platform, StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; -import { colors, A, P, Caption, darkColors } from '~/common/styleguide'; +import { A, P, Caption } from '~/common/styleguide'; import { FILTER_MODULE_TYPE } from '~/components/Filters/helpers'; import { ConfigPluginContent, getConfigPluginText } from '~/components/Library/ConfigPlugin'; import Tooltip from '~/components/Tooltip'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; import { type LibraryType, type MetadataEntryType } from '~/types'; import { formatBytes } from '~/util/formatBytes'; import { pluralize } from '~/util/strings'; +import tw from '~/util/tailwind'; import { DirectoryScore } from './DirectoryScore'; import { @@ -36,17 +35,22 @@ type Props = { skipExamples?: boolean; }; -function generateData( - { github, score, npm, npmPkg, matchingScoreModifiers, template }: LibraryType, - isDark: boolean -): MetadataEntryType[] { - const iconColor = isDark ? darkColors.pewter : colors.gray5; +const linkStyle = tw`text-[15px] font-light -outline-offset-1`; + +function generateData({ + github, + score, + npm, + npmPkg, + matchingScoreModifiers, + template, +}: LibraryType): MetadataEntryType[] { return [ { id: 'score', icon: , content: ( - + Directory Score ), @@ -54,12 +58,12 @@ function generateData( npm?.downloads ? { id: 'downloads', - icon: , + icon: , content: ( + style={linkStyle} + containerStyle={{ textOverflow: 'ellipsis' }}> {`${npm.downloads.toLocaleString()}`} monthly downloads ), @@ -67,9 +71,9 @@ function generateData( : null, { id: 'star', - icon: , + icon: , content: ( - + {github.stats.stars.toLocaleString()} {pluralize('star', github.stats.stars)} ), @@ -77,16 +81,16 @@ function generateData( github.stats?.dependencies !== undefined ? { id: 'dependencies', - icon: , + icon: , content: template ? ( -

          - {`${github.stats.dependencies} ${pluralize('dependency', github.stats.dependencies)}`} +

          + {`${github.stats.dependencies} ${pluralize('dependency', github.stats?.dependencies ?? 0)}`}

          ) : ( - {`${github.stats.dependencies} ${pluralize('dependency', github.stats.dependencies)}`} + style={linkStyle}> + {`${github.stats.dependencies} ${pluralize('dependency', github.stats?.dependencies ?? 0)}`} ), } @@ -94,9 +98,9 @@ function generateData( npm?.size ? { id: 'size', - icon: , + icon: , content: ( - + {`${formatBytes(npm.size)} package size`} ), @@ -105,9 +109,9 @@ function generateData( github.stats.forks ? { id: 'forks', - icon: , + icon: , content: ( - + {`${github.stats.forks.toLocaleString()}`} ), @@ -117,9 +121,9 @@ function generateData( github.stats.subscribers ? { id: 'subscribers', - icon: , + icon: , content: ( - + {`${github.stats.subscribers.toLocaleString()}`} ), @@ -129,9 +133,9 @@ function generateData( github.stats.issues ? { id: 'issues', - icon: , + icon: , content: ( - + {`${github.stats.issues.toLocaleString()}`} ), @@ -141,32 +145,25 @@ function generateData( ]; } -function generateSecondaryData( - library: LibraryType, - isDark: boolean, - skipExamples: boolean -): MetadataEntryType[] { +function generateSecondaryData(library: LibraryType, skipExamples: boolean): MetadataEntryType[] { const { github, examples, nightlyProgram } = library; const configPlugin = library.configPlugin ?? library.github.configPlugin; - const secondaryTextColor = { - color: isDark ? darkColors.secondary : colors.gray5, - }; - const iconColor = isDark ? darkColors.pewter : skipExamples ? colors.gray5 : colors.secondary; - const paragraphStyles = [styles.secondaryText, !skipExamples && secondaryTextColor]; - const linkStyles = [...paragraphStyles, !skipExamples && styles.mutedLink]; - const hoverStyle = { - textDecorationColor: colors.gray4, - color: isDark ? colors.gray3 : colors.gray5, - }; + const iconColor = [ + tw`text-tertiary`, + skipExamples && tw`text-palette-gray5`, + tw`dark:text-pewter`, + ]; + const paragraphStyles = [tw`text-[13px] font-light`, !skipExamples && tw`text-secondary`]; + const hoverStyle = tw`text-hover decoration-palette-gray4`; return [ github.urls.homepage ? { id: 'web', - icon: , + icon: , content: ( - + Website ), @@ -175,12 +172,12 @@ function generateSecondaryData( github.license ? { id: 'license', - icon: , + icon: , content: github.license.name === 'Other' ? (

          Unrecognized License

          ) : ( - + {github.license.name} ), @@ -189,19 +186,19 @@ function generateSecondaryData( github.hasNativeCode ? { id: 'nativeCode', - icon: , + icon: , content:

          Native code

          , } : null, configPlugin ? { id: 'configPlugin', - icon: , + icon: , content: ( ), @@ -211,11 +208,11 @@ function generateSecondaryData( nightlyProgram ? { id: 'nightlyProgram', - icon: , + icon: , content: ( Nightly Program @@ -226,7 +223,7 @@ function generateSecondaryData( skipExamples && library.github.moduleType ? { id: 'moduleType', - icon: , + icon: , content: (

          { @@ -241,14 +238,14 @@ function generateSecondaryData( github.hasTypes ? { id: 'types', - icon: , + icon: , content:

          TypeScript Types

          , } : null, !skipExamples && examples && examples.length ? { id: 'examples', - icon: , + icon: , content: ( <> Examples: @@ -256,7 +253,7 @@ function generateSecondaryData( #{index + 1} @@ -269,10 +266,8 @@ function generateSecondaryData( } export default function MetaData({ library, secondary, skipExamples = false }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - if (secondary) { - const data = generateSecondaryData(library, isDark, skipExamples).filter(Boolean); + const data = generateSecondaryData(library, skipExamples).filter(Boolean); return ( <> {data @@ -283,12 +278,11 @@ export default function MetaData({ library, secondary, skipExamples = false }: P key={id} // @ts-expect-error RNW complains about 'fit-content' style={{ - ...styles.displayHorizontal, - ...(i + 1 !== data.length ? styles.datumContainer : {}), - ...styles.secondaryContainer, + ...(i + 1 !== data.length ? tw`mb-2 min-h-[22px] overflow-hidden` : {}), + ...tw`flex-row items-center mb-0 pr-[3px]`, width: 'fit-content', }}> - {icon} + {icon} {content} ); @@ -304,7 +298,7 @@ export default function MetaData({ library, secondary, skipExamples = false }: P ); } else { - const data = generateData(library, isDark).filter(entry => !!entry); + const data = generateData(library).filter(entry => !!entry); const [bottomData, listData] = partition>(data, ({ id }) => { return ['forks', 'subscribers', 'issues'].includes(id); }); @@ -313,19 +307,22 @@ export default function MetaData({ library, secondary, skipExamples = false }: P {listData.map(({ id, icon, content }, i) => ( - {icon} + style={[ + tw`flex-row items-center`, + i + 1 !== data.length && tw`mb-2 min-h-[22px] overflow-hidden`, + ]}> + {icon} {content} ))} - + {bottomData.map(({ id, icon, content, tooltip }) => ( - + {icon}}> + trigger={{icon}}> {tooltip} {content} @@ -336,55 +333,3 @@ export default function MetaData({ library, secondary, skipExamples = false }: P ); } } - -const styles = StyleSheet.create({ - datumContainer: { - marginBottom: 8, - minHeight: 22, - overflow: 'hidden', - }, - displayHorizontal: { - flexDirection: 'row', - alignItems: 'center', - }, - bottomStats: { - gap: 16, - }, - iconContainer: { - marginRight: 7, - width: 22, - alignItems: 'center', - }, - linkContainer: { - display: 'contents', - ...Platform.select({ - web: { - textOverflow: 'ellipsis', - }, - }), - }, - link: { - fontSize: 15, - fontWeight: 300, - outlineOffset: -1, - }, - mutedLink: { - backgroundColor: 'transparent', - }, - secondaryText: { - fontSize: 13, - fontWeight: 300, - }, - secondaryContainer: { - marginBottom: 0, - paddingRight: 3, - }, - secondaryIconContainer: { - marginRight: 4, - }, - exampleLink: { - marginLeft: 2, - marginRight: 4, - fontSize: 13, - }, -}); diff --git a/components/Library/NewArchitectureTag.tsx b/components/Library/NewArchitectureTag.tsx index f5a833069..ec79a8019 100644 --- a/components/Library/NewArchitectureTag.tsx +++ b/components/Library/NewArchitectureTag.tsx @@ -1,10 +1,10 @@ -import { useContext } from 'react'; -import { View, StyleSheet } from 'react-native'; +import { View } from 'react-native'; -import { A, colors, darkColors, Label } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { A, Label } from '~/common/styleguide'; import { type LibraryType } from '~/types'; import { getNewArchSupportStatus, NewArchSupportStatus } from '~/util/newArchStatus'; +import { pluralize } from '~/util/strings'; +import tw from '~/util/tailwind'; import { Check, Question, XIcon } from '../Icons'; import { Tag } from '../Tag'; @@ -15,30 +15,22 @@ type Props = { }; export function NewArchitectureTag({ library }: Props) { - const { isDark } = useContext(CustomAppearanceContext); const status = getNewArchSupportStatus(library); - - const icon = - status === NewArchSupportStatus.Unsupported ? ( - - ) : status === NewArchSupportStatus.Supported || status === NewArchSupportStatus.NewArchOnly ? ( - - ) : ( - - ); + const icon = getTagIcon(status); const newArchitectureNote = library.newArchitectureNote && library.newArchitectureNote && ( - + ); - // Do not show alternatives in new arch tag for unmaintained libraries since + // Do not show alternatives in New Arch tag for unmaintained libraries since // we already show the alternatives in unmaintained label const alternatives = library.alternatives && library.alternatives.length > 0 && !library.unmaintained && ( - @@ -75,43 +67,26 @@ export function NewArchitectureTag({ library }: Props) { ); } -function getIconColor(status: NewArchSupportStatus, isDark: boolean) { +function getTagColor(status: NewArchSupportStatus) { switch (status) { case NewArchSupportStatus.NewArchOnly: case NewArchSupportStatus.Supported: - return colors.primaryDark; + return tw`bg-[#edf6fc] border-[#d4ebfa] dark:bg-[#142733] dark:border-[#203b4d]`; case NewArchSupportStatus.Unsupported: - return isDark ? darkColors.warning : colors.warningDark; + return tw`bg-[#fffae8] border-[#faebaf] dark:bg-[#292005] dark:border-[#3d3206]`; default: - return colors.gray4; + return tw`border-dashed border-palette-gray2 dark:border-default`; } } -function getTagColor(status: NewArchSupportStatus, isDark: boolean) { +function getTagIcon(status: NewArchSupportStatus) { switch (status) { case NewArchSupportStatus.NewArchOnly: case NewArchSupportStatus.Supported: - return { - backgroundColor: isDark ? '#142733' : '#edf6fc', - borderColor: isDark ? '#203b4d' : '#d4ebfa', - }; + return ; case NewArchSupportStatus.Unsupported: - return { - backgroundColor: isDark ? '#292005' : '#fffae8', - borderColor: isDark ? '#3d3206' : '#faebaf', - }; + return ; default: - return { - borderColor: isDark ? darkColors.border : colors.gray2, - borderStyle: 'dashed' as const, - }; + return ; } } - -const styles = StyleSheet.create({ - note: { - display: 'flex', - marginVertical: 4, - color: '#fff', - }, -}); diff --git a/components/Library/Thumbnail.tsx b/components/Library/Thumbnail.tsx index ac164528b..22280a172 100644 --- a/components/Library/Thumbnail.tsx +++ b/components/Library/Thumbnail.tsx @@ -1,9 +1,8 @@ import * as HoverCard from '@radix-ui/react-hover-card'; -import { useContext, memo, useState } from 'react'; -import { ActivityIndicator, useWindowDimensions } from 'react-native'; +import { memo, useState } from 'react'; +import { ActivityIndicator, type ColorValue, useWindowDimensions, View } from 'react-native'; -import { colors, darkColors } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import tw from '~/util/tailwind'; import { Thumbnail as ThumbnailIcon } from '../Icons'; @@ -14,80 +13,58 @@ type Props = { const GITHUB_PREVIEW_MIN_WIDTH = 640; function Thumbnail({ url }: Props) { - const { isDark } = useContext(CustomAppearanceContext); const { width, height } = useWindowDimensions(); + const [isLoaded, setLoaded] = useState(false); + const [showPreview, setShowPreview] = useState(false); + const maxPreviewWidth = width < GITHUB_PREVIEW_MIN_WIDTH ? width : GITHUB_PREVIEW_MIN_WIDTH; const maxPreviewImageWidth = maxPreviewWidth - 20; const maxPreviewHeight = height / 2; const maxImgPreviewHeight = maxPreviewHeight - 20; - const [isLoaded, setLoaded] = useState(false); - const [showPreview, setShowPreview] = useState(false); - - const iconFill = isDark - ? showPreview - ? colors.primaryDark - : darkColors.pewter - : showPreview - ? colors.primaryDark - : undefined; - return ( setShowPreview(open)}> -
          + style={tw`mr-2.5 my-1 p-1.5 pb-0 min-h-7.5 box-border overflow-hidden text-center rounded border border-palette-gray2 dark:border-default`}> {showPreview && !isLoaded ? ( -
          - +
          +
          ) : ( - + )} -
          + -
          +
          +
          diff --git a/components/Library/TrendingMark.tsx b/components/Library/TrendingMark.tsx index 546c20c74..ea2aceda4 100644 --- a/components/Library/TrendingMark.tsx +++ b/components/Library/TrendingMark.tsx @@ -1,10 +1,9 @@ -import { useContext } from 'react'; -import { StyleSheet, View, type ViewStyle } from 'react-native'; +import { View, type ViewStyle } from 'react-native'; -import { colors, darkColors, P, A } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { P, A, HoverEffect } from '~/common/styleguide'; import { type LibraryType } from '~/types'; import { getPopularityGrade } from '~/util/scoring'; +import tw from '~/util/tailwind'; type Props = { library: LibraryType | { popularity: number }; @@ -13,28 +12,30 @@ type Props = { }; export default function TrendingMark({ library, style, markOnly = false }: Props) { - const { isDark } = useContext(CustomAppearanceContext); const { popularity = -100 } = library; - const popularityStyles = getPopularityStyles(popularity, markOnly); - const markBackgroundColor = isDark ? darkColors.border : colors.gray2; + const popularityStyles = getPopularityStyles(popularity); const content = ( <> + -

          {getPopularityGrade(popularity)} @@ -44,70 +45,26 @@ export default function TrendingMark({ library, style, markOnly = false }: Props ); return markOnly ? ( - {content} + {content} ) : ( - - {content} - + + + {content} + + ); } -function getPopularityStyles(popularity: number, markOnly: boolean) { - const top = markOnly ? 11 : 7; +function getPopularityStyles(popularity: number) { if (popularity > 0.5) { - return { - width: 32, - backgroundColor: '#fb0d9e', - top, - }; + return tw`w-8 bg-[#fb0d9e]`; } else if (popularity > 0.25) { - return { - width: 24, - backgroundColor: '#e70a2f', - top, - }; + return tw`w-6 bg-[#e70a2f]`; } else if (popularity > 0.1) { - return { - width: 18, - backgroundColor: '#ff5900', - top, - }; + return tw`w-4.5 bg-[#ff5900]`; } else if (popularity > 0) { - return { - width: 12, - backgroundColor: '#dc9a00', - top, - }; + return tw`w-3 bg-[#dc9a00]`; } else { - return { - width: 6, - backgroundColor: colors.gray4, - top, - }; + return tw`w-1.5 bg-palette-gray4`; } } - -const styles = StyleSheet.create({ - container: { - marginBottom: 4, - }, - popularityMark: { - height: 6, - position: 'absolute', - borderRadius: 4, - }, - popularityMarkBackground: { - width: 32, - }, - popularityScore: { - paddingLeft: 40, - fontWeight: 700, - }, - scoringLink: { - textDecorationLine: 'none', - position: 'relative', - backgroundColor: 'none', - display: 'flex', - alignItems: 'flex-start', - }, -}); diff --git a/components/Library/UnmaintainedLabel.tsx b/components/Library/UnmaintainedLabel.tsx index 16a1b461a..36321f376 100644 --- a/components/Library/UnmaintainedLabel.tsx +++ b/components/Library/UnmaintainedLabel.tsx @@ -1,9 +1,9 @@ -import { Fragment, useContext } from 'react'; -import { type StyleProp, StyleSheet, type TextStyle, View } from 'react-native'; +import { Fragment } from 'react'; +import { View } from 'react-native'; -import { A, colors, darkColors, Label, useLayout } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { A, Label, useLayout } from '~/common/styleguide'; import { type LibraryDataEntryType } from '~/types'; +import tw from '~/util/tailwind'; import { Warning } from '../Icons'; @@ -13,49 +13,29 @@ type Props = { }; export default function UnmaintainedLabel({ alternatives, block }: Props) { - const { isDark } = useContext(CustomAppearanceContext); const { isSmallScreen } = useLayout(); - const linkHoverStyle: StyleProp = isDark && { color: colors.secondary }; - const contentColor = isDark ? darkColors.secondary : colors.gray5; - return ( - + - - - + + + {alternatives && alternatives.length > 0 && ( - ); } - -const styles = StyleSheet.create({ - unmaintainedTextWrapper: { - flexDirection: 'row', - gap: 6, - flexShrink: 1, - }, - unmaintainedTextContainer: { - alignItems: 'flex-start', - marginLeft: -20, - marginBottom: 8, - paddingLeft: 20, - paddingRight: 12, - paddingVertical: 6, - borderTopRightRadius: 4, - borderBottomRightRadius: 4, - gap: 4, - borderWidth: 1, - borderLeftWidth: 0, - flexShrink: 1, - flexWrap: 'wrap', - }, - unmaintainedTextContainerBlock: { - marginLeft: 0, - borderLeftWidth: 1, - paddingLeft: 12, - paddingVertical: 8, - borderRadius: 8, - borderTopRightRadius: 8, - borderBottomRightRadius: 8, - }, -}); diff --git a/components/Library/UpdateAtView.tsx b/components/Library/UpdateAtView.tsx index 00772dc8f..93fbfa366 100644 --- a/components/Library/UpdateAtView.tsx +++ b/components/Library/UpdateAtView.tsx @@ -1,12 +1,11 @@ -import { useContext } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; -import { A, colors, darkColors } from '~/common/styleguide'; +import { A } from '~/common/styleguide'; import Tooltip from '~/components/Tooltip'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; import { type LibraryType } from '~/types'; import { getTimeSinceToday } from '~/util/datetime'; import { parseGitHubUrl } from '~/util/parseGitHubUrl'; +import tw from '~/util/tailwind'; import { Calendar } from '../Icons'; @@ -15,15 +14,8 @@ type Props = { }; export default function UpdatedAtView({ library }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - - const iconColor = isDark ? darkColors.pewter : colors.secondary; - const textColor = isDark ? darkColors.secondary : colors.gray5; - const decorationColor = isDark ? colors.gray5 : colors.gray3; - const unmaintainedIconColor = isDark ? darkColors.warning : colors.warningDark; - const unmaintainedTextColor = isDark ? darkColors.warning : colors.warningDark; - const { branchName, packagePath } = parseGitHubUrl(library.githubUrl); + const repoUrl = library.github.urls.repo; const tooltipContent = ` Last update (based on Git activity) @@ -37,10 +29,14 @@ export default function UpdatedAtView({ library }: Props) { + @@ -48,24 +44,18 @@ export default function UpdatedAtView({ library }: Props) { + hoverStyle={ + library.unmaintained + ? tw`text-warning-dark dark:text-warning decoration-warning-dark dark:decoration-warning` + : tw`decoration-palette-gray4 text-hover` + }> {getTimeSinceToday(library.github.stats.pushedAt)} @@ -74,17 +64,3 @@ export default function UpdatedAtView({ library }: Props) { ); } - -const styles = StyleSheet.create({ - updatedAtContainer: { - gap: 8, - flexDirection: 'row', - alignItems: 'flex-start', - }, - link: { - fontSize: 13, - fontWeight: 300, - backgroundColor: 'transparent', - textDecorationColor: 'transparent', - }, -}); diff --git a/components/Library/index.tsx b/components/Library/index.tsx index ac3f6175d..a0e9f9f5a 100644 --- a/components/Library/index.tsx +++ b/components/Library/index.tsx @@ -1,12 +1,11 @@ -import { useContext } from 'react'; -import { Platform, StyleSheet, View } from 'react-native'; +import { Platform, View } from 'react-native'; -import { colors, useLayout, A, darkColors, HoverEffect } from '~/common/styleguide'; +import { useLayout, A, HoverEffect } from '~/common/styleguide'; import { GitHub } from '~/components/Icons'; import LibraryDescription from '~/components/Library/LibraryDescription'; import UpdatedAtView from '~/components/Library/UpdateAtView'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; import { type LibraryType } from '~/types'; +import tw from '~/util/tailwind'; import MetaData from './MetaData'; import Thumbnail from './Thumbnail'; @@ -22,7 +21,6 @@ type Props = { }; export default function Library({ library, skipMetadata, showTrendingMark }: Props) { - const { isDark } = useContext(CustomAppearanceContext); const { github } = library; const { isSmallScreen, isBelowMaxWidth } = useLayout(); @@ -37,38 +35,28 @@ export default function Library({ library, skipMetadata, showTrendingMark }: Pro return ( - + {library.unmaintained && ( )} {showTrendingMark && library.popularity && ( - + }> Trending Score is based on the last week to last month download rate. @@ -78,40 +66,44 @@ export default function Library({ library, skipMetadata, showTrendingMark }: Pro - + + style={tw`font-bold text-[19px]`} + hoverStyle={tw`text-hover`}> {libName} - - + + {!showTrendingMark && !library.unmaintained && } - + - + - {!skipMetadata && Platform.OS === 'web' && library.images && library.images.length ? ( - + {!skipMetadata && Platform.OS === 'web' && library.images && library.images.length > 0 && ( + {library.images.map((image, index) => ( ))} - ) : null} + )} {hasSecondaryMetadata ? ( - - + + @@ -120,16 +112,8 @@ export default function Library({ library, skipMetadata, showTrendingMark }: Pro {skipMetadata ? null : ( @@ -137,154 +121,3 @@ export default function Library({ library, skipMetadata, showTrendingMark }: Pro ); } - -const styles = StyleSheet.create({ - container: { - marginBottom: 16, - borderWidth: 1, - borderRadius: 6, - flexDirection: 'row', - overflow: 'hidden', - }, - containerColumn: { - flexDirection: 'column', - }, - columnDesktop: { - paddingBottom: 14, - }, - columnOne: { - ...Platform.select({ - web: { - flex: 1, - }, - }), - padding: 16, - paddingLeft: 20, - }, - columnTwo: { - ...Platform.select({ - web: { - flex: 0.35, - }, - }), - padding: 16, - borderLeftWidth: 1, - }, - columnTwoSmall: { - borderLeftWidth: 0, - borderTopWidth: 1, - }, - nameWrapper: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - }, - name: { - backgroundColor: 'transparent', - fontWeight: 700, - fontSize: 19, - textDecorationLine: 'none', - }, - displayHorizontal: { - flexDirection: 'row', - alignItems: 'center', - rowGap: 2, - }, - exampleLink: { - marginRight: 6, - }, - recommendedContainer: { - paddingVertical: 4, - paddingHorizontal: 8, - borderRadius: 2, - marginLeft: 10, - top: 1, - }, - recommendedContainerSmall: { - marginLeft: 0, - marginTop: 8, - alignSelf: 'flex-start', - }, - recommendedTextContainer: { - flexDirection: 'row', - alignItems: 'center', - }, - recommendedText: { - marginLeft: 6, - }, - verticalMargin: { - marginTop: 12, - }, - imagesContainer: { - flexWrap: 'wrap', - marginTop: 8, - }, - secondaryStats: { - marginTop: 12, - flexWrap: 'wrap', - gap: 10, - }, - secondaryText: { - fontSize: 13, - color: colors.gray5, - }, - bottomBar: { - width: '100%', - marginTop: 'auto', - }, - bottomBarSmall: { - position: 'relative', - minHeight: 'auto', - paddingLeft: 0, - paddingRight: 0, - marginTop: 6, - marginBottom: -4, - }, - filler: { - flex: 1, - paddingBottom: 34, - }, - noMetaContainer: { - width: '48.5%', - marginHorizontal: '0.75%', - minHeight: 206, - }, - noMetaColumnContainer: { - maxHeight: 'auto', - width: '98.5%', - maxWidth: '98.5%', - }, - unmaintained: { - opacity: 0.88, - }, - trendingMarkContainer: { - justifyContent: 'space-between', - marginBottom: 4, - }, - link: { - fontSize: 13, - fontWeight: 300, - backgroundColor: 'transparent', - }, - updatedAtContainer: { - gap: 24, - flexDirection: 'row', - alignItems: 'flex-start', - justifyContent: 'space-between', - }, - updatedAtContainerSmall: { - gap: 8, - justifyContent: 'flex-start', - alignSelf: 'flex-start', - }, - detailsButton: { - display: 'flex', - width: 16, - height: 16, - alignItems: 'center', - }, - githubButton: { - width: 20, - height: 20, - }, -}); diff --git a/components/Navigation.tsx b/components/Navigation.tsx index 2bc5119d2..97717778f 100644 --- a/components/Navigation.tsx +++ b/components/Navigation.tsx @@ -1,10 +1,10 @@ -import { type PropsWithChildren, type ReactElement, useContext } from 'react'; -import { StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native'; +import { type PropsWithChildren, type ReactElement } from 'react'; +import { type StyleProp, View, type ViewStyle } from 'react-native'; -import { colors, darkColors, H1, H2, useLayout } from '~/common/styleguide'; +import { H1, H2, useLayout } from '~/common/styleguide'; import { Logo } from '~/components/Icons'; import TopBar from '~/components/TopBar'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import tw from '~/util/tailwind'; type NavigationProps = PropsWithChildren<{ title?: string; @@ -20,7 +20,6 @@ export default function Navigation({ header, style, }: NavigationProps) { - const { isDark } = useContext(CustomAppearanceContext); const { isSmallScreen } = useLayout(); return ( @@ -29,55 +28,26 @@ export default function Navigation({ {header ? ( header ) : ( - + -

          {title}

          -

          {description}

          +

          + {title} +

          +

          + {description} +

          {children}
          )} ); } - -const styles = StyleSheet.create({ - headerWrapper: { - paddingVertical: 40, - overflow: 'hidden', - }, - header: { - textAlign: 'center', - color: colors.white, - fontSize: 42, - paddingHorizontal: 20, - }, - headerSmall: { - fontSize: 32, - }, - headerDescription: { - textAlign: 'center', - color: colors.pewter, - fontWeight: '500', - fontSize: 16, - paddingTop: 4, - paddingBottom: 6, - paddingHorizontal: 40, - }, - logoBackground: { - position: 'absolute', - left: '50%', - top: -76, - marginLeft: -280, - opacity: 0.15, - }, -}); diff --git a/components/NavigationTab.tsx b/components/NavigationTab.tsx index 04bd42cee..5839cad0b 100644 --- a/components/NavigationTab.tsx +++ b/components/NavigationTab.tsx @@ -1,10 +1,9 @@ import { useRouter } from 'next/router'; -import { useContext } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; -import { A, colors, darkColors, P } from '~/common/styleguide'; +import { A, P } from '~/common/styleguide'; import EntityCounter from '~/components/Package/EntityCounter'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import tw from '~/util/tailwind'; type Props = { title: string; @@ -13,43 +12,30 @@ type Props = { }; function NavigationTab({ title, counter, path = `/${title.toLowerCase()}` }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - const router = useRouter(); const isActive = router.asPath.split('?')[0] === path; return ( - -

          {title}

          + +

          {title}

          {!!counter && ( )}
          @@ -57,40 +43,4 @@ function NavigationTab({ title, counter, path = `/${title.toLowerCase()}` }: Pro ); } -const styles = StyleSheet.create({ - tabLink: { - backgroundColor: 'transparent', - textDecorationLine: 'none', - color: colors.white, - borderRadius: 4, - // @ts-expect-error Transition is a valid web style property - transition: 'color 0.33s, background-color 0.33s', - }, - tabContainer: { - flexDirection: 'row', - alignItems: 'center', - gap: 8, - paddingHorizontal: 16, - paddingTop: 6, - paddingBottom: 8, - }, - tabActive: { - backgroundColor: darkColors.powder, - }, - tabTitle: { - color: 'inherit', - }, - tabActiveTitle: { - fontWeight: 500, - }, - tabCounter: { - color: 'inherit', - marginTop: 3, - fontSize: 11, - borderRadius: 12, - paddingVertical: 2, - paddingHorizontal: 6, - }, -}); - export default NavigationTab; diff --git a/components/NotFoundContent.tsx b/components/NotFoundContent.tsx index 5c657ecdb..262840581 100644 --- a/components/NotFoundContent.tsx +++ b/components/NotFoundContent.tsx @@ -1,7 +1,8 @@ import { type ReactElement } from 'react'; -import { Image, StyleSheet, View } from 'react-native'; +import { Image, View } from 'react-native'; import { A, H3, P } from '~/common/styleguide'; +import tw from '~/util/tailwind'; type Props = { header?: string; @@ -15,11 +16,14 @@ export default function NotFoundContent({ bottomSlot, }: Props) { return ( - - {alt} -

          {header}

          - -

          + + {alt} +

          {header}

          +

          Want to contribute a library you like? Submit a PR to the{' '} GitHub Repo.

          @@ -27,25 +31,3 @@ export default function NotFoundContent({
          ); } - -const styles = StyleSheet.create({ - container: { - alignItems: 'center', - width: '100%', - paddingHorizontal: 24, - marginTop: 40, - marginBottom: 80, - }, - img: { - marginTop: 48, - marginBottom: 24, - width: 64, - height: 64, - }, - text: { - textAlign: 'center', - }, - separator: { - marginTop: 20, - }, -}); diff --git a/components/Package/DependencyRow.tsx b/components/Package/DependencyRow.tsx index cdccc1d1c..c6146f7b9 100644 --- a/components/Package/DependencyRow.tsx +++ b/components/Package/DependencyRow.tsx @@ -1,8 +1,8 @@ -import { type ReactNode, useContext } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { type ReactNode } from 'react'; +import { View } from 'react-native'; -import { A, colors, darkColors, Label } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { A, Label } from '~/common/styleguide'; +import tw from '~/util/tailwind'; type Props = { name: string; @@ -10,24 +10,19 @@ type Props = { }; export default function DependencyRow({ name, version }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - const versionLabel = getVersionLabel(version); const hasLongVersion = typeof versionLabel === 'string' && versionLabel.length > 26; return ( - + + containerStyle={tw`flex-shrink`} + style={tw`text-xs leading-[14px] font-mono font-light`}> {name} - @@ -37,7 +32,7 @@ export default function DependencyRow({ name, version }: Props) { function getVersionLabel(version: string): ReactNode { if (version.startsWith('http')) { return ( - + URL ); @@ -55,26 +50,3 @@ function extractPatchedVersion(entry: string): string | null { const match = entry.match(/@npm%3A([^#]+)/); return match ? decodeURIComponent(match[1]) : null; } - -const styles = StyleSheet.create({ - linkContainer: { - flexShrink: 1, - }, - mutedLink: { - fontWeight: 300, - backgroundColor: 'transparent', - }, - dependencyEntry: { - display: 'flex', - flexDirection: 'row', - columnGap: 8, - justifyContent: 'space-between', - fontFamily: 'monospace', - }, - withLongVersion: { - flexWrap: 'wrap', - }, - dependencyLabel: { - fontSize: 12, - }, -}); diff --git a/components/Package/DetailsNavigation.tsx b/components/Package/DetailsNavigation.tsx index 0be3b7405..4d84f8f75 100644 --- a/components/Package/DetailsNavigation.tsx +++ b/components/Package/DetailsNavigation.tsx @@ -1,10 +1,9 @@ -import { StyleSheet } from 'react-native'; - import { useLayout } from '~/common/styleguide'; import ContentContainer from '~/components/ContentContainer'; import Navigation from '~/components/Navigation'; import NavigationTab from '~/components/NavigationTab'; import { type LibraryType } from '~/types'; +import tw from '~/util/tailwind'; type Props = { library: LibraryType; @@ -16,8 +15,8 @@ export default function DetailsNavigation({ library }: Props) { return ( - + style={[tw`pt-9 pb-3 gap-1`, isSmallScreen && tw`pt-5 gap-1.5`]}> + ); } - -const styles = StyleSheet.create({ - tabsNav: { - paddingBottom: 12, - paddingTop: 36, - gap: 4, - }, - tabsNavSmall: { - paddingTop: 20, - gap: 6, - }, - tabsContainer: { - gap: 8, - flexDirection: 'row', - paddingHorizontal: 20, - }, -}); diff --git a/components/Package/EntityCounter.tsx b/components/Package/EntityCounter.tsx index cea2981d4..20b78e466 100644 --- a/components/Package/EntityCounter.tsx +++ b/components/Package/EntityCounter.tsx @@ -1,8 +1,7 @@ -import { useContext } from 'react'; -import { type StyleProp, StyleSheet, type TextStyle } from 'react-native'; +import { type StyleProp, type TextStyle } from 'react-native'; -import { colors, darkColors, Label } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { Label } from '~/common/styleguide'; +import tw from '~/util/tailwind'; type Props = { count: number; @@ -10,26 +9,13 @@ type Props = { }; export default function EntityCounter({ count, style }: Props) { - const { isDark } = useContext(CustomAppearanceContext); return ( ); } - -const styles = StyleSheet.create({ - tabCounter: { - color: 'inherit', - marginTop: 3, - fontSize: 11, - borderRadius: 12, - paddingVertical: 2, - paddingHorizontal: 6, - }, -}); diff --git a/components/Package/ExampleBox.tsx b/components/Package/ExampleBox.tsx index 53138619a..a77ea4d15 100644 --- a/components/Package/ExampleBox.tsx +++ b/components/Package/ExampleBox.tsx @@ -1,10 +1,9 @@ import { LI } from '@expo/html-elements'; -import { useContext } from 'react'; -import { StyleSheet, Text, View } from 'react-native'; +import { Text, View } from 'react-native'; -import { A, colors, darkColors, useLayout } from '~/common/styleguide'; +import { A, useLayout } from '~/common/styleguide'; import { CodeBrackets, GitHub, Snack } from '~/components/Icons'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import tw from '~/util/tailwind'; type Props = { example: string; @@ -12,44 +11,24 @@ type Props = { }; export default function ExampleBox({ example, index }: Props) { - const { isDark } = useContext(CustomAppearanceContext); const { isSmallScreen } = useLayout(); return (
        • - - {example.includes('github.com') && ( - - )} - {example.includes('snack.expo.dev') && ( - - )} + style={tw`flex flex-row items-center justify-between px-4 py-2.5 rounded-lg no-underline border border-palette-gray2 dark:border-default`} + hoverStyle={tw`bg-palette-gray1 dark:bg-dark`}> + + {example.includes('github.com') && } + {example.includes('snack.expo.dev') && } {!example.includes('github.com') && !example.includes('snack.expo.dev') && ( - + )} - - {getExampleDescription(example)} - + {getExampleDescription(example)} + style={[tw`opacity-30 text-2xl leading-[28px] text-icon`, isSmallScreen && tw`hidden`]}> #{index + 1} @@ -69,34 +48,3 @@ export function getExampleDescription(url: string) { } return 'Code example'; } - -const styles = StyleSheet.create({ - exampleBox: { - display: 'flex', - flex: 1, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 16, - paddingVertical: 10, - borderRadius: 12, - borderWidth: 1, - borderStyle: 'solid', - textDecorationLine: 'none', - }, - exampleLabelWrapper: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - fontWeight: 400, - maxWidth: '100%', - gap: 10, - }, - exampleLabel: { - fontWeight: 300, - }, - exampleIndex: { - opacity: 0.33, - fontSize: 24, - }, -}); diff --git a/components/Package/NotFound.tsx b/components/Package/NotFound.tsx index f214d7298..5fc9aa2b2 100644 --- a/components/Package/NotFound.tsx +++ b/components/Package/NotFound.tsx @@ -1,20 +1,21 @@ -import { StyleSheet } from 'react-native'; - import { Button } from '~/components/Button'; import ContentContainer from '~/components/ContentContainer'; import Navigation from '~/components/Navigation'; import NotFoundContent from '~/components/NotFoundContent'; +import PageMeta from '~/components/PageMeta'; +import tw from '~/util/tailwind'; export default function NotFound() { return ( <> - + + } /> + } @@ -23,12 +24,3 @@ export default function NotFound() { ); } - -const styles = StyleSheet.create({ - homeButton: { - color: 'inherit', - marginVertical: 20, - paddingHorizontal: 12, - paddingVertical: 6, - }, -}); diff --git a/components/Package/PackageAuthor.tsx b/components/Package/PackageAuthor.tsx index ae697efc7..e43e239ec 100644 --- a/components/Package/PackageAuthor.tsx +++ b/components/Package/PackageAuthor.tsx @@ -1,21 +1,22 @@ import SHA256 from 'crypto-js/sha256'; -import { useContext } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; -import { A, colors, darkColors, Label } from '~/common/styleguide'; +import { A, Caption, Label } from '~/common/styleguide'; import UserAvatar from '~/components/Package/UserAvatar'; import Tooltip from '~/components/Tooltip'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; import { type NpmUser } from '~/types'; +import tw from '~/util/tailwind'; type Props = { author?: NpmUser; compact?: boolean; }; -export default function PackageAuthor({ author, compact }: Props) { - const { isDark } = useContext(CustomAppearanceContext); +const authorContainerStyle = tw`flex flex-row gap-3 items-center bg-transparent`; +const labelStyle = tw`leading-[18px]`; +const sublabelStyle = tw`text-[11px] font-light text-palette-gray4 dark:text-secondary`; +export default function PackageAuthor({ author, compact }: Props) { if (!author) { return ( @@ -24,8 +25,6 @@ export default function PackageAuthor({ author, compact }: Props) { ); } - const sublabelStyle = { color: isDark ? darkColors.secondary : colors.gray4 }; - const potentialHref = author.url ?? author.email; // URL @@ -37,14 +36,11 @@ export default function PackageAuthor({ author, compact }: Props) { return ( - + - {ghUsername} - {validName} + {ghUsername} + {validName} @@ -54,7 +50,7 @@ export default function PackageAuthor({ author, compact }: Props) { return ( - + {author.name ?? 'Unknown'} ); @@ -64,7 +60,7 @@ export default function PackageAuthor({ author, compact }: Props) { if (potentialHref && potentialHref.includes('@')) { if (compact) { return ( - + }> - - {author.name} - {potentialHref} + + {author.name} + {potentialHref} @@ -84,14 +80,14 @@ export default function PackageAuthor({ author, compact }: Props) { } return ( - + - {author.name} - {potentialHref} + {author.name} + {potentialHref} ); @@ -99,7 +95,7 @@ export default function PackageAuthor({ author, compact }: Props) { return ( - + {getValidName(author.name) ?? 'Unknown'} ); } @@ -111,20 +107,3 @@ function getValidName(potentialName: string): string { .join(' '); return cleanName.length ? cleanName : potentialName.replace(/[<>()]/g, ''); } - -const styles = StyleSheet.create({ - authorContainer: { - display: 'flex', - flexDirection: 'row', - gap: 12, - alignItems: 'center', - backgroundColor: 'transparent', - }, - sublabel: { - fontSize: 11, - fontWeight: 300, - }, - tooltipContent: { - display: 'flex', - }, -}); diff --git a/components/Package/PackageHeader.tsx b/components/Package/PackageHeader.tsx index dea59c822..fc140ea7f 100644 --- a/components/Package/PackageHeader.tsx +++ b/components/Package/PackageHeader.tsx @@ -1,7 +1,7 @@ -import { useContext, type ReactNode } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { type ReactNode } from 'react'; +import { View } from 'react-native'; -import { A, colors, darkColors, HoverEffect, P, useLayout } from '~/common/styleguide'; +import { A, HoverEffect, P, useLayout } from '~/common/styleguide'; import CompatibilityTags from '~/components/CompatibilityTags'; import { GitHub } from '~/components/Icons'; import LibraryDescription from '~/components/Library/LibraryDescription'; @@ -9,8 +9,8 @@ import UnmaintainedLabel from '~/components/Library/UnmaintainedLabel'; import TrustedBadge from '~/components/Package/TrustedBadge'; import UserAvatar from '~/components/Package/UserAvatar'; import Tooltip from '~/components/Tooltip'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; import { type LibraryType, type NpmRegistryVersionData } from '~/types'; +import tw from '~/util/tailwind'; type Props = { library: LibraryType; @@ -19,45 +19,45 @@ type Props = { }; export default function PackageHeader({ library, registryData, rightSlot }: Props) { - const { isDark } = useContext(CustomAppearanceContext); const { isSmallScreen } = useLayout(); const ghUsername = library.github.fullName.split('/')[0]; - const headerColorStyle = { - color: isDark ? darkColors.secondary : colors.gray5, - }; - return ( <> {library.unmaintained && } - - + + }> {ghUsername} -

          {library.npmPkg}

          +

          {library.npmPkg}

          {registryData && ( - -

          {registryData.version}

          + +

          {registryData.version}

          {registryData._npmUser?.trustedPublisher && }
          )} - - + +
          @@ -68,47 +68,3 @@ export default function PackageHeader({ library, registryData, rightSlot }: Prop ); } - -const styles = StyleSheet.create({ - nameRow: { - gap: 24, - minHeight: 26, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - nameRowMobile: { - gap: 8, - alignItems: 'flex-start', - flexDirection: 'column', - }, - nameWrapper: { - columnGap: 8, - rowGap: 4, - flexDirection: 'row', - alignItems: 'center', - flexWrap: 'wrap', - }, - name: { - fontWeight: '600', - fontSize: 20, - lineHeight: 26, - marginTop: -2, - }, - versionContainer: { - columnGap: 4, - alignItems: 'center', - flexDirection: 'row', - }, - githubButton: { - width: 20, - height: 20, - }, - avatar: { - width: 24, - height: 24, - borderRadius: 6, - borderWidth: 1, - borderStyle: 'solid', - }, -}); diff --git a/components/Package/ReadmeBox.tsx b/components/Package/ReadmeBox.tsx index d5b1448e3..db237ed74 100644 --- a/components/Package/ReadmeBox.tsx +++ b/components/Package/ReadmeBox.tsx @@ -1,34 +1,28 @@ import { Md } from '@m2d/react-markdown/client'; import { capitalize } from 'es-toolkit'; import { useEffect, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize from 'rehype-sanitize'; import remarkEmoji from 'remark-emoji'; import remarkGfm from 'remark-gfm'; -import { A, colors, darkColors, P } from '~/common/styleguide'; +import { A, P } from '~/common/styleguide'; import { ReadmeFile } from '~/components/Icons'; import ReadmeCodeBlock from '~/components/Package/ReadmeCodeBlock'; import { ThreeDotsLoader } from '~/components/Package/ThreeDotsLoader'; import { extractAndStripBlockquoteType } from '~/util/extractAndStripBlockquoteType'; import { getReadmeAssetURL } from '~/util/getReadmeAssetUrl'; +import tw from '~/util/tailwind'; type Props = { packageName?: string; githubUrl?: string; isTemplate?: boolean; - isDark?: boolean; loader?: boolean; }; -export default function ReadmeBox({ - packageName, - githubUrl, - isTemplate, - isDark = false, - loader = false, -}: Props) { +export default function ReadmeBox({ packageName, githubUrl, isTemplate, loader = false }: Props) { const [readmeContent, setReadmeContent] = useState(undefined); useEffect(() => { @@ -85,29 +79,20 @@ export default function ReadmeBox({ } const readmeFallbackContent = getReadmeFallbackContent(readmeContent); - const borderColorStyle = { borderColor: isDark ? darkColors.border : colors.gray2 }; return ( - - + style={tw`rounded-xl my-2 border border-palette-gray2 text-black dark:border-default dark:text-white`}> + +

          Readme

          - + {!readmeContent && readmeFallbackContent ? ( - - {readmeContent === undefined && ( - - )} -

          {readmeFallbackContent}

          + + {readmeContent === undefined && } +

          {readmeFallbackContent}

          ) : ( ); }, @@ -173,12 +157,10 @@ export default function ReadmeBox({
          {blockquoteType.type && ( @@ -192,10 +174,7 @@ export default function ReadmeBox({ details: ({ children }: any) => { return (
          + style={tw`rounded-xl mt-3 pb-3 pt-1 pr-4 border border-palette-gray2 dark:border-default`}> {children}
          ); @@ -221,48 +200,3 @@ function getReadmeFallbackContent(readmeContent?: string | null): string | null } return null; } - -const styles = StyleSheet.create({ - readmeWrapper: { - borderRadius: 12, - borderWidth: 1, - borderStyle: 'solid', - marginVertical: 8, - }, - readmeHeader: { - flexDirection: 'row', - gap: 8, - alignItems: 'center', - paddingVertical: 12, - paddingHorizontal: 16, - borderBottomWidth: 1, - borderStyle: 'solid', - }, - readmeContainer: { - padding: 16, - paddingTop: 12, - fontWeight: 300, - }, - statusContainer: { - paddingVertical: 24, - gap: 16, - }, - statusContent: { - textAlign: 'center', - }, - detailsWrapper: { - borderRadius: 12, - borderWidth: 1, - borderStyle: 'solid', - marginTop: 12, - paddingBottom: 12, - paddingTop: 4, - paddingRight: 16, - }, - copyCodeButton: { - position: 'absolute', - backgroundColor: 'transparent', - top: 22, - right: 10, - }, -}); diff --git a/components/Package/ReadmeCodeBlock.tsx b/components/Package/ReadmeCodeBlock.tsx index 519233337..1473f720c 100644 --- a/components/Package/ReadmeCodeBlock.tsx +++ b/components/Package/ReadmeCodeBlock.tsx @@ -1,22 +1,20 @@ -import { StyleSheet } from 'react-native'; import { useShikiHighlighter } from 'react-shiki'; -import { colors, darkColors } from '~/common/styleguide'; import { Button } from '~/components/Button'; import { Copy } from '~/components/Icons'; import Tooltip from '~/components/Tooltip'; +import tw from '~/util/tailwind'; type Props = { code: string; lang: string; - isDark: boolean; }; -export default function ReadmeCodeBlock({ code, lang, isDark }: Props) { +export default function ReadmeCodeBlock({ code, lang }: Props) { const highlighter = useShikiHighlighter( code, lang, - isDark ? 'github-dark-default' : 'github-light-default', + tw.prefixMatch('dark') ? 'github-dark-default' : 'github-light-default', { langAlias: { gradle: 'groovy' }, } @@ -27,14 +25,14 @@ export default function ReadmeCodeBlock({ code, lang, isDark }: Props) { sideOffset={2} trigger={ }> Copy code @@ -43,7 +41,7 @@ export default function ReadmeCodeBlock({ code, lang, isDark }: Props) { if (!highlighter) { return ( -
          +
                     {code}
                   
          @@ -53,24 +51,9 @@ export default function ReadmeCodeBlock({ code, lang, isDark }: Props) { } return ( -
          +
          {highlighter} {copyButton}
          ); } - -const styles = StyleSheet.create({ - codeBlockContainer: { - position: 'relative', - marginTop: 8, - }, - copyCodeContainer: { - position: 'absolute', - top: 12, - right: 12, - }, - copyCodeButton: { - backgroundColor: 'transparent', - }, -}); diff --git a/components/Package/ThreeDotsLoader.tsx b/components/Package/ThreeDotsLoader.tsx index 16fd85f05..4342d71eb 100644 --- a/components/Package/ThreeDotsLoader.tsx +++ b/components/Package/ThreeDotsLoader.tsx @@ -1,17 +1,15 @@ import { View } from 'react-native'; type ThreeDotsProps = { - color: string; label?: string; }; -export function ThreeDotsLoader({ color, label = 'Loading' }: ThreeDotsProps) { - const colorStyle = { backgroundColor: color }; +export function ThreeDotsLoader({ label = 'Loading' }: ThreeDotsProps) { return ( - - - + + + ); } diff --git a/components/Package/TrustedBadge.tsx b/components/Package/TrustedBadge.tsx index 67fe95bd1..0747e0eff 100644 --- a/components/Package/TrustedBadge.tsx +++ b/components/Package/TrustedBadge.tsx @@ -1,15 +1,15 @@ import { View } from 'react-native'; -import { colors } from '~/common/styleguide'; import { Verified } from '~/components/Icons'; import Tooltip from '~/components/Tooltip'; +import tw from '~/util/tailwind'; export default function TrustedBadge() { return ( - + }> Trusted publisher diff --git a/components/Package/UserAvatar.tsx b/components/Package/UserAvatar.tsx index 76039370d..00035f150 100644 --- a/components/Package/UserAvatar.tsx +++ b/components/Package/UserAvatar.tsx @@ -1,26 +1,19 @@ -import { type ImgHTMLAttributes, useContext } from 'react'; -import { StyleSheet } from 'react-native'; +import { type ImgHTMLAttributes } from 'react'; -import { colors, darkColors } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import tw from '~/util/tailwind'; type Props = ImgHTMLAttributes & { hideOnError?: boolean; }; export default function UserAvatar({ src, alt, style, hideOnError = true, ...rest }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - - const avatarStyle = { - ...styles.avatar, - backgroundColor: isDark ? darkColors.powder : colors.gray2, - ...style, - }; - return ( {alt} - + {label && {label}} - {label && {` • `}} - - {versionData.version} - + {label && {` • `}} + {versionData.version} - - - + {versionData.dependencies && ( - - - + + + {`${Object.keys(versionData.dependencies).length} ${pluralize('dependency', Object.keys(versionData.dependencies).length)}`} )} {versionData.dist.unpackedSize && ( - - - + + + {`${formatBytes(versionData.dist.unpackedSize)} package size`} @@ -89,48 +60,3 @@ export default function VersionBox({ label, time, versionData }: Props) { ); } - -const styles = StyleSheet.create({ - versionBox: { - display: 'flex', - flex: 1, - paddingHorizontal: 16, - paddingVertical: 10, - borderRadius: 12, - borderWidth: 1, - gap: 2, - borderStyle: 'solid', - textDecorationLine: 'none', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - versionBoxSmall: { - gap: 8, - alignItems: 'flex-start', - justifyContent: 'flex-start', - flexDirection: 'column', - }, - versionMetadataContainer: { - flexDirection: 'row', - flexWrap: 'wrap', - columnGap: 16, - rowGap: 4, - }, - versionMetadata: { - flexDirection: 'row', - gap: 8, - }, - versionLabelWrapper: { - minHeight: 16, - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - fontWeight: 400, - maxWidth: '100%', - gap: 4, - }, - versionLabel: { - fontWeight: 300, - }, -}); diff --git a/components/Pagination.tsx b/components/Pagination.tsx index f02504b15..f815630b4 100644 --- a/components/Pagination.tsx +++ b/components/Pagination.tsx @@ -1,16 +1,14 @@ import Link from 'next/link'; -import { useContext } from 'react'; -import { StyleSheet, View, type ViewStyle } from 'react-native'; +import { View, type ViewStyle } from 'react-native'; -import { colors, Caption, darkColors, HoverEffect } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { Caption, HoverEffect } from '~/common/styleguide'; +import { Arrow } from '~/components/Icons'; import { type Query } from '~/types'; import { NUM_PER_PAGE } from '~/util/Constants'; import { getPageQuery } from '~/util/search'; +import tw from '~/util/tailwind'; import urlWithQuery from '~/util/urlWithQuery'; -import { Arrow } from './Icons'; - type Props = { query: Query; total?: number | null; @@ -22,39 +20,6 @@ type ArrowButtonProps = { disabled?: boolean; }; -function BackArrow({ disabled }: ArrowButtonProps) { - const { isDark } = useContext(CustomAppearanceContext); - return ( - - - - ); -} - -function ForwardArrow({ disabled }: ArrowButtonProps) { - const { isDark } = useContext(CustomAppearanceContext); - return ( - - - - ); -} - export default function Pagination({ query, total, style, basePath = '/' }: Props) { const currentOffset = query.offset ? parseInt(query.offset, 10) : 0; const currentPage = Math.floor(currentOffset / NUM_PER_PAGE) + 1; @@ -70,7 +35,7 @@ export default function Pagination({ query, total, style, basePath = '/' }: Prop const pageQuery = getPageQuery(basePath, query); return ( - + {backDisabled ? ( ) : ( @@ -80,14 +45,13 @@ export default function Pagination({ query, total, style, basePath = '/' }: Prop ...pageQuery, offset: (currentOffset - NUM_PER_PAGE).toString(), })} - style={{ borderRadius: 4 }} + style={tw`rounded`} aria-label="Previous page"> )} - - + {currentPage > 0 ? currentPage : '1'} of {totalPages} {forwardDisabled ? ( @@ -99,7 +63,7 @@ export default function Pagination({ query, total, style, basePath = '/' }: Prop ...pageQuery, offset: (currentOffset + NUM_PER_PAGE).toString(), })} - style={{ borderRadius: 4 }} + style={tw`rounded`} aria-label="Next page"> @@ -109,32 +73,26 @@ export default function Pagination({ query, total, style, basePath = '/' }: Prop ); } -const styles = StyleSheet.create({ - container: { - flex: 1, - flexDirection: 'row', - justifyContent: 'flex-end', - alignItems: 'center', - flexGrow: 0, - flexBasis: 1, - minHeight: 28, - }, - arrowContainer: { - height: 24, - width: 24, - justifyContent: 'center', - alignItems: 'center', - borderRadius: 4, - }, - rotate: { - transform: 'rotate(180deg)', - }, - disabled: { - opacity: 0.5, - }, - text: { - marginHorizontal: 6, - minWidth: 60, - textAlign: 'center', - }, -}); +function BackArrow({ disabled }: ArrowButtonProps) { + return ( + + + + ); +} + +function ForwardArrow({ disabled }: ArrowButtonProps) { + return ( + + + + ); +} diff --git a/components/Score/ScoringCriterion.tsx b/components/Score/ScoringCriterion.tsx index 8a4af352b..f76d3b164 100644 --- a/components/Score/ScoringCriterion.tsx +++ b/components/Score/ScoringCriterion.tsx @@ -1,35 +1,26 @@ -import { type PropsWithChildren, useContext } from 'react'; -import { StyleSheet, View, type ViewStyle } from 'react-native'; +import { type PropsWithChildren } from 'react'; +import { type StyleProp, View, type ViewStyle } from 'react-native'; -import { colors, darkColors, Headline, P } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { Headline, P } from '~/common/styleguide'; +import tw from '~/util/tailwind'; type Props = PropsWithChildren<{ headline: string; score?: number; - style?: ViewStyle; + style?: StyleProp; }>; export function ScoringCriterion({ children, headline, style, score = undefined }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - - const textStyle = { - color: isDark ? colors.gray2 : colors.black, - }; - const borderStyle = { - borderColor: isDark ? darkColors.border : colors.gray2, - }; - const isPositiveModifier = (score ?? 0) > 0; + return ( - - + + {score && ( {isPositiveModifier ? '+' : ''} {score} @@ -37,43 +28,7 @@ export function ScoringCriterion({ children, headline, style, score = undefined )} {headline} -

          {children}

          +

          {children}

          ); } - -const styles = StyleSheet.create({ - paragraph: { - lineHeight: 24, - marginBottom: 16, - fontWeight: 300, - }, - criterionWrapper: { - paddingVertical: 14, - paddingHorizontal: 20, - borderWidth: 1, - borderStyle: 'solid', - borderRadius: 6, - marginBottom: 16, - }, - criterion: { - display: 'flex', - gap: 12, - fontSize: 17, - fontWeight: '600', - marginBottom: 4, - }, - criterionScore: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - minWidth: 50, - fontSize: 15, - fontWeight: '700', - padding: 1, - borderWidth: 1, - borderStyle: 'solid', - borderRadius: 4, - textAlign: 'center', - }, -}); diff --git a/components/Search.tsx b/components/Search.tsx index 1808392f9..8e3122291 100644 --- a/components/Search.tsx +++ b/components/Search.tsx @@ -1,12 +1,12 @@ import { useRouter } from 'next/router'; -import { useContext, useEffect, useMemo, useRef, useState } from 'react'; -import { TextInput, StyleSheet, View } from 'react-native'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { type ColorValue, TextInput, View } from 'react-native'; import { useDebouncedCallback } from 'use-debounce'; -import { layout, colors, P, darkColors, useLayout, Label } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { P, useLayout, Label } from '~/common/styleguide'; import { type Query } from '~/types'; import isAppleDevice from '~/util/isAppleDevice'; +import tw from '~/util/tailwind'; import urlWithQuery from '~/util/urlWithQuery'; import { Filters } from './Filters'; @@ -29,7 +29,6 @@ export default function Search({ query, total }: Props) { const { replace } = useRouter(); const { isSmallScreen } = useLayout(); - const { isDark } = useContext(CustomAppearanceContext); useEffect(() => { if (isApple !== null) { @@ -58,19 +57,16 @@ export default function Search({ query, total }: Props) { void replace(urlWithQuery('/', { search, offset: undefined })); } + const focusHintLabel = tw`text-palette-gray4 font-light`; + const focusHintKey = tw`text-tertiary text-center py-[3px] px-1 min-w-6 rounded-[3px] tracking-[0.75px] bg-palette-gray5 dark:bg-powder`; + return ( <> - - - - - + + + + + setInputFocused(false)} onChangeText={typingCallback} placeholder="Search libraries..." - style={[ - styles.textInput, - { - borderColor: isDark ? darkColors.border : colors.gray5, - }, - ]} + style={tw`flex flex-1 h-12.5 p-4 pl-11 font-sans text-xl text-white rounded-md -outline-offset-2 border-2 border-palette-gray5 dark:border-default`} defaultValue={search} - placeholderTextColor={colors.gray4} + placeholderTextColor={tw`text-palette-gray4`.color as ColorValue} /> - {!isSmallScreen && - (isInputFocused ? ( - - - - - - ) : ( - - - - - - ))} + {!isSmallScreen && ( + + {isInputFocused ? ( + <> + + + + + ) : ( + <> + + + + + )} + + )} {total ? ( -

          -

          {total}

          {total === 1 ? 'entry' : 'entries'} +

          +

          {total}

          {' '} + {total === 1 ? 'entry' : 'entries'}

          ) : (

          )} - + setFilterVisible(!isFilterVisible)} onClearAllPress={handleClearAllPress} @@ -190,76 +154,3 @@ export default function Search({ query, total }: Props) { ); } - -const styles = StyleSheet.create({ - wrapper: { - paddingVertical: 14, - alignItems: 'center', - }, - container: { - width: '100%', - maxWidth: layout.maxWidth, - paddingHorizontal: 16, - }, - displayHorizontal: { - alignItems: 'center', - flexDirection: 'row', - }, - textInput: { - flex: 1, - height: 50, - borderWidth: 2, - borderRadius: 6, - padding: 16, - paddingLeft: 44, - fontSize: 20, - color: colors.white, - fontFamily: 'inherit', - outlineOffset: -2, - }, - searchIcon: { - position: 'absolute', - left: 16, - pointerEvents: 'none', - }, - focusHint: { - position: 'absolute', - right: 16, - pointerEvents: 'none', - flexDirection: 'row', - gap: 4, - alignItems: 'center', - }, - focusHintKey: { - color: colors.secondary, - textAlign: 'center', - paddingVertical: 3, - paddingHorizontal: 4, - minWidth: 24, - borderRadius: 3, - letterSpacing: 0.75, - }, - focusHintLabel: { - color: colors.gray4, - fontWeight: 300, - }, - resultsContainer: { - marginTop: 8, - justifyContent: 'space-between', - }, - smallResultsContainer: { - flexDirection: 'column', - alignItems: 'flex-start', - }, - buttonsContainer: { - marginTop: 6, - }, - totalCount: { - color: colors.primary, - fontWeight: 700, - }, - totalText: { - color: colors.white, - marginTop: 4, - }, -}); diff --git a/components/Sort.tsx b/components/Sort.tsx index 84bdbb881..d2e66ed8f 100644 --- a/components/Sort.tsx +++ b/components/Sort.tsx @@ -1,11 +1,11 @@ import { Picker } from '@react-native-picker/picker'; import { useRouter } from 'next/router'; -import { useContext, useState } from 'react'; -import { Pressable, StyleSheet, View } from 'react-native'; +import { useState } from 'react'; +import { Pressable, View } from 'react-native'; -import { colors, darkColors, P } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { P } from '~/common/styleguide'; import { type Query, type QueryOrder, type QueryOrderDirection } from '~/types'; +import tw from '~/util/tailwind'; import urlWithQuery from '~/util/urlWithQuery'; import { Sort as SortIcon } from './Icons'; @@ -19,7 +19,6 @@ export function SortButton({ query: { order, direction, search }, query }: SortB const [isSortIconHovered, setIsSortIconHovered] = useState(false); const { asPath, push } = useRouter(); - const { isDark } = useContext(CustomAppearanceContext); const currentSortValue: QueryOrder | undefined = order; const currentDirection: QueryOrderDirection | undefined = direction; @@ -31,20 +30,15 @@ export function SortButton({ query: { order, direction, search }, query }: SortB } return ( - - + + setIsSortIconHovered(true)} onHoverOut={() => setIsSortIconHovered(false)} - style={currentDirection === 'ascending' && styles.flippedIcon} + style={currentDirection === 'ascending' && tw`-scale-y-100`} aria-label="Toggle sort direction" role="button" onPress={() => { @@ -55,24 +49,19 @@ export function SortButton({ query: { order, direction, search }, query }: SortB }) ); }}> - + }> Toggle sort order -

          Sort:

          +

          Sort:

          - + { updatePath( urlWithQuery('/', { @@ -83,12 +72,7 @@ export function SortButton({ query: { order, direction, search }, query }: SortB ); }}> {SORTS.map(sort => ( - + ))} @@ -142,42 +126,3 @@ const SORTS: { param: QueryOrder; label: string }[] = [ label: 'Bundle Size', }, ]; - -const styles = StyleSheet.create({ - container: { - backgroundColor: colors.gray5, - height: 24, - marginLeft: 8, - paddingLeft: 8, - borderRadius: 4, - }, - displayHorizontal: { - flexDirection: 'row', - alignItems: 'center', - }, - title: { - color: colors.white, - fontWeight: 400, - marginLeft: 6, - marginRight: 2, - fontSize: 14, - userSelect: 'none', - }, - pickerContainer: { - top: 1, - }, - picker: { - color: colors.white, - borderWidth: 0, - borderRadius: 4, - position: 'relative', - top: -1, - fontSize: 14, - fontFamily: 'inherit', - cursor: 'pointer', - fontWeight: 600, - }, - flippedIcon: { - transform: 'scaleY(-1)', - }, -}); diff --git a/components/Tag.tsx b/components/Tag.tsx index b6b45ad57..21ba246dd 100644 --- a/components/Tag.tsx +++ b/components/Tag.tsx @@ -1,8 +1,8 @@ -import { type ReactElement, useContext } from 'react'; -import { StyleSheet, View, type ViewStyle } from 'react-native'; +import { type ReactElement } from 'react'; +import { View, type ViewStyle } from 'react-native'; -import { colors, darkColors, Label } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { Label } from '~/common/styleguide'; +import tw from '~/util/tailwind'; import { Check } from './Icons'; @@ -15,34 +15,17 @@ type Props = { export function Tag({ label, tagStyle, - icon = , + icon = , }: Props) { - const { isDark } = useContext(CustomAppearanceContext); return ( - + {icon} - + ); } - -const styles = StyleSheet.create({ - tag: { - flexDirection: 'row', - alignItems: 'center', - borderWidth: 1, - borderRadius: 4, - paddingHorizontal: 8, - paddingVertical: 4, - userSelect: 'none', - minHeight: 25.5, - gap: 5, - }, -}); diff --git a/components/Tools/GitHubButton.tsx b/components/Tools/GitHubButton.tsx index d390d7499..aeca2beae 100644 --- a/components/Tools/GitHubButton.tsx +++ b/components/Tools/GitHubButton.tsx @@ -1,8 +1,5 @@ -import { useContext } from 'react'; -import { StyleSheet } from 'react-native'; - -import { colors, darkColors, P } from '~/common/styleguide'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import { P } from '~/common/styleguide'; +import tw from '~/util/tailwind'; import { Button } from '../Button'; import { GitHub } from '../Icons'; @@ -12,30 +9,13 @@ type Props = { }; export default function GitHubButton({ href }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - - const primaryButtonColorStyle = { - backgroundColor: isDark ? darkColors.primaryDark : colors.primary, - }; - return ( - ); } - -const styles = StyleSheet.create({ - button: { - flexDirection: 'row', - paddingHorizontal: 12, - minHeight: 32, - fontSize: 14, - gap: 6, - }, - githubButtonLabel: { - color: 'inherit', - fontSize: 14, - }, -}); diff --git a/components/Tools/ToolEntry.tsx b/components/Tools/ToolEntry.tsx index 95e7bcd2a..c492e8189 100644 --- a/components/Tools/ToolEntry.tsx +++ b/components/Tools/ToolEntry.tsx @@ -1,9 +1,8 @@ -import { useContext } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; -import { colors, darkColors, H3, P } from '~/common/styleguide'; +import { H3, P } from '~/common/styleguide'; import { Button } from '~/components/Button'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import tw from '~/util/tailwind'; import GitHubButton from './GitHubButton'; @@ -18,24 +17,21 @@ type Props = { }; export default function ToolEntry({ name, description, githubUrl, buttons }: Props) { - const { isDark } = useContext(CustomAppearanceContext); - - const textColorStyle = { - color: isDark ? colors.gray2 : colors.black, - }; - const buttonColorStyle = { - backgroundColor: isDark ? darkColors.border : colors.gray3, - color: isDark ? colors.white : colors.black, - }; - return ( - -

          {name}

          -

          {description}

          - + +

          {name}

          +

          {description}

          + {buttons.map(({ label, href }) => ( - ))} @@ -43,34 +39,3 @@ export default function ToolEntry({ name, description, githubUrl, buttons }: Pro ); } - -const styles = StyleSheet.create({ - box: { - borderRadius: 12, - borderWidth: 1, - borderStyle: 'solid', - paddingVertical: 16, - paddingHorizontal: 20, - }, - header: { - fontSize: 20, - marginBottom: 4, - }, - paragraph: { - lineHeight: 29, - marginBottom: 12, - fontWeight: 300, - }, - buttonsContainer: { - flexDirection: 'row', - flexWrap: 'wrap', - gap: 12, - }, - button: { - flexDirection: 'row', - paddingHorizontal: 12, - minHeight: 32, - fontSize: 14, - gap: 6, - }, -}); diff --git a/components/TopBar.tsx b/components/TopBar.tsx index 2456c323c..6cbfae5f3 100644 --- a/components/TopBar.tsx +++ b/components/TopBar.tsx @@ -1,48 +1,36 @@ import { Header as HtmlHeader } from '@expo/html-elements'; import Link from 'next/link'; import { useContext } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; -import { layout, colors, H5, P, darkColors, useLayout } from '~/common/styleguide'; +import { H5, P, useLayout } from '~/common/styleguide'; import ContentContainer from '~/components/ContentContainer'; import NavigationTab from '~/components/NavigationTab'; import CustomAppearanceContext from '~/context/CustomAppearanceContext'; +import tw from '~/util/tailwind'; import { Button } from './Button'; import { GitHub, Logo, Plus, ThemeDark, ThemeLight, Tools } from './Icons'; import Tooltip from './Tooltip'; export default function TopBar() { - const { isDark, setIsDark } = useContext(CustomAppearanceContext); + const { toggleTheme } = useContext(CustomAppearanceContext); const { isSmallScreen, isBelowMaxWidth } = useLayout(); return ( - - - -
          - + style={tw`py-3.5 justify-center items-center overflow-hidden gap-y-2.5 bg-palette-gray7 dark:bg-very-dark`}> + + + +
          + {isBelowMaxWidth ? 'Directory' : 'React Native Directory'}
          - - + + @@ -50,18 +38,21 @@ export default function TopBar() { }> @@ -73,8 +64,8 @@ export default function TopBar() { }> @@ -82,13 +73,13 @@ export default function TopBar() { + }> @@ -97,22 +88,19 @@ export default function TopBar() {
          - + @@ -120,71 +108,3 @@ export default function TopBar() { ); } - -const styles = StyleSheet.create({ - header: { - paddingVertical: 14, - justifyContent: 'center', - alignItems: 'center', - overflow: 'hidden', - rowGap: 10, - }, - headerContents: { - flexDirection: 'row', - width: '100%', - alignItems: 'center', - justifyContent: 'space-between', - maxWidth: layout.maxWidth, - paddingHorizontal: 16, - marginTop: -1.5, - }, - headerTitle: { - marginTop: -2, - }, - headerContentsTitle: { - color: colors.primary, - paddingLeft: 8, - fontWeight: 700, - textDecorationLine: 'none', - }, - headerSubpageTitle: { - color: colors.white, - marginLeft: 32, - }, - headerSubpageTitleSmall: { - marginLeft: 20, - }, - headerSide: { - minWidth: 255, - }, - tabsWrapper: { - flexDirection: 'row', - paddingHorizontal: 16, - gap: 10, - }, - displayHorizontal: { - flexDirection: 'row', - alignItems: 'center', - }, - smallTitle: { - fontSize: 18, - }, - button: { - maxHeight: 34, - minHeight: 34, - paddingVertical: 8, - paddingHorizontal: 16, - }, - themeButtonText: { - fontSize: 18, - marginTop: -1, - userSelect: 'none', - }, - themeButtonSmall: { - paddingHorizontal: 4, - paddingVertical: 0, - backgroundColor: 'none', - borderRadius: 40, - minWidth: 34, - }, -}); diff --git a/context/CustomAppearanceContext.ts b/context/CustomAppearanceContext.ts index 19bc1973f..e50b98145 100644 --- a/context/CustomAppearanceContext.ts +++ b/context/CustomAppearanceContext.ts @@ -1,8 +1,9 @@ import { createContext } from 'react'; export type CustomAppearanceContextType = { - isDark: boolean; - setIsDark: (value: boolean) => void; + toggleTheme: () => void; }; -export default createContext({ isDark: false, setIsDark() {} }); +export default createContext({ + toggleTheme() {}, +}); diff --git a/context/CustomAppearanceProvider.tsx b/context/CustomAppearanceProvider.tsx index aec9c7499..a3b9b28a0 100644 --- a/context/CustomAppearanceProvider.tsx +++ b/context/CustomAppearanceProvider.tsx @@ -1,53 +1,59 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; -import { type PropsWithChildren, useEffect, useState } from 'react'; -import { Appearance, View } from 'react-native'; +import { type PropsWithChildren, useEffect } from 'react'; -import CustomAppearanceContext, { - type CustomAppearanceContextType, -} from './CustomAppearanceContext'; +import tw, { useDeviceContext, useAppColorScheme } from '~/util/tailwind'; + +import CustomAppearanceContext from './CustomAppearanceContext'; const appearanceStorageKey = '@ReactNativeDirectory:CustomAppearanceContext'; const shouldRehydrate = true; - const defaultState = { isDark: false }; export default function CustomAppearanceProvider({ children }: PropsWithChildren) { - const colorScheme = Appearance.getColorScheme(); - const [isDark, setIsDark] = useState(colorScheme === 'dark'); - const [isLoaded, setLoaded] = useState(false); + const [colorScheme, , setColorScheme] = useAppColorScheme(tw); + + function toggleTheme() { + if (colorScheme === 'dark') { + document.documentElement.classList.remove('dark'); + } else { + document.documentElement.classList.add('dark'); + } + + setColorScheme(colorScheme === 'dark' ? 'light' : 'dark'); + } + + useDeviceContext(tw, { + observeDeviceColorSchemeChanges: false, + initialColorScheme: colorScheme === 'dark' ? 'dark' : 'light', + }); useEffect(() => { async function rehydrateAsync() { try { const { isDark } = await rehydrateAppearanceState(); - setIsDark(isDark); + isDark && toggleTheme(); } catch {} - setLoaded(true); } void rehydrateAsync(); }, []); - if (!isLoaded) { - return ; - } else { - return ( - { - setIsDark(isDark); - void cacheAppearanceState({ isDark }); - }, - }}> - {children} - - ); - } + return ( + { + toggleTheme(); + void cacheAppearanceState(colorScheme !== 'dark'); + }, + }}> + {children} + + ); } -async function cacheAppearanceState(appearance: Omit) { - await AsyncStorage.setItem(appearanceStorageKey, JSON.stringify(appearance)); +async function cacheAppearanceState(isDark: boolean) { + await AsyncStorage.setItem(appearanceStorageKey, JSON.stringify({ isDark })); } async function rehydrateAppearanceState() { @@ -57,7 +63,7 @@ async function rehydrateAppearanceState() { try { const item = await AsyncStorage.getItem(appearanceStorageKey); - return item ? JSON.parse(item) : null; + return item ? JSON.parse(item) : defaultState; } catch { return defaultState; } diff --git a/next.config.ts b/next.config.ts index 665460df1..1d2f7da2c 100644 --- a/next.config.ts +++ b/next.config.ts @@ -16,6 +16,7 @@ const PACKAGES_TO_OPTIMIZE = [ 'react-native-web', 'react-shiki', 'shiki/*', + 'twrnc', ]; const withBundleAnalyzer = BundleAnalyzer({ @@ -36,7 +37,6 @@ export default withPlugins([withExpo, withImages, withFonts, withBundleAnalyzer] forceSwcTransforms: true, webpackBuildWorker: true, browserDebugInfoInTerminal: true, - useLightningcss: true, optimizePackageImports: PACKAGES_TO_OPTIMIZE, }, async headers() { diff --git a/package.json b/package.json index 7ee5c0d5a..d71fd0cd6 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "expo-font": "^14.0.10", "next": "^16.1.1", "node-emoji": "^2.2.0", + "postcss": "^8.5.6", "react": "19.2.3", "react-content-loader": "^7.1.1", "react-dom": "19.2.3", @@ -45,6 +46,8 @@ "rehype-sanitize": "^6.0.0", "remark-emoji": "^5.0.2", "remark-gfm": "^4.0.1", + "tailwindcss": "^3.4.19", + "twrnc": "^4.16.0", "use-debounce": "^10.0.6" }, "devDependencies": { diff --git a/pages/_app.tsx b/pages/_app.tsx index 75cd315e7..7e21559bc 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -3,10 +3,9 @@ import { type AppProps } from 'next/app'; import Head from 'next/head'; import { SafeAreaProvider } from 'react-native-safe-area-context'; -import { colors, darkColors } from '~/common/styleguide'; import Footer from '~/components/Footer'; -import CustomAppearanceContext from '~/context/CustomAppearanceContext'; import CustomAppearanceProvider from '~/context/CustomAppearanceProvider'; +import tw from '~/util/tailwind'; import '~/styles/styles.css'; @@ -24,57 +23,16 @@ Sentry.init({ function App({ pageProps, Component }: AppProps) { return ( - - {context => ( - - - - - - -