From f649634e7c6954d7d848a33df1b898a1d48b4641 Mon Sep 17 00:00:00 2001 From: "github-action[bot]" Date: Thu, 18 Dec 2025 19:42:45 +0100 Subject: [PATCH] Update On Thu Dec 18 19:42:44 CET 2025 --- .github/update.log | 1 + clash-meta/go.mod | 2 +- clash-meta/go.sum | 4 +- clash-nyanpasu/.gitignore | 1 - clash-nyanpasu/.vscode/extensions.json | 16 + clash-nyanpasu/.vscode/settings.json | 7 + .../backend/tauri/src/utils/resolve.rs | 2 +- clash-nyanpasu/eslint.config.js | 15 +- .../frontend/nyanpasu/.vscode/extensions.json | 3 + .../frontend/nyanpasu/messages/en.json | 21 + .../frontend/nyanpasu/messages/ru.json | 21 + .../frontend/nyanpasu/messages/zh-cn.json | 21 + .../frontend/nyanpasu/messages/zh-tw.json | 21 + clash-nyanpasu/frontend/nyanpasu/package.json | 4 +- .../nyanpasu/project.inlang/.gitignore | 1 + .../nyanpasu/project.inlang/project_id | 1 + .../nyanpasu/project.inlang/settings.json | 12 + .../nyanpasu/src/assets/styles/tailwind.css | 18 + .../src/components/logo/animated-logo.tsx | 86 +++ .../providers/block-task-provider.tsx | 138 ++++ .../src/components/router/animated-outlet.tsx | 87 +++ .../src/components/settings/system-proxy.tsx | 90 +++ .../nyanpasu/src/components/ui/button.tsx | 62 +- .../nyanpasu/src/components/ui/card.tsx | 186 +++++ .../nyanpasu/src/components/ui/circle.tsx | 5 +- .../nyanpasu/src/components/ui/input.tsx | 400 +++++++++++ .../nyanpasu/src/components/ui/progress.tsx | 6 +- .../src/components/ui/scroll-area.tsx | 71 +- .../nyanpasu/src/components/ui/switch.tsx | 55 ++ .../nyanpasu/src/hooks/use-is-moblie.tsx | 9 + .../nyanpasu/src/hooks/use-lock-fn.ts | 37 + .../pages/(experimental)/_modules/header.tsx | 10 +- .../pages/(experimental)/_modules/navbar.tsx | 39 +- .../_modules/window-control.tsx | 4 +- .../experimental/dashboard/route.tsx | 11 +- .../settings/_modules/settings-card.tsx | 25 + .../settings/_modules/settings-navigate.tsx | 149 ++++ .../settings/_modules/settings-title.tsx | 55 ++ .../experimental/settings/about/route.tsx | 11 + .../settings/clash-core/route.tsx | 11 + .../clash-external-controll/route.tsx | 11 + .../settings/clash-filed/route.tsx | 11 + .../settings/clash-port/route.tsx | 11 + .../settings/clash-settings/route.tsx | 13 + .../settings/debug-utils/route.tsx | 11 + .../experimental/settings/index.tsx | 32 + .../settings/nyanpasu-config/route.tsx | 11 + .../experimental/settings/route.tsx | 36 +- .../settings/system-behavior/route.tsx | 11 + .../_modules/current-system-proxy.tsx | 37 + .../_modules/proxy-bypass-config.tsx | 18 + .../_modules/proxy-guard-config.tsx | 24 + .../_modules/proxy-guard-switch.tsx | 38 + .../settings/system-proxy/route.tsx | 48 ++ .../settings/system-service/route.tsx | 11 + .../settings/user-interface/route.tsx | 28 + .../experimental/settings/web-ui/route.tsx | 11 + .../src/pages/(experimental)/route.tsx | 101 +-- .../frontend/nyanpasu/src/pages/__root.tsx | 21 +- .../frontend/nyanpasu/src/route-tree.gen.ts | 342 ++++++++- .../frontend/nyanpasu/src/utils/chain.ts | 21 +- .../frontend/nyanpasu/tsconfig.json | 2 +- .../frontend/nyanpasu/vite.config.ts | 5 + clash-nyanpasu/manifest/version.json | 4 +- clash-nyanpasu/pnpm-lock.yaml | 259 ++++++- lede/package/boot/uboot-rockchip/Makefile | 3 +- .../boot/dts/rockchip/rk3588-armsom-lm7.dtsi | 455 ++++++++++++ .../boot/dts/rockchip/rk3588-armsom-w3.dts | 408 +++++++++++ lede/target/linux/rockchip/image/armv8.mk | 11 + mieru/pkg/protocol/segment.go | 6 +- mieru/pkg/protocol/session.go | 50 +- mieru/pkg/protocol/underlay_packet.go | 2 +- mihomo/go.mod | 2 +- mihomo/go.sum | 4 +- openwrt-packages/luci-app-store/Makefile | 6 +- .../luasrc/controller/store.lua | 11 + .../luci-app-store/root/bin/is-opkg | 12 +- .../luci-app-store/root/etc/config/istore | 1 + .../root/usr/libexec/istore/ipv4-bin/curl | 8 + .../root/usr/libexec/istore/ipv4-bin/wget | 8 + .../luasrc/passwall/util_xray.lua | 6 +- .../view/passwall/node_list/node_list.htm | 2 - .../root/usr/share/passwall/app.sh | 2 +- openwrt-passwall2/luci-app-passwall2/Makefile | 2 +- .../resources/view/passwall2/qrcode.min.js | 2 +- .../luasrc/controller/passwall2.lua | 25 + .../model/cbi/passwall2/client/acl_config.lua | 9 +- .../model/cbi/passwall2/client/global.lua | 38 +- .../cbi/passwall2/client/node_config.lua | 3 + .../cbi/passwall2/client/node_subscribe.lua | 8 +- .../client/node_subscribe_config.lua | 27 +- .../cbi/passwall2/client/socks_config.lua | 61 +- .../model/cbi/passwall2/client/type/ray.lua | 116 ++- .../cbi/passwall2/client/type/sing-box.lua | 104 ++- .../model/cbi/passwall2/server/type/ray.lua | 10 +- .../cbi/passwall2/server/type/sing-box.lua | 10 +- .../model/cbi/passwall2/server/user.lua | 11 +- .../luasrc/passwall2/util_sing-box.lua | 14 +- .../luasrc/passwall2/util_xray.lua | 17 +- .../view/passwall2/cbi/nodes_listvalue.htm | 145 ++++ .../passwall2/cbi/nodes_listvalue_com.htm | 666 ++++++++++++++++++ .../view/passwall2/cbi/nodes_multivalue.htm | 119 ++++ .../passwall2/cbi/nodes_multivalue_com.htm | 313 ++++++++ .../luasrc/view/passwall2/global/footer.htm | 126 ++-- .../passwall2/node_list/link_add_node.htm | 217 ++++-- .../passwall2/node_list/link_share_man.htm | 6 +- .../view/passwall2/node_list/node_list.htm | 88 ++- .../luci-app-passwall2/po/zh-cn/passwall2.po | 14 +- .../luci-app-passwall2/po/zh-tw/passwall2.po | 16 +- .../root/etc/init.d/passwall2 | 6 + .../root/etc/init.d/passwall2_server | 8 +- .../root/usr/share/passwall2/0_default_config | 2 - shadowsocks-rust/README.md | 2 +- shadowsocks-rust/snap/snapcraft.yaml | 6 +- sing-box/.github/CRONET_GO_VERSION | 2 +- sing-box/docs/changelog.md | 18 + sing-box/docs/configuration/inbound/naive.md | 40 +- .../docs/configuration/inbound/naive.zh.md | 38 +- sing-box/docs/configuration/outbound/naive.md | 19 + .../docs/configuration/outbound/naive.zh.md | 19 + sing-box/experimental/libbox/log.go | 9 +- sing-box/go.mod | 52 +- sing-box/go.sum | 104 +-- sing-box/include/quic_stub.go | 2 +- sing-box/option/naive.go | 27 +- sing-box/protocol/naive/inbound.go | 5 +- sing-box/protocol/naive/outbound.go | 27 +- sing-box/protocol/naive/quic/inbound_init.go | 72 +- sing-box/test/go.mod | 53 +- sing-box/test/go.sum | 106 ++- sing-box/test/naive_self_test.go | 194 ++++- small/gn/Makefile | 2 +- .../luasrc/passwall/util_xray.lua | 6 +- .../root/usr/share/passwall/app.sh | 2 +- small/luci-app-passwall2/Makefile | 2 +- .../resources/view/passwall2/qrcode.min.js | 2 +- .../luasrc/controller/passwall2.lua | 25 + .../model/cbi/passwall2/client/acl_config.lua | 9 +- .../model/cbi/passwall2/client/global.lua | 38 +- .../cbi/passwall2/client/node_config.lua | 3 + .../cbi/passwall2/client/node_subscribe.lua | 8 +- .../client/node_subscribe_config.lua | 27 +- .../cbi/passwall2/client/socks_config.lua | 61 +- .../model/cbi/passwall2/client/type/ray.lua | 116 ++- .../cbi/passwall2/client/type/sing-box.lua | 104 ++- .../model/cbi/passwall2/server/type/ray.lua | 10 +- .../cbi/passwall2/server/type/sing-box.lua | 10 +- .../model/cbi/passwall2/server/user.lua | 11 +- .../luasrc/passwall2/util_sing-box.lua | 14 +- .../luasrc/passwall2/util_xray.lua | 17 +- .../view/passwall2/cbi/nodes_listvalue.htm | 145 ++++ .../passwall2/cbi/nodes_listvalue_com.htm | 666 ++++++++++++++++++ .../view/passwall2/cbi/nodes_multivalue.htm | 119 ++++ .../passwall2/cbi/nodes_multivalue_com.htm | 313 ++++++++ .../luasrc/view/passwall2/global/footer.htm | 126 ++-- .../passwall2/node_list/link_add_node.htm | 217 ++++-- .../passwall2/node_list/link_share_man.htm | 6 +- .../view/passwall2/node_list/node_list.htm | 88 ++- .../luci-app-passwall2/po/zh-cn/passwall2.po | 14 +- .../luci-app-passwall2/po/zh-tw/passwall2.po | 16 +- .../root/etc/init.d/passwall2 | 6 + .../root/etc/init.d/passwall2_server | 8 +- .../root/usr/share/passwall2/0_default_config | 2 - small/v2ray-geodata/Makefile | 4 +- .../src/main/res/values-zh-rTW/strings.xml | 34 +- 165 files changed, 8111 insertions(+), 930 deletions(-) create mode 100644 clash-nyanpasu/.vscode/extensions.json create mode 100644 clash-nyanpasu/.vscode/settings.json create mode 100644 clash-nyanpasu/frontend/nyanpasu/.vscode/extensions.json create mode 100644 clash-nyanpasu/frontend/nyanpasu/messages/en.json create mode 100644 clash-nyanpasu/frontend/nyanpasu/messages/ru.json create mode 100644 clash-nyanpasu/frontend/nyanpasu/messages/zh-cn.json create mode 100644 clash-nyanpasu/frontend/nyanpasu/messages/zh-tw.json create mode 100644 clash-nyanpasu/frontend/nyanpasu/project.inlang/.gitignore create mode 100644 clash-nyanpasu/frontend/nyanpasu/project.inlang/project_id create mode 100644 clash-nyanpasu/frontend/nyanpasu/project.inlang/settings.json create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/components/logo/animated-logo.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/components/providers/block-task-provider.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/components/router/animated-outlet.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/components/settings/system-proxy.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/components/ui/card.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/components/ui/input.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/components/ui/switch.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/hooks/use-is-moblie.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/hooks/use-lock-fn.ts create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/_modules/settings-card.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/_modules/settings-navigate.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/_modules/settings-title.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/about/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/clash-core/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/clash-external-controll/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/clash-filed/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/clash-port/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/clash-settings/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/debug-utils/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/index.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/nyanpasu-config/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/system-behavior/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/system-proxy/_modules/current-system-proxy.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/system-proxy/_modules/proxy-bypass-config.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/system-proxy/_modules/proxy-guard-config.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/system-proxy/_modules/proxy-guard-switch.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/system-proxy/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/system-service/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/user-interface/route.tsx create mode 100644 clash-nyanpasu/frontend/nyanpasu/src/pages/(experimental)/experimental/settings/web-ui/route.tsx create mode 100644 lede/target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3588-armsom-lm7.dtsi create mode 100644 lede/target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3588-armsom-w3.dts create mode 100755 openwrt-packages/luci-app-store/root/usr/libexec/istore/ipv4-bin/curl create mode 100755 openwrt-packages/luci-app-store/root/usr/libexec/istore/ipv4-bin/wget create mode 100644 openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue.htm create mode 100644 openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue_com.htm create mode 100644 openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_multivalue.htm create mode 100644 openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_multivalue_com.htm create mode 100644 small/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue.htm create mode 100644 small/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue_com.htm create mode 100644 small/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_multivalue.htm create mode 100644 small/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_multivalue_com.htm diff --git a/.github/update.log b/.github/update.log index 6aaec17849..cd3014c89f 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1215,3 +1215,4 @@ Update On Sun Dec 14 19:39:26 CET 2025 Update On Mon Dec 15 19:43:13 CET 2025 Update On Tue Dec 16 19:42:39 CET 2025 Update On Wed Dec 17 19:43:54 CET 2025 +Update On Thu Dec 18 19:42:36 CET 2025 diff --git a/clash-meta/go.mod b/clash-meta/go.mod index de5eacdccf..e15afab758 100644 --- a/clash-meta/go.mod +++ b/clash-meta/go.mod @@ -34,7 +34,7 @@ require ( github.com/metacubex/sing-shadowsocks v0.2.12 github.com/metacubex/sing-shadowsocks2 v0.2.7 github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 - github.com/metacubex/sing-tun v0.4.10 + github.com/metacubex/sing-tun v0.4.11 github.com/metacubex/sing-vmess v0.2.4 github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 diff --git a/clash-meta/go.sum b/clash-meta/go.sum index a07e350f93..2a01c51737 100644 --- a/clash-meta/go.sum +++ b/clash-meta/go.sum @@ -133,8 +133,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= -github.com/metacubex/sing-tun v0.4.10 h1:DllQTERAcqQyiEl4L/R7Ia0jCiSzZzikw2kL8N85p0E= -github.com/metacubex/sing-tun v0.4.10/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= +github.com/metacubex/sing-tun v0.4.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM= +github.com/metacubex/sing-tun v0.4.11/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I= github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= diff --git a/clash-nyanpasu/.gitignore b/clash-nyanpasu/.gitignore index 2e7171fec5..a73c547d73 100644 --- a/clash-nyanpasu/.gitignore +++ b/clash-nyanpasu/.gitignore @@ -5,7 +5,6 @@ dist-ssr *.local update.json scripts/_env.sh -.vscode .eslintcache .stylelintcache diff --git a/clash-nyanpasu/.vscode/extensions.json b/clash-nyanpasu/.vscode/extensions.json new file mode 100644 index 0000000000..479d676f8e --- /dev/null +++ b/clash-nyanpasu/.vscode/extensions.json @@ -0,0 +1,16 @@ +{ + "recommendations": [ + "inlang.vs-code-extension", + "editorconfig.editorconfig", + "vadimcn.vscode-lldb", + "bungcip.better-toml", + "dbaeumer.vscode-eslint", + "denoland.vscode-deno", + "esbenp.prettier-vscode", + "yoavbls.pretty-ts-errors", + "rust-lang.rust-analyzer", + "syler.sass-indented", + "stylelint.vscode-stylelint", + "bradlc.vscode-tailwindcss" + ] +} diff --git a/clash-nyanpasu/.vscode/settings.json b/clash-nyanpasu/.vscode/settings.json new file mode 100644 index 0000000000..7ce082b5ea --- /dev/null +++ b/clash-nyanpasu/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "tailwindCSS.experimental.classRegex": [ + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] + ], + "files.eol": "\n" +} diff --git a/clash-nyanpasu/backend/tauri/src/utils/resolve.rs b/clash-nyanpasu/backend/tauri/src/utils/resolve.rs index a47be596b0..23ee02d55f 100644 --- a/clash-nyanpasu/backend/tauri/src/utils/resolve.rs +++ b/clash-nyanpasu/backend/tauri/src/utils/resolve.rs @@ -240,7 +240,7 @@ pub fn create_window(app_handle: &AppHandle) { .title("Clash Nyanpasu") .fullscreen(false) .always_on_top(always_on_top) - .min_inner_size(600.0, 520.0); + .min_inner_size(400.0, 600.0); let win_state = &Config::verge().latest().window_size_state.clone(); match win_state { diff --git a/clash-nyanpasu/eslint.config.js b/clash-nyanpasu/eslint.config.js index d4e11b48cd..ceb584891e 100644 --- a/clash-nyanpasu/eslint.config.js +++ b/clash-nyanpasu/eslint.config.js @@ -80,7 +80,7 @@ export default tseslint.config( { files: ['**/*.{ts,tsx,mtsx}'], extends: [...tseslint.configs.recommended], - ignores, + ignores: [...ignores, '**/vite.config.ts', '**/tailwind.config.ts'], rules: { '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-explicit-any': 'warn', @@ -92,6 +92,19 @@ export default tseslint.config( }, }, }, + { + files: ['**/vite.config.ts', '**/tailwind.config.ts'], + extends: [...tseslint.configs.recommended], + rules: { + '@typescript-eslint/no-unused-vars': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + }, + languageOptions: { + parserOptions: { + project: './frontend/nyanpasu/tsconfig.node.json', + }, + }, + }, { files: ['**/*.{jsx,mjsx,tsx,mtsx}'], languageOptions: { diff --git a/clash-nyanpasu/frontend/nyanpasu/.vscode/extensions.json b/clash-nyanpasu/frontend/nyanpasu/.vscode/extensions.json new file mode 100644 index 0000000000..8cf06c2f61 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["inlang.vs-code-extension"] +} diff --git a/clash-nyanpasu/frontend/nyanpasu/messages/en.json b/clash-nyanpasu/frontend/nyanpasu/messages/en.json new file mode 100644 index 0000000000..ec447d095a --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/messages/en.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "language": "English", + "navbar_label_dashboard": "Dashboard", + "navbar_label_proxies": "Proxies", + "navbar_label_profiles": "Profiles", + "navbar_label_connections": "Connections", + "navbar_label_logs": "Logs", + "navbar_label_rules": "Rules", + "navbar_label_settings": "Settings", + "navbar_label_providers": "Providers", + "settings_system_proxy_title": "System Settings", + "settings_system_proxy_system_proxy_label": "System Proxy", + "settings_system_proxy_tun_mode_label": "TUN Mode", + "settings_system_proxy_proxy_guard_label": "System Proxy Guard", + "settings_system_proxy_proxy_guard_interval_label": "System Proxy Guard Interval", + "settings_system_proxy_proxy_bypass_label": "System Proxy Bypass", + "settings_system_proxy_current_system_proxy_label": "Current System Proxy", + "settings_user_interface_title": "User Interface", + "unit_seconds": "s" +} diff --git a/clash-nyanpasu/frontend/nyanpasu/messages/ru.json b/clash-nyanpasu/frontend/nyanpasu/messages/ru.json new file mode 100644 index 0000000000..353f3fc9d0 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/messages/ru.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "language": "Русский", + "navbar_label_dashboard": "Панель управления", + "navbar_label_proxies": "Прокси", + "navbar_label_profiles": "Профили", + "navbar_label_connections": "Соединения", + "navbar_label_logs": "Журналы", + "navbar_label_rules": "Правила", + "navbar_label_settings": "Настройки", + "navbar_label_providers": "Поставщики", + "settings_system_proxy_title": "Системный прокси", + "settings_system_proxy_system_proxy_label": "Системный прокси", + "settings_system_proxy_tun_mode_label": "TUN режим", + "settings_system_proxy_proxy_guard_label": "Охрана прокси", + "settings_system_proxy_proxy_guard_interval_label": "Интервал охраны прокси", + "settings_system_proxy_proxy_bypass_label": "Обход прокси", + "settings_system_proxy_current_system_proxy_label": "Текущий системный прокси", + "settings_user_interface_title": "Интерфейс пользователя", + "unit_seconds": "секунды" +} diff --git a/clash-nyanpasu/frontend/nyanpasu/messages/zh-cn.json b/clash-nyanpasu/frontend/nyanpasu/messages/zh-cn.json new file mode 100644 index 0000000000..d731200aab --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/messages/zh-cn.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "language": "简体中文", + "navbar_label_dashboard": "概览", + "navbar_label_proxies": "代理", + "navbar_label_profiles": "配置", + "navbar_label_connections": "连接", + "navbar_label_logs": "日志", + "navbar_label_rules": "规则", + "navbar_label_settings": "设置", + "navbar_label_providers": "资源", + "settings_system_proxy_title": "系统设置", + "settings_system_proxy_system_proxy_label": "系统代理", + "settings_system_proxy_tun_mode_label": "TUN 模式", + "settings_system_proxy_proxy_guard_label": "系统代理守卫", + "settings_system_proxy_proxy_guard_interval_label": "系统代理守卫间隔", + "settings_system_proxy_proxy_bypass_label": "系统代理绕过", + "settings_system_proxy_current_system_proxy_label": "当前系统代理", + "settings_user_interface_title": "用户界面", + "unit_seconds": "秒" +} diff --git a/clash-nyanpasu/frontend/nyanpasu/messages/zh-tw.json b/clash-nyanpasu/frontend/nyanpasu/messages/zh-tw.json new file mode 100644 index 0000000000..1a3dbaf0f9 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/messages/zh-tw.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "language": "繁體中文", + "navbar_label_dashboard": "概覽", + "navbar_label_proxies": "代理組", + "navbar_label_profiles": "配置", + "navbar_label_connections": "連接", + "navbar_label_logs": "日誌", + "navbar_label_rules": "規則", + "navbar_label_settings": "設置", + "navbar_label_providers": "資源", + "settings_system_proxy_title": "系統設置", + "settings_system_proxy_system_proxy_label": "系統代理", + "settings_system_proxy_tun_mode_label": "TUN 模式", + "settings_system_proxy_proxy_guard_label": "系統代理守衛", + "settings_system_proxy_proxy_guard_interval_label": "系統代理守衛間隔", + "settings_system_proxy_proxy_bypass_label": "系統代理繞過", + "settings_system_proxy_current_system_proxy_label": "當前系統代理", + "settings_user_interface_title": "使用者介面", + "unit_seconds": "秒" +} diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index 233e4ea670..99c06ffc43 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -14,6 +14,7 @@ "@dnd-kit/sortable": "10.0.0", "@dnd-kit/utilities": "3.2.2", "@emotion/styled": "11.14.1", + "@inlang/paraglide-js": "2.7.0", "@juggle/resize-observer": "3.4.0", "@material/material-color-utilities": "0.3.0", "@mui/icons-material": "7.3.5", @@ -24,6 +25,7 @@ "@nyanpasu/ui": "workspace:^", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-slot": "1.2.4", + "@radix-ui/react-switch": "^1.2.6", "@tailwindcss/postcss": "4.1.17", "@tanstack/router-zod-adapter": "1.81.5", "@tauri-apps/api": "2.8.0", @@ -84,7 +86,7 @@ "clsx": "2.1.1", "core-js": "3.46.0", "filesize": "11.0.13", - "meta-json-schema": "1.19.16", + "meta-json-schema": "1.19.17", "monaco-yaml": "5.4.0", "nanoid": "5.1.6", "sass-embedded": "1.93.3", diff --git a/clash-nyanpasu/frontend/nyanpasu/project.inlang/.gitignore b/clash-nyanpasu/frontend/nyanpasu/project.inlang/.gitignore new file mode 100644 index 0000000000..5e46596759 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/project.inlang/.gitignore @@ -0,0 +1 @@ +cache \ No newline at end of file diff --git a/clash-nyanpasu/frontend/nyanpasu/project.inlang/project_id b/clash-nyanpasu/frontend/nyanpasu/project.inlang/project_id new file mode 100644 index 0000000000..0e2e346f22 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/project.inlang/project_id @@ -0,0 +1 @@ +hmmAR8W6ML07bAYbAQ \ No newline at end of file diff --git a/clash-nyanpasu/frontend/nyanpasu/project.inlang/settings.json b/clash-nyanpasu/frontend/nyanpasu/project.inlang/settings.json new file mode 100644 index 0000000000..0596557864 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/project.inlang/settings.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://inlang.com/schema/project-settings", + "baseLocale": "en", + "locales": ["en", "zh-cn", "zh-tw", "ru"], + "modules": [ + "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js" + ], + "plugin.inlang.messageFormat": { + "pathPattern": "./messages/{locale}.json" + } +} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/assets/styles/tailwind.css b/clash-nyanpasu/frontend/nyanpasu/src/assets/styles/tailwind.css index 71333853fa..96d9b30306 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/assets/styles/tailwind.css +++ b/clash-nyanpasu/frontend/nyanpasu/src/assets/styles/tailwind.css @@ -76,6 +76,24 @@ --fallback-bg: --value(--color-*); } +@layer components { + svg.logo-colorized #element { + fill: var(--color-primary); + } + + svg.logo-colorized #bg { + fill: var(--color-surface); + } + + .dark svg.logo-colorized #element { + fill: var(--color-on-primary); + } + + .dark svg.logo-colorized #bg { + fill: var(--color-on-surface); + } +} + @keyframes progress-spin { 12.5% { transform: rotate(135deg); diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logo/animated-logo.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/logo/animated-logo.tsx new file mode 100644 index 0000000000..d5fd59e147 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logo/animated-logo.tsx @@ -0,0 +1,86 @@ +import { motion, useAnimationControls } from 'framer-motion' +import { + ComponentProps, + KeyboardEvent, + useCallback, + useEffect, + useRef, +} from 'react' +import LogoSvg from '@/assets/image/logo.svg?react' + +export default function AnimatedLogo( + props: Omit, 'children'>, +) { + const logoControls = useAnimationControls() + const intensityRef = useRef(0) + const resetTimeoutRef = useRef | null>(null) + + useEffect(() => { + return () => { + if (resetTimeoutRef.current) { + clearTimeout(resetTimeoutRef.current) + } + } + }, []) + + const scheduleReset = useCallback(() => { + if (resetTimeoutRef.current) { + clearTimeout(resetTimeoutRef.current) + } + + // reset intensity after 1 seconds + resetTimeoutRef.current = setTimeout(() => { + intensityRef.current = 0 + logoControls.start({ + scale: 1, + transition: { duration: 0.4, ease: 'easeInOut' }, + }) + }, 1000) + }, [logoControls]) + + const triggerShake = useCallback(() => { + const nextIntensity = Math.min(12, intensityRef.current + 1) + intensityRef.current = nextIntensity + + // calculate the scale based on the intensity + const scale = 1 + Math.min(0.75, Math.pow(nextIntensity, 1.3) * 0.03) + + // apply the animation to logo + logoControls.start({ + rotate: [-10, 10, -8, 8, 0], + scale, + transition: { duration: 0.6, ease: 'easeInOut' }, + }) + + scheduleReset() + }, [logoControls, scheduleReset]) + + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + triggerShake() + } + }, + [triggerShake], + ) + + return ( + + + + ) +} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/providers/block-task-provider.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/providers/block-task-provider.tsx new file mode 100644 index 0000000000..bf37f49347 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/providers/block-task-provider.tsx @@ -0,0 +1,138 @@ +import { + createContext, + PropsWithChildren, + useCallback, + useContext, + useRef, + useState, +} from 'react' +import { useLockFn } from '@/hooks/use-lock-fn' + +type BlockTaskStatus = 'idle' | 'pending' | 'success' | 'error' + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +interface BlockTask { + id: string + status: BlockTaskStatus + data?: T + error?: Error + startTime: number + endTime?: number +} + +interface BlockTaskContextType { + tasks: Record + run: (key: string, fn: () => Promise) => Promise + getTask: (key: string) => BlockTask | undefined + clearTask: (key: string) => void +} + +const BlockContext = createContext(null) + +export const useBlockTaskContext = () => { + const context = useContext(BlockContext) + + if (!context) { + throw new Error('useBlockContext must be used within a BlockProvider') + } + + return context +} + +export const useBlockTask = (key: string, fn: () => Promise) => { + const { run, tasks } = useBlockTaskContext() + + const execute = useLockFn(async () => { + return await run(key, fn) + }) + + return { + execute, + isPending: tasks[key]?.status === 'pending', + isSuccess: tasks[key]?.status === 'success', + isError: tasks[key]?.status === 'error', + data: tasks[key]?.data, + error: tasks[key]?.error, + } +} + +export const BlockTaskProvider = ({ children }: PropsWithChildren) => { + const [tasks, setTasks] = useState>({}) + + const tasksRef = useRef>({}) + + const run = useCallback( + async (key: string, fn: () => Promise): Promise => { + const task: BlockTask = { + id: key, + status: 'pending', + startTime: Date.now(), + } + + setTasks((prev) => ({ ...prev, [key]: task })) + tasksRef.current[key] = task + + try { + const data = await fn() + + const successTask: BlockTask = { + ...task, + status: 'success', + data, + endTime: Date.now(), + } + + setTasks((prev) => ({ + ...prev, + [key]: successTask, + })) + + tasksRef.current[key] = successTask + + return data + } catch (error) { + const errorTask: BlockTask = { + ...task, + status: 'error', + error: error instanceof Error ? error : new Error(String(error)), + endTime: Date.now(), + } + + setTasks((prev) => ({ + ...prev, + [key]: errorTask, + })) + + tasksRef.current[key] = errorTask + + throw error + } + }, + [], + ) + + const getTask = useCallback((key: string) => tasks[key], [tasks]) + + const clearTask = useCallback((key: string) => { + setTasks((prev) => { + const newTasks = { ...prev } + delete newTasks[key] + return newTasks + }) + + delete tasksRef.current[key] + }, []) + + return ( + + {children} + + ) +} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/router/animated-outlet.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/router/animated-outlet.tsx new file mode 100644 index 0000000000..b228b0ed7a --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/router/animated-outlet.tsx @@ -0,0 +1,87 @@ +import { AnimatePresence, motion, useIsPresent } from 'framer-motion' +import { cloneDeep } from 'lodash-es' +import { ComponentProps, useContext, useRef } from 'react' +import { + getRouterContext, + Outlet, + useMatch, + useMatches, +} from '@tanstack/react-router' + +export function AnimatedOutlet({ + ref, + ...props +}: ComponentProps) { + const isPresent = useIsPresent() + + const matches = useMatches() + const prevMatches = useRef(matches) + + const RouterContext = getRouterContext() + const routerContext = useContext(RouterContext) + + let renderedContext = routerContext + + if (isPresent) { + prevMatches.current = cloneDeep(matches) + } else { + renderedContext = cloneDeep(routerContext) + renderedContext.__store.state.matches = [ + ...matches.map((m, i) => ({ + ...(prevMatches.current[i] || m), + id: m.id, + })), + ...prevMatches.current.slice(matches.length), + ] + } + + return ( + + + + + + ) +} + +export function AnimatedOutletPreset(props: ComponentProps) { + const matches = useMatches() + const match = useMatch({ strict: false }) + const nextMatchIndex = matches.findIndex((d) => d.id === match.id) + 1 + const nextMatch = matches[nextMatchIndex] + + const id = nextMatch ? nextMatch.id : '' + + return ( + + + + ) +} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/settings/system-proxy.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/settings/system-proxy.tsx new file mode 100644 index 0000000000..b49ff06dd5 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/settings/system-proxy.tsx @@ -0,0 +1,90 @@ +import NetworkPing from '~icons/material-symbols/network-ping-rounded' +import SettingsEthernet from '~icons/material-symbols/settings-ethernet-rounded' +import { useBlockTask } from '@/components/providers/block-task-provider' +import { Button, ButtonProps } from '@/components/ui/button' +import { CircularProgress } from '@/components/ui/progress' +import { m } from '@/paraglide/messages' +import { useSetting } from '@nyanpasu/interface' +import { cn } from '@nyanpasu/ui' + +const ProxyButton = ({ + className, + isActive, + loading, + children, + ...props +}: ButtonProps & { + isActive?: boolean +}) => { + return ( + + ) +} + +export const SystemProxyButton = ( + props: Omit, +) => { + const systemProxy = useSetting('enable_system_proxy') + + const { execute, isPending } = useBlockTask('system-proxy', async () => { + await systemProxy.upsert(!systemProxy.value) + }) + + return ( + + + {m.settings_system_proxy_system_proxy_label()} + + ) +} + +export const TunModeButton = ( + props: Omit, +) => { + const tunMode = useSetting('enable_tun_mode') + + const { execute, isPending } = useBlockTask('tun-mode', async () => { + await tunMode.upsert(!tunMode.value) + }) + + return ( + + + {m.settings_system_proxy_tun_mode_label()} + + ) +} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/ui/button.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/ui/button.tsx index 7945946126..58cc030dba 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/ui/button.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/ui/button.tsx @@ -14,7 +14,7 @@ export const buttonVariants = cva( 'relative overflow-hidden', 'h-10 text-sm font-medium', 'rounded-full', - 'transition-[background-color,color,shadow]', + 'transition-[background-color,color,shadow,filter]', ], { variants: { @@ -25,6 +25,35 @@ export const buttonVariants = cva( 'bg-transparent-fallback-surface dark:bg-transparent-fallback-on-surface', 'hover:bg-primary-container dark:hover:bg-surface-variant', ], + raised: [ + 'px-6', + 'text-primary dark:text-on-surface', + 'shadow-xs hover:shadow-sm focus:shadow-sm', + 'bg-surface', + 'hover:bg-surface-variant', + ], + stroked: [ + 'px-6', + 'text-primary', + 'border border-primary', + 'bg-transparent-fallback-surface dark:bg-transparent-fallback-on-surface', + 'hover:bg-primary-container dark:hover:bg-surface-variant', + ], + flat: [ + 'px-6', + 'text-surface dark:text-on-surface', + 'bg-primary dark:bg-primary-container', + 'dark:hover:bg-on-primary', + ], + fab: [ + 'px-4 h-14', + 'rounded-2xl', + 'shadow-sm', + 'text-on-primary-container dark:text-on-primary-container', + 'bg-primary-container dark:bg-on-primary', + 'hover:shadow-md', + 'hover:brightness-95 dark:hover:brightness-105', + ], }, disabled: { true: 'cursor-not-allowed shadow-none hover:shadow-none focus:shadow-none', @@ -36,10 +65,41 @@ export const buttonVariants = cva( }, }, compoundVariants: [ + { + variant: 'basic', + disabled: true, + className: 'text-zinc-900/40 hover:bg-transparent', + }, + { + variant: 'raised', + disabled: true, + className: 'bg-gray-900/20 text-zinc-900/40 hover:bg-gray-900/20', + }, + { + variant: 'stroked', + disabled: true, + className: 'text-zinc-900/40 hover:bg-transparent border-zinc-300', + }, + { + variant: 'flat', + disabled: true, + className: 'bg-gray-900/20 text-gray-900/40 hover:bg-primary', + }, + { + variant: 'fab', + disabled: true, + className: + 'bg-gray-900/20 text-gray-900/40 hover:brightness-100 hover:shadow-container-xl', + }, { icon: true, className: 'w-10', }, + { + variant: 'fab', + icon: true, + className: 'w-14', + }, ], defaultVariants: { variant: 'basic', diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/ui/card.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/ui/card.tsx new file mode 100644 index 0000000000..457c04e579 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/ui/card.tsx @@ -0,0 +1,186 @@ +import { cva, type VariantProps } from 'class-variance-authority' +import { createContext, HTMLAttributes, useContext } from 'react' +import { cn } from '@nyanpasu/ui' +import { Slot } from '@radix-ui/react-slot' + +export const cardVariants = cva('rounded-3xl text-on-surface overflow-hidden', { + variants: { + variant: { + basic: ['shadow-sm', 'bg-surface dark:bg-surface'], + raised: ['shadow-sm', 'bg-primary-container dark:bg-on-primary'], + outline: [ + 'bg-surface dark:bg-surface', + 'border border-outline-variant dark:border-outline-variant', + ], + }, + }, + defaultVariants: { + variant: 'basic', + }, +}) + +export type CardVariantsProps = VariantProps + +export const cardContentVariants = cva(['flex flex-col gap-4 p-4']) + +export type CardContentVariantsProps = VariantProps + +export const cardHeaderVariants = cva( + ['flex items-center gap-4 text-xl', 'px-4'], + { + variants: { + variant: { + basic: 'border-surface-variant dark:border-surface-variant', + raised: 'border-inverse-primary dark:border-primary-container', + outline: 'border-outline-variant dark:border-outline-variant', + }, + divider: { + true: 'border-b py-4 ', + false: 'pt-4', + }, + }, + defaultVariants: { + divider: false, + variant: 'basic', + }, + }, +) + +export type CardHeaderVariantsProps = VariantProps + +export const cardFooterVariants = cva( + ['flex flex-row-reverse items-center gap-4', 'px-2'], + { + variants: { + variant: { + basic: 'border-surface-variant dark:border-surface-variant', + raised: 'border-inverse-primary dark:border-primary-container', + outline: 'border-outline-variant dark:border-outline-variant', + }, + divider: { + true: 'border-t py-2', + false: 'pb-2', + }, + }, + defaultVariants: { + divider: false, + variant: 'basic', + }, + }, +) + +export type CardFooterVariantsProps = VariantProps + +type CardContextType = { + variant: CardVariantsProps['variant'] + divider: CardHeaderVariantsProps['divider'] & + CardFooterVariantsProps['divider'] +} + +const CardContext = createContext(null) + +const useCardContext = () => { + const context = useContext(CardContext) + + if (!context) { + throw new Error('useCardContext must be used within a CardProvider') + } + + return context +} + +export interface CardProps + extends + HTMLAttributes, + CardVariantsProps, + Partial { + asChild?: boolean +} + +export const Card = ({ + variant, + divider, + asChild, + className, + ...props +}: CardProps) => { + const Comp = asChild ? Slot : 'div' + + return ( + + + + ) +} + +export type CardContentProps = HTMLAttributes & + CardContentVariantsProps + +export const CardContent = ({ className, ...props }: CardContentProps) => { + return
+} + +export type CardHeaderProps = HTMLAttributes & + CardHeaderVariantsProps & { + asChild?: boolean + } + +export const CardHeader = ({ + divider, + variant, + className, + ...props +}: CardHeaderProps) => { + const context = useCardContext() + + return ( +
+ ) +} + +export interface CardFooterProps + extends HTMLAttributes, CardFooterVariantsProps {} + +export const CardFooter = ({ + divider, + variant, + className, + ...props +}: CardFooterProps) => { + const context = useCardContext() + + return ( +
+ ) +} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/ui/circle.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/ui/circle.tsx index da2381e4a5..f52333865d 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/ui/circle.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/ui/circle.tsx @@ -46,7 +46,10 @@ export function CircleSVG({ className, ...props }: ComponentProps<'svg'>) { return ( + +export const inputVariants = cva( + [ + 'peer', + 'w-full border-none p-0', + 'bg-transparent placeholder-transparent outline-hidden', + ], + { + variants: { + variant: { + filled: '', + outlined: '', + }, + haveValue: { + true: '', + false: '', + }, + haveLabel: { + true: '', + false: '', + }, + }, + compoundVariants: [ + { + variant: 'filled', + haveValue: true, + haveLabel: true, + className: 'mt-3', + }, + ], + defaultVariants: { + variant: 'filled', + haveValue: false, + haveLabel: false, + }, + }, +) + +export type InputVariants = VariantProps + +export const inputLabelVariants = cva( + [ + 'absolute', + 'left-4 top-4', + 'pointer-events-none', + 'text-base select-none', + // TODO: only transition position, not text color + 'transition-all duration-200', + ], + { + variants: { + variant: { + filled: [ + 'group-data-[state=open]:top-2 group-data-[state=open]:dark:text-surface', + 'group-data-[state=open]:text-xs group-data-[state=open]:text-primary', + ], + outlined: [ + 'group-data-[state=open]:-top-2', + 'group-data-[state=open]:text-sm', + 'group-data-[state=open]:text-primary', + + 'dark:group-data-[state=open]:text-inverse-primary', + 'dark:group-data-[state=closed]:text-primary-container', + + // "before:absolute before:inset-0 before:content-['']", + // "before:-z-10 before:-mx-1", + // "before:bg-transparent ", + // "before:inline-block", + ], + }, + focus: { + true: '', + false: '', + }, + }, + compoundVariants: [ + { + variant: 'filled', + focus: true, + className: 'top-2 text-xs', + }, + { + variant: 'outlined', + focus: true, + className: '-top-2 text-sm', + }, + ], + defaultVariants: { + variant: 'filled', + focus: false, + }, + }, +) + +export type InputLabelVariants = VariantProps + +export const inputLineVariants = cva('', { + variants: { + variant: { + filled: [ + 'absolute inset-x-0 bottom-0 w-full border-b border-on-primary-container', + 'transition-all duration-200', + // pseudo elements be overlay parent element, will not affect the box size + 'after:absolute after:inset-x-0 after:bottom-0 after:z-10', + "after:scale-x-0 after:border-b-2 after:opacity-0 after:content-['']", + 'after:transition-all after:duration-200', + 'after:border-primary dark:after:border-on-primary-container', + // sync parent group state, state from radix-ui + 'group-data-[state=open]:border-b-0', + 'group-data-[state=open]:after:scale-x-100', + 'group-data-[state=open]:after:opacity-100', + 'peer-focus:border-b-0', + 'peer-focus:after:scale-x-100', + 'peer-focus:after:opacity-100', + ], + // hidden line for outlined variant + outlined: 'hidden', + }, + }, + defaultVariants: { + variant: 'filled', + }, +}) + +export type InputLineVariants = VariantProps + +export const inputLabelFieldsetVariants = cva('pointer-events-none', { + variants: { + variant: { + // only for outlined variant + filled: 'hidden', + outlined: [ + 'absolute inset-0 text-left', + 'rounded transition-all duration-200', + // may open border width will be 1.5, idk + 'group-data-[state=closed]:border', + 'group-data-[state=open]:border-2', + 'peer-not-focus:border', + 'peer-focus:border-2', + // different material web border color, i think this looks better + 'group-data-[state=closed]:border-primary-container', + 'group-data-[state=open]:border-primary', + 'peer-not-focus:border-primary-container', + 'peer-focus:border-primary', + // dark must be prefixed + 'dark:group-data-[state=closed]:border-primary-container', + 'dark:group-data-[state=open]:border-inverse-primary', + 'dark:peer-not-focus:border-primary-container', + 'dark:peer-focus:border-inverse-primary', + ], + }, + }, + defaultVariants: { + variant: 'filled', + }, +}) + +export type InputLabelFieldsetVariants = VariantProps< + typeof inputLabelFieldsetVariants +> + +export const inputLabelLegendVariants = cva('', { + variants: { + variant: { + // only for outlined variant + filled: 'hidden', + outlined: 'invisible ml-2 px-2 text-sm h-0', + }, + haveValue: { + true: '', + false: '', + }, + }, + compoundVariants: [ + { + variant: 'outlined', + haveValue: false, + className: ['group-data-[state=closed]:hidden', 'group-not-focus:hidden'], + }, + ], + defaultVariants: { + variant: 'filled', + haveValue: false, + }, +}) + +export type InputLabelLegendVariants = VariantProps< + typeof inputLabelLegendVariants +> + +type InputContextType = { + haveLabel?: boolean + haveValue?: boolean +} & InputContainerVariants + +const InputContext = React.createContext(null) + +const useInputContext = () => { + const context = React.useContext(InputContext) + + if (!context) { + throw new Error('InputContext is undefined') + } + + return context +} + +export const InputContainer = ({ + className, + ...props +}: React.ComponentProps<'div'>) => { + const { variant } = useInputContext() + + return ( +
+ ) +} + +export const InputLine = ({ + className, + ...props +}: React.ComponentProps<'input'>) => { + const { variant } = useInputContext() + + return ( +
+ ) +} + +export type InputProps = React.ComponentProps<'input'> & { + label?: string +} & InputContainerVariants + +export const Input = ({ + variant, + className, + label, + children, + onChange, + ...props +}: InputProps) => { + const [haveValue, setHaveValue] = React.useState(false) + + const haveLabel = useCreation(() => { + if (label) { + return true + } + + if (React.isValidElement(children)) { + if (typeof children.type !== 'string') { + if ('displayName' in children.type) { + if (children.type.displayName === InputLabel.displayName) { + return true + } + } + } + } + + return false + }, []) + + useEffect(() => { + if (props.value || props.defaultValue) { + setHaveValue(true) + } else { + setHaveValue(false) + } + }, [props.value, props.defaultValue]) + + const handleChange = (event: React.ChangeEvent) => { + setHaveValue(event.target.value.length > 0) + onChange?.(event) + } + + useEffect(() => { + console.log('haveValue', haveValue) + }, [haveValue]) + + return ( + + + + + {label && ( + <> +
+ + {label} + +
+ + {label} + + )} + + {children} + + +
+
+ ) +} + +Input.displayName = 'Input' + +export const InputLabel = ({ + className, + ...props +}: React.ComponentProps<'label'>) => { + const { haveValue, variant } = useInputContext() + + return ( +