Compare commits

..

19 Commits

Author SHA1 Message Date
Maofeng f442c07dbf chore(zed): update workspace editor settings 2026-09-20 15:58:00 +08:00
Maofeng 93e7941dc4 fix(docs): include section in article breadcrumb 2026-09-20 15:57:47 +08:00
Maofeng e70ad64b19 fix(docs): adapt code blocks to color scheme 2026-09-20 15:57:47 +08:00
Maofeng 2dbf51ba24 chore(format): normalize repository files 2026-09-20 15:54:12 +08:00
Maofeng dc80bd8ae0 feat(docs): add bilingual package documentation site 2026-09-20 15:53:54 +08:00
Maofeng 60f6f1fb1a chore(repo): add shared editor defaults 2026-09-20 15:53:07 +08:00
Maofeng a03b04ed3f fix(lint): resolve remaining static analysis warnings 2026-09-20 15:53:02 +08:00
Maofeng f246d81088 feat(web): add dashboard composer preview 2026-09-20 15:52:52 +08:00
Maofeng 6cd981d6a2 feat(i18n): standardize bilingual catalogs and tooling 2026-09-20 15:52:48 +08:00
Maofeng ea00a71112 refactor(icons): migrate workspace to Icones 2026-09-20 15:52:22 +08:00
Maofeng 1fc56b6982 refactor(search): extract search into workspace package 2026-09-20 15:52:09 +08:00
Maofeng e02c3dceb5 feat(preferences): add extensible preference store 2026-09-20 15:52:03 +08:00
Maofeng 2043c7d777 chore(web): move the development server to port 7100
Use port 7100 for the local Vite server while retaining external host access.
2026-08-08 21:14:37 +08:00
Maofeng 6cd7d436ef feat(web): add login and customer chat demos
Add a responsive login screen using the shared appearance controls and a browser-only customer-service simulator for the chat workspace. Register both routes and include mock media resolution, message sending, and earlier-history loading for interactive verification.
2026-08-08 21:14:31 +08:00
Maofeng 87df34a8ab feat(blocks): add Mini Program customer service workspace
Add a reusable one-to-one chat workspace modeled on WeChat Mini Program customer-service messages, covering text, image, link, and Mini Program page payloads. Include conversation search, message history, responsive navigation, media resolution, Unicode emoji selection, Lexical plain-text composition, attachments, localized copy, and tests for rendering and send behavior.
2026-08-08 21:14:26 +08:00
Maofeng 2c05cfa86d refactor(blocks): refine the responsive application shell
Present chat threads in a drawer on mobile and a popover on larger screens, with shared responsive triggers and automatic close after selection. Align header action icon sizing and tighten navigation spacing for the updated shell.
2026-08-08 21:14:04 +08:00
Maofeng 5fced3817c feat(blocks): add reusable theme toggle button
Expose a shared appearance control that resolves system color preference, reacts to preference changes, and toggles between light and dark modes. Disable interaction until client mounting to keep the initial rendered state stable.
2026-08-08 21:13:54 +08:00
Maofeng c64864b3b5 feat(lexical): support styling declarative actions
Accept className on action declarations and carry it through compiled actions, default toolbar controls, menu items, custom renderers, and custom controls. Keep preset action ordering consistent across toolbar and bubble placements, with coverage for default and custom rendering paths.
2026-08-08 21:13:49 +08:00
Maofeng bc84912e48 fix(ui): preserve popover arrows around scrollable content
Keep the anchored surface overflow-visible so the arrow is not clipped. Move height-constrained scrolling into an inner viewport and expose viewportClassName for content-specific styling.
2026-08-08 21:13:43 +08:00
386 changed files with 12727 additions and 2956 deletions
+8
View File
@@ -0,0 +1,8 @@
---
name: "icones"
description: "The same icon vocabulary, with an API that belongs in your React application."
---
Up-to-date icones documentation for this project. Read it before working with icones:
https://icones.go-slim.dev/llms/react/llms-full.txt
+17
View File
@@ -0,0 +1,17 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120
tab_width = 2
[*.{json,md}]
insert_final_newline = false
[*.md]
indent_size = 4
+1 -1
View File
@@ -19,4 +19,4 @@
},
"tabWidth": 2,
"trailingComma": "es5"
}
}
+1 -1
View File
@@ -30,4 +30,4 @@
"import",
"vitest"
]
}
}
+3 -2
View File
@@ -22,5 +22,6 @@
"!vscode-css-language-server"
]
}
}
}
},
"format_on_save": "off"
}
+1 -1
View File
@@ -20,4 +20,4 @@
"rtl": true,
"menuColor": "default",
"menuAccent": "subtle"
}
}
+10 -5
View File
@@ -1,6 +1,6 @@
{
"sourceLocale": "en",
"locales": ["en", "zh-Hans"],
"sourceLocale": "en-US",
"locales": ["en-US", "zh-Hans"],
"catalogPath": "src/locales/{locale}/messages",
"catalogSources": [
"@workspace/ui/locales/{locale}",
@@ -10,9 +10,14 @@
"@workspace/blocks/media/locales/{locale}",
"@workspace/blocks/navigation/locales/{locale}",
"@workspace/blocks/notifications/locales/{locale}",
"@workspace/blocks/search/locales/{locale}",
"@workspace/search/locales/{locale}",
"@workspace/lexical/locales/{locale}"
],
"include": ["src", "../../packages/blocks/src", "../../packages/ui/src"],
"include": [
"src",
"../../packages/blocks/src",
"../../packages/search/src",
"../../packages/ui/src"
],
"exclude": ["**/*.test.{ts,tsx}", "**/node_modules/**"]
}
}
+4 -5
View File
@@ -19,7 +19,7 @@
"@base-ui/react": "^1.6.0",
"@base-ui/utils": "^0.3.1",
"@floating-ui/utils": "^0.2.12",
"@iconify/react": "^6.0.2",
"@icones/react": "^0.0.1",
"@lingui/core": "^6",
"@tanstack/react-form": "^1.33.2",
"@tanstack/react-hotkeys": "^0.10.0",
@@ -27,12 +27,10 @@
"@tanstack/react-router": "^1.170.18",
"@tanstack/react-start": "^1.168.32",
"@workspace/blocks": "workspace:*",
"@workspace/icons": "workspace:*",
"@workspace/i18n": "workspace:*",
"@workspace/lexical": "workspace:*",
"@workspace/search": "workspace:*",
"@workspace/ui": "workspace:*",
"country-flag-icons": "^1.6.20",
"lucide-react": "^1.27.0",
"react": "^19.2.6",
"react-day-picker": "^10.0.1",
"react-dom": "^19.2.6",
@@ -40,6 +38,7 @@
},
"devDependencies": {
"@tailwindcss/vite": "^4",
"@icones/vite": "^0.0.1",
"@testing-library/react": "^16.3.2",
"@types/node": "^24",
"@types/react": "^19",
@@ -51,4 +50,4 @@
"vite": "^8",
"vitest": "^4.1.10"
}
}
}
+19
View File
@@ -0,0 +1,19 @@
import type React from "react"
type Props = Omit<React.ComponentProps<"svg">, "role" | "viewBox" | "xmlns">
export function Logo(props: Props) {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="#7AB55C"
aria-hidden="true"
{...props}
>
<title>Shopify</title>
<path d="M15.337 23.979l7.216-1.561s-2.604-17.613-2.625-17.73c-.018-.116-.114-.192-.211-.192s-1.929-.136-1.929-.136-1.275-1.274-1.439-1.411c-.045-.037-.075-.057-.121-.074l-.914 21.104h.023zM11.71 11.305s-.81-.424-1.774-.424c-1.447 0-1.504.906-1.504 1.141 0 1.232 3.24 1.715 3.24 4.629 0 2.295-1.44 3.76-3.406 3.76-2.354 0-3.54-1.465-3.54-1.465l.646-2.086s1.245 1.066 2.28 1.066c.675 0 .975-.545.975-.932 0-1.619-2.654-1.694-2.654-4.359-.034-2.237 1.571-4.416 4.827-4.416 1.257 0 1.875.361 1.875.361l-.945 2.715-.02.01zM11.17.83c.136 0 .271.038.405.135-.984.465-2.064 1.639-2.508 3.992-.656.213-1.293.405-1.889.578C7.697 3.75 8.951.84 11.17.84V.83zm1.235 2.949v.135c-.754.232-1.583.484-2.394.736.466-1.777 1.333-2.645 2.085-2.971.193.501.309 1.176.309 2.1zm.539-2.234c.694.074 1.141.867 1.429 1.755-.349.114-.735.231-1.158.366v-.252c0-.752-.096-1.371-.271-1.871v.002zm2.992 1.289c-.02 0-.06.021-.078.021s-.289.075-.714.21c-.423-1.233-1.176-2.37-2.508-2.37h-.115C12.135.209 11.669 0 11.265 0 8.159 0 6.675 3.877 6.21 5.846c-1.194.365-2.063.636-2.16.674-.675.213-.694.232-.772.87-.075.462-1.83 14.063-1.83 14.063L15.009 24l.927-21.166z" />
</svg>
)
}
+1 -1
View File
@@ -8,7 +8,7 @@ import {
TriangleAlertIcon,
OctagonXIcon,
Loader2Icon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
const Toaster = ({ ...props }: ToasterProps) => {
const [themeMode] = useUiState("theme-mode")
+77 -137
View File
@@ -1,63 +1,3 @@
import {
Alert02Icon,
Analytics01Icon,
AnalyticsUpIcon,
AppWindowIcon,
ArrowDataTransferHorizontalIcon,
BubbleChatIcon,
Calculator01Icon,
CallMissed01Icon,
CancelCircleIcon,
Chart01Icon,
ChartIncreaseIcon,
CheckmarkBadge01Icon,
CheckmarkCircle01Icon,
ClipboardListIcon,
Clock01Icon,
CodeSquareIcon,
Compass01Icon,
CreditCardIcon,
CrownIcon,
Delete02Icon,
DeliveryTruck01Icon,
Discount01Icon,
File01Icon,
GalleryHorizontalIcon,
GiftIcon,
HistoryIcon,
IdentityCardIcon,
InboxDownloadIcon,
InboxUploadIcon,
Invoice03Icon,
Key01Icon,
LinkCircleIcon,
Location01Icon,
LockKeyIcon,
Medal01Icon,
MoneySendSquareIcon,
Note01Icon,
Package02Icon,
PackageIcon,
Refresh01Icon,
RoadLocation01Icon,
SecurityWarningIcon,
Settings01Icon,
Settings03Icon,
Share01Icon,
ShoppingBag01Icon,
ShoppingCart01Icon,
StarCircleIcon,
StarIcon,
Store01Icon,
Structure01Icon,
Task01Icon,
Ticket01Icon,
UserGroupIcon,
UserShield01Icon,
Wallet01Icon,
WalletCardsIcon,
} from "@workspace/icons"
import type { NavigationGroup } from "@workspace/blocks/navigation"
import { navigationMessages } from "./navigation-messages"
@@ -71,19 +11,19 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "overview-dashboard",
to: "/",
label: navigationMessages["overview-dashboard"],
icon: Analytics01Icon,
icon: "huge:analytics-01",
},
{
id: "overview-realtime",
to: "/analytics/realtime",
label: navigationMessages["overview-realtime"],
icon: AnalyticsUpIcon,
icon: "huge:analytics-up",
},
{
id: "overview-tasks",
to: "/workspace/tasks",
label: navigationMessages["overview-tasks"],
icon: Task01Icon,
icon: "huge:task-01",
},
],
},
@@ -94,42 +34,42 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
{
id: "catalog-products",
label: navigationMessages["catalog-products"],
icon: ShoppingBag01Icon,
icon: "huge:shopping-bag-01",
items: [
{
id: "catalog-products-list",
to: "/catalog/products/list",
label: navigationMessages["catalog-products-list"],
icon: ShoppingBag01Icon,
icon: "huge:shopping-bag-01",
},
{
id: "catalog-products-drafts",
to: "/catalog/products/drafts",
label: navigationMessages["catalog-products-drafts"],
icon: File01Icon,
icon: "huge:file-01",
},
{
id: "catalog-products-bundles",
to: "/catalog/products/bundles",
label: navigationMessages["catalog-products-bundles"],
icon: Package02Icon,
icon: "huge:package-02",
},
{
id: "catalog-products-attributes",
label: navigationMessages["catalog-products-attributes"],
icon: Settings03Icon,
icon: "huge:settings-03",
items: [
{
id: "catalog-product-specifications",
to: "/catalog/products/attributes/specifications",
label: navigationMessages["catalog-product-specifications"],
icon: ClipboardListIcon,
icon: "huge:clipboard",
},
{
id: "catalog-product-brands",
to: "/catalog/products/attributes/brands",
label: navigationMessages["catalog-product-brands"],
icon: StarIcon,
icon: "huge:star",
},
],
},
@@ -139,19 +79,19 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "catalog-categories",
to: "/catalog/categories",
label: navigationMessages["catalog-categories"],
icon: ClipboardListIcon,
icon: "huge:clipboard",
items: [
{
id: "catalog-category-tree",
to: "/catalog/categories/tree",
label: navigationMessages["catalog-category-tree"],
icon: Structure01Icon,
icon: "huge:structure-01",
},
{
id: "catalog-category-navigation",
to: "/catalog/categories/navigation",
label: navigationMessages["catalog-category-navigation"],
icon: Compass01Icon,
icon: "huge:compass-01",
},
],
},
@@ -159,37 +99,37 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "inventory-center",
to: "/inventory",
label: navigationMessages["inventory-center"],
icon: PackageIcon,
icon: "huge:package",
items: [
{
id: "inventory-overview",
to: "/inventory/overview",
label: navigationMessages["inventory-overview"],
icon: Chart01Icon,
icon: "huge:chart-01",
},
{
id: "inventory-inbound",
to: "/inventory/inbound",
label: navigationMessages["inventory-inbound"],
icon: InboxDownloadIcon,
icon: "huge:inbox-download",
},
{
id: "inventory-outbound",
to: "/inventory/outbound",
label: navigationMessages["inventory-outbound"],
icon: InboxUploadIcon,
icon: "huge:inbox-upload",
},
{
id: "inventory-transfers",
to: "/inventory/transfers",
label: navigationMessages["inventory-transfers"],
icon: ArrowDataTransferHorizontalIcon,
icon: "huge:arrow-data-transfer-horizontal",
},
{
id: "inventory-alerts",
to: "/inventory/alerts",
label: navigationMessages["inventory-alerts"],
icon: Alert02Icon,
icon: "huge:alert-02",
},
],
},
@@ -197,25 +137,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "content-assets",
to: "/content",
label: navigationMessages["content-assets"],
icon: File01Icon,
icon: "huge:file-01",
items: [
{
id: "content-articles",
to: "/content/articles",
label: navigationMessages["content-articles"],
icon: File01Icon,
icon: "huge:file-01",
},
{
id: "content-media",
to: "/content/media",
label: navigationMessages["content-media"],
icon: GalleryHorizontalIcon,
icon: "huge:album-01",
},
{
id: "content-pages",
to: "/content/pages",
label: navigationMessages["content-pages"],
icon: AppWindowIcon,
icon: "huge:browser",
},
],
},
@@ -229,37 +169,37 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "trade-orders",
to: "/trade/orders",
label: navigationMessages["trade-orders"],
icon: ShoppingCart01Icon,
icon: "huge:shopping-cart-01",
items: [
{
id: "trade-orders-awaiting-payment",
to: "/trade/orders/awaiting-payment",
label: navigationMessages["trade-orders-awaiting-payment"],
icon: WalletCardsIcon,
icon: "huge:wallet-cards",
},
{
id: "trade-orders-awaiting-shipment",
to: "/trade/orders/awaiting-shipment",
label: navigationMessages["trade-orders-awaiting-shipment"],
icon: PackageIcon,
icon: "huge:package",
},
{
id: "trade-orders-shipped",
to: "/trade/orders/shipped",
label: navigationMessages["trade-orders-shipped"],
icon: DeliveryTruck01Icon,
icon: "huge:delivery-truck-01",
},
{
id: "trade-orders-completed",
to: "/trade/orders/completed",
label: navigationMessages["trade-orders-completed"],
icon: CheckmarkCircle01Icon,
icon: "huge:checkmark-circle-01",
},
{
id: "trade-orders-canceled",
to: "/trade/orders/canceled",
label: navigationMessages["trade-orders-canceled"],
icon: CancelCircleIcon,
icon: "huge:cancel-circle",
},
],
},
@@ -267,25 +207,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "fulfillment-center",
to: "/fulfillment",
label: navigationMessages["fulfillment-center"],
icon: DeliveryTruck01Icon,
icon: "huge:delivery-truck-01",
items: [
{
id: "fulfillment-deliveries",
to: "/fulfillment/deliveries",
label: navigationMessages["fulfillment-deliveries"],
icon: Location01Icon,
icon: "huge:location-01",
},
{
id: "fulfillment-pickups",
to: "/fulfillment/pickups",
label: navigationMessages["fulfillment-pickups"],
icon: Store01Icon,
icon: "huge:store-01",
},
{
id: "fulfillment-freight-rules",
to: "/fulfillment/freight-rules",
label: navigationMessages["fulfillment-freight-rules"],
icon: RoadLocation01Icon,
icon: "huge:road-location-01",
},
],
},
@@ -293,25 +233,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "after-sales-center",
to: "/after-sales",
label: navigationMessages["after-sales-center"],
icon: CallMissed01Icon,
icon: "huge:call-missed-01",
items: [
{
id: "after-sales-returns",
to: "/after-sales/returns",
label: navigationMessages["after-sales-returns"],
icon: Refresh01Icon,
icon: "huge:refresh-01",
},
{
id: "after-sales-refunds",
to: "/after-sales/refunds",
label: navigationMessages["after-sales-refunds"],
icon: MoneySendSquareIcon,
icon: "huge:money-send-square",
},
{
id: "after-sales-disputes",
to: "/after-sales/disputes",
label: navigationMessages["after-sales-disputes"],
icon: SecurityWarningIcon,
icon: "huge:security-warning",
},
],
},
@@ -325,31 +265,31 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "customer-center",
to: "/customers",
label: navigationMessages["customer-center"],
icon: IdentityCardIcon,
icon: "huge:identity-card",
items: [
{
id: "customer-profiles",
to: "/customers/profiles",
label: navigationMessages["customer-profiles"],
icon: IdentityCardIcon,
icon: "huge:identity-card",
},
{
id: "customer-segments",
to: "/customers/segments",
label: navigationMessages["customer-segments"],
icon: UserGroupIcon,
icon: "huge:user-group",
},
{
id: "customer-tiers",
to: "/customers/tiers",
label: navigationMessages["customer-tiers"],
icon: CrownIcon,
icon: "huge:crown",
},
{
id: "customer-risk-list",
to: "/customers/risk-list",
label: navigationMessages["customer-risk-list"],
icon: SecurityWarningIcon,
icon: "huge:security-warning",
},
],
},
@@ -357,31 +297,31 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "marketing-campaigns",
to: "/marketing/campaigns",
label: navigationMessages["marketing-campaigns"],
icon: Discount01Icon,
icon: "huge:discount-01",
items: [
{
id: "marketing-coupons",
to: "/marketing/coupons",
label: navigationMessages["marketing-coupons"],
icon: Ticket01Icon,
icon: "huge:ticket-01",
},
{
id: "marketing-flash-sales",
to: "/marketing/flash-sales",
label: navigationMessages["marketing-flash-sales"],
icon: Clock01Icon,
icon: "huge:clock-01",
},
{
id: "marketing-gift-promotions",
to: "/marketing/gift-promotions",
label: navigationMessages["marketing-gift-promotions"],
icon: GiftIcon,
icon: "huge:gift",
},
{
id: "marketing-referrals",
to: "/marketing/referrals",
label: navigationMessages["marketing-referrals"],
icon: Share01Icon,
icon: "huge:share-01",
},
],
},
@@ -389,25 +329,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "loyalty-center",
to: "/loyalty",
label: navigationMessages["loyalty-center"],
icon: Medal01Icon,
icon: "huge:medal-01",
items: [
{
id: "loyalty-points",
to: "/loyalty/points",
label: navigationMessages["loyalty-points"],
icon: StarCircleIcon,
icon: "huge:star-circle",
},
{
id: "loyalty-stored-value",
to: "/loyalty/stored-value",
label: navigationMessages["loyalty-stored-value"],
icon: Wallet01Icon,
icon: "huge:wallet-01",
},
{
id: "loyalty-benefits",
to: "/loyalty/benefits",
label: navigationMessages["loyalty-benefits"],
icon: CheckmarkBadge01Icon,
icon: "huge:checkmark-badge-01",
},
],
},
@@ -421,31 +361,31 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "report-center",
to: "/reports",
label: navigationMessages["report-center"],
icon: Chart01Icon,
icon: "huge:chart-01",
items: [
{
id: "report-sales",
to: "/reports/sales",
label: navigationMessages["report-sales"],
icon: AnalyticsUpIcon,
icon: "huge:analytics-up",
},
{
id: "report-products",
to: "/reports/products",
label: navigationMessages["report-products"],
icon: ShoppingBag01Icon,
icon: "huge:shopping-bag-01",
},
{
id: "report-customers",
to: "/reports/customers",
label: navigationMessages["report-customers"],
icon: UserGroupIcon,
icon: "huge:user-group",
},
{
id: "report-channels",
to: "/reports/channels",
label: navigationMessages["report-channels"],
icon: ChartIncreaseIcon,
icon: "huge:chart-increase",
},
],
},
@@ -453,31 +393,31 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "finance-center",
to: "/finance",
label: navigationMessages["finance-center"],
icon: WalletCardsIcon,
icon: "huge:wallet-cards",
items: [
{
id: "finance-settlements",
to: "/finance/settlements",
label: navigationMessages["finance-settlements"],
icon: Invoice03Icon,
icon: "huge:invoice-03",
},
{
id: "finance-reconciliation",
to: "/finance/reconciliation",
label: navigationMessages["finance-reconciliation"],
icon: Calculator01Icon,
icon: "huge:calculator-01",
},
{
id: "finance-invoices",
to: "/finance/invoices",
label: navigationMessages["finance-invoices"],
icon: File01Icon,
icon: "huge:file-01",
},
{
id: "finance-payouts",
to: "/finance/payouts",
label: navigationMessages["finance-payouts"],
icon: MoneySendSquareIcon,
icon: "huge:money-send-square",
},
],
},
@@ -491,25 +431,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "system-members",
to: "/system/members",
label: navigationMessages["system-members"],
icon: UserShield01Icon,
icon: "huge:user-shield-01",
items: [
{
id: "system-members-admins",
to: "/system/members/admins",
label: navigationMessages["system-members-admins"],
icon: UserShield01Icon,
icon: "huge:user-shield-01",
},
{
id: "system-members-roles",
to: "/system/members/roles",
label: navigationMessages["system-members-roles"],
icon: UserGroupIcon,
icon: "huge:user-group",
},
{
id: "system-members-permissions",
to: "/system/members/permissions",
label: navigationMessages["system-members-permissions"],
icon: LockKeyIcon,
icon: "huge:lock-key",
},
],
},
@@ -517,19 +457,19 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "system-notifications",
to: "/system/notifications",
label: navigationMessages["system-notifications"],
icon: BubbleChatIcon,
icon: "huge:bubble-chat",
items: [
{
id: "system-notification-templates",
to: "/system/notifications/templates",
label: navigationMessages["system-notification-templates"],
icon: File01Icon,
icon: "huge:file-01",
},
{
id: "system-notification-logs",
to: "/system/notifications/logs",
label: navigationMessages["system-notification-logs"],
icon: HistoryIcon,
icon: "huge:time-schedule",
},
],
},
@@ -537,25 +477,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "system-audit",
to: "/system/audit",
label: navigationMessages["system-audit"],
icon: Note01Icon,
icon: "huge:note-01",
},
{
id: "system-integrations",
to: "/system/integrations",
label: navigationMessages["system-integrations"],
icon: CodeSquareIcon,
icon: "huge:code-square",
items: [
{
id: "system-integration-api-keys",
to: "/system/integrations/api-keys",
label: navigationMessages["system-integration-api-keys"],
icon: Key01Icon,
icon: "huge:key-01",
},
{
id: "system-integration-webhooks",
to: "/system/integrations/webhooks",
label: navigationMessages["system-integration-webhooks"],
icon: LinkCircleIcon,
icon: "huge:link-circle",
},
],
},
@@ -563,25 +503,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "system-settings",
to: "/system-configs",
label: navigationMessages["system-settings"],
icon: Settings01Icon,
icon: "huge:settings-01",
items: [
{
id: "system-settings-store",
to: "/system-configs/store",
label: navigationMessages["system-settings-store"],
icon: Store01Icon,
icon: "huge:store-01",
},
{
id: "system-settings-payments",
to: "/system-configs/payments",
label: navigationMessages["system-settings-payments"],
icon: CreditCardIcon,
icon: "huge:credit-card",
},
{
id: "system-settings-shipping",
to: "/system-configs/shipping",
label: navigationMessages["system-settings-shipping"],
icon: DeliveryTruck01Icon,
icon: "huge:delivery-truck-01",
},
],
},
@@ -589,7 +529,7 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
id: "system-recycle-bin",
to: "/system/recycle-bin",
label: navigationMessages["system-recycle-bin"],
icon: Delete02Icon,
icon: "huge:delete-02",
},
],
},
+4 -10
View File
@@ -1,9 +1,3 @@
import {
DeliveryTruck01Icon,
Mail01Icon,
Message01Icon,
PackageIcon,
} from "@workspace/icons"
import type { QueryFunctionContext } from "@tanstack/react-query"
import type { Notification } from "@workspace/blocks/notifications"
@@ -68,7 +62,7 @@ export const fallbackNotifications: readonly Notification[] = [
isRead: true,
media: {
kind: "icon",
icon: PackageIcon,
icon: "huge:package",
tone: "warning",
},
},
@@ -81,7 +75,7 @@ export const fallbackNotifications: readonly Notification[] = [
isRead: true,
media: {
kind: "icon",
icon: DeliveryTruck01Icon,
icon: "huge:delivery-truck-01",
tone: "success",
},
},
@@ -94,7 +88,7 @@ export const fallbackNotifications: readonly Notification[] = [
isRead: true,
media: {
kind: "icon",
icon: Message01Icon,
icon: "huge:message-01",
tone: "info",
},
},
@@ -106,7 +100,7 @@ export const fallbackNotifications: readonly Notification[] = [
isRead: true,
media: {
kind: "icon",
icon: Mail01Icon,
icon: "huge:mail-01",
tone: "destructive",
},
},
+2 -2
View File
@@ -19,7 +19,7 @@ import {
I18nProvider as UiI18nProvider,
type CalendarLocale,
} from "@workspace/ui/components/i18n"
import { calendarLocale as englishCalendarLocale } from "@workspace/ui/locales/en"
import { calendarLocale as englishCalendarLocale } from "@workspace/ui/locales/en-US"
import { calendarLocale as simplifiedChineseCalendarLocale } from "@workspace/ui/locales/zh-Hans"
import { appLocales, DEFAULT_APP_LOCALE, type AppLocale } from "./app-locales"
@@ -27,7 +27,7 @@ import { AppI18nDevtool } from "./app-i18n-devtool"
import { setAppLocale as persistAppLocale } from "@/server/app-locale.functions"
const appCalendarLocales: Partial<Record<AppLocale, CalendarLocale>> = {
en: englishCalendarLocale,
"en-US": englishCalendarLocale,
"zh-Hans": simplifiedChineseCalendarLocale,
}
@@ -7,16 +7,11 @@ import { AppLanguageSwitcher } from "./app-language-switcher"
vi.mock("./app-i18n-provider", () => ({
useAppLocale: () => ({
locale: "en",
locale: "en-US",
setLocale: vi.fn(),
}),
}))
vi.mock("country-flag-icons/react/1x1", () => ({
CN: () => <svg />,
US: () => <svg />,
}))
beforeAll(() => {
vi.stubGlobal(
"ResizeObserver",
+6 -9
View File
@@ -1,4 +1,4 @@
import { CN, US } from "country-flag-icons/react/1x1"
import { Icon } from "@icones/react"
import {
DropdownMenu,
DropdownMenuContent,
@@ -13,11 +13,6 @@ import { HeaderActionButton } from "@workspace/blocks/layout"
import { appLocales, isAppLocale, type AppLocaleCountry } from "./app-locales"
import { useAppLocale } from "./app-i18n-provider"
const localeFlags = {
CN,
US,
} satisfies Record<AppLocaleCountry, typeof CN>
function LocaleFlag({
country,
className,
@@ -25,8 +20,6 @@ function LocaleFlag({
className?: string
country: AppLocaleCountry
}) {
const Flag = localeFlags[country]
return (
<span
aria-hidden="true"
@@ -35,7 +28,11 @@ function LocaleFlag({
className
)}
>
<Flag className="size-full" />
{country === "CN" ? (
<Icon name="flag:cn-square" className="size-full" />
) : (
<Icon name="flag:us-square" className="size-full" />
)}
</span>
)
}
+2 -2
View File
@@ -1,6 +1,6 @@
import type { LocaleDefinition } from "@workspace/i18n"
export type AppLocale = "en" | "zh-Hans"
export type AppLocale = "en-US" | "zh-Hans"
export type AppLocaleCountry = "CN" | "US"
export interface AppLocaleDefinition extends LocaleDefinition {
@@ -16,7 +16,7 @@ export const appLocales = [
direction: "ltr",
languageTag: "en-US",
label: "English",
locale: "en",
locale: "en-US",
},
{
country: "CN",
@@ -4,7 +4,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: en\n"
"Language: en-US\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
@@ -23,6 +23,21 @@ msgstr "Account password"
msgid "blocks.appearance.active"
msgstr "Active"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:16
msgid "blocks.chats.actions.addEmoji"
msgstr "Add emoji"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:4
msgid "blocks.chats.actions.addFiles"
msgstr "Add files"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:8
msgid "blocks.chats.actions.addImage"
msgstr "Add image"
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:276
msgid "app.navigation.system-members-admins"
@@ -33,6 +48,11 @@ msgstr "Administrator accounts"
msgid "app.navigation.after-sales-center"
msgstr "After-sales"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:12
msgid "blocks.chats.emojiPicker.allEmojis"
msgstr "All emojis"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/appearance/messages.ts:91
msgid "blocks.appearance.color.amber"
@@ -73,6 +93,11 @@ msgstr "Awaiting payment"
msgid "app.navigation.trade-orders-awaiting-shipment"
msgstr "Awaiting shipment"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:20
msgid "blocks.chats.actions.backToConversations"
msgstr "Back to conversations"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/appearance/messages.ts:16
msgid "blocks.appearance.baseColor"
@@ -124,7 +149,7 @@ msgid "app.navigation.report-channels"
msgstr "Channel analysis"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:4
#: ../../packages/blocks/src/blocks/chats/messages.ts:112
msgid "blocks.chats.title"
msgstr "Chats"
@@ -133,6 +158,11 @@ msgstr "Chats"
msgid "blocks.notifications.error.description"
msgstr "Check your network connection and try again."
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:108
msgid "blocks.chats.selectConversation.description"
msgstr "Choose a conversation from the list to read its messages."
#. js-lingui-explicit-id
#: ../../packages/ui/src/components/i18n.tsx:29
msgid "ui.common.close"
@@ -158,6 +188,11 @@ msgstr "Completed orders"
msgid "app.navigation.content-assets"
msgstr "Content"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:24
msgid "blocks.chats.actions.conversationDetails"
msgstr "Conversation details"
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:192
msgid "app.navigation.marketing-coupons"
@@ -218,6 +253,11 @@ msgstr "Dark theme"
msgid "app.navigation.data-and-finance"
msgstr "Data and finance"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:52
msgid "blocks.chats.messageStatus.delivered"
msgstr "Delivered"
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:296
msgid "app.navigation.system-notification-logs"
@@ -343,6 +383,11 @@ msgstr "Light theme"
msgid "blocks.appearance.color.lime"
msgstr "Lime"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:28
msgid "blocks.chats.actions.loadEarlier"
msgstr "Load earlier messages"
#. js-lingui-explicit-id
#: ../../packages/ui/src/components/i18n.tsx:37
msgid "ui.common.loading"
@@ -383,6 +428,11 @@ msgstr "Media"
msgid "app.navigation.loyalty-center"
msgstr "Membership benefits"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:84
msgid "blocks.chats.emptyMessages.description"
msgstr "Messages in this conversation will appear here."
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/appearance/messages.ts:127
msgid "blocks.appearance.color.mist"
@@ -433,11 +483,31 @@ msgstr "Next"
msgid "ui.carousel.nextSlide"
msgstr "Next slide"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:80
msgid "blocks.chats.emptyConversations.title"
msgstr "No conversations"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:92
msgid "blocks.chats.emptySearch"
msgstr "No conversations match your search."
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:88
msgid "blocks.chats.emptyMessages.title"
msgstr "No messages yet"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/notifications/messages.ts:12
msgid "blocks.notifications.empty.title"
msgstr "No notifications"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:56
msgid "blocks.chats.messageStatus.failed"
msgstr "Not sent"
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:292
msgid "app.navigation.system-notification-templates"
@@ -543,6 +613,11 @@ msgstr "Pink"
msgid "app.navigation.loyalty-points"
msgstr "Points rules"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:44
msgid "blocks.chats.actions.sendOnEnter"
msgstr "Press Enter to send"
#. js-lingui-explicit-id
#: ../../packages/ui/src/components/i18n.tsx:65
msgid "ui.pagination.previous"
@@ -598,11 +673,21 @@ msgstr "Products and inventory"
msgid "blocks.appearance.color.purple"
msgstr "Purple"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:60
msgid "blocks.chats.messageStatus.read"
msgstr "Read"
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:12
msgid "app.navigation.overview-realtime"
msgstr "Real-time monitoring"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:64
msgid "blocks.chats.emojiPicker.recentEmojis"
msgstr "Recently used"
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:256
msgid "app.navigation.finance-reconciliation"
@@ -638,6 +723,11 @@ msgstr "Refresh the current page data"
msgid "app.navigation.after-sales-refunds"
msgstr "Refund review"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:32
msgid "blocks.chats.actions.removeAttachment"
msgstr "Remove {name}"
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:228
msgid "app.navigation.report-center"
@@ -668,6 +758,36 @@ msgstr "Rose"
msgid "app.navigation.report-sales"
msgstr "Sales analysis"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:100
msgid "blocks.chats.search"
msgstr "Search conversations"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:104
msgid "blocks.chats.selectConversation"
msgstr "Select conversation {title}"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:40
msgid "blocks.chats.actions.send"
msgstr "Send"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:72
msgid "blocks.chats.messageStatus.sending"
msgstr "Sending"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:48
msgid "blocks.chats.actions.sending"
msgstr "Sending…"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:68
msgid "blocks.chats.messageStatus.sent"
msgstr "Sent"
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:252
msgid "app.navigation.finance-settlements"
@@ -698,6 +818,11 @@ msgstr "Sky"
msgid "app.navigation.catalog-product-specifications"
msgstr "Specification templates"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:76
msgid "blocks.chats.emptyConversations.description"
msgstr "Start a conversation to see it here."
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:80
msgid "app.navigation.inventory-transfers"
@@ -803,6 +928,11 @@ msgstr "Toggle theme"
msgid "app.navigation.after-sales-disputes"
msgstr "Transaction disputes"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:36
msgid "blocks.chats.actions.retry"
msgstr "Try again"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/notifications/messages.ts:36
msgid "blocks.notifications.retry"
@@ -843,6 +973,11 @@ msgstr "Violet"
msgid "app.navigation.system-integration-webhooks"
msgstr "Webhook configuration"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:96
msgid "blocks.chats.messagePlaceholder"
msgstr "Write a message…"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/appearance/messages.ts:179
msgid "blocks.appearance.color.yellow"
File diff suppressed because one or more lines are too long
+136 -1
View File
@@ -23,6 +23,21 @@ msgstr "账号密码"
msgid "blocks.appearance.active"
msgstr "使用中"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:16
msgid "blocks.chats.actions.addEmoji"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:4
msgid "blocks.chats.actions.addFiles"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:8
msgid "blocks.chats.actions.addImage"
msgstr ""
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:276
msgid "app.navigation.system-members-admins"
@@ -33,6 +48,11 @@ msgstr "管理员账号"
msgid "app.navigation.after-sales-center"
msgstr "售后中心"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:12
msgid "blocks.chats.emojiPicker.allEmojis"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/appearance/messages.ts:91
msgid "blocks.appearance.color.amber"
@@ -73,6 +93,11 @@ msgstr "待付款订单"
msgid "app.navigation.trade-orders-awaiting-shipment"
msgstr "待发货订单"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:20
msgid "blocks.chats.actions.backToConversations"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/appearance/messages.ts:16
msgid "blocks.appearance.baseColor"
@@ -124,7 +149,7 @@ msgid "app.navigation.report-channels"
msgstr "渠道分析"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:4
#: ../../packages/blocks/src/blocks/chats/messages.ts:112
msgid "blocks.chats.title"
msgstr "聊天"
@@ -133,6 +158,11 @@ msgstr "聊天"
msgid "blocks.notifications.error.description"
msgstr "请检查网络连接后重试。"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:108
msgid "blocks.chats.selectConversation.description"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/ui/src/components/i18n.tsx:29
msgid "ui.common.close"
@@ -158,6 +188,11 @@ msgstr "已完成订单"
msgid "app.navigation.content-assets"
msgstr "内容素材"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:24
msgid "blocks.chats.actions.conversationDetails"
msgstr ""
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:192
msgid "app.navigation.marketing-coupons"
@@ -218,6 +253,11 @@ msgstr "深色主题"
msgid "app.navigation.data-and-finance"
msgstr "数据与财务"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:52
msgid "blocks.chats.messageStatus.delivered"
msgstr ""
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:296
msgid "app.navigation.system-notification-logs"
@@ -343,6 +383,11 @@ msgstr "浅色主题"
msgid "blocks.appearance.color.lime"
msgstr "青柠色"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:28
msgid "blocks.chats.actions.loadEarlier"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/ui/src/components/i18n.tsx:37
msgid "ui.common.loading"
@@ -383,6 +428,11 @@ msgstr "媒体资源"
msgid "app.navigation.loyalty-center"
msgstr "会员权益"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:84
msgid "blocks.chats.emptyMessages.description"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/appearance/messages.ts:127
msgid "blocks.appearance.color.mist"
@@ -433,11 +483,31 @@ msgstr "下一页"
msgid "ui.carousel.nextSlide"
msgstr "下一张"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:80
msgid "blocks.chats.emptyConversations.title"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:92
msgid "blocks.chats.emptySearch"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:88
msgid "blocks.chats.emptyMessages.title"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/notifications/messages.ts:12
msgid "blocks.notifications.empty.title"
msgstr "暂无通知"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:56
msgid "blocks.chats.messageStatus.failed"
msgstr ""
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:292
msgid "app.navigation.system-notification-templates"
@@ -543,6 +613,11 @@ msgstr "粉色"
msgid "app.navigation.loyalty-points"
msgstr "积分规则"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:44
msgid "blocks.chats.actions.sendOnEnter"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/ui/src/components/i18n.tsx:65
msgid "ui.pagination.previous"
@@ -598,11 +673,21 @@ msgstr "商品与库存"
msgid "blocks.appearance.color.purple"
msgstr "紫色"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:60
msgid "blocks.chats.messageStatus.read"
msgstr ""
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:12
msgid "app.navigation.overview-realtime"
msgstr "实时监控"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:64
msgid "blocks.chats.emojiPicker.recentEmojis"
msgstr ""
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:256
msgid "app.navigation.finance-reconciliation"
@@ -638,6 +723,11 @@ msgstr "刷新当前页面的数据"
msgid "app.navigation.after-sales-refunds"
msgstr "退款审核"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:32
msgid "blocks.chats.actions.removeAttachment"
msgstr ""
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:228
msgid "app.navigation.report-center"
@@ -668,6 +758,36 @@ msgstr "玫红色"
msgid "app.navigation.report-sales"
msgstr "销售分析"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:100
msgid "blocks.chats.search"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:104
msgid "blocks.chats.selectConversation"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:40
msgid "blocks.chats.actions.send"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:72
msgid "blocks.chats.messageStatus.sending"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:48
msgid "blocks.chats.actions.sending"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:68
msgid "blocks.chats.messageStatus.sent"
msgstr ""
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:252
msgid "app.navigation.finance-settlements"
@@ -698,6 +818,11 @@ msgstr "天蓝色"
msgid "app.navigation.catalog-product-specifications"
msgstr "规格模板"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:76
msgid "blocks.chats.emptyConversations.description"
msgstr ""
#. js-lingui-explicit-id
#: src/data/navigation-messages.ts:80
msgid "app.navigation.inventory-transfers"
@@ -803,6 +928,11 @@ msgstr "切换主题"
msgid "app.navigation.after-sales-disputes"
msgstr "交易纠纷"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:36
msgid "blocks.chats.actions.retry"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/notifications/messages.ts:36
msgid "blocks.notifications.retry"
@@ -843,6 +973,11 @@ msgstr "紫罗兰色"
msgid "app.navigation.system-integration-webhooks"
msgstr "Webhook 配置"
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/chats/messages.ts:96
msgid "blocks.chats.messagePlaceholder"
msgstr ""
#. js-lingui-explicit-id
#: ../../packages/blocks/src/blocks/appearance/messages.ts:179
msgid "blocks.appearance.color.yellow"
File diff suppressed because one or more lines are too long
+256
View File
@@ -0,0 +1,256 @@
import * as React from "react"
import {
ChatWorkspace,
type ChatConversation,
type ChatMessage,
} from "@workspace/blocks/chats"
const mockMediaUrls: Record<string, string> = {
"campaign-banner":
"https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?auto=format&fit=crop&w=960&q=80",
"order-card-thumbnail":
"https://images.unsplash.com/photo-1556740749-887f6717d7e4?auto=format&fit=crop&w=320&q=80",
}
const initialConversations: readonly ChatConversation[] = [
{
avatarUrl:
"https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/images/mock/avatar/avatar-1.webp",
description: "微信小程序客服会话",
id: "customer-service",
lastActivityLabel: "刚刚",
lastMessage: "我的订单",
messages: [
{
author: { id: "customer-zhang", name: "张晓敏" },
createdAt: "2026-07-31T09:30:00+08:00",
direction: "incoming",
id: "welcome",
msgtype: "text",
text: { content: "你好,我想了解一下订单的配送进度。" },
},
{
createdAt: "2026-07-31T09:31:00+08:00",
direction: "outgoing",
id: "campaign-image",
image: { media_id: "campaign-banner" },
msgtype: "image",
},
{
createdAt: "2026-07-31T09:32:00+08:00",
direction: "outgoing",
id: "help-link",
link: {
description: "查看商品、订单与售后服务说明。",
thumb_url:
"https://images.unsplash.com/photo-1556740758-90de374c12ad?auto=format&fit=crop&w=640&q=80",
title: "帮助中心",
url: "https://example.com/help",
},
msgtype: "link",
},
{
createdAt: "2026-07-31T09:33:00+08:00",
direction: "outgoing",
id: "orders-card",
miniprogrampage: {
pagepath: "pages/orders/index",
thumb_media_id: "order-card-thumbnail",
title: "我的订单",
},
msgtype: "miniprogrampage",
},
],
title: "张晓敏",
unreadCount: 3,
},
{
avatarUrl:
"https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/images/mock/avatar/avatar-4.webp",
description: "微信小程序客服会话",
id: "shipping",
lastActivityLabel: "昨天",
lastMessage: "请问我的包裹什么时候可以送到?",
messages: [
{
author: { id: "customer-li", name: "李雨晴" },
createdAt: "2026-07-30T16:20:00+08:00",
direction: "incoming",
id: "shipping-status",
msgtype: "text",
text: { content: "请问我的包裹什么时候可以送到?" },
},
],
title: "李雨晴",
},
]
export function ChatsDemoPage() {
const [activeConversationId, setActiveConversationId] = React.useState(
initialConversations[0]!.id
)
const [conversations, setConversations] = React.useState(initialConversations)
const [hasMoreHistory, setHasMoreHistory] = React.useState(true)
const [mediaUrls, setMediaUrls] = React.useState(mockMediaUrls)
const objectUrlsRef = React.useRef(new Set<string>())
React.useEffect(
() => () => {
for (const url of objectUrlsRef.current) URL.revokeObjectURL(url)
},
[]
)
const appendMessages = React.useCallback(
(conversationId: string, messages: readonly ChatMessage[]) => {
if (messages.length === 0) return
const lastMessage = messages.at(-1)!
setConversations((current) =>
current.map((conversation) =>
conversation.id === conversationId
? {
...conversation,
lastActivityLabel: "刚刚",
lastMessage: getMessagePreview(lastMessage),
messages: [...conversation.messages, ...messages],
unreadCount: 0,
}
: conversation
)
)
},
[]
)
const handleSend = React.useCallback(
async ({
conversation,
files,
text,
}: Parameters<
NonNullable<React.ComponentProps<typeof ChatWorkspace>["onSend"]>
>[0]) => {
const createdAt = new Date().toISOString()
const prefix = `${Date.now()}`
const messages: ChatMessage[] = []
if (text) {
messages.push({
createdAt,
direction: "outgoing",
id: `${prefix}:text`,
msgtype: "text",
status: "sent",
text: { content: text },
})
}
const newMediaUrls: Record<string, string> = {}
for (const [index, file] of files.entries()) {
const url = URL.createObjectURL(file)
objectUrlsRef.current.add(url)
if (file.type.startsWith("image/")) {
const mediaId = `${prefix}:image:${index}`
newMediaUrls[mediaId] = url
messages.push({
createdAt,
direction: "outgoing",
id: mediaId,
image: { media_id: mediaId },
msgtype: "image",
status: "sent",
})
continue
}
messages.push({
createdAt,
direction: "outgoing",
id: `${prefix}:link:${index}`,
link: {
description: "本地选择的文件(仅用于模拟)。",
thumb_url: "",
title: file.name,
url,
},
msgtype: "link",
status: "sent",
})
}
if (Object.keys(newMediaUrls).length > 0) {
setMediaUrls((current) => ({ ...current, ...newMediaUrls }))
}
appendMessages(conversation.id, messages)
},
[appendMessages]
)
const handleLoadEarlierMessages = React.useCallback(
(conversation: ChatConversation) => {
setHasMoreHistory(false)
setConversations((current) =>
current.map((currentConversation) =>
currentConversation.id === conversation.id
? {
...currentConversation,
messages: [
{
author: { id: "customer-zhang", name: "张晓敏" },
createdAt: "2026-07-31T09:20:00+08:00",
direction: "incoming",
id: `${conversation.id}:earlier-message`,
msgtype: "text",
text: { content: "我昨天已经联系过客服了。" },
},
...currentConversation.messages,
],
}
: currentConversation
)
)
},
[]
)
return (
<div className="flex min-w-0 flex-col gap-6">
<div>
<h1 className="text-xl font-medium">聊天模拟器</h1>
<p className="mt-1 text-sm text-muted-foreground">
所有数据仅保存在浏览器内,可模拟微信小程序客服的文本、图片、图文链接和小程序卡片消息。
</p>
</div>
<ChatWorkspace
activeConversationId={activeConversationId}
conversations={conversations}
hasMoreMessages={
hasMoreHistory && activeConversationId === "customer-service"
}
maxHeight="calc(100svh - 15rem)"
minHeight="min(36rem, calc(100svh - 15rem))"
onActiveConversationChange={(conversation) =>
setActiveConversationId(conversation.id)
}
onLoadEarlierMessages={handleLoadEarlierMessages}
onSend={handleSend}
resolveMediaUrl={(mediaId) => mediaUrls[mediaId]}
/>
</div>
)
}
function getMessagePreview(message: ChatMessage) {
switch (message.msgtype) {
case "text":
return message.text.content
case "image":
return "[图片]"
case "link":
return message.link.title
case "miniprogrampage":
return message.miniprogrampage.title
}
}
+110
View File
@@ -1,4 +1,5 @@
import * as React from "react"
import { createPortal } from "react-dom"
import { formatForDisplay } from "@tanstack/react-hotkeys"
import {
LexicalActions,
@@ -10,6 +11,37 @@ import {
LexicalRoot,
} from "@workspace/lexical"
import { Button } from "@workspace/ui/components/button"
import { MessageScrollerProvider } from "@workspace/ui/components/message-scroller"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@workspace/ui/components/card"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
} from "@workspace/ui/components/input-group"
import { Icon } from "@icones/react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@workspace/ui/components/dropdown-menu"
import {
GlobeIcon,
ImageIcon,
PaperclipIcon,
PlusIcon,
TelescopeIcon,
} from "@workspace/ui/components/icon"
import { Checkbox } from "@workspace/ui/components/checkbox"
const initialEditorHtml = [
"<h2>Welcome to the Lexical editor</h2>",
@@ -19,9 +51,87 @@ const initialEditorHtml = [
export function DashboardPage() {
const [html, setHtml] = React.useState(initialEditorHtml)
const [portalTarget, setPortalTarget] = React.useState<HTMLElement>()
React.useEffect(() => {
setPortalTarget(document.body)
}, [])
return (
<div className="flex min-w-0 flex-col gap-6 bg-background">
{portalTarget &&
createPortal(
<MessageScrollerProvider>
<Card className="fixed inset-y-20 left-1/2 z-1000 flex hidden w-4xl -translate-x-1/2 flex-col rounded-2xl border bg-background shadow-lg">
<CardHeader className="shrink-0">
<CardTitle>CardTitle</CardTitle>
<CardDescription>CardDescription</CardDescription>
<CardAction>CardAction</CardAction>
</CardHeader>
<CardContent className="flex-1"></CardContent>
<CardFooter className="shrink-0">
<form className="w-full">
<InputGroup>
<div className="h-14 w-full px-3 py-2.5" />
<InputGroupAddon align="block-end" className="gap-8">
<DropdownMenu>
<DropdownMenuTrigger
render={
<InputGroupButton
aria-label="Add files"
type="button"
size="icon-sm"
variant="outline"
>
<PlusIcon />
</InputGroupButton>
}
/>
<DropdownMenuContent
align="start"
side="top"
className="w-44"
>
<DropdownMenuItem>
<PaperclipIcon />
Add Photos & Files
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<ImageIcon />
Create Image
</DropdownMenuItem>
<DropdownMenuItem>
<TelescopeIcon />
Deep Research
</DropdownMenuItem>
<DropdownMenuItem>
<GlobeIcon />
Web Search
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<label className="ml-auto inline-flex items-center gap-2">
<Checkbox />
按 Enter 发送
</label>
<InputGroupButton
variant="default"
size="icon-sm"
className="rounded-full"
>
<Icon name="huge:arrow-up-01" />
<span className="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</form>
</CardFooter>
</Card>
</MessageScrollerProvider>,
portalTarget
)}
<div>
<h1 className="text-xl font-medium">Lexical editor</h1>
<p className="mt-1 text-sm text-muted-foreground">
+257
View File
@@ -0,0 +1,257 @@
import React, { useState } from "react"
import { useMutation } from "@tanstack/react-query"
import { Button } from "@workspace/ui/components/button"
import { Input } from "@workspace/ui/components/input"
import { EyeIcon, EyeOffIcon } from "@workspace/ui/components/icon"
import { cn } from "@workspace/ui/lib/utils"
import { ThemeToggleButton } from "@workspace/blocks/blocks/appearance"
import { Logo } from "@/components/logo"
export function LoginPage() {
return (
<div className="isolate h-svh min-h-148 w-svw lg:min-h-164.5">
<header className="relative z-10 h-0">
<div className="flex h-14 items-center justify-between px-6">
<div className="size-6 opacity-0 lg:opacity-100">
<Logo />
</div>
<div>
<ThemeToggleButton />
</div>
</div>
</header>
<main className="relative isolate flex size-full before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:bg-[url(https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/background/background-3-blur.webp)] before:bg-cover before:bg-center before:bg-no-repeat before:opacity-20">
<div className="pointer-events-none hidden w-120 flex-col justify-center gap-16 bg-linear-to-r from-secondary/10 to-muted/20 p-6 pt-18 select-none lg:flex">
<div className="text-center">
<h2 className="text-3xl font-bold tracking-tight text-foreground">
欢迎回来 👋🏻
</h2>
<p className="mt-4 text-muted-foreground">
让商品、订单与运营管理更简单高效。
</p>
</div>
<img
src="https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/illustrations/illustration-dashboard.webp"
alt=""
className="aspect-4/3 w-full object-cover object-center"
/>
<Features />
</div>
<div className="flex flex-1 flex-col items-start gap-7 bg-sidebar/80 px-6 py-20 lg:justify-center lg:px-8">
<div className="mx-auto mb-10 w-full max-w-md lg:hidden">
<Logo className="size-12" />
</div>
<LoginForm />
<div className="lg:h-24" />
</div>
</main>
</div>
)
}
const features = [
{
label: "Jwt",
img: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/icons/platforms/ic-jwt.svg",
},
{
label: "Firebase",
img: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/icons/platforms/ic-firebase.svg",
},
{
label: "Amplify",
img: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/icons/platforms/ic-amplify.svg",
},
{
label: "Auth0",
img: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/icons/platforms/ic-auth0.svg",
},
{
label: "Supabase",
img: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/icons/platforms/ic-supabase.svg",
},
]
function Features() {
const [index, setIndex] = React.useState(0)
React.useEffect(() => {
const timerId = setInterval(() => {
setIndex((currentIndex) => (currentIndex + 1) % features.length)
}, 1000)
return () => {
clearInterval(timerId)
}
}, [])
return (
<ul className="flex items-center justify-center gap-4">
{features.map((f, i) => (
<li
key={f.label}
className={cn(
"transition-[filter] duration-1000 ease-linear",
i === index ? "grayscale-0" : "grayscale-100"
)}
>
<img className="size-8" alt={f.label} src={f.img} />
</li>
))}
</ul>
)
}
function LoginForm() {
// const navigate = useNavigate()
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const mutation = useMutation({
// mutationFn: () => {
// return api.post<AdminAuthResponse>("/api/v1/admin/auth/login", {
// username,
// password,
// })
// },
// onSuccess: () => {
// login()
// Toast("登录成功", "success")
// navigate("/", { replace: true })
// },
// onError: (err: Error) => toast(err.message || "登录失败", "error"),
})
return (
<div className="mx-auto w-full max-w-md">
<h5 className="mb-3 text-2xl font-bold tracking-tight text-foreground">
账号登录
</h5>
<p className="lg:text-md text-sm text-muted-foreground">
<span className="text-muted-foreground">
请输入您的帐户信息以开始管理您的系统
</span>
</p>
<form
className="mt-8 space-y-8"
onSubmit={(e) => {
e.preventDefault()
mutation.mutate()
}}
>
<Field
label="用户名"
inputId="username"
Component={UsernameInput}
value={username}
setValue={setUsername}
/>
<Field
label="密码"
inputId="password"
Component={PasswordInput}
value={password}
setValue={setPassword}
/>
<Button
type="submit"
className="mt-2 h-12 w-full rounded-lg"
disabled={mutation.isPending}
>
{mutation.isPending ? "登录中..." : "登录"}
</Button>
</form>
</div>
)
}
type FieldInputProps = {
inputId: string
className?: string
value: string
setValue: React.Dispatch<React.SetStateAction<string>>
}
type FieldInputComponent = React.ComponentType<FieldInputProps>
function Field({
label,
Component,
...props
}: Omit<FieldInputProps, "className"> & {
label: string
Component: FieldInputComponent
}) {
return (
<fieldset className="relative flex h-12 items-center rounded-lg border-2 border-border p-0 focus-within:border-primary focus-within:[&_label]:opacity-100">
<legend className="ml-2 flex h-0 px-0">
<label
className="h-fit -translate-y-1/2 px-1 text-sm opacity-50"
htmlFor={props.inputId}
>
{label}
</label>
</legend>
<Component
className="h-full rounded-none border-none bg-transparent outline-none autofill:bg-transparent focus-visible:border-none focus-visible:ring-0 [&:-webkit-autofill]:[-webkit-background-clip:text] [&:-webkit-autofill]:[-webkit-text-fill-color:var(--foreground)] [&:-webkit-autofill]:[transition:background-color_9999s_ease-out]"
{...props}
/>
</fieldset>
)
}
function UsernameInput({
inputId,
className,
value,
setValue,
}: FieldInputProps) {
return (
<Input
id={inputId}
value={value}
className={className}
onChange={(e) => setValue(e.target.value)}
placeholder="请输入用户名"
required
/>
)
}
function PasswordInput({
inputId,
className,
value,
setValue,
}: FieldInputProps) {
const [isVisible, setVisile] = React.useState(false)
return (
<>
<Input
id={inputId}
type={isVisible ? "text" : "password"}
value={value}
className={cn("relative z-1 pe-24", className)}
onChange={(e) => setValue(e.target.value)}
placeholder="请输入登录密码"
required
/>
<div className="absolute top-0 right-0 z-2 flex h-11 w-11 items-center justify-center">
<Button
type="button"
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
setVisile((v) => !v)
}}
className="rounded-sm"
>
{isVisible ? <EyeIcon /> : <EyeOffIcon />}
</Button>
</div>
</>
)
}
+39 -3
View File
@@ -10,13 +10,20 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as AppRouteImport } from './routes/_app'
import { Route as LoginRouteImport } from './routes/login'
import { Route as AppIndexRouteImport } from './routes/_app.index'
import { Route as AppSplatRouteImport } from './routes/_app.$'
import { Route as AppChatsRouteImport } from './routes/_app.chats'
const AppRoute = AppRouteImport.update({
id: '/_app',
getParentRoute: () => rootRouteImport,
} as any)
const LoginRoute = LoginRouteImport.update({
id: '/login',
path: '/login',
getParentRoute: () => rootRouteImport,
} as any)
const AppIndexRoute = AppIndexRouteImport.update({
id: '/',
path: '/',
@@ -27,31 +34,43 @@ const AppSplatRoute = AppSplatRouteImport.update({
path: '/$',
getParentRoute: () => AppRoute,
} as any)
const AppChatsRoute = AppChatsRouteImport.update({
id: '/chats',
path: '/chats',
getParentRoute: () => AppRoute,
} as any)
export interface FileRoutesByFullPath {
'/': typeof AppIndexRoute
'/login': typeof LoginRoute
'/$': typeof AppSplatRoute
'/chats': typeof AppChatsRoute
}
export interface FileRoutesByTo {
'/login': typeof LoginRoute
'/$': typeof AppSplatRoute
'/chats': typeof AppChatsRoute
'/': typeof AppIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/_app': typeof AppRouteWithChildren
'/login': typeof LoginRoute
'/_app/$': typeof AppSplatRoute
'/_app/chats': typeof AppChatsRoute
'/_app/': typeof AppIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/$'
fullPaths: '/' | '/login' | '/$' | '/chats'
fileRoutesByTo: FileRoutesByTo
to: '/$' | '/'
id: '__root__' | '/_app' | '/_app/$' | '/_app/'
to: '/login' | '/$' | '/chats' | '/'
id: '__root__' | '/_app' | '/login' | '/_app/$' | '/_app/chats' | '/_app/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
AppRoute: typeof AppRouteWithChildren
LoginRoute: typeof LoginRoute
}
declare module '@tanstack/react-router' {
@@ -63,6 +82,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppRouteImport
parentRoute: typeof rootRouteImport
}
'/login': {
id: '/login'
path: '/login'
fullPath: '/login'
preLoaderRoute: typeof LoginRouteImport
parentRoute: typeof rootRouteImport
}
'/_app/': {
id: '/_app/'
path: '/'
@@ -77,16 +103,25 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppSplatRouteImport
parentRoute: typeof AppRoute
}
'/_app/chats': {
id: '/_app/chats'
path: '/chats'
fullPath: '/chats'
preLoaderRoute: typeof AppChatsRouteImport
parentRoute: typeof AppRoute
}
}
}
interface AppRouteChildren {
AppSplatRoute: typeof AppSplatRoute
AppChatsRoute: typeof AppChatsRoute
AppIndexRoute: typeof AppIndexRoute
}
const AppRouteChildren: AppRouteChildren = {
AppSplatRoute: AppSplatRoute,
AppChatsRoute: AppChatsRoute,
AppIndexRoute: AppIndexRoute,
}
@@ -94,6 +129,7 @@ const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
const rootRouteChildren: RootRouteChildren = {
AppRoute: AppRouteWithChildren,
LoginRoute: LoginRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
+7
View File
@@ -0,0 +1,7 @@
import { createFileRoute } from "@tanstack/react-router"
import { ChatsDemoPage } from "@/pages/chats-demo-page"
export const Route = createFileRoute("/_app/chats")({
component: ChatsDemoPage,
})
+6
View File
@@ -0,0 +1,6 @@
import { LoginPage } from "@/pages/login-page"
import { createFileRoute } from "@tanstack/react-router"
export const Route = createFileRoute("/login")({
component: LoginPage,
})
+1
View File
@@ -1,3 +1,4 @@
// oxlint-disable-next-line import/no-unassigned-import -- Enforces server-only module usage.
import "@tanstack/react-start/server-only"
import { getCookie, setCookie } from "@tanstack/react-start/server"
+1
View File
@@ -1,3 +1,4 @@
// oxlint-disable-next-line import/no-unassigned-import -- Enforces server-only module usage.
import "@tanstack/react-start/server-only"
import { getCookie, setCookie } from "@tanstack/react-start/server"
import {
+1
View File
@@ -1,6 +1,7 @@
@import "@workspace/ui/globals.css";
@import "@workspace/blocks/globals.css";
@import "@workspace/lexical/globals.css";
@import "@workspace/search/globals.css";
:root {
--ui-content-top-gap: --spacing(2);
+1 -1
View File
@@ -28,4 +28,4 @@
}
},
"include": ["src"]
}
}
+1 -1
View File
@@ -11,4 +11,4 @@
"@workspace/ui/*": ["../../packages/ui/src/*"]
}
}
}
}
+1 -1
View File
@@ -21,4 +21,4 @@
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
}
+3 -4
View File
@@ -1,5 +1,6 @@
import path from "path"
import tailwindcss from "@tailwindcss/vite"
import { icones } from "@icones/vite"
import { tanstackStart } from "@tanstack/react-start/plugin/vite"
import { i18n } from "@workspace/i18n/vite"
import react from "@vitejs/plugin-react"
@@ -14,11 +15,9 @@ export default defineConfig({
"@": path.resolve(__dirname, "./src"),
},
},
optimizeDeps: {
include: ["use-sync-external-store/shim/with-selector"],
},
plugins: [
i18n(),
icones({ mode: "symbol", emitData: false }),
tailwindcss(),
tanstackStart({
router: {
@@ -31,6 +30,6 @@ export default defineConfig({
],
server: {
host: "0.0.0.0",
port: 31573,
port: 7100,
},
})
+2 -1
View File
@@ -1,9 +1,10 @@
import path from "node:path"
import { icones } from "@icones/vite"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vitest/config"
export default defineConfig({
plugins: [react()],
plugins: [icones({ mode: "symbol", emitData: false }), react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
+445 -53
View File
@@ -17,7 +17,7 @@
"@base-ui/react": "^1.6.0",
"@base-ui/utils": "^0.3.1",
"@floating-ui/utils": "^0.2.12",
"@iconify/react": "^6.0.2",
"@icones/react": "^0.0.1",
"@lingui/core": "^6",
"@tanstack/react-form": "^1.33.2",
"@tanstack/react-hotkeys": "^0.10.0",
@@ -26,17 +26,16 @@
"@tanstack/react-start": "^1.168.32",
"@workspace/blocks": "workspace:*",
"@workspace/i18n": "workspace:*",
"@workspace/icons": "workspace:*",
"@workspace/lexical": "workspace:*",
"@workspace/search": "workspace:*",
"@workspace/ui": "workspace:*",
"country-flag-icons": "^1.6.20",
"lucide-react": "^1.27.0",
"react": "^19.2.6",
"react-day-picker": "^10.0.1",
"react-dom": "^19.2.6",
"sonner": "^2.0.7",
},
"devDependencies": {
"@icones/vite": "^0.0.1",
"@tailwindcss/vite": "^4",
"@testing-library/react": "^16.3.2",
"@types/node": "^24",
@@ -50,6 +49,39 @@
"vitest": "^4.1.10",
},
},
"docs": {
"name": "docs",
"version": "0.0.0",
"dependencies": {
"@icones/react": "^0.0.1",
"@workspace/i18n": "workspace:*",
"@workspace/preferences": "workspace:*",
"@workspace/search": "workspace:*",
"@workspace/ui": "workspace:*",
"clsx": "^2.1.1",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"tailwind-merge": "^3.7.0",
},
"devDependencies": {
"@babel/core": "^7.29.7",
"@icones/vite": "^0.0.1",
"@rolldown/plugin-babel": "^0.2.3",
"@tailwindcss/vite": "^4.3.3",
"@types/babel__core": "^7.20.5",
"@types/node": "^24.13.3",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.7",
"@vitejs/plugin-react": "^6.1.1",
"babel-plugin-react-compiler": "^1.0.0",
"oxlint": "^1.81.0",
"satteri": "^0.10.5",
"tailwindcss": "^4.3.3",
"typescript": "~6.0.2",
"vite": "^8.3.0",
"vite-plugin-satteri": "^0.3.5",
},
},
"packages/blocks": {
"name": "@workspace/blocks",
"version": "0.0.0",
@@ -57,14 +89,14 @@
"@base-ui/react": "^1.6.0",
"@base-ui/utils": "^0.3.1",
"@floating-ui/utils": "^0.2.12",
"@icones/react": "^0.0.1",
"@lexical/react": "^0.48.0",
"@tanstack/react-hotkeys": "^0.10.0",
"@tanstack/react-query": "^5.101.4",
"@tanstack/react-router": "^1.170.18",
"@workspace/i18n": "workspace:*",
"@workspace/icons": "workspace:*",
"@workspace/ui": "workspace:*",
"cmdk": "^1.1.1",
"lucide-react": "^1.27.0",
"lexical": "^0.48.0",
"react": "^19.2.6",
"react-dom": "^19.2.6",
},
@@ -89,9 +121,9 @@
"@lingui/core": "^6",
"@lingui/react": "^6",
"@vitejs/plugin-react": "^6",
"@workspace/ui": "workspace:*",
"ansi-to-react": "^6.2.6",
"cac": "^7.0.0",
"lucide-react": "^1.27.0",
"open": "^11.0.0",
"react": "^19.2.6",
"react-dom": "^19.2.6",
@@ -107,19 +139,6 @@
"vitest": "^4",
},
},
"packages/icons": {
"name": "@workspace/icons",
"version": "0.0.0",
"dependencies": {
"@hugeicons/core-free-icons": "^4.2.3",
"@hugeicons/react": "^1.1.9",
"react": "^19.2.6",
},
"devDependencies": {
"@types/react": "^19",
"typescript": "~6",
},
},
"packages/lexical": {
"name": "@workspace/lexical",
"version": "0.0.0",
@@ -135,7 +154,6 @@
"@workspace/i18n": "workspace:*",
"@workspace/ui": "workspace:*",
"lexical": "^0.48.0",
"lucide-react": "^1.27.0",
"react": "^19.2.6",
"react-dom": "^19.2.6",
},
@@ -148,6 +166,42 @@
"vitest": "^4.1.10",
},
},
"packages/preferences": {
"name": "@workspace/preferences",
"version": "0.0.0",
"dependencies": {
"react": "^19.2.6",
},
"devDependencies": {
"@testing-library/react": "^16.3.2",
"@types/react": "^19",
"@types/react-dom": "^19",
"jsdom": "^30.0.1",
"react-dom": "^19.2.6",
"typescript": "~6",
"vitest": "^4.1.10",
},
},
"packages/search": {
"name": "@workspace/search",
"version": "0.0.0",
"dependencies": {
"@tanstack/react-hotkeys": "^0.10.0",
"@workspace/i18n": "workspace:*",
"@workspace/ui": "workspace:*",
"cmdk": "^1.1.1",
"react": "^19.2.6",
},
"devDependencies": {
"@testing-library/react": "^16.3.2",
"@types/react": "^19",
"@types/react-dom": "^19",
"jsdom": "^30.0.1",
"react-dom": "^19.2.6",
"typescript": "~6",
"vitest": "^4.1.10",
},
},
"packages/ui": {
"name": "@workspace/ui",
"version": "0.0.0",
@@ -155,6 +209,7 @@
"@base-ui/react": "^1.6.0",
"@base-ui/utils": "^0.3.1",
"@fontsource-variable/inter": "^5.3.0",
"@icones/react": "^0.0.1",
"@shadcn/react": "^0.2.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -162,7 +217,6 @@
"date-fns": "^4.4.0",
"embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2",
"lucide-react": "^1.27.0",
"react": "^19.2.6",
"react-day-picker": "^10.0.1",
"react-dom": "^19.2.6",
@@ -184,6 +238,8 @@
},
},
"packages": {
"@antfu/install-pkg": ["@antfu/install-pkg@2.0.1", "", { "dependencies": { "package-manager-detector": "^1.7.0", "tinyexec": "^1.2.4" } }, "sha512-iCKVQcIC0e3oDxEfs3SHQGW+ovhBMZmS1TE+bTk50rVyMCBmCfClv7Qi3HQKlumYwvjb/iIMeWCW2i67q6kFfQ=="],
"@asamuzakjp/css-color": ["@asamuzakjp/css-color@6.0.5", "", { "dependencies": { "@csstools/css-calc": "^3.2.1", "@csstools/css-color-parser": "^4.1.9", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "lru-cache": "^11.5.2" } }, "sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA=="],
"@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@8.3.0", "", { "dependencies": { "bidi-js": "^1.0.3", "css-tree": "^3.2.1", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.5.2" } }, "sha512-UJLfKXBhrc8i1vH2eJXuYQMwlsLKWFw3O+CPqXSuVEiikeAim3UgrfWX0k4tA/X8cRFM8iZ7OaqBokFGbYusdg=="],
@@ -252,8 +308,42 @@
"@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="],
"@bruits/satteri-darwin-arm64": ["@bruits/satteri-darwin-arm64@0.10.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA=="],
"@bruits/satteri-darwin-x64": ["@bruits/satteri-darwin-x64@0.10.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q=="],
"@bruits/satteri-linux-arm64-gnu": ["@bruits/satteri-linux-arm64-gnu@0.10.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w=="],
"@bruits/satteri-linux-arm64-musl": ["@bruits/satteri-linux-arm64-musl@0.10.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA=="],
"@bruits/satteri-linux-x64-gnu": ["@bruits/satteri-linux-x64-gnu@0.10.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw=="],
"@bruits/satteri-linux-x64-musl": ["@bruits/satteri-linux-x64-musl@0.10.5", "", { "os": "linux", "cpu": "x64" }, "sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg=="],
"@bruits/satteri-wasm32-wasi": ["@bruits/satteri-wasm32-wasi@0.10.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.2.3" }, "cpu": "none" }, "sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw=="],
"@bruits/satteri-win32-arm64-msvc": ["@bruits/satteri-win32-arm64-msvc@0.10.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-siTV88nb0LRqNpkL2gXboqCwVdq95sLtzMHS1/3eONV2gLbB3NAK46wmSMvCO/yquBvI2lvaFIfd8P12ecsxBw=="],
"@bruits/satteri-win32-x64-msvc": ["@bruits/satteri-win32-x64-msvc@0.10.5", "", { "os": "win32", "cpu": "x64" }, "sha512-C3IfPvfvMXmlzBxaMPKFS1XiuV9pu2mC7YqkPk7PSvTgPZ8gbdASIpHpztDLvTTQjqZ0z1Ol8tK5X+V6XXC0wQ=="],
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="],
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="],
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260908.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-t3juyCXFn12OklBL0S7UC98py3nLEmuioBOUOazeyeVsLUu8fv+pfJrR+XzwSH2Uh6rCXZNogRh+LtV0YpzMcQ=="],
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260908.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-I1nwA4qm/fUNSKhPSO76YA6JAhcA0KU63yFcYHN6AiDowGbDyfjDPH9KCVopT5rI1LY4GfbdAi5b9gODqZsAkQ=="],
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260908.1", "", { "os": "linux", "cpu": "x64" }, "sha512-s/h5uSW1UC6dGeVKSqrPLpTu+vo0fKJZCNEokW1Ol1qcCB2oN6WCRHhoyzo4Y69oONAXRgLfLBYaHWe7z51Vnw=="],
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260908.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-PP/nUKl0R6colwfocggL8dctO/dFMqMft12unzFUkoAJr0aXQeT+ia0JuNmOUWXe6EfvyCSmAbijEsTKQ5t/ew=="],
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260908.1", "", { "os": "win32", "cpu": "x64" }, "sha512-jkaS5EKKTvzAdIlbMfOYrHoTucWVRwYBwIig+xRsjXQv5BXTLA2Llg+iM8djlKtk0CjkztZhXmuxuqoqWKZmFw=="],
"@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
"@csstools/color-helpers": ["@csstools/color-helpers@6.1.0", "", {}, "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg=="],
"@csstools/css-calc": ["@csstools/css-calc@3.3.0", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ=="],
@@ -356,14 +446,74 @@
"@hono/node-server": ["@hono/node-server@1.19.15", "", { "peerDependencies": { "hono": "^4" } }, "sha512-Za2ai6TLdKjUvnur+eenO6nuYYipVAEhyCAdaV8IRvmU9kK8crOZUSYvIXn72E4f8fJqyAbpcJuTsYYmZp9Deg=="],
"@hugeicons/core-free-icons": ["@hugeicons/core-free-icons@4.2.3", "", {}, "sha512-fpiU0qFZkv4ABi23xJ2m4qNr0BBfuIaYjPboN8WDA6aj9D+OzXa7eykxKOpACZRuGbDz1U9S5DsNx1d5KNnsgA=="],
"@icones/core": ["@icones/core@0.0.1", "", { "dependencies": { "@icones/names": "^0.0.1" } }, "sha512-75LHdh7YxW+55sIU56vQx6LO+0PFUk3XU8mf4kchgP7r9HtSlMIZomCBIQ3dytZllKGDOvSbJlcHJ59fIGJpbQ=="],
"@hugeicons/react": ["@hugeicons/react@1.1.9", "", { "peerDependencies": { "react": ">=16.0.0" } }, "sha512-O+lWSWjbijoAvMCxn4K2bQWCGN5+mP1y5j+X99j23mXMj+s0X25fs71T6t9YJLaBodwmZdaewD27dzS/PiboQw=="],
"@icones/icons": ["@icones/icons@0.0.1", "", {}, "sha512-5krQCp13yYEsyUY/kshQNqmcuIiIBUHU9BkSoIc68IG7AOym29ocw5pilHftu+OX1il5BhyusuYVR3qTwyH8Ug=="],
"@iconify/react": ["@iconify/react@6.0.2", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-SMmC2sactfpJD427WJEDN6PMyznTFMhByK9yLW0gOTtnjzzbsi/Ke/XqsumsavFPwNiXs8jSiYeZTmLCLwO+Fg=="],
"@icones/names": ["@icones/names@0.0.1", "", {}, "sha512-Vl/e1pmZv5PPbRTDE8fd39m0ohpx2vJSEGH8stkk425cmQFFbGhmXhhip/Q1MJ3kx4ZWgww5MrV7fzrHtdEvAw=="],
"@icones/react": ["@icones/react@0.0.1", "", { "dependencies": { "@icones/core": "^0.0.1" }, "peerDependencies": { "react": "^18.2.0 || ^19.0.0" } }, "sha512-80hN0gf6ozcL8oMUfjZIVLfxegatSOzU2jdmJuzAz6Zq2VIwJHOogi4EfKVJnyq7sWHt4MQr1R0XQrYldK/fFg=="],
"@icones/vite": ["@icones/vite@0.0.1", "", { "dependencies": { "@icones/core": "^0.0.1", "@icones/icons": "^0.0.1", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.7", "parse5": "^8.0.1", "saxes": "^6.0.0" }, "peerDependencies": { "vite": "^8.0.0" } }, "sha512-QLzjlZm79awfLTdt+p/mvQEKY6fB1QB1nyRUfUNoAq5ukolEmKAfbmANq5z3gR/FKk1wWHABMcK7LICzVIUkIA=="],
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
"@iconify/utils": ["@iconify/utils@3.1.7", "", { "dependencies": { "@antfu/install-pkg": "^2.0.1", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w=="],
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.1" }, "os": "darwin", "cpu": "arm64" }, "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.1" }, "os": "darwin", "cpu": "x64" }, "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw=="],
"@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.2", "", { "dependencies": { "@img/sharp-wasm32": "0.35.2" }, "os": "freebsd" }, "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.1", "", { "os": "linux", "cpu": "arm" }, "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng=="],
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.1", "", { "os": "linux", "cpu": "none" }, "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.1" }, "os": "linux", "cpu": "arm" }, "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.1" }, "os": "linux", "cpu": "arm64" }, "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.1" }, "os": "linux", "cpu": "ppc64" }, "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg=="],
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.1" }, "os": "linux", "cpu": "none" }, "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.1" }, "os": "linux", "cpu": "s390x" }, "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.1" }, "os": "linux", "cpu": "x64" }, "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.1" }, "os": "linux", "cpu": "arm64" }, "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.1" }, "os": "linux", "cpu": "x64" }, "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.2", "", { "dependencies": { "@emnapi/runtime": "^1.11.1" } }, "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw=="],
"@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.2", "", { "dependencies": { "@img/sharp-wasm32": "0.35.2" }, "cpu": "none" }, "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.2", "", { "os": "win32", "cpu": "x64" }, "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ=="],
"@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
@@ -448,7 +598,7 @@
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
@@ -542,6 +692,12 @@
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.76.0", "", { "os": "win32", "cpu": "x64" }, "sha512-5qcirPHO8nKfkoowEVWtpAoVTcYDy6g0UT0NGic450Qv8J2NrOqg4uQ8QppRP4MDTC7Xx47lbZnmadTH03CGGA=="],
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
"@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="],
"@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
"@preact/signals-core": ["@preact/signals-core@1.14.4", "", {}, "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA=="],
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.7", "", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="],
@@ -578,6 +734,8 @@
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.12.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw=="],
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.7", "", { "os": "android", "cpu": "arm" }, "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="],
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw=="],
@@ -608,6 +766,8 @@
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="],
"@rolldown/plugin-babel": ["@rolldown/plugin-babel@0.2.4", "", { "dependencies": { "picomatch": "^4.0.7" }, "peerDependencies": { "@babel/core": "^7.29.0 || ^8.0.0-rc.1", "@babel/plugin-transform-runtime": "^7.29.0 || ^8.0.0-rc.1", "@babel/runtime": "^7.27.0 || ^8.0.0-rc.1", "rolldown": "^1.0.0-rc.5", "vite": "^8.0.0" }, "optionalPeers": ["@babel/plugin-transform-runtime", "@babel/runtime", "vite"] }, "sha512-ygmm2j/mN+aCvstHo0x3IOu4uc6LqXT9SJ3q4kpfG/+Yhfz0B+E47FITcC0FljZADwUFARP6E2kiE+Lc26D8wg=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
@@ -616,8 +776,12 @@
"@sinclair/typebox": ["@sinclair/typebox@0.27.12", "", {}, "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g=="],
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
"@speed-highlight/core": ["@speed-highlight/core@1.2.24", "", {}, "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
@@ -714,6 +878,14 @@
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
@@ -738,20 +910,28 @@
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
"@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
"@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="],
"@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
"@types/node": ["@types/node@24.13.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q=="],
"@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
"@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"@types/react-dom": ["@types/react-dom@19.3.0", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="],
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
"@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
@@ -766,7 +946,7 @@
"@vitejs/devtools-kit": ["@vitejs/devtools-kit@0.4.9", "", { "dependencies": { "@devframes/hub": "^0.7.14", "@devframes/json-render": "^0.7.14", "devframe": "^0.7.14", "local-pkg": "^1.2.1", "mlly": "^1.8.2", "nostics": "^1.2.0", "nypm": "^0.6.8" }, "peerDependencies": { "vite": "*" } }, "sha512-uI1Gk4rfc6qCAK+5m5o3d5HCNVPpnGYi+RlGnLuWn6j9XNcvuP1MiZrgEMyXvpbQHEN3N4mcfUswIPc8XnBvsg=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-XcCQz0TBpBgljhj0gMuuDj49i6Ytqh5q1osT/Gp5uAVJUCTWxyskk/l1jwYYiu2xcNHHipdMz40EGfM1VdamVg=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.1.1", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "oxc-transform-react": "^0.145.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler", "oxc-transform-react"] }, "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw=="],
"@vitest/expect": ["@vitest/expect@4.1.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA=="],
@@ -804,10 +984,12 @@
"@workspace/i18n": ["@workspace/i18n@workspace:packages/i18n"],
"@workspace/icons": ["@workspace/icons@workspace:packages/icons"],
"@workspace/lexical": ["@workspace/lexical@workspace:packages/lexical"],
"@workspace/preferences": ["@workspace/preferences@workspace:packages/preferences"],
"@workspace/search": ["@workspace/search@workspace:packages/search"],
"@workspace/ui": ["@workspace/ui@workspace:packages/ui"],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
@@ -844,6 +1026,8 @@
"babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="],
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.11.4", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-s4+sLr9mZ/CyqeRritFeYV/Zx73OAtmaHn6kkBS1XRoJn1hrg3xIDUcpicAEX68tkcIN0iBCgti31C8zxtkhsQ=="],
@@ -852,6 +1036,8 @@
"birpc": ["birpc@4.0.0", "", {}, "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw=="],
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
"body-parser": ["body-parser@2.3.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", "qs": "^6.15.2", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw=="],
"brace-expansion": ["brace-expansion@5.0.8", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg=="],
@@ -926,8 +1112,6 @@
"cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="],
"country-flag-icons": ["country-flag-icons@1.6.20", "", {}, "sha512-py8JiEKzjhYw6HPJ0L7SxLgCYim36UPRTZX43/kqGueUCZLSvnrqAiwW8HtQibur7mdkFQUkjOgdK+o/9FBtaw=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"crossws": ["crossws@0.4.10", "", { "peerDependencies": { "srvx": ">=0.11.5" }, "optionalPeers": ["srvx"] }, "sha512-pz3oubH/dt12KjqsUB0IuXW4nwRDQ583iDsP4555Cpdqx0NoU7pGlWBcayyFI8f/l/idRpgjMEfwuOxSWJYlIA=="],
@@ -998,6 +1182,8 @@
"diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
"docs": ["docs@workspace:docs"],
"dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
"dot-prop": ["dot-prop@6.0.1", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA=="],
@@ -1032,6 +1218,8 @@
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
"error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
@@ -1154,6 +1342,8 @@
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="],
@@ -1240,29 +1430,29 @@
"lib0": ["lib0@0.2.117", "", { "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0serve": "bin/0serve.js", "0gentesthtml": "bin/gentesthtml.js", "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js" } }, "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw=="],
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
"lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
@@ -1278,8 +1468,6 @@
"lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="],
"lucide-react": ["lucide-react@1.27.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-rJicGl/3Fly/E0rOH1YmPZ6e49JCnKknh1ox1vpHnkfjujAkKA6sqUZvH3MTAaXXjgexyUwgNwTJzTtYuAFYJw=="],
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
@@ -1306,6 +1494,8 @@
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"miniflare": ["miniflare@5.20260908.0-alpha", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "0.35.2", "undici": "7.29.0", "workerd": "1.20260908.1", "ws": "8.21.0", "youch": "4.1.0-beta.10" } }, "sha512-BHIknb0u6vLIvb+R8PsUAzgoWWk3OlW85DrV2SG0EydfSpIba28YT0rH6xZThjnSwDxzNuW3FDRNSWvbiI/DVw=="],
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -1318,7 +1508,7 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
"nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
@@ -1370,6 +1560,8 @@
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
"package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
@@ -1386,7 +1578,7 @@
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
@@ -1404,7 +1596,7 @@
"pofile-ts": ["pofile-ts@4.0.3", "", {}, "sha512-sz1pnjgEfPyZ+QvaeX3NtCmbYnEvG01LZRLoN/uXoLtPZtxCIH5IctL7yXXc0fFyk/fqV6K8g3hlNfr6IJwupA=="],
"postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="],
"postcss": ["postcss@8.5.28", "", { "dependencies": { "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A=="],
"postcss-selector-parser": ["postcss-selector-parser@7.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg=="],
@@ -1484,6 +1676,8 @@
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"satteri": ["satteri@0.10.5", "", { "dependencies": { "@types/estree-jsx": "^1.0.5", "@types/hast": "^3.0.5", "@types/mdast": "^4.0.4", "@types/unist": "^3.0.3" }, "optionalDependencies": { "@bruits/satteri-darwin-arm64": "0.10.5", "@bruits/satteri-darwin-x64": "0.10.5", "@bruits/satteri-linux-arm64-gnu": "0.10.5", "@bruits/satteri-linux-arm64-musl": "0.10.5", "@bruits/satteri-linux-x64-gnu": "0.10.5", "@bruits/satteri-linux-x64-musl": "0.10.5", "@bruits/satteri-wasm32-wasi": "0.10.5", "@bruits/satteri-win32-arm64-msvc": "0.10.5", "@bruits/satteri-win32-x64-msvc": "0.10.5" } }, "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ=="],
"saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
@@ -1502,6 +1696,8 @@
"shadcn": ["shadcn@4.15.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "undici": "^7.27.2", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-fFTpfOuRwqjpXGp/sKpAxJkjdgv1jf8bDrW1xi0cVn2k7WJ5ijV/gjkAlkAidGDjhEkgcUuxVdm4oRB9r8BivA=="],
"sharp": ["sharp@0.35.2", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.4" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.2", "@img/sharp-darwin-x64": "0.35.2", "@img/sharp-freebsd-wasm32": "0.35.2", "@img/sharp-libvips-darwin-arm64": "1.3.1", "@img/sharp-libvips-darwin-x64": "1.3.1", "@img/sharp-libvips-linux-arm": "1.3.1", "@img/sharp-libvips-linux-arm64": "1.3.1", "@img/sharp-libvips-linux-ppc64": "1.3.1", "@img/sharp-libvips-linux-riscv64": "1.3.1", "@img/sharp-libvips-linux-s390x": "1.3.1", "@img/sharp-libvips-linux-x64": "1.3.1", "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", "@img/sharp-libvips-linuxmusl-x64": "1.3.1", "@img/sharp-linux-arm": "0.35.2", "@img/sharp-linux-arm64": "0.35.2", "@img/sharp-linux-ppc64": "0.35.2", "@img/sharp-linux-riscv64": "0.35.2", "@img/sharp-linux-s390x": "0.35.2", "@img/sharp-linux-x64": "0.35.2", "@img/sharp-linuxmusl-arm64": "0.35.2", "@img/sharp-linuxmusl-x64": "0.35.2", "@img/sharp-webcontainers-wasm32": "0.35.2", "@img/sharp-win32-arm64": "0.35.2", "@img/sharp-win32-ia32": "0.35.2", "@img/sharp-win32-x64": "0.35.2" } }, "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
@@ -1520,6 +1716,8 @@
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"smol-toml": ["smol-toml@1.8.0", "", {}, "sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ=="],
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
@@ -1554,7 +1752,7 @@
"tabbable": ["tabbable@6.5.0", "", {}, "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA=="],
"tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="],
"tailwind-merge": ["tailwind-merge@3.7.0", "", {}, "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA=="],
"tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="],
@@ -1634,7 +1832,9 @@
"victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="],
"vite": ["vite@8.1.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.17", "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw=="],
"vite": ["vite@8.3.0", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.28", "rolldown": "~1.2.6", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ=="],
"vite-plugin-satteri": ["vite-plugin-satteri@0.3.5", "", { "dependencies": { "smol-toml": "^1.6.1", "yaml": "^2.6.0" }, "peerDependencies": { "satteri": ">=0.10.0", "vite": "^5 || ^6 || ^7 || ^8" } }, "sha512-X41QiFHwlkfcnNE+nOIe6tCeE32M6BKyVN00Wd7irp94lsOmIqXyNtF+lLc2oPIxno0qlVjSyNNPMIZJDghtJQ=="],
"vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
@@ -1658,8 +1858,14 @@
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
"workerd": ["workerd@1.20260908.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260908.1", "@cloudflare/workerd-darwin-arm64": "1.20260908.1", "@cloudflare/workerd-linux-64": "1.20260908.1", "@cloudflare/workerd-linux-arm64": "1.20260908.1", "@cloudflare/workerd-windows-64": "1.20260908.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-rYhpW6NWHD++p34ej+VXWzi5Pdnmd7ncdbB/ux4s+i3mf7IeOpW3O4roqClQ+Z8ernvyMCknBLCb76z1rA+a7Q=="],
"wrangler": ["wrangler@4.130.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "5.20260908.0-alpha", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260908.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20260908.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js", "cf-wrangler": "bin/cf-wrangler.js" } }, "sha512-fzNjnTzyZl31PGJOFXbiLeZqEIToQ9KOzkkvGdQz4wlB+BoHnl3BRwCR5xg8AqYyzVunvvMHlMzvlbThQ7rrAA=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
"xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
@@ -1670,12 +1876,18 @@
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="],
"yjs": ["yjs@13.6.31", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-Eq+5BRfbeGyqGVrTJL3bEcr8gKkxPuyuoHmAwpk52fDb8kOVMrfVSTRPd6yiGgX5Fskb96qCRjzjbRjrL4YEnw=="],
"yocto-spinner": ["yocto-spinner@1.2.2", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng=="],
"yoctocolors": ["yoctocolors@2.2.0", "", {}, "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg=="],
"youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
"zigpty": ["zigpty@0.2.1", "", {}, "sha512-MR9JqJx2wf5f4wz8zpx050AlqrmWeIW+1h0SO5iEyhG3HFRjY5luC3szS2ux2EGuPjE5OU9ZAuiCBeMWBTrqZw=="],
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
@@ -1684,6 +1896,8 @@
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
"@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
@@ -1692,10 +1906,20 @@
"@dotenvx/dotenvx/undici": ["undici@7.29.0", "", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="],
"@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
"@jest/types/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
"@reduxjs/toolkit/immer": ["immer@11.1.15", "", {}, "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ=="],
"@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="],
"@rolldown/plugin-babel/picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="],
"@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
@@ -1712,12 +1936,16 @@
"@tanstack/start-plugin-core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
"@tanstack/start-plugin-core/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
"@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
"@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@vue/compiler-sfc/postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="],
"body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"conf/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
@@ -1730,6 +1958,8 @@
"data-urls/whatwg-url": ["whatwg-url@16.0.1", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw=="],
"docs/oxlint": ["oxlint@1.83.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.83.0", "@oxlint/binding-android-arm64": "1.83.0", "@oxlint/binding-darwin-arm64": "1.83.0", "@oxlint/binding-darwin-x64": "1.83.0", "@oxlint/binding-freebsd-x64": "1.83.0", "@oxlint/binding-linux-arm-gnueabihf": "1.83.0", "@oxlint/binding-linux-arm-musleabihf": "1.83.0", "@oxlint/binding-linux-arm64-gnu": "1.83.0", "@oxlint/binding-linux-arm64-musl": "1.83.0", "@oxlint/binding-linux-ppc64-gnu": "1.83.0", "@oxlint/binding-linux-riscv64-gnu": "1.83.0", "@oxlint/binding-linux-riscv64-musl": "1.83.0", "@oxlint/binding-linux-s390x-gnu": "1.83.0", "@oxlint/binding-linux-x64-gnu": "1.83.0", "@oxlint/binding-linux-x64-musl": "1.83.0", "@oxlint/binding-openharmony-arm64": "1.83.0", "@oxlint/binding-win32-arm64-msvc": "1.83.0", "@oxlint/binding-win32-ia32-msvc": "1.83.0", "@oxlint/binding-win32-x64-msvc": "1.83.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-cyDzSzaw3uzP0TeCeq3lLRPPoaUxkbB4ZOXj+kn+5r+BX9V+4bNVGk9lxer+WrgcpebH4JxLlJ3KQjveVztOLQ=="],
"dot-prop/is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="],
"enquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -1740,6 +1970,8 @@
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"miniflare/undici": ["undici@7.29.0", "", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="],
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
@@ -1760,18 +1992,34 @@
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
"shadcn/ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
"shadcn/postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="],
"shadcn/tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="],
"shadcn/undici": ["undici@7.29.0", "", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="],
"shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"sharp/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
"string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"vite/picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="],
"vite/rolldown": ["rolldown@1.2.7", "", { "dependencies": { "@oxc-project/types": "=0.148.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.7", "@rolldown/binding-android-arm64": "1.2.7", "@rolldown/binding-darwin-arm64": "1.2.7", "@rolldown/binding-darwin-x64": "1.2.7", "@rolldown/binding-freebsd-x64": "1.2.7", "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", "@rolldown/binding-linux-arm64-gnu": "1.2.7", "@rolldown/binding-linux-arm64-musl": "1.2.7", "@rolldown/binding-linux-ppc64-gnu": "1.2.7", "@rolldown/binding-linux-s390x-gnu": "1.2.7", "@rolldown/binding-linux-x64-gnu": "1.2.7", "@rolldown/binding-linux-x64-musl": "1.2.7", "@rolldown/binding-openharmony-arm64": "1.2.7", "@rolldown/binding-win32-arm64-msvc": "1.2.7", "@rolldown/binding-win32-x64-msvc": "1.2.7" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig=="],
"vitest/vite": ["vite@8.1.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.17", "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw=="],
"youch/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
"@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
"@dotenvx/dotenvx/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
@@ -1792,10 +2040,94 @@
"@jest/types/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
"@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
"@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
"@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
"@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
"@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
"@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
"@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
"@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
"@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
"@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"@tanstack/react-router/@tanstack/react-store/@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
"@tanstack/start-plugin-core/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"@vue/compiler-sfc/postcss/nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"docs/oxlint/@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.83.0", "", { "os": "android", "cpu": "arm" }, "sha512-0yGY24EwsLk5YDe6F+VkmZyRHSwJDALa3nIrPpq7FXmp2lV2d0TzvBCGeZk+wgiULRGr5blhyr4QMp5KCXJUqA=="],
"docs/oxlint/@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.83.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hHfJ0vc17A4iUjH5p9BsTUPYbYRNxGpvD2lbu1aBRk54bzNIx9o5TtYF39QPZcV95DagZd+4DEAw2RH3G2ZsMg=="],
"docs/oxlint/@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.83.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hsOjYjszLb/3zym/TkzUMPAoQlTJcuzSyEPOAyA+skXJIX9M0o+4JfOtqopX/Vf4hSLrJ98j0nvFo23gzk8auQ=="],
"docs/oxlint/@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.83.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-mjh5oH2EA+wl5yRJYT9K9G61O2zFlpuv+yf2JwZOi0+dq2FnTUtm1h8i+5Ik0fXPWIu/k84I1psZR9aQsLAnyA=="],
"docs/oxlint/@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.83.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fNHr64/YaO8YssuoDVC8+F4Uk5enR86q5uxfHkQrjAPs1dbAILOrD2uaud+J7MO8Fx774g44ERLD0IGIvZE48w=="],
"docs/oxlint/@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.83.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Qpwy3zzAwMj+8/lyYItHmkSMwbkprFNWTK7jPYDOxSyxEhaSLOWYUTCMkjF334J8/WD0nznCCsoBbIH6hpsuIw=="],
"docs/oxlint/@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.83.0", "", { "os": "linux", "cpu": "arm" }, "sha512-s+BirYLFq7JL2k9sP0XI3ZXJ9dYvJ8sX3jLCLoag7tt+zrSHpZxP0jqznfL+Gdgwu7ay0dYgGYJXrQvq3iWloA=="],
"docs/oxlint/@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.83.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-7lihXt3vKr+GIyapNbHrnFHm/biiW30le6Zv/DExbAFPF6YwCQXVFlONPFehxs0CpGO4CBfYPM9rdDT+XMoIlg=="],
"docs/oxlint/@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.83.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-q63JalLYVkZiZvls1z3PPUnpmQluOMXp0khqQMznCeAPLGydfNY8JhvuA4WlK57JfrvikU8wB5lPVveqpIXvew=="],
"docs/oxlint/@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.83.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-krQmDF+dRbxvdqVPV88ZuOoPPu8X5BuqDA8Hd+qcS4YMRQCb+nexA57DazgGsc/rGdKBe3QmV0mnv0bdpW/p5g=="],
"docs/oxlint/@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.83.0", "", { "os": "linux", "cpu": "none" }, "sha512-MmOl8Y6txEAXZU1RG8Rr264jQ6D7VPmqFsU/45x/FeWsGe32hklTqGrLE6UxHzp5Rjt0wP+20tY8YXKgSFB3mw=="],
"docs/oxlint/@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.83.0", "", { "os": "linux", "cpu": "none" }, "sha512-u1rMymh0W3JZkq370kzQsYPULGWqhE09pZRqnZvUSoYaI9pVO5yVX+iYIslmWuEgwuzH9YAaOsScJiobWCHoOw=="],
"docs/oxlint/@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.83.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-y0zK3HNwGysu7rqtE+BQG/d0bx5gh/KwlOtghN8oWeK1KcWzeaLqtZrbm8owqdma1lFyrce/hTO5ismuNu+INQ=="],
"docs/oxlint/@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.83.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rS5gM0NgD7ngmuJmbIehsidtrOwKkLFwCQbKEeb9KuyQrrWNq5Zkn0uV6AYdXOMJ0grrWEiLwBuvMxt8w5vsNw=="],
"docs/oxlint/@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.83.0", "", { "os": "linux", "cpu": "x64" }, "sha512-W2IH4EtpcPaWcvNGCA95YoDg4vxqE/ZiPCi3arrxEEpsK7+JQN9WYwrlYFx9pcdP6KPXqRqkv3zdQPHcx7b6YQ=="],
"docs/oxlint/@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.83.0", "", { "os": "none", "cpu": "arm64" }, "sha512-6LyKkUyoajssTPLlZmDbZIbu4IZ5B4bGuRUnBgCGpEvHP3FQMaYITncHA/unPUo7q+Z+pIu2HhdkQ+8d1SG7iA=="],
"docs/oxlint/@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.83.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Uz/fObEtF0jmNJQJ8CGRBKfefYstS0/wjD3s6IGzP8nUwsJykHQJBiN3npHwKiGRGn/vvBEgNr4B3cCzmmatvg=="],
"docs/oxlint/@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.83.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-u7XcvPW6Bk58tY5iWs2ESb0vJjoE/kuSpHxopbwp/p3ZtWVQXZ6wor5w3ssVTHOqd/v8b+QdhSFWQ4grEUNWpA=="],
"docs/oxlint/@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.83.0", "", { "os": "win32", "cpu": "x64" }, "sha512-LZRubd7ph13QmAg4fFecTYVZkiYbROR2Htaxh/ufWRkDhPOm2wrwaEYR89e0YpPFD3dqBrPoxS7myBw5hmYA7Q=="],
"jest-validate/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"jest-validate/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
@@ -1810,8 +2142,68 @@
"shadcn/ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"shadcn/postcss/nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
"vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.148.0", "", {}, "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A=="],
"vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.7", "", { "os": "android", "cpu": "arm64" }, "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw=="],
"vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw=="],
"vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ=="],
"vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA=="],
"vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.7", "", { "os": "linux", "cpu": "arm" }, "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ=="],
"vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg=="],
"vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q=="],
"vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg=="],
"vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw=="],
"vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.7", "", { "os": "linux", "cpu": "x64" }, "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA=="],
"vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.7", "", { "os": "linux", "cpu": "x64" }, "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw=="],
"vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.7", "", { "os": "none", "cpu": "arm64" }, "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w=="],
"vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw=="],
"vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.7", "", { "os": "win32", "cpu": "x64" }, "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw=="],
"vitest/vite/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
"vitest/vite/postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="],
"shadcn/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
"shadcn/ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
"vitest/vite/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
"vitest/vite/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
"vitest/vite/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
"vitest/vite/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
"vitest/vite/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
"vitest/vite/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
"vitest/vite/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
"vitest/vite/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
"vitest/vite/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
"vitest/vite/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
"vitest/vite/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"vitest/vite/postcss/nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
}
}
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
+35
View File
@@ -0,0 +1,35 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## React Compiler
The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information.
Note: This will impact Vite dev & build performances.
You can also try [the experimental native React Compiler support in plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md#rust-react-compiler) by using `compiler: true` in the plugin options instead of using the Babel plugin.
## Expanding the Oxlint configuration
If you are developing a production application, we recommend enabling type-aware lint rules by installing `oxlint-tsgolint` and editing `.oxlintrc.json`:
```json
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"options": {
"typeAware": true
},
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
```
See the [Oxlint rules documentation](https://oxc.rs/docs/guide/usage/linter/rules) for the full list of rules and categories.
+255
View File
@@ -0,0 +1,255 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "docs",
"dependencies": {
"react": "^19.2.8",
"react-dom": "^19.2.8",
},
"devDependencies": {
"@babel/core": "^7.29.7",
"@rolldown/plugin-babel": "^0.2.3",
"@types/babel__core": "^7.20.5",
"@types/node": "^24.13.3",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.7",
"@vitejs/plugin-react": "^6.1.1",
"babel-plugin-react-compiler": "^1.0.0",
"oxlint": "^1.81.0",
"typescript": "~6.0.2",
"vite": "^8.3.0",
},
},
},
"packages": {
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
"@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
"@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="],
"@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="],
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
"@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
"@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
"@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="],
"@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
"@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="],
"@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@oxc-project/types": ["@oxc-project/types@0.149.0", "", {}, "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.83.0", "", { "os": "android", "cpu": "arm" }, "sha512-0yGY24EwsLk5YDe6F+VkmZyRHSwJDALa3nIrPpq7FXmp2lV2d0TzvBCGeZk+wgiULRGr5blhyr4QMp5KCXJUqA=="],
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.83.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hHfJ0vc17A4iUjH5p9BsTUPYbYRNxGpvD2lbu1aBRk54bzNIx9o5TtYF39QPZcV95DagZd+4DEAw2RH3G2ZsMg=="],
"@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.83.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hsOjYjszLb/3zym/TkzUMPAoQlTJcuzSyEPOAyA+skXJIX9M0o+4JfOtqopX/Vf4hSLrJ98j0nvFo23gzk8auQ=="],
"@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.83.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-mjh5oH2EA+wl5yRJYT9K9G61O2zFlpuv+yf2JwZOi0+dq2FnTUtm1h8i+5Ik0fXPWIu/k84I1psZR9aQsLAnyA=="],
"@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.83.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fNHr64/YaO8YssuoDVC8+F4Uk5enR86q5uxfHkQrjAPs1dbAILOrD2uaud+J7MO8Fx774g44ERLD0IGIvZE48w=="],
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.83.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Qpwy3zzAwMj+8/lyYItHmkSMwbkprFNWTK7jPYDOxSyxEhaSLOWYUTCMkjF334J8/WD0nznCCsoBbIH6hpsuIw=="],
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.83.0", "", { "os": "linux", "cpu": "arm" }, "sha512-s+BirYLFq7JL2k9sP0XI3ZXJ9dYvJ8sX3jLCLoag7tt+zrSHpZxP0jqznfL+Gdgwu7ay0dYgGYJXrQvq3iWloA=="],
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.83.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-7lihXt3vKr+GIyapNbHrnFHm/biiW30le6Zv/DExbAFPF6YwCQXVFlONPFehxs0CpGO4CBfYPM9rdDT+XMoIlg=="],
"@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.83.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-q63JalLYVkZiZvls1z3PPUnpmQluOMXp0khqQMznCeAPLGydfNY8JhvuA4WlK57JfrvikU8wB5lPVveqpIXvew=="],
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.83.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-krQmDF+dRbxvdqVPV88ZuOoPPu8X5BuqDA8Hd+qcS4YMRQCb+nexA57DazgGsc/rGdKBe3QmV0mnv0bdpW/p5g=="],
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.83.0", "", { "os": "linux", "cpu": "none" }, "sha512-MmOl8Y6txEAXZU1RG8Rr264jQ6D7VPmqFsU/45x/FeWsGe32hklTqGrLE6UxHzp5Rjt0wP+20tY8YXKgSFB3mw=="],
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.83.0", "", { "os": "linux", "cpu": "none" }, "sha512-u1rMymh0W3JZkq370kzQsYPULGWqhE09pZRqnZvUSoYaI9pVO5yVX+iYIslmWuEgwuzH9YAaOsScJiobWCHoOw=="],
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.83.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-y0zK3HNwGysu7rqtE+BQG/d0bx5gh/KwlOtghN8oWeK1KcWzeaLqtZrbm8owqdma1lFyrce/hTO5ismuNu+INQ=="],
"@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.83.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rS5gM0NgD7ngmuJmbIehsidtrOwKkLFwCQbKEeb9KuyQrrWNq5Zkn0uV6AYdXOMJ0grrWEiLwBuvMxt8w5vsNw=="],
"@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.83.0", "", { "os": "linux", "cpu": "x64" }, "sha512-W2IH4EtpcPaWcvNGCA95YoDg4vxqE/ZiPCi3arrxEEpsK7+JQN9WYwrlYFx9pcdP6KPXqRqkv3zdQPHcx7b6YQ=="],
"@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.83.0", "", { "os": "none", "cpu": "arm64" }, "sha512-6LyKkUyoajssTPLlZmDbZIbu4IZ5B4bGuRUnBgCGpEvHP3FQMaYITncHA/unPUo7q+Z+pIu2HhdkQ+8d1SG7iA=="],
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.83.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Uz/fObEtF0jmNJQJ8CGRBKfefYstS0/wjD3s6IGzP8nUwsJykHQJBiN3npHwKiGRGn/vvBEgNr4B3cCzmmatvg=="],
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.83.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-u7XcvPW6Bk58tY5iWs2ESb0vJjoE/kuSpHxopbwp/p3ZtWVQXZ6wor5w3ssVTHOqd/v8b+QdhSFWQ4grEUNWpA=="],
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.83.0", "", { "os": "win32", "cpu": "x64" }, "sha512-LZRubd7ph13QmAg4fFecTYVZkiYbROR2Htaxh/ufWRkDhPOm2wrwaEYR89e0YpPFD3dqBrPoxS7myBw5hmYA7Q=="],
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.8", "", { "os": "android", "cpu": "arm" }, "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.8", "", { "os": "android", "cpu": "arm64" }, "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ=="],
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA=="],
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA=="],
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ=="],
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.8", "", { "os": "linux", "cpu": "arm" }, "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q=="],
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew=="],
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA=="],
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA=="],
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ=="],
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.8", "", { "os": "linux", "cpu": "x64" }, "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g=="],
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.8", "", { "os": "linux", "cpu": "x64" }, "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA=="],
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.8", "", { "os": "none", "cpu": "arm64" }, "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg=="],
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A=="],
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.8", "", { "os": "win32", "cpu": "x64" }, "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng=="],
"@rolldown/plugin-babel": ["@rolldown/plugin-babel@0.2.4", "", { "dependencies": { "picomatch": "^4.0.7" }, "peerDependencies": { "@babel/core": "^7.29.0 || ^8.0.0-rc.1", "@babel/plugin-transform-runtime": "^7.29.0 || ^8.0.0-rc.1", "@babel/runtime": "^7.27.0 || ^8.0.0-rc.1", "rolldown": "^1.0.0-rc.5", "vite": "^8.0.0" }, "optionalPeers": ["@babel/plugin-transform-runtime", "@babel/runtime", "vite"] }, "sha512-ygmm2j/mN+aCvstHo0x3IOu4uc6LqXT9SJ3q4kpfG/+Yhfz0B+E47FITcC0FljZADwUFARP6E2kiE+Lc26D8wg=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/node": ["@types/node@24.13.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-TXyindR+lBr22aJIdMQzCFHPHR6cR4js838mRDCSz5hOKWZvZwsXSSiXDmjRj4iJmgl+sR9O+1mkoVBSMadNug=="],
"@types/react": ["@types/react@19.3.0", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="],
"@types/react-dom": ["@types/react-dom@19.3.0", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.1.1", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "oxc-transform-react": "^0.145.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler", "oxc-transform-react"] }, "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw=="],
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.11.24", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg=="],
"browserslist": ["browserslist@4.29.0", "", { "dependencies": { "baseline-browser-mapping": "^2.11.23", "caniuse-lite": "^1.0.30001810", "electron-to-chromium": "^1.5.427", "node-releases": "^2.0.55", "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"electron-to-chromium": ["electron-to-chromium@1.5.430", "", {}, "sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.19", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug=="],
"node-releases": ["node-releases@2.0.55", "", {}, "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ=="],
"oxlint": ["oxlint@1.83.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.83.0", "@oxlint/binding-android-arm64": "1.83.0", "@oxlint/binding-darwin-arm64": "1.83.0", "@oxlint/binding-darwin-x64": "1.83.0", "@oxlint/binding-freebsd-x64": "1.83.0", "@oxlint/binding-linux-arm-gnueabihf": "1.83.0", "@oxlint/binding-linux-arm-musleabihf": "1.83.0", "@oxlint/binding-linux-arm64-gnu": "1.83.0", "@oxlint/binding-linux-arm64-musl": "1.83.0", "@oxlint/binding-linux-ppc64-gnu": "1.83.0", "@oxlint/binding-linux-riscv64-gnu": "1.83.0", "@oxlint/binding-linux-riscv64-musl": "1.83.0", "@oxlint/binding-linux-s390x-gnu": "1.83.0", "@oxlint/binding-linux-x64-gnu": "1.83.0", "@oxlint/binding-linux-x64-musl": "1.83.0", "@oxlint/binding-openharmony-arm64": "1.83.0", "@oxlint/binding-win32-arm64-msvc": "1.83.0", "@oxlint/binding-win32-ia32-msvc": "1.83.0", "@oxlint/binding-win32-x64-msvc": "1.83.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-cyDzSzaw3uzP0TeCeq3lLRPPoaUxkbB4ZOXj+kn+5r+BX9V+4bNVGk9lxer+WrgcpebH4JxLlJ3KQjveVztOLQ=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="],
"postcss": ["postcss@8.5.28", "", { "dependencies": { "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A=="],
"react": ["react@19.3.0", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="],
"react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="],
"rolldown": ["rolldown@1.2.8", "", { "dependencies": { "@oxc-project/types": "=0.149.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.8", "@rolldown/binding-android-arm64": "1.2.8", "@rolldown/binding-darwin-arm64": "1.2.8", "@rolldown/binding-darwin-x64": "1.2.8", "@rolldown/binding-freebsd-x64": "1.2.8", "@rolldown/binding-linux-arm-gnueabihf": "1.2.8", "@rolldown/binding-linux-arm64-gnu": "1.2.8", "@rolldown/binding-linux-arm64-musl": "1.2.8", "@rolldown/binding-linux-ppc64-gnu": "1.2.8", "@rolldown/binding-linux-s390x-gnu": "1.2.8", "@rolldown/binding-linux-x64-gnu": "1.2.8", "@rolldown/binding-linux-x64-musl": "1.2.8", "@rolldown/binding-openharmony-arm64": "1.2.8", "@rolldown/binding-win32-arm64-msvc": "1.2.8", "@rolldown/binding-win32-x64-msvc": "1.2.8" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ=="],
"scheduler": ["scheduler@0.28.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="],
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"update-browserslist-db": ["update-browserslist-db@1.3.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ=="],
"vite": ["vite@8.3.0", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.28", "rolldown": "~1.2.6", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
}
}
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>docs</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+42
View File
@@ -0,0 +1,42 @@
{
"name": "docs",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "oxlint",
"preview": "vite preview",
"typecheck": "tsc -b"
},
"dependencies": {
"@icones/react": "^0.0.1",
"@workspace/i18n": "workspace:*",
"@workspace/preferences": "workspace:*",
"@workspace/ui": "workspace:*",
"@workspace/search": "workspace:*",
"clsx": "^2.1.1",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"tailwind-merge": "^3.7.0"
},
"devDependencies": {
"@babel/core": "^7.29.7",
"@icones/vite": "^0.0.1",
"@rolldown/plugin-babel": "^0.2.3",
"@tailwindcss/vite": "^4.3.3",
"@types/babel__core": "^7.20.5",
"@types/node": "^24.13.3",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.7",
"@vitejs/plugin-react": "^6.1.1",
"babel-plugin-react-compiler": "^1.0.0",
"oxlint": "^1.81.0",
"satteri": "^0.10.5",
"tailwindcss": "^4.3.3",
"typescript": "~6.0.2",
"vite": "^8.3.0",
"vite-plugin-satteri": "^0.3.5"
}
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="bluesky-icon" viewBox="0 0 16 17">
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
</symbol>
<symbol id="discord-icon" viewBox="0 0 20 19">
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
</symbol>
<symbol id="documentation-icon" viewBox="0 0 21 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
</symbol>
<symbol id="github-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
</symbol>
<symbol id="social-icon" viewBox="0 0 20 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
</symbol>
<symbol id="x-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

+145
View File
@@ -0,0 +1,145 @@
import {
Suspense,
lazy,
startTransition,
useEffect,
useState,
type ComponentType,
} from "react"
import { rootCSSVariables } from "./components/layout"
import { LocaleProvider } from "./components/locale-provider"
import { DocsPreferencesProvider } from "./components/preferences-provider"
import { DocsSearchProvider } from "./components/docs-search-provider"
import { resolveDocLocale } from "./content/registry"
import { useTranslate } from "./lib/locale"
const Index = lazy(() => import("./pages/index"))
const Article = lazy(() => import("./pages/article"))
const Settings = lazy(() => import("./pages/settings"))
type RouteDefinition = {
Component: ComponentType
matches: (pathname: string) => boolean
}
const routes: RouteDefinition[] = [
{
Component: Index,
matches: (pathname) =>
pathname === "/" || /^\/(?:en-US|zh-Hans)\/?$/.test(pathname),
},
{
Component: Article,
matches: (pathname) =>
/^(?:\/(?:en-US|zh-Hans))?\/articles?(?:\/|$)/.test(pathname),
},
{
Component: Settings,
matches: (pathname) =>
/^(?:\/(?:en-US|zh-Hans))?\/settings\/?$/.test(pathname),
},
]
function App() {
const [currentUrl, setCurrentUrl] = useState(() => window.location.href)
useEffect(() => {
function updateRoute() {
startTransition(() => setCurrentUrl(window.location.href))
}
function navigate(event: MouseEvent) {
if (
event.defaultPrevented ||
event.button !== 0 ||
event.metaKey ||
event.ctrlKey ||
event.shiftKey ||
event.altKey
) {
return
}
const target = event.target
if (!(target instanceof Element)) return
const anchor = target.closest<HTMLAnchorElement>("a[href]")
if (
!anchor ||
anchor.hasAttribute("download") ||
(anchor.target && anchor.target !== "_self")
) {
return
}
const destination = new URL(anchor.href, window.location.href)
if (destination.origin !== window.location.origin) return
const current = new URL(window.location.href)
if (
destination.pathname === current.pathname &&
destination.search === current.search &&
destination.hash
) {
return
}
event.preventDefault()
window.history.pushState(null, "", destination)
window.scrollTo({ left: 0, top: 0 })
updateRoute()
}
window.addEventListener("popstate", updateRoute)
document.addEventListener("click", navigate)
return () => {
window.removeEventListener("popstate", updateRoute)
document.removeEventListener("click", navigate)
}
}, [])
const { pathname } = new URL(currentUrl)
const Route = routes.find((route) => route.matches(pathname))?.Component
const locale = resolveDocLocale(pathname)
return (
<DocsPreferencesProvider routeLocale={locale}>
<LocaleProvider locale={locale}>
<DocsSearchProvider>
<div style={rootCSSVariables}>
<Suspense
fallback={
<p className="p-6 text-slate-500 dark:text-slate-400">
Loading…
</p>
}
>
{Route ? <Route key={pathname} /> : <NotFound />}
</Suspense>
</div>
</DocsSearchProvider>
</LocaleProvider>
</DocsPreferencesProvider>
)
}
function NotFound() {
const t = useTranslate()
return (
<main className="mx-auto flex min-h-dvh max-w-xl flex-col justify-center px-6">
<h1 className="text-4xl">
{t({ zh: "页面不存在", us: "Page not found" })}
</h1>
<a
className="mt-4 w-fit text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
href="/"
>
{t({ zh: "返回首页", us: "Back to home" })}
</a>
</main>
)
}
export default App
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

View File
@@ -0,0 +1,179 @@
import { I18nProvider } from "@workspace/i18n"
import {
SearchDialog,
SearchProvider,
type SearchAdapter,
type SearchResultItem,
} from "@workspace/search"
import { messages as searchMessagesEnUS } from "@workspace/search/locales/en-US"
import { messages as searchMessagesZhHans } from "@workspace/search/locales/zh-Hans"
import React from "react"
import {
getDocHref,
getDocPackages,
getDocSearchText,
getDocSectionLabel,
} from "../content/registry"
import type {
DocLocale,
DocPackage,
DocPage,
DocTocItem,
} from "../content/types"
import { useLocale } from "../lib/locale"
const searchCatalogs = {
"en-US": searchMessagesEnUS,
"zh-Hans": searchMessagesZhHans,
} as const
type DocsSearchPayload = {
href: string
}
export function DocsSearchProvider({
children,
}: {
children: React.ReactNode
}) {
const locale = useLocale()
const adapter = React.useMemo(() => createDocsSearchAdapter(locale), [locale])
return (
<I18nProvider catalogs={searchCatalogs} locale={locale}>
<SearchProvider
adapter={adapter}
historyStorageKey={`docs-search-history:${locale}`}
onSelect={({ item }) => {
if (isDocsSearchPayload(item.payload)) navigate(item.payload.href)
}}
>
{children}
<SearchDialog className="font-sans" />
</SearchProvider>
</I18nProvider>
)
}
function createDocsSearchAdapter(locale: DocLocale): SearchAdapter {
const docPackages = getDocPackages(locale)
return {
async search({ query, signal }) {
const normalizedQuery = normalize(query)
if (!normalizedQuery) return { groups: [] }
const terms = normalizedQuery.split(/\s+/).filter(Boolean)
const groups = docPackages.flatMap((docPackage) => {
const items = docPackage.pages
.map((page) => ({
item: createResultItem(docPackage, page, locale),
score: getMatchScore(
normalizedQuery,
terms,
docPackage.label,
page
),
}))
.filter((result) => result.score >= 0)
.sort(
(left, right) =>
right.score - left.score ||
left.item.title.localeCompare(right.item.title, locale)
)
.map((result) => result.item)
return items.length > 0
? [
{
id: docPackage.key,
label: docPackage.label,
items,
total: items.length,
},
]
: []
})
if (signal.aborted) return { groups: [] }
return { groups }
},
}
}
function createResultItem(
docPackage: DocPackage,
page: DocPage,
locale: DocLocale
): SearchResultItem {
return {
id: `${page.section}/${page.slug}`,
title: page.title,
description: page.description,
keywords: [
docPackage.label,
getDocSectionLabel(page.section, locale),
...page.toc.flatMap(flattenTocTitles),
getDocSearchText(page),
],
meta: [getDocSectionLabel(page.section, locale)],
payload: {
href: getDocHref(docPackage, page),
} satisfies DocsSearchPayload,
}
}
function getMatchScore(
query: string,
terms: readonly string[],
packageLabel: string,
page: DocPage
) {
const title = normalize(page.title)
const description = normalize(page.description)
const packageName = normalize(packageLabel)
const toc = normalize(page.toc.flatMap(flattenTocTitles).join(" "))
const body = normalize(getDocSearchText(page))
const searchableText = [packageName, title, description, toc, body].join(" ")
if (!terms.every((term) => searchableText.includes(term))) return -1
let score = 0
if (title === query) score += 100
else if (title.startsWith(query)) score += 70
else if (title.includes(query)) score += 50
if (packageName === query) score += 40
else if (packageName.includes(query)) score += 20
if (description.includes(query)) score += 15
if (toc.includes(query)) score += 10
if (page.section === "overview") score += 2
return score
}
function flattenTocTitles(item: DocTocItem): readonly string[] {
return [item.title, ...(item.items?.flatMap(flattenTocTitles) ?? [])]
}
function normalize(value: string) {
return value.normalize("NFKC").toLocaleLowerCase().trim()
}
function isDocsSearchPayload(value: unknown): value is DocsSearchPayload {
return (
typeof value === "object" &&
value !== null &&
"href" in value &&
typeof value.href === "string"
)
}
function navigate(href: string) {
const destination = new URL(href, window.location.href)
if (destination.href === window.location.href) return
window.history.pushState(null, "", destination)
window.scrollTo({ left: 0, top: 0 })
window.dispatchEvent(new PopStateEvent("popstate"))
}
+264
View File
@@ -0,0 +1,264 @@
import { Icon } from "@icones/react"
import { usePreference } from "@workspace/preferences"
import { useEffect, useId, useRef, useState } from "react"
import { createPortal } from "react-dom"
import { LocaleFlag } from "./locale-flag"
import { getLocalizedHref } from "../content/registry"
import type { DocLocale } from "../content/types"
import { useLocale, useTranslate } from "../lib/locale"
import { useIsMobile } from "../lib/use-mobile"
type LocaleOption = {
label: string
locale: DocLocale
}
const localeOptions: readonly LocaleOption[] = [
{ label: "简体中文", locale: "zh-Hans" },
{ label: "English", locale: "en-US" },
]
export function LanguageSwitcher() {
const locale = useLocale()
const t = useTranslate()
const isMobile = useIsMobile()
const [, setLocaleMode] = usePreference("locale-mode")
const [, setPreferredLocale] = usePreference("locale")
const [open, setOpen] = useState(false)
const rootRef = useRef<HTMLDivElement>(null)
const menuRef = useRef<HTMLDivElement>(null)
const triggerRef = useRef<HTMLButtonElement>(null)
const menuId = useId()
const selectedLocale = getLocaleOption(locale)
useEffect(() => {
if (!open) return
function isInsideSwitcher(target: EventTarget | null) {
return (
target instanceof Node &&
(rootRef.current?.contains(target) || menuRef.current?.contains(target))
)
}
function closeAndRestoreFocus() {
setOpen(false)
window.requestAnimationFrame(() => triggerRef.current?.focus())
}
function handlePointerDown(event: PointerEvent) {
if (isInsideSwitcher(event.target)) return
if (isMobile) {
closeAndRestoreFocus()
} else {
setOpen(false)
}
}
function handleFocusIn(event: FocusEvent) {
if (!isMobile && !isInsideSwitcher(event.target)) setOpen(false)
}
function handleKeyDown(event: KeyboardEvent) {
if (event.key === "Escape") {
event.preventDefault()
closeAndRestoreFocus()
return
}
if (!isMobile || event.key !== "Tab") return
const items = getMenuItems(menuRef.current)
const firstItem = items[0]
const lastItem = items.at(-1)
if (!firstItem || !lastItem) return
if (event.shiftKey && document.activeElement === firstItem) {
event.preventDefault()
lastItem.focus()
} else if (!event.shiftKey && document.activeElement === lastItem) {
event.preventDefault()
firstItem.focus()
}
}
const previousOverflow = document.body.style.overflow
if (isMobile) {
document.body.style.overflow = "hidden"
window.requestAnimationFrame(() => {
menuRef.current
?.querySelector<HTMLElement>('[aria-checked="true"]')
?.focus()
})
}
document.addEventListener("pointerdown", handlePointerDown)
document.addEventListener("focusin", handleFocusIn)
document.addEventListener("keydown", handleKeyDown)
return () => {
document.removeEventListener("pointerdown", handlePointerDown)
document.removeEventListener("focusin", handleFocusIn)
document.removeEventListener("keydown", handleKeyDown)
if (isMobile) document.body.style.overflow = previousOverflow
}
}, [isMobile, open])
function focusMenuItem(position: "first" | "last") {
setOpen(true)
window.requestAnimationFrame(() => {
const items = getMenuItems(menuRef.current)
const item = position === "first" ? items[0] : items.at(-1)
item?.focus()
})
}
function selectLocale(nextLocale: DocLocale) {
setPreferredLocale(nextLocale)
setLocaleMode("manual")
setOpen(false)
window.requestAnimationFrame(() => triggerRef.current?.focus())
}
const menu = (
<div
ref={menuRef}
id={menuId}
role={isMobile ? "dialog" : undefined}
aria-label={t({ zh: "语言", us: "Language" })}
aria-modal={isMobile || undefined}
className="w-[min(80vw,20rem)] rounded-xl border border-slate-200 bg-white/95 p-4 shadow-2xl backdrop-blur-md md:w-auto md:min-w-40 md:p-1 dark:border-slate-800 dark:bg-slate-950/95 dark:shadow-black/60"
>
<p className="px-2 py-2 text-sm text-slate-500 md:text-xs dark:text-slate-400">
{t({ zh: "语言", us: "Language" })}
</p>
<ul
role="menu"
aria-label={t({ zh: "语言", us: "Languages" })}
onKeyDown={(event) => {
if (
event.key !== "ArrowDown" &&
event.key !== "ArrowUp" &&
event.key !== "Home" &&
event.key !== "End"
) {
return
}
event.preventDefault()
const items = getMenuItems(event.currentTarget)
const currentIndex = items.indexOf(
document.activeElement as HTMLButtonElement
)
let nextIndex = currentIndex
if (event.key === "Home") nextIndex = 0
if (event.key === "End") nextIndex = items.length - 1
if (event.key === "ArrowDown") {
nextIndex = (currentIndex + 1) % items.length
}
if (event.key === "ArrowUp") {
nextIndex = (currentIndex - 1 + items.length) % items.length
}
items[nextIndex]?.focus()
}}
>
{localeOptions.map((option) => {
const selected = option.locale === locale
return (
<li role="none" key={option.locale}>
<a
role="menuitemradio"
aria-checked={selected}
lang={option.locale}
href={getLocalizedHref(window.location.pathname, option.locale)}
className="flex h-11 w-full cursor-pointer items-center gap-2 rounded-lg px-2 text-left whitespace-nowrap transition-colors hover:bg-slate-100 focus:bg-slate-100 focus:outline-none aria-checked:text-indigo-600 dark:hover:bg-slate-900 dark:focus:bg-slate-900 dark:aria-checked:text-indigo-400"
onClick={() => selectLocale(option.locale)}
>
<LocaleFlag
locale={option.locale}
className="size-5 shrink-0"
/>
<span>{option.label}</span>
{selected && (
<Icon
name="tabler:check"
className="ml-auto size-4 shrink-0"
/>
)}
</a>
</li>
)
})}
</ul>
</div>
)
return (
<div ref={rootRef} className="flex items-center md:relative">
<button
ref={triggerRef}
type="button"
aria-controls={open ? menuId : undefined}
aria-expanded={open}
aria-haspopup={isMobile ? "dialog" : "menu"}
aria-label={t({ zh: "选择语言", us: "Choose language" })}
title={t({ zh: "选择语言", us: "Choose language" })}
className="inline-flex size-8 cursor-pointer items-center justify-center rounded-full transition-colors outline-none hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 dark:hover:bg-slate-800 dark:focus-visible:ring-indigo-400 dark:focus-visible:ring-offset-slate-950"
onClick={() => setOpen((current) => !current)}
onKeyDown={(event) => {
if (event.key === "ArrowDown") {
event.preventDefault()
focusMenuItem("first")
} else if (event.key === "ArrowUp") {
event.preventDefault()
focusMenuItem("last")
}
}}
>
<LocaleFlag locale={selectedLocale.locale} className="size-7" />
</button>
{open &&
(isMobile ? (
createPortal(
<div className="fixed inset-0 z-100 flex items-center justify-center p-4">
<button
type="button"
tabIndex={-1}
aria-label={t({
zh: "关闭语言菜单",
us: "Close language menu",
})}
className="absolute inset-0 cursor-default bg-black/60 backdrop-blur-sm"
onClick={() => {
setOpen(false)
window.requestAnimationFrame(() =>
triggerRef.current?.focus()
)
}}
/>
<div className="relative">{menu}</div>
</div>,
document.body
)
) : (
<div className="absolute top-full right-0 z-100 pt-2">{menu}</div>
))}
</div>
)
}
function getMenuItems(root: ParentNode | null) {
return Array.from(
root?.querySelectorAll<HTMLElement>('[role="menuitemradio"]') ?? []
)
}
function getLocaleOption(locale: DocLocale) {
return localeOptions.find((option) => option.locale === locale)!
}
+94
View File
@@ -0,0 +1,94 @@
import { Icon } from "@icones/react"
import { cn } from "../lib/utils"
import { useTranslate } from "../lib/locale"
import React from "react"
import { SearchTrigger } from "./search-trigger"
import { SettingsLink } from "./settings-link"
// oxlint-disable-next-line react/only-export-components -- Shared layout tokens are consumed by App.
export const rootCSSVariables = {
"--cw": "60rem",
"--max-lw": "18rem",
"--max-rw": "15rem",
"--min-lw": "12rem",
"--min-rw": "10rem",
"--w": "calc(var(--max-lw) + var(--cw) + var(--max-rw))",
"--page-pl": "calc(2rem + max(0px, (100vw - var(--w)) * 6 / 11 - 1px))",
"--page-pr": "calc(2rem + max(0px, 100vw - var(--w)) * 5 / 11)",
} as const as React.CSSProperties
export function Container({ className, ...rest }: React.ComponentProps<"div">) {
return (
<div
className={cn("px-4 md:pr-(--page-pr) md:pl-(--page-pl)", className)}
{...rest}
/>
)
}
export function HomeButton({
href = "/",
label,
}: {
href?: string
label?: string
}) {
const t = useTranslate()
return (
<a
href={href}
className="flex items-center gap-2 py-1 transition-colors hover:text-indigo-500 dark:hover:text-indigo-400"
>
<Icon name="tabler:smart-home" className="size-5" />
{label ?? t({ zh: "首页", us: "Home" })}
</a>
)
}
export function BackButton({
href,
children,
}: {
href?: string
children?: React.ReactNode
}) {
const t = useTranslate()
function handleClick(e: React.MouseEvent) {
if (!href) {
e.preventDefault()
history.back()
}
}
return (
<a
href={href ?? "/"}
onClick={href ? undefined : handleClick}
className="flex items-center gap-2 py-1 transition-colors hover:text-indigo-500 dark:hover:text-indigo-400"
>
<span className="inline-flex h-6 w-4 items-center justify-center rounded-xl bg-slate-200 dark:bg-slate-800">
<Icon name="tabler:chevron-compact-left" className="size-4" />
</span>
{children ?? <span>{t({ zh: "返回", us: "Back" })}</span>}
</a>
)
}
export function SlashLine({ className }: { className?: string }) {
return (
<div
className={cn("mx-2 h-4 w-px rotate-30 bg-current opacity-35", className)}
/>
)
}
export function Actions({ className }: { className?: string }) {
return (
<div className={cn("inline-flex items-center gap-2", className)}>
<SearchTrigger />
<SettingsLink />
</div>
)
}
+17
View File
@@ -0,0 +1,17 @@
import { Icon } from "@icones/react"
import type { DocLocale } from "../content/types"
export function LocaleFlag({
className,
locale,
}: {
className?: string
locale: DocLocale
}) {
return locale === "zh-Hans" ? (
<Icon name="flag:cn-circle" className={className} />
) : (
<Icon name="flag:us-circle" className={className} />
)
}
+20
View File
@@ -0,0 +1,20 @@
import { useEffect, type ReactNode } from "react"
import type { DocLocale } from "../content/types"
import { LocaleContext } from "../lib/locale"
export function LocaleProvider({
children,
locale,
}: {
children: ReactNode
locale: DocLocale
}) {
useEffect(() => {
document.documentElement.lang = locale
}, [locale])
return (
<LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
)
}
+93
View File
@@ -0,0 +1,93 @@
import type { MdxComponents } from "../content/types"
/* oxlint-disable react/only-export-components -- This module exports an MDX component registry. */
export const mdxComponents: MdxComponents = {
a: MdxAnchor,
blockquote: MdxBlockquote,
code: MdxCode,
h2: MdxHeading2,
h3: MdxHeading3,
pre: MdxPre,
}
function MdxHeading2({ children, id, ...props }: React.ComponentProps<"h2">) {
return (
<h2
id={id}
className="group scroll-mt-24 pt-7 text-2xl font-semibold tracking-tight text-slate-950 first:pt-0 dark:text-slate-100"
{...props}
>
{id ? (
<a className="text-inherit no-underline" href={`#${id}`}>
{children}
<span className="ml-2 text-indigo-500 opacity-0 transition-opacity group-hover:opacity-100 dark:text-indigo-400">
#
</span>
</a>
) : (
children
)}
</h2>
)
}
function MdxHeading3({ children, id, ...props }: React.ComponentProps<"h3">) {
return (
<h3
id={id}
className="group scroll-mt-24 pt-5 text-xl font-semibold tracking-tight text-slate-950 dark:text-slate-100"
{...props}
>
{id ? (
<a className="text-inherit no-underline" href={`#${id}`}>
{children}
<span className="ml-2 text-indigo-500 opacity-0 transition-opacity group-hover:opacity-100 dark:text-indigo-400">
#
</span>
</a>
) : (
children
)}
</h3>
)
}
function MdxAnchor({ className, ...props }: React.ComponentProps<"a">) {
return (
<a
className={`font-medium text-indigo-600 underline decoration-indigo-300 underline-offset-4 transition-colors hover:text-indigo-500 dark:text-indigo-400 dark:decoration-indigo-800 dark:hover:text-indigo-300 ${className ?? ""}`}
{...props}
/>
)
}
function MdxBlockquote({
className,
...props
}: React.ComponentProps<"blockquote">) {
return (
<blockquote
className={`rounded-r-lg border-l-2 border-indigo-500 bg-indigo-50 px-4 py-3 text-slate-700 dark:border-indigo-400 dark:bg-indigo-950/30 dark:text-slate-300 ${className ?? ""}`}
{...props}
/>
)
}
function MdxCode({ className, ...props }: React.ComponentProps<"code">) {
return (
<code
className={`rounded bg-slate-100 px-1.5 py-0.5 font-mono text-[0.9em] text-slate-900 dark:bg-slate-900 dark:text-slate-100 ${className ?? ""}`}
{...props}
/>
)
}
function MdxPre({ className, ...props }: React.ComponentProps<"pre">) {
return (
<pre
className={`overflow-x-auto rounded-xl border border-slate-200 bg-slate-50 p-4 font-mono text-sm leading-6 text-slate-900 shadow-sm dark:border-slate-800 dark:bg-black/40 dark:text-slate-100 dark:shadow-none [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit ${className ?? ""}`}
{...props}
/>
)
}
/* oxlint-enable react/only-export-components */
@@ -0,0 +1,228 @@
import {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
type MutableRefObject,
type ReactNode,
} from "react"
import {
PreferencesProvider,
usePreference,
usePreferences,
type PreferenceChangeHandler,
type PreferenceUpdate,
type Preferences,
} from "@workspace/preferences"
import type { DocLocale } from "../content/types"
import { navigateToLocale } from "../lib/locale-navigation"
import {
detectLocale,
docsPreferenceEffects,
isDocsPreferenceUpdate,
parseDocsPreferences,
persistDocsPreference,
readDocsPreferences,
} from "../lib/preferences"
const preferencesChannelName = "docs-preferences-sync"
type PreferencesMessage =
| { type: "request" }
| { preferences: unknown; type: "snapshot" }
| { type: "update"; update: unknown }
export function DocsPreferencesProvider({
children,
routeLocale,
}: {
children: ReactNode
routeLocale: DocLocale
}) {
const [initialPreferences] = useState(() => readDocsPreferences(routeLocale))
const channelRef = useRef<BroadcastChannel>(null)
const applyingExternalUpdateRef = useRef(false)
const handlePreferenceChange = useCallback<PreferenceChangeHandler>(
(update) => {
persistDocsPreference(update)
if (!applyingExternalUpdateRef.current) {
channelRef.current?.postMessage({ type: "update", update })
}
},
[]
)
return (
<PreferencesProvider
effects={docsPreferenceEffects}
initialPreferences={initialPreferences}
onPreferenceChange={handlePreferenceChange}
>
<PreferencesRuntime
applyingExternalUpdateRef={applyingExternalUpdateRef}
channelRef={channelRef}
routeLocale={routeLocale}
/>
{children}
</PreferencesProvider>
)
}
function PreferencesRuntime({
applyingExternalUpdateRef,
channelRef,
routeLocale,
}: {
applyingExternalUpdateRef: MutableRefObject<boolean>
channelRef: MutableRefObject<BroadcastChannel | null>
routeLocale: DocLocale
}) {
const preferences = usePreferences()
const preferencesRef = useRef(preferences)
const [, setAccentColor] = usePreference("accent-color")
const [, setBackgroundShade] = usePreference("background-shade")
const [, setForegroundShade] = usePreference("foreground-shade")
const [, setLocaleMode] = usePreference("locale-mode")
const [, setLocale] = usePreference("locale")
const [, setThemeMode] = usePreference("theme-mode")
useLayoutEffect(() => {
preferencesRef.current = preferences
}, [preferences])
useEffect(() => {
if (!("BroadcastChannel" in window)) return
const channel = new BroadcastChannel(preferencesChannelName)
channelRef.current = channel
function applyUpdate(update: PreferenceUpdate) {
applyingExternalUpdateRef.current = true
try {
switch (update.key) {
case "accent-color":
setAccentColor(update.value)
break
case "background-shade":
setBackgroundShade(update.value)
break
case "foreground-shade":
setForegroundShade(update.value)
break
case "locale-mode":
setLocaleMode(update.value)
break
case "locale":
setLocale(update.value)
break
case "theme-mode":
setThemeMode(update.value)
break
}
} finally {
applyingExternalUpdateRef.current = false
}
}
function applySnapshot(nextPreferences: Preferences) {
preferencesRef.current = nextPreferences
for (const [key, value] of Object.entries(nextPreferences)) {
const update = { key, value }
if (isDocsPreferenceUpdate(update)) applyUpdate(update)
}
}
function synchronizeRoute(nextPreferences: Preferences) {
const locale =
nextPreferences["locale-mode"] === "auto"
? detectLocale()
: nextPreferences.locale
navigateToLocale(locale, true)
}
function handleMessage(event: MessageEvent<unknown>) {
const message = event.data as Partial<PreferencesMessage> | undefined
if (!message || typeof message !== "object") return
if (message.type === "request") {
channel.postMessage({
type: "snapshot",
preferences: preferencesRef.current,
} satisfies PreferencesMessage)
return
}
if (message.type === "update" && isDocsPreferenceUpdate(message.update)) {
const nextPreferences = {
...preferencesRef.current,
[message.update.key]: message.update.value,
}
preferencesRef.current = nextPreferences
applyUpdate(message.update)
synchronizeRoute(nextPreferences)
return
}
if (message.type === "snapshot") {
const nextPreferences = parseDocsPreferences(message.preferences)
if (!nextPreferences) return
applySnapshot(nextPreferences)
synchronizeRoute(nextPreferences)
}
}
channel.addEventListener("message", handleMessage)
channel.postMessage({ type: "request" } satisfies PreferencesMessage)
return () => {
channelRef.current = null
channel.close()
}
}, [
applyingExternalUpdateRef,
channelRef,
setAccentColor,
setBackgroundShade,
setForegroundShade,
setLocale,
setLocaleMode,
setThemeMode,
])
useEffect(() => {
if (preferences["locale-mode"] !== "auto") return
function synchronizeDetectedLocale() {
navigateToLocale(detectLocale(), true)
}
function handleVisibilityChange() {
if (document.visibilityState === "visible") synchronizeDetectedLocale()
}
synchronizeDetectedLocale()
window.addEventListener("focus", synchronizeDetectedLocale)
window.addEventListener("languagechange", synchronizeDetectedLocale)
document.addEventListener("visibilitychange", handleVisibilityChange)
return () => {
window.removeEventListener("focus", synchronizeDetectedLocale)
window.removeEventListener("languagechange", synchronizeDetectedLocale)
document.removeEventListener("visibilitychange", handleVisibilityChange)
}
}, [preferences])
useEffect(() => {
if (
preferences["locale-mode"] === "manual" &&
preferences.locale !== routeLocale
) {
setLocale(routeLocale)
}
}, [preferences, routeLocale, setLocale])
return null
}
+24
View File
@@ -0,0 +1,24 @@
import { Icon } from "@icones/react"
import { SearchTrigger as RawSearchTrigger } from "@workspace/search"
import { useTranslate } from "../lib/locale"
export function SearchTrigger() {
const t = useTranslate()
const label = t({ zh: "搜索文档", us: "Search documentation" })
return (
<RawSearchTrigger
type="button"
aria-label={label}
title={label}
className="inline-flex h-9 shrink-0 items-center gap-2 rounded-lg px-2 text-slate-500 transition-colors sm:hover:bg-slate-100 hover:text-slate-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500 sm:px-3 dark:text-slate-400 sm:dark:hover:bg-slate-900 dark:hover:text-slate-100"
>
<Icon name="tabler:search" className="size-5" />
<span className="hidden sm:inline">{label}</span>
<kbd className="hidden rounded border border-slate-200 bg-white px-1.5 py-0.5 font-sans text-[0.6875rem] leading-none text-slate-400 lg:inline dark:border-slate-700 dark:bg-slate-950 dark:text-slate-500">
⌘K
</kbd>
</RawSearchTrigger>
)
}
+39
View File
@@ -0,0 +1,39 @@
import { Icon } from "@icones/react"
import type { ComponentProps } from "react"
import { getLocalizedHref } from "../content/registry"
import { useLocale, useTranslate } from "../lib/locale"
import { cn } from "../lib/utils"
export type SettingsLinkProps = Omit<
ComponentProps<"a">,
"children" | "href"
> & {
label?: string
}
export function SettingsLink({
className,
label,
...props
}: SettingsLinkProps) {
const locale = useLocale()
const t = useTranslate()
const accessibleLabel = label ?? t({ zh: "设置", us: "Settings" })
return (
<a
href={getLocalizedHref("/settings", locale)}
aria-label={accessibleLabel}
title={accessibleLabel}
className={cn(
"inline-flex size-9 shrink-0 items-center justify-center rounded-lg text-slate-500 transition-colors hover:text-slate-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500 dark:text-slate-400 dark:hover:text-slate-100",
className
)}
{...props}
>
<Icon name="tabler:settings" className="size-5" />
<span className="sr-only">{accessibleLabel}</span>
</a>
)
}
+48
View File
@@ -0,0 +1,48 @@
import { Icon } from "@icones/react"
import type { ComponentProps } from "react"
import { cn } from "../lib/utils"
export type SwatchProps = Omit<ComponentProps<"button">, "children"> & {
light: string
dark?: string
selected?: boolean
}
export function Swatch({
className,
light,
dark = light,
selected = false,
...props
}: SwatchProps) {
return (
<button
type="button"
aria-pressed={selected}
className={cn(
"relative inline-flex flex-col size-10 shrink-0 cursor-pointer gap-px overflow-hidden rounded-full border border-slate-200 transition-transform outline-none hover:scale-105 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 active:scale-95 aria-pressed:ring-2 aria-pressed:ring-indigo-500 aria-pressed:ring-offset-2 dark:border-slate-700 dark:ring-offset-(--docs-background-dark) text-xs text-center",
className
)}
{...props}
>
<span
aria-hidden="true"
className="block size-full dark:hidden"
style={{ backgroundColor: light }}
/>
<span
aria-hidden="true"
className="hidden dark:block size-full"
style={{ backgroundColor: dark }}
/>
{selected && (
<span className="absolute inset-0 flex items-center justify-center">
<span className="flex size-5 items-center justify-center rounded-full bg-white/90 text-slate-950 shadow-sm ring-1 ring-black/10">
<Icon name="tabler:check" className="size-3.5" />
</span>
</span>
)}
</button>
)
}
+55
View File
@@ -0,0 +1,55 @@
import { Icon } from "@icones/react"
import { usePreference } from "@workspace/preferences"
import { useResolvedTheme } from "../lib/appearance"
import { useTranslate } from "../lib/locale"
export function ThemeSwitch({ className }: { className?: string }) {
const t = useTranslate()
const [, setThemeMode] = usePreference("theme-mode")
const resolvedTheme = useResolvedTheme()
const dark = resolvedTheme === "dark"
const label = dark
? t({ zh: "切换到浅色主题", us: "Switch to light theme" })
: t({ zh: "切换到深色主题", us: "Switch to dark theme" })
return (
<button
type="button"
role="switch"
aria-checked={dark}
aria-label={label}
title={label}
className={[
"relative inline-flex h-7 w-13 shrink-0 cursor-pointer items-center rounded-full border transition-colors duration-200",
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500 motion-reduce:transition-none",
dark
? "border-zinc-700 bg-zinc-900 shadow-inner hover:border-zinc-600"
: "border-slate-300 bg-slate-100 shadow-inner hover:border-slate-400",
className,
]
.filter(Boolean)
.join(" ")}
onClick={() => {
setThemeMode(dark ? "light" : "dark")
}}
>
<span
aria-hidden="true"
className={[
"absolute top-px left-px inline-flex size-6 items-center justify-center rounded-full ring-1 shadow-sm transition-transform duration-200 ease-out motion-reduce:transition-none",
dark
? "translate-x-6 bg-black text-zinc-100 ring-zinc-700"
: "translate-x-0 bg-white text-slate-500 ring-slate-200",
].join(" ")}
>
<Icon
name="tabler:sun-high"
altName="tabler:moon"
showAlt={dark}
className="z-10 size-4"
/>
</span>
</button>
)
}
+355
View File
@@ -0,0 +1,355 @@
import {
defaultDocLocale,
docLocales,
docSections,
type DocLocale,
type DocLocation,
type DocPackage,
type DocPage,
type DocSection,
type DocTocItem,
type MdxComponents,
} from "./types"
import { trans, type Translation } from "../lib/locale"
type DocModule = {
default: React.ComponentType<{ components?: MdxComponents }>
frontmatter: Record<string, unknown>
}
type LocalizedText = Translation<string>
type PackageDefinition = {
key: string
label: string
description: LocalizedText
}
type ParsedDocPage = DocPage & {
packageKey: string
searchText: string
}
const packageDefinitions: readonly PackageDefinition[] = [
{
key: "blocks",
label: "Blocks",
description: {
zh: "构建应用外壳,并接入导航、通知、聊天、媒体和外观控制。",
us: "Build an application shell and add navigation, notifications, chat, media, and appearance controls.",
},
},
{
key: "i18n",
label: "I18n",
description: {
zh: "接入应用自有词典、本地化运行时、开发工具和完整翻译工作流。",
us: "Add application-owned catalogs, localized runtime rendering, development tools, and translation workflows.",
},
},
{
key: "lexical",
label: "Lexical",
description: {
zh: "从根节点和内容区域开始,逐步构建支持自定义操作与媒体的富文本编辑器。",
us: "Build a rich-text editor progressively from its root and content surface to custom actions and media.",
},
},
{
key: "preferences",
label: "Preferences",
description: {
zh: "使用 SSR 快照、持久化回调和可组合副作用管理类型安全且可扩展的应用偏好。",
us: "Manage typed, extensible application preferences with SSR snapshots, persistence callbacks, and composable effects.",
},
},
{
key: "search",
label: "Search",
description: {
zh: "通过可替换的 Adapter、分组结果、分页和搜索历史构建应用级搜索体验。",
us: "Build application search with a replaceable adapter, grouped results, pagination, and search history.",
},
},
{
key: "ui",
label: "UI",
description: {
zh: "配置共享 UI 层,并学习组合组件、Hooks、图标和本地化词典。",
us: "Set up the shared UI layer and learn how to compose its components, hooks, icons, and locale catalogs.",
},
},
]
const docModules = import.meta.glob<DocModule>(
"../../../packages/*/docs/*/**/*.mdx",
{ eager: true }
)
const docSources = import.meta.glob<string>(
"../../../packages/*/docs/*/**/*.mdx",
{ eager: true, import: "default", query: "?docs-search-raw" }
)
const parsedPages = Object.entries(docModules).map(([path, module]) =>
parseDocPage(path, module, docSources[path] ?? "")
)
const packagesByLocale: Record<DocLocale, readonly DocPackage[]> = {
"zh-Hans": packageDefinitions.map((definition) =>
createDocPackage(definition, "zh-Hans")
),
"en-US": packageDefinitions.map((definition) =>
createDocPackage(definition, "en-US")
),
}
validateLocalizedPagePairs(packagesByLocale)
export function getDocPackages(locale: DocLocale = defaultDocLocale) {
return packagesByLocale[locale]
}
export function getDocHref(docPackage: DocPackage, page?: DocPage) {
const prefix = getLocalePrefix(docPackage.locale)
const target = page ?? getOverviewPage(docPackage)
const pagePath =
target.section === "overview"
? ""
: `/${target.section}/${encodeURIComponent(target.slug)}`
return `${prefix}/articles/${docPackage.key}${pagePath}`
}
export function getDocsHomeHref(locale: DocLocale) {
return getLocalePrefix(locale) || "/"
}
export function getLocalizedHref(pathname: string, locale: DocLocale) {
if (/^(?:\/(?:en-US|zh-Hans))?\/settings\/?$/.test(pathname)) {
return `${getLocalePrefix(locale)}/settings`
}
const location = resolveDocLocation(pathname)
if (!location) return getDocsHomeHref(locale)
const docPackage = getDocPackages(locale).find(
(item) => item.key === location.package.key
)
const page = docPackage?.pages.find(
(item) =>
item.section === location.page.section && item.slug === location.page.slug
)
return docPackage && page
? getDocHref(docPackage, page)
: getDocsHomeHref(locale)
}
export function resolveDocLocale(pathname: string): DocLocale {
const firstSegment = pathname.split("/").filter(Boolean)[0]
return isDocLocale(firstSegment) ? firstSegment : defaultDocLocale
}
export function resolveDocLocation(pathname: string): DocLocation | undefined {
const segments = pathname.split("/").filter(Boolean)
let locale: DocLocale = defaultDocLocale
if (isDocLocale(segments[0])) {
locale = segments.shift() as DocLocale
}
const [resource, packageKey, sectionValue, pageSlug, ...extra] = segments
if (resource !== "articles" || extra.length > 0) return undefined
const docPackage = getDocPackages(locale).find(
(item) => item.key === packageKey
)
if (!docPackage) return undefined
if (sectionValue === undefined) {
const page = getOverviewPage(docPackage)
return { locale, package: docPackage, page }
}
if (sectionValue === "overview" || !isDocSection(sectionValue) || !pageSlug) {
return undefined
}
const page = docPackage.pages.find(
(item) => item.section === sectionValue && item.slug === pageSlug
)
return page ? { locale, package: docPackage, page } : undefined
}
export function getDocSectionLabel(section: DocSection, locale: DocLocale) {
const labels: Record<DocSection, Translation> = {
overview: { zh: "概览", us: "Overview" },
guide: { zh: "指南", us: "Guide" },
advanced: { zh: "进阶", us: "Advanced" },
examples: { zh: "示例", us: "Examples" },
}
return trans(locale, labels[section])
}
export function getDocSearchText(page: DocPage) {
return (page as ParsedDocPage).searchText
}
function createDocPackage(
definition: PackageDefinition,
locale: DocLocale
): DocPackage {
const pages = parsedPages
.filter(
(page) => page.packageKey === definition.key && page.locale === locale
)
.sort(
(left, right) =>
docSections.indexOf(left.section) -
docSections.indexOf(right.section) ||
left.order - right.order ||
left.slug.localeCompare(right.slug)
)
const overviewCount = pages.filter(
(page) => page.section === "overview"
).length
if (overviewCount !== 1) {
throw new Error(
`${definition.key}/${locale} must contain exactly one docs/${locale}/overview.mdx file.`
)
}
for (const section of docSections.slice(1)) {
if (!pages.some((page) => page.section === section)) {
throw new Error(
`${definition.key}/${locale} must contain at least one ${section} document.`
)
}
}
return {
key: definition.key,
label: definition.label,
description: trans(locale, definition.description),
locale,
pages,
}
}
function parseDocPage(
path: string,
module: DocModule,
source: string
): ParsedDocPage {
const match = path.match(
/\/packages\/([^/]+)\/docs\/([^/]+)\/(overview|guide|advanced|examples)(?:\/([^/]+))?\.mdx$/
)
if (!match) {
throw new Error(
`Invalid documentation path "${path}". Expected docs/<locale>/overview.mdx or docs/<locale>/<guide|advanced|examples>/*.mdx.`
)
}
const [, packageKey, localeValue, sectionValue, nestedSlug] = match
if (!isDocLocale(localeValue)) {
throw new Error(
`Unsupported documentation locale "${localeValue}" in "${path}".`
)
}
if (!isDocSection(sectionValue)) {
throw new Error(`Unsupported documentation section in "${path}".`)
}
if (
(sectionValue === "overview" && nestedSlug !== undefined) ||
(sectionValue !== "overview" && nestedSlug === undefined)
) {
throw new Error(`Documentation file is in the wrong directory: "${path}".`)
}
const frontmatter = module.frontmatter
return {
Content: module.default,
description: readString(frontmatter, "description"),
locale: localeValue,
order: readNumber(frontmatter, "order"),
packageKey,
searchText: source,
section: sectionValue,
slug: nestedSlug ?? "overview",
title: readString(frontmatter, "title"),
toc: readToc(frontmatter.toc),
}
}
function validateLocalizedPagePairs(
packages: Record<DocLocale, readonly DocPackage[]>
) {
for (const definition of packageDefinitions) {
const keysByLocale = docLocales.map((locale) => {
const docPackage = packages[locale].find(
(item) => item.key === definition.key
)!
return docPackage.pages.map((page) => `${page.section}/${page.slug}`)
})
if (keysByLocale[0]?.join("\n") !== keysByLocale[1]?.join("\n")) {
throw new Error(
`${definition.key} documentation must contain matching zh-Hans and en-US page paths.`
)
}
}
}
function getOverviewPage(docPackage: DocPackage) {
return docPackage.pages.find((page) => page.section === "overview")!
}
function getLocalePrefix(locale: DocLocale) {
return locale === defaultDocLocale ? "" : `/${locale}`
}
function isDocLocale(value: unknown): value is DocLocale {
return docLocales.some((locale) => locale === value)
}
function isDocSection(value: unknown): value is DocSection {
return docSections.some((section) => section === value)
}
function readString(frontmatter: Record<string, unknown>, key: string) {
const value = frontmatter[key]
if (typeof value === "string" && value.trim()) return value
throw new Error(`Documentation frontmatter requires a string "${key}".`)
}
function readNumber(frontmatter: Record<string, unknown>, key: string) {
const value = frontmatter[key]
if (typeof value === "number" && Number.isFinite(value)) return value
throw new Error(`Documentation frontmatter requires a number "${key}".`)
}
function readToc(value: unknown): readonly DocTocItem[] {
if (value === undefined) return []
if (!Array.isArray(value)) {
throw new Error('Documentation frontmatter "toc" must be an array.')
}
return value.map((item) => {
if (!isRecord(item)) {
throw new Error('Each documentation "toc" entry must be an object.')
}
return {
id: readString(item, "id"),
title: readString(item, "title"),
items: item.items === undefined ? undefined : readToc(item.items),
}
})
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null
}
+45
View File
@@ -0,0 +1,45 @@
import type { ComponentType, ElementType } from "react"
export const docLocales = ["zh-Hans", "en-US"] as const
export const defaultDocLocale = "zh-Hans" as const
export const docSections = [
"overview",
"guide",
"advanced",
"examples",
] as const
export type DocLocale = (typeof docLocales)[number]
export type DocSection = (typeof docSections)[number]
export type MdxComponents = Record<string, ElementType>
export type DocTocItem = {
id: string
title: string
items?: readonly DocTocItem[]
}
export type DocPage = {
Content: ComponentType<{ components?: MdxComponents }>
description: string
locale: DocLocale
order: number
section: DocSection
slug: string
title: string
toc: readonly DocTocItem[]
}
export type DocPackage = {
key: string
label: string
description: string
locale: DocLocale
pages: readonly DocPage[]
}
export type DocLocation = {
locale: DocLocale
package: DocPackage
page: DocPage
}
+86
View File
@@ -0,0 +1,86 @@
@import "@workspace/ui/globals.css";
@import "@workspace/search/globals.css";
@custom-variant dark (&:where(.dark, .dark *));
@layer base {
*,
*::before,
*::after {
@apply border-slate-200 dark:border-slate-800;
}
html {
@apply antialiased;
background-color: var(--docs-background-light, var(--color-white));
color: var(--docs-foreground-light, var(--color-slate-950));
font-family: "DM Serif Display", Georgia, serif;
}
html.dark {
background-color: var(--docs-background-dark, var(--color-slate-950));
color: var(--docs-foreground-dark, var(--color-slate-100));
}
:root {
--background: var(--docs-background-light, var(--color-white));
--foreground: var(--docs-foreground-light, var(--color-slate-950));
--card: var(--docs-background-light, var(--color-white));
--card-foreground: var(--docs-foreground-light, var(--color-slate-950));
--popover: var(--docs-background-light, var(--color-white));
--popover-foreground: var(--docs-foreground-light, var(--color-slate-950));
--primary: var(--color-indigo-600);
--primary-foreground: var(--color-white);
--secondary: var(--docs-surface-light, var(--color-slate-100));
--secondary-foreground: var(
--docs-foreground-light,
var(--color-slate-950)
);
--muted: var(--docs-surface-light, var(--color-slate-100));
--muted-foreground: var(--docs-muted-light, var(--color-slate-600));
--accent: var(--docs-surface-light, var(--color-slate-100));
--accent-foreground: var(--docs-foreground-light, var(--color-slate-950));
--border: var(--color-slate-200);
--input: var(--color-slate-200);
--ring: var(--color-indigo-500);
--sidebar: var(--docs-background-light, var(--color-white));
--sidebar-foreground: var(--docs-foreground-light, var(--color-slate-950));
--sidebar-border: var(--color-slate-200);
}
html.dark {
--background: var(--docs-background-dark, var(--color-slate-950));
--foreground: var(--docs-foreground-dark, var(--color-slate-100));
--card: var(--docs-surface-dark, var(--color-slate-900));
--card-foreground: var(--docs-foreground-dark, var(--color-slate-100));
--popover: var(--docs-surface-dark, var(--color-slate-900));
--popover-foreground: var(--docs-foreground-dark, var(--color-slate-100));
--primary: var(--color-indigo-400);
--primary-foreground: var(--color-slate-950);
--secondary: var(--color-slate-800);
--secondary-foreground: var(--docs-foreground-dark, var(--color-slate-100));
--muted: var(--color-slate-800);
--muted-foreground: var(--docs-muted-dark, var(--color-slate-400));
--accent: var(--color-slate-800);
--accent-foreground: var(--docs-foreground-dark, var(--color-slate-100));
--border: var(--color-slate-800);
--input: var(--color-slate-700);
--ring: var(--color-indigo-400);
--sidebar: var(--docs-surface-dark, var(--color-slate-900));
--sidebar-foreground: var(--docs-foreground-dark, var(--color-slate-100));
--sidebar-border: var(--color-slate-800);
}
body,
#root {
@apply min-h-dvh;
}
body {
@apply bg-inherit text-inherit;
}
::selection {
@apply bg-indigo-200 text-indigo-950 dark:bg-indigo-500/40 dark:text-indigo-50;
}
}
+155
View File
@@ -0,0 +1,155 @@
import { useCallback, useSyncExternalStore } from "react"
import { usePreference } from "@workspace/preferences"
import colors from "tailwindcss/colors"
export const themeModes = ["light", "dark", "system"] as const
export const accentColors = [
"neutral",
"blue",
"cyan",
"emerald",
"fuchsia",
"green",
"indigo",
"lime",
"orange",
"pink",
"purple",
"red",
"rose",
"sky",
"teal",
"violet",
"yellow",
] as const
export const backgroundShades = [
"neutral",
"stone",
"zinc",
"mauve",
"olive",
"mist",
"taupe",
] as const
export const foregroundShades = ["slate", "neutral", "stone"] as const
export type ThemeMode = (typeof themeModes)[number]
export type AccentColor = (typeof accentColors)[number]
export type BackgroundShade = (typeof backgroundShades)[number]
export type ForegroundShade = (typeof foregroundShades)[number]
export type ResolvedTheme = Exclude<ThemeMode, "system">
export type Appearance = {
accentColor: AccentColor
backgroundShade: BackgroundShade
foregroundShade: ForegroundShade
themeMode: ThemeMode
}
export type AppearanceController = Appearance & {
updateAppearance: (update: Partial<Appearance>) => void
}
const systemThemeQuery = "(prefers-color-scheme: dark)"
const colorSteps = [
50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950,
] as const
type ColorStep = (typeof colorSteps)[number]
type ColorScale = Record<ColorStep, string>
export function useAppearance(): AppearanceController {
const [accentColor, setAccentColor] = usePreference("accent-color")
const [backgroundShade, setBackgroundShade] =
usePreference("background-shade")
const [foregroundShade, setForegroundShade] =
usePreference("foreground-shade")
const [themeMode, setThemeMode] = usePreference("theme-mode")
const updateAppearance = useCallback(
(update: Partial<Appearance>) => {
if (update.accentColor !== undefined) {
setAccentColor(update.accentColor)
}
if (update.backgroundShade !== undefined) {
setBackgroundShade(update.backgroundShade)
}
if (update.foregroundShade !== undefined) {
setForegroundShade(update.foregroundShade)
}
if (update.themeMode !== undefined) setThemeMode(update.themeMode)
},
[setAccentColor, setBackgroundShade, setForegroundShade, setThemeMode]
)
return {
accentColor,
backgroundShade,
foregroundShade,
themeMode,
updateAppearance,
}
}
export function useResolvedTheme() {
const [themeMode] = usePreference("theme-mode")
const systemTheme = useSyncExternalStore(
subscribeToSystemTheme,
getSystemTheme,
() => "light" as const
)
return themeMode === "system" ? systemTheme : themeMode
}
export function getPaletteColor(
color: AccentColor | BackgroundShade | ForegroundShade,
step: ColorStep = 500
) {
return getColorScale(color)[step]
}
export function applyAppearance(appearance: Appearance) {
if (typeof document === "undefined") return
const root = document.documentElement
const resolvedTheme =
appearance.themeMode === "system" ? getSystemTheme() : appearance.themeMode
const accentScale = getColorScale(appearance.accentColor)
const backgroundScale = getColorScale(appearance.backgroundShade)
const foregroundScale = getColorScale(appearance.foregroundShade)
root.classList.toggle("dark", resolvedTheme === "dark")
root.style.colorScheme = resolvedTheme
root.dataset.themeMode = appearance.themeMode
root.dataset.accentColor = appearance.accentColor
root.dataset.backgroundShade = appearance.backgroundShade
root.dataset.foregroundShade = appearance.foregroundShade
for (const step of colorSteps) {
root.style.setProperty(`--color-indigo-${step}`, accentScale[step])
}
root.style.setProperty("--docs-background-light", backgroundScale[50])
root.style.setProperty("--docs-background-dark", backgroundScale[950])
root.style.setProperty("--docs-surface-light", backgroundScale[100])
root.style.setProperty("--docs-surface-dark", backgroundScale[900])
root.style.setProperty("--docs-foreground-light", foregroundScale[950])
root.style.setProperty("--docs-foreground-dark", foregroundScale[50])
root.style.setProperty("--docs-muted-light", foregroundScale[600])
root.style.setProperty("--docs-muted-dark", foregroundScale[400])
}
export function subscribeToSystemTheme(listener: VoidFunction) {
const mediaQuery = window.matchMedia(systemThemeQuery)
mediaQuery.addEventListener("change", listener)
return () => mediaQuery.removeEventListener("change", listener)
}
export function getSystemTheme(): ResolvedTheme {
return window.matchMedia(systemThemeQuery).matches ? "dark" : "light"
}
function getColorScale(color: string) {
return colors[color as keyof typeof colors] as ColorScale
}
+14
View File
@@ -0,0 +1,14 @@
import { getLocalizedHref } from "../content/registry"
import type { DocLocale } from "../content/types"
export function navigateToLocale(locale: DocLocale, replace = false) {
const href = getLocalizedHref(window.location.pathname, locale)
if (href === window.location.pathname) return
if (replace) {
window.history.replaceState(null, "", href)
} else {
window.history.pushState(null, "", href)
}
window.dispatchEvent(new PopStateEvent("popstate"))
}
+32
View File
@@ -0,0 +1,32 @@
import { createContext, useCallback, useContext } from "react"
import { defaultDocLocale, type DocLocale } from "../content/types"
export type Translation<Value = string> = Readonly<{
zh: Value
us: Value
}>
export type Translate = <Value>(translation: Translation<Value>) => Value
export const LocaleContext = createContext<DocLocale>(defaultDocLocale)
export function useLocale() {
return useContext(LocaleContext)
}
export function useTranslate(): Translate {
const locale = useLocale()
return useCallback(
<Value>(translation: Translation<Value>) => trans(locale, translation),
[locale]
)
}
export function trans<Value>(
locale: DocLocale,
translation: Translation<Value>
): Value {
return locale === "zh-Hans" ? translation.zh : translation.us
}
+188
View File
@@ -0,0 +1,188 @@
import {
createPreferenceUpdateGuard,
type PreferenceDefinitions,
type PreferenceEffect,
type PreferenceUpdate,
type Preferences,
} from "@workspace/preferences"
import { defaultDocLocale, docLocales, type DocLocale } from "../content/types"
import {
accentColors,
applyAppearance,
backgroundShades,
foregroundShades,
subscribeToSystemTheme,
themeModes,
type AccentColor,
type Appearance,
type BackgroundShade,
type ForegroundShade,
type ThemeMode,
} from "./appearance"
export type LocaleMode = "auto" | "manual"
export type DocsPreferences = {
"accent-color": AccentColor
"background-shade": BackgroundShade
"foreground-shade": ForegroundShade
"locale-mode": LocaleMode
locale: DocLocale
"theme-mode": ThemeMode
}
declare module "@workspace/preferences" {
interface PreferencesCustom extends DocsPreferences {}
}
export const docsPreferenceDefinitions = {
"accent-color": createStringPreference(
"docs-accent-color",
"indigo",
accentColors
),
"background-shade": createStringPreference(
"docs-background-shade",
"neutral",
backgroundShades
),
"foreground-shade": createStringPreference(
"docs-foreground-shade",
"slate",
foregroundShades
),
"locale-mode": {
cookie: "docs-locale-auto-detect",
defaultValue: "manual",
is: (value): value is LocaleMode => value === "auto" || value === "manual",
parse: (value) => (value === "1" || value === "auto" ? "auto" : "manual"),
serialize: (value) => (value === "auto" ? "1" : "0"),
},
locale: createStringPreference("docs-locale", defaultDocLocale, docLocales),
"theme-mode": createStringPreference("docs-theme", "system", themeModes),
} satisfies PreferenceDefinitions<DocsPreferences>
export const isDocsPreferenceUpdate = createPreferenceUpdateGuard(
docsPreferenceDefinitions
)
export const docsPreferenceEffects = [
{
layoutEffect: ({ preferences }) => {
applyAppearance(toAppearance(preferences))
},
effect: ({ preferences, store }) => {
if (preferences["theme-mode"] !== "system") return
return subscribeToSystemTheme(() => {
applyAppearance(toAppearance(store.getSnapshot()))
})
},
},
] satisfies readonly PreferenceEffect[]
export function initializeDocsPreferences() {
applyAppearance(toAppearance(readDocsPreferences()))
}
export function readDocsPreferences(
fallbackLocale: DocLocale = defaultDocLocale
): Preferences {
const preferences = Object.fromEntries(
(
Object.keys(docsPreferenceDefinitions) as Array<keyof DocsPreferences>
).map((key) => {
const definition = docsPreferenceDefinitions[key]
return [key, readStoredValue(definition.cookie, definition.parse)]
})
) as DocsPreferences
if (!readStoredString(docsPreferenceDefinitions.locale.cookie)) {
preferences.locale = fallbackLocale
}
return preferences
}
export function persistDocsPreference(update: PreferenceUpdate) {
const definition = docsPreferenceDefinitions[update.key]
const serializedValue =
update.key === "locale-mode"
? docsPreferenceDefinitions["locale-mode"].serialize(update.value)
: update.value
writeStoredString(definition.cookie, serializedValue)
}
export function parseDocsPreferences(value: unknown): Preferences | undefined {
if (!value || typeof value !== "object") return undefined
const candidate = value as Partial<Record<keyof DocsPreferences, unknown>>
for (const key of Object.keys(docsPreferenceDefinitions) as Array<
keyof DocsPreferences
>) {
if (!docsPreferenceDefinitions[key].is(candidate[key])) return undefined
}
return candidate as DocsPreferences
}
export function toAppearance(preferences: Preferences): Appearance {
return {
accentColor: preferences["accent-color"],
backgroundShade: preferences["background-shade"],
foregroundShade: preferences["foreground-shade"],
themeMode: preferences["theme-mode"],
}
}
export function detectLocale(): DocLocale {
const languages = navigator.languages.length
? navigator.languages
: [navigator.language]
return languages.some((language) => language.toLowerCase().startsWith("zh"))
? "zh-Hans"
: "en-US"
}
function createStringPreference<const Options extends readonly string[]>(
cookie: string,
defaultValue: Options[number],
options: Options
) {
const is = (value: unknown): value is Options[number] =>
typeof value === "string" && options.some((option) => option === value)
return {
cookie,
defaultValue,
is,
parse: (value: string | undefined) => (is(value) ? value : defaultValue),
serialize: (value: Options[number]) => value,
}
}
function readStoredValue<Value>(
key: string,
parse: (value: string | undefined) => Value
) {
return parse(readStoredString(key))
}
function readStoredString(key: string) {
if (typeof window === "undefined") return undefined
try {
return window.sessionStorage.getItem(key) ?? undefined
} catch {
return undefined
}
}
function writeStoredString(key: string, value: string) {
try {
window.sessionStorage.setItem(key, value)
} catch {
// The in-memory preference remains active when storage is unavailable.
}
}
+19
View File
@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}
+8
View File
@@ -0,0 +1,8 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export type { ClassValue }
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
+18
View File
@@ -0,0 +1,18 @@
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import App from "./App.tsx"
import "./index.css"
import { initializeDocsPreferences } from "./lib/preferences"
document.documentElement.lang = window.location.pathname.startsWith("/en-US")
? "en-US"
: "zh-Hans"
initializeDocsPreferences()
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>
)
+719
View File
@@ -0,0 +1,719 @@
import * as React from "react"
import { createPortal } from "react-dom"
import { Icon } from "@icones/react"
import {
getDocHref,
getDocPackages,
getDocSectionLabel,
getDocsHomeHref,
resolveDocLocation,
} from "../content/registry"
import type {
DocLocation,
DocPage,
DocTocItem,
} from "../content/types"
import { Actions, Container, HomeButton, SlashLine } from "../components/layout"
import { mdxComponents } from "../components/mdx"
import { useLocale, useTranslate } from "../lib/locale"
const noTocItems: readonly DocTocItem[] = []
export default function ArticlePage() {
const t = useTranslate()
const docLocation = resolveDocLocation(window.location.pathname)
const visibleTocIds = useVisibleTocIds(docLocation?.page.toc ?? noTocItems)
const [mobilePanel, setMobilePanel] = React.useState<
"navigation" | "toc" | null
>(null)
const mobilePanelRef = React.useRef<HTMLDivElement>(null)
const navigationTriggerRef = React.useRef<HTMLButtonElement>(null)
const tocTriggerRef = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
if (!mobilePanel) return
const previousOverflow = document.body.style.overflow
document.body.style.overflow = "hidden"
const unavailableAt = window.matchMedia(
mobilePanel === "navigation" ? "(min-width: 48rem)" : "(min-width: 64rem)"
)
const animationFrame = window.requestAnimationFrame(() => {
mobilePanelRef.current
?.querySelector<HTMLElement>(
'a[href], button:not(:disabled), [tabindex]:not([tabindex="-1"])'
)
?.focus()
})
function handleKeyDown(event: KeyboardEvent) {
if (event.key === "Escape") {
event.preventDefault()
setMobilePanel(null)
const trigger =
mobilePanel === "navigation"
? navigationTriggerRef.current
: tocTriggerRef.current
trigger?.focus()
return
}
if (event.key !== "Tab") return
const focusableElements = Array.from(
mobilePanelRef.current?.querySelectorAll<HTMLElement>(
'a[href], button:not(:disabled), [tabindex]:not([tabindex="-1"])'
) ?? []
)
const firstElement = focusableElements[0]
const lastElement = focusableElements.at(-1)
if (!firstElement || !lastElement) return
if (event.shiftKey && document.activeElement === firstElement) {
event.preventDefault()
lastElement.focus()
} else if (!event.shiftKey && document.activeElement === lastElement) {
event.preventDefault()
firstElement.focus()
}
}
function handleBreakpointChange(event: MediaQueryListEvent) {
if (event.matches) setMobilePanel(null)
}
document.addEventListener("keydown", handleKeyDown)
unavailableAt.addEventListener("change", handleBreakpointChange)
return () => {
window.cancelAnimationFrame(animationFrame)
document.removeEventListener("keydown", handleKeyDown)
unavailableAt.removeEventListener("change", handleBreakpointChange)
document.body.style.overflow = previousOverflow
}
}, [mobilePanel])
if (!docLocation) return <ArticleNotFound />
const currentIndex = docLocation.package.pages.indexOf(docLocation.page)
const previousPage = docLocation.package.pages[currentIndex - 1]
const nextPage = docLocation.package.pages[currentIndex + 1]
const previous = previousPage
? {
locale: docLocation.locale,
package: docLocation.package,
page: previousPage,
}
: undefined
const next = nextPage
? {
locale: docLocation.locale,
package: docLocation.package,
page: nextPage,
}
: undefined
return (
<>
<header
data-article-sticky-header
className="z-10 h-16 border-b bg-white/70 backdrop-blur-sm md:sticky md:top-0 dark:border-slate-800 dark:bg-slate-950/80"
>
<Container className="flex size-full items-center gap-4">
<HomeButton href={getDocsHomeHref(docLocation.locale)} />
<SlashLine />
<h1 className="truncate">{docLocation.package.label}</h1>
<Actions className="ml-auto" />
</Container>
</header>
<section
data-article-sticky-header
className="sticky top-0 z-20 flex items-center justify-between border-b bg-white/70 backdrop-blur-sm md:hidden dark:border-slate-800 dark:bg-slate-950/80"
>
<button
ref={navigationTriggerRef}
type="button"
className="inline-flex h-12 cursor-pointer items-center gap-2 px-4 transition-colors"
aria-controls="article-mobile-navigation"
aria-expanded={mobilePanel === "navigation"}
onClick={() =>
setMobilePanel((current) =>
current === "navigation" ? null : "navigation"
)
}
>
<Icon name="tabler:menu" className="size-5" />
<span>{t({ zh: "文档菜单", us: "Docs Menu" })}</span>
</button>
<button
ref={tocTriggerRef}
type="button"
className="ml-auto inline-flex h-12 cursor-pointer items-center gap-2 px-4 transition-colors"
aria-controls="article-mobile-toc"
aria-expanded={mobilePanel === "toc"}
onClick={() =>
setMobilePanel((current) => (current === "toc" ? null : "toc"))
}
>
<span>{t({ zh: "本页内容", us: "On this page" })}</span>
<Icon name="tabler:list" className="size-5 rotate-180" />
</button>
</section>
{mobilePanel &&
typeof document !== "undefined" &&
createPortal(
<div className="fixed inset-0 z-50">
<div
className="absolute inset-0 bg-slate-950/50 backdrop-blur-[1px] dark:bg-black/70"
aria-hidden="true"
onClick={() => {
const trigger =
mobilePanel === "navigation"
? navigationTriggerRef.current
: tocTriggerRef.current
setMobilePanel(null)
window.requestAnimationFrame(() => trigger?.focus())
}}
/>
<div
ref={mobilePanelRef}
id={
mobilePanel === "navigation"
? "article-mobile-navigation"
: "article-mobile-toc"
}
role="dialog"
aria-modal="true"
aria-label={
mobilePanel === "navigation"
? t({ zh: "文档导航", us: "Documentation navigation" })
: t({ zh: "本页内容", us: "On this page" })
}
className={[
"absolute inset-y-0 w-[min(88vw,35rem)] overflow-y-auto overscroll-contain bg-white p-7 shadow-2xl dark:bg-slate-950 dark:shadow-black/60",
mobilePanel === "navigation" ? "left-0" : "right-0",
].join(" ")}
onClick={(event) => {
if (
event.target instanceof Element &&
event.target.closest("a[href]")
) {
setMobilePanel(null)
}
}}
>
{mobilePanel === "navigation" ? (
<nav aria-label="Documentation navigation">
<Sidebar current={docLocation} />
</nav>
) : (
<TableOfContents
activeIds={visibleTocIds}
items={docLocation.page.toc}
/>
)}
</div>
</div>,
document.body
)}
<div className="flex w-full">
<div
data-slot="left"
className="sticky top-16 hidden h-[calc(100svh-(--spacing(16)))] min-w-(--min-lw) flex-[6_1_var(--min-lw)] overflow-y-auto border-r bg-slate-50 md:block dark:border-slate-800 dark:bg-slate-900/50"
>
<aside className="ml-auto w-full max-w-(--max-lw) p-8">
<Sidebar current={docLocation} />
</aside>
</div>
<div
data-slot="center"
className="min-w-0 flex-1 sm:flex-[0_1_var(--cw)]"
>
<main className="w-full max-w-(--cw) px-4 pt-8 pb-16 md:px-22">
<Header docLocation={docLocation} />
<ArticleContent page={docLocation.page} />
<Footer next={next} previous={previous} />
</main>
</div>
<div
data-slot="right"
className="sticky top-16 hidden h-[calc(100svh-(--spacing(16)))] min-w-(--min-rw) flex-[5_1_var(--min-rw)] lg:block"
>
<div className="h-full w-full max-w-(--max-rw)">
<TableOfContents
activeIds={visibleTocIds}
items={docLocation.page.toc}
/>
</div>
</div>
</div>
<footer className="border-t py-8 text-slate-500 dark:border-slate-800 dark:text-slate-400">
<Container className="flex h-full w-full items-center">
Copyright © 2026
</Container>
</footer>
</>
)
}
function Sidebar({ current }: { current: DocLocation }) {
const overview = current.package.pages.find(
(page) => page.section === "overview"
)
const groups = current.package.pages
.filter((page) => page.section !== "overview")
.reduce<Array<{ section: DocPage["section"]; pages: DocPage[] }>>(
(result, page) => {
const group = result.at(-1)
if (group?.section === page.section) {
group.pages.push(page)
} else {
result.push({ section: page.section, pages: [page] })
}
return result
},
[]
)
return (
<ul>
{overview && (
<li>
<SidebarPageLink current={current} page={overview} prominent />
</li>
)}
{groups.map((group) => (
<li className="not-first:mt-8" key={group.section}>
<h2 className="font-semibold">
{getDocSectionLabel(group.section, current.locale)}
</h2>
<ul className="mt-1.5">
{group.pages.map((page) => (
<li key={`${page.section}/${page.slug}`}>
<SidebarPageLink current={current} page={page} />
</li>
))}
</ul>
</li>
))}
</ul>
)
}
function SidebarPageLink({
current,
page,
prominent = false,
}: {
current: DocLocation
page: DocPage
prominent?: boolean
}) {
return (
<a
className={`block py-1 transition-colors hover:text-indigo-500 aria-[current=page]:text-indigo-500 dark:hover:text-indigo-300 dark:aria-[current=page]:text-indigo-400 ${prominent ? "font-semibold text-slate-950 dark:text-slate-100" : "text-gray-600 aria-[current=page]:font-medium dark:text-slate-400"}`}
href={getDocHref(current.package, page)}
aria-current={
current.page.section === page.section && current.page.slug === page.slug
? "page"
: undefined
}
>
{page.title}
</a>
)
}
function Header({ docLocation }: { docLocation: DocLocation }) {
return (
<header className="mb-12 border-b pb-12">
<ul className="flex items-center gap-2">
<li>
<a
href={getDocHref(docLocation.package)}
className="transition-colors hover:text-indigo-500 dark:hover:text-indigo-300"
>
{docLocation.package.label}
</a>
</li>
<li>
<span className="text-slate-200 dark:text-slate-700">/</span>
</li>
{docLocation.page.section !== "overview" && (
<>
<li>
<span className="text-slate-500 dark:text-slate-400">
{getDocSectionLabel(
docLocation.page.section,
docLocation.locale
)}
</span>
</li>
<li>
<span className="text-slate-200 dark:text-slate-700">/</span>
</li>
</>
)}
<li>
<span className="text-indigo-500 dark:text-indigo-400">
{docLocation.page.title}
</span>
</li>
</ul>
<h1 className="mt-4 text-4xl">{docLocation.page.title}</h1>
<p className="mt-2 text-slate-500 dark:text-slate-400">
{docLocation.page.description}
</p>
</header>
)
}
function ArticleContent({ page }: { page: DocPage }) {
const Content = page.Content
return (
<article
data-article-content
className="space-y-5 text-slate-700 dark:text-slate-300 [&_hr]:my-12 [&_hr]:border-slate-200 dark:[&_hr]:border-slate-800 [&_li]:leading-7 [&_ol]:list-decimal [&_ol]:space-y-2 [&_ol]:pl-6 [&_p]:leading-7 [&_strong]:font-semibold [&_strong]:text-slate-950 dark:[&_strong]:text-slate-100 [&_table]:w-full [&_table]:border-collapse [&_td]:border [&_td]:border-slate-200 [&_td]:p-3 dark:[&_td]:border-slate-800 [&_th]:border [&_th]:border-slate-200 [&_th]:bg-slate-50 [&_th]:p-3 [&_th]:text-left dark:[&_th]:border-slate-800 dark:[&_th]:bg-slate-900 [&_ul]:list-disc [&_ul]:space-y-2 [&_ul]:pl-6"
>
<Content components={mdxComponents} />
</article>
)
}
function TableOfContents({
activeIds,
items,
}: {
activeIds: readonly string[]
items: readonly DocTocItem[]
}) {
const t = useTranslate()
const label = t({ zh: "本页内容", us: "On this page" })
const rootRef = React.useRef<HTMLElement>(null)
const railRef = React.useRef<HTMLDivElement>(null)
const [marker, setMarker] = React.useState<{
height: number
top: number
} | null>(null)
const activeKey = activeIds.join("\n")
React.useLayoutEffect(() => {
const root = rootRef.current
const rail = railRef.current
if (!root || !rail) return
function measureMarker() {
const activeIdSet = new Set(activeIds)
const activeRows = Array.from(
root!.querySelectorAll<HTMLElement>("[data-toc-id]")
).filter((row) => activeIdSet.has(row.dataset.tocId ?? ""))
if (activeRows.length === 0 || rail!.offsetHeight === 0) {
setMarker(null)
return
}
const railRect = rail!.getBoundingClientRect()
const firstRect = activeRows[0]!.getBoundingClientRect()
const lastRect = activeRows.at(-1)!.getBoundingClientRect()
setMarker({
top: firstRect.top - railRect.top,
height: lastRect.bottom - firstRect.top,
})
}
measureMarker()
const resizeObserver = new ResizeObserver(measureMarker)
resizeObserver.observe(root)
window.addEventListener("resize", measureMarker)
return () => {
resizeObserver.disconnect()
window.removeEventListener("resize", measureMarker)
}
}, [activeKey, activeIds])
return (
<aside
ref={rootRef}
aria-label={label}
className="relative max-h-full overflow-y-auto p-8"
>
<div
ref={railRef}
className="absolute top-8 bottom-6 left-8 hidden w-px bg-slate-200 md:block dark:bg-slate-800"
>
<div
className="absolute left-0 w-px bg-indigo-500 transition-[height,transform,opacity] duration-200 dark:bg-indigo-400"
style={{
height: marker?.height ?? 0,
opacity: marker ? 1 : 0,
transform: `translateY(${marker?.top ?? 0}px)`,
}}
/>
</div>
<div className="md:ps-4">
<h2 className="shrink-0">{label}</h2>
<ul className="mt-2">
{items.map((item) => (
<TocItem activeIds={activeIds} item={item} key={item.id} />
))}
</ul>
</div>
</aside>
)
}
function TocItem({
activeIds,
item,
}: {
activeIds: readonly string[]
item: DocTocItem
}) {
const active = activeIds.includes(item.id)
return (
<li>
<TocAnchor
active={active}
href={`#${item.id}`}
itemId={item.id}
title={item.title}
/>
{item.items && item.items.length > 0 && (
<ul className="ps-4">
{item.items.map((child) => (
<TocItem activeIds={activeIds} item={child} key={child.id} />
))}
</ul>
)}
</li>
)
}
function TocAnchor({
active,
href,
itemId,
title,
}: {
active: boolean
href: string
itemId: string
title: string
}) {
return (
<div className="py-1" data-toc-id={itemId}>
<a
href={href}
data-active={active || undefined}
className="text-slate-600 transition-colors hover:text-indigo-500 data-active:font-medium data-active:text-indigo-600 dark:text-slate-400 dark:hover:text-indigo-300 dark:data-active:text-indigo-400"
>
{title}
</a>
</div>
)
}
type TocVisibilityEntry = {
id: string
parentIds: readonly string[]
}
function useVisibleTocIds(items: readonly DocTocItem[]) {
const entries = React.useMemo(() => flattenTocItems(items), [items])
const [activeIds, setActiveIds] = React.useState<readonly string[]>([])
React.useEffect(() => {
if (entries.length === 0) {
setActiveIds([])
return
}
let animationFrame: number | undefined
const articleElement = document.querySelector<HTMLElement>(
"[data-article-content]"
)
if (!articleElement) return
const article = articleElement
function update() {
animationFrame = undefined
const headings = entries.flatMap((entry) => {
const element = document.getElementById(entry.id)
return element ? [{ element, entry }] : []
})
const viewportTop = getVisibleViewportTop()
const viewportBottom = window.innerHeight
const articleBottom = article.getBoundingClientRect().bottom
const visibleIds = new Set<string>()
headings.forEach(({ element, entry }, index) => {
const sectionTop = element.getBoundingClientRect().top
const sectionBottom =
headings[index + 1]?.element.getBoundingClientRect().top ??
articleBottom
if (sectionBottom > viewportTop && sectionTop < viewportBottom) {
entry.parentIds.forEach((id) => visibleIds.add(id))
visibleIds.add(entry.id)
}
})
const nextActiveIds = entries
.map((entry) => entry.id)
.filter((id) => visibleIds.has(id))
setActiveIds((current) =>
arraysEqual(current, nextActiveIds) ? current : nextActiveIds
)
}
function scheduleUpdate() {
if (animationFrame !== undefined) return
animationFrame = window.requestAnimationFrame(update)
}
scheduleUpdate()
const resizeObserver = new ResizeObserver(scheduleUpdate)
resizeObserver.observe(article)
window.addEventListener("scroll", scheduleUpdate, { passive: true })
window.addEventListener("resize", scheduleUpdate)
return () => {
if (animationFrame !== undefined) {
window.cancelAnimationFrame(animationFrame)
}
resizeObserver.disconnect()
window.removeEventListener("scroll", scheduleUpdate)
window.removeEventListener("resize", scheduleUpdate)
}
}, [entries])
return activeIds
}
function flattenTocItems(
items: readonly DocTocItem[],
parentIds: readonly string[] = []
): readonly TocVisibilityEntry[] {
return items.flatMap((item) => [
{ id: item.id, parentIds },
...flattenTocItems(item.items ?? [], [...parentIds, item.id]),
])
}
function getVisibleViewportTop() {
return Array.from(
document.querySelectorAll<HTMLElement>("[data-article-sticky-header]")
).reduce((bottom, element) => {
const rect = element.getBoundingClientRect()
return rect.top <= 0 && rect.bottom > 0
? Math.max(bottom, rect.bottom)
: bottom
}, 0)
}
function arraysEqual(left: readonly string[], right: readonly string[]) {
return (
left.length === right.length &&
left.every((value, index) => value === right[index])
)
}
function Footer({
next,
previous,
}: {
next?: DocLocation
previous?: DocLocation
}) {
return (
<footer className="mt-12 flex flex-col gap-4 border-t border-dashed pt-12 md:grid md:grid-cols-2 md:gap-8">
{previous && (
<Surround
href={getDocHref(previous.package, previous.page)}
title={previous.page.title}
description={previous.page.description}
align="left"
/>
)}
{next && (
<Surround
href={getDocHref(next.package, next.page)}
title={next.page.title}
description={next.page.description}
align="right"
/>
)}
</footer>
)
}
function Surround({
href,
title,
description,
align = "right",
}: {
title: string
description: string
align?: "left" | "right"
href: string
}) {
return (
<a
href={href}
className={[
"group flex min-w-0 flex-col gap-1 rounded-xl border p-4",
"transition-colors hover:bg-slate-50 dark:hover:bg-slate-900",
align === "left"
? "col-start-1 items-start text-left"
: "col-start-2 items-end text-right",
].join(" ")}
>
<div className="flex size-8 items-center justify-center rounded-full border bg-slate-50 text-sm font-bold transition-colors group-hover:border-indigo-500 group-hover:bg-indigo-100 group-hover:text-indigo-500 dark:border-slate-700 dark:bg-slate-900 dark:group-hover:border-indigo-400 dark:group-hover:bg-indigo-950/70 dark:group-hover:text-indigo-300">
{align === "left" ? (
<Icon name="tabler:arrow-left" />
) : (
<Icon name="tabler:arrow-right" />
)}
</div>
<h2 className="mt-4 w-full min-w-0 truncate text-lg">{title}</h2>
<p className="text-md mt-1 line-clamp-2 w-full min-w-0 break-all text-slate-500 dark:text-slate-400">
{description}
</p>
</a>
)
}
function ArticleNotFound() {
const locale = useLocale()
const t = useTranslate()
return (
<main className="mx-auto flex min-h-dvh max-w-xl flex-col justify-center px-6">
<h1 className="text-4xl">
{t({ zh: "未找到文档", us: "Document not found" })}
</h1>
<p className="mt-3 text-slate-500 dark:text-slate-400">
{t({
zh: "请求的文档包或页面不存在。",
us: "The requested package or document does not exist.",
})}
</p>
<a
className="mt-6 w-fit text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
href={getDocHref(getDocPackages(locale)[0]!)}
>
{t({ zh: "打开文档", us: "Open the documentation" })}
</a>
</main>
)
}
+49
View File
@@ -0,0 +1,49 @@
import { Actions, Container } from "../components/layout"
import { getDocHref, getDocPackages } from "../content/registry"
import { useLocale, useTranslate } from "../lib/locale"
export default function IndexPage() {
const locale = useLocale()
const t = useTranslate()
const docPackages = getDocPackages(locale)
return (
<>
<Container className="flex items-center justify-between gap-4 pt-8 pb-10 sm:pt-12 sm:pb-14 lg:pb-16">
<h1 className="min-w-0 text-2xl font-semibold capitalize sm:text-3xl">
my-shadcn-ui
</h1>
<Actions className="ml-auto" />
</Container>
<Container
className="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3 lg:gap-8"
aria-label={t({ zh: "文档导航", us: "Documentation" })}
>
{docPackages.map((docPackage, index) => (
<a
key={docPackage.key}
href={getDocHref(docPackage)}
className="block overflow-hidden rounded-xl border border-slate-200 bg-indigo-50 outline-0 outline-offset-2 transition-colors hover:border-indigo-500 hover:bg-indigo-50 focus-visible:outline-2 focus-visible:outline-indigo-500 dark:border-slate-800 dark:bg-slate-900 dark:hover:border-indigo-400 dark:hover:bg-slate-800/80 dark:focus-visible:outline-indigo-400"
>
<div className="min-h-fit p-4 sm:min-h-40 lg:min-h-46">
<div className="text-slate-400 dark:text-slate-500">
{index > 9 ? `${index + 1}` : `0${index + 1}`}
</div>
<h2 className="mt-2 mb-4 text-xl font-semibold text-slate-950 dark:text-slate-100">
{docPackage.label}
</h2>
<p className="text-slate-700 dark:text-slate-300">
{docPackage.description}
</p>
</div>
</a>
))}
</Container>
<footer className="py-8 sm:py-12">
<Container className="text-center text-slate-500 dark:text-slate-400">
<div>Copyright © 2026</div>
</Container>
</footer>
</>
)
}
+365
View File
@@ -0,0 +1,365 @@
import { Icon } from "@icones/react"
import { usePreference } from "@workspace/preferences"
import { BackButton, Container, SlashLine } from "../components/layout"
import { LocaleFlag } from "../components/locale-flag"
import { SearchTrigger } from "../components/search-trigger"
import { Swatch } from "../components/swatch"
import { getLocalizedHref } from "../content/registry"
import type { DocLocale } from "../content/types"
import {
accentColors,
backgroundShades,
getPaletteColor,
themeModes,
useAppearance,
type AccentColor,
type BackgroundShade,
type ForegroundShade,
type ThemeMode,
} from "../lib/appearance"
import { useLocale, useTranslate, type Translation } from "../lib/locale"
import { navigateToLocale } from "../lib/locale-navigation"
import { detectLocale } from "../lib/preferences"
const foregroundOptions: ReadonlyArray<{
label: Translation
value: ForegroundShade
}> = [
{ value: "slate", label: { zh: "冷色", us: "Cool" } },
{ value: "neutral", label: { zh: "中性", us: "Neutral" } },
{ value: "stone", label: { zh: "暖色", us: "Warm" } },
]
const messages = {
accent: { zh: "强调色", us: "Accent color" },
appearance: { zh: "外观", us: "Appearance" },
autoDetect: { zh: "自动检测", us: "Automatic detection" },
autoDetectDescription: {
zh: "跟随浏览器或操作系统语言设置。",
us: "Follow the browser or operating system language.",
},
background: { zh: "背景色调", us: "Background shade" },
enable: { zh: "启用", us: "Enable" },
foreground: { zh: "前景色调", us: "Foreground shade" },
language: { zh: "语言", us: "Language" },
settings: { zh: "设置", us: "Settings" },
theme: { zh: "主题", us: "Theme" },
themeModes: {
light: { zh: "浅色", us: "Light" },
dark: { zh: "深色", us: "Dark" },
system: { zh: "跟随系统", us: "System" },
},
} as const
export default function SettingsPage() {
const locale = useLocale()
const t = useTranslate()
const appearance = useAppearance()
const [localeMode, setLocaleMode] = usePreference("locale-mode")
const [, setPreferredLocale] = usePreference("locale")
const autoDetectLocale = localeMode === "auto"
function selectLocale(nextLocale: DocLocale) {
setPreferredLocale(nextLocale)
setLocaleMode("manual")
navigateToLocale(nextLocale)
}
function setAutomaticLocale(enabled: boolean) {
if (enabled) {
setLocaleMode("auto")
navigateToLocale(detectLocale())
} else {
setPreferredLocale(locale)
setLocaleMode("manual")
}
}
return (
<>
<header className="border-b bg-white/70 backdrop-blur-sm dark:border-slate-800 dark:bg-slate-950/80">
<Container className="flex h-16 items-center gap-4">
<BackButton />
<SlashLine />
<h1 className="text-xl font-semibold">{t(messages.settings)}</h1>
<div className="ml-auto">
<SearchTrigger />
</div>
</Container>
</header>
<main>
<Container className="py-8 sm:py-12">
<section aria-labelledby="appearance-heading">
<h2 id="appearance-heading" className="mb-4 text-xl font-semibold">
{t(messages.appearance)}
</h2>
<div className="space-y-8 rounded-xl border bg-white/50 p-5 sm:p-8 dark:border-slate-800 dark:bg-slate-900/30">
<fieldset>
<legend className="mb-3">{t(messages.theme)}</legend>
<div
role="radiogroup"
aria-label={t(messages.theme)}
className="w-full space-y-1 rounded-xl border p-1 sm:w-fit sm:min-w-56 dark:border-slate-700"
onKeyDown={(event) => {
if (
event.key !== "ArrowDown" &&
event.key !== "ArrowUp" &&
event.key !== "ArrowRight" &&
event.key !== "ArrowLeft" &&
event.key !== "Home" &&
event.key !== "End"
) {
return
}
event.preventDefault()
const options = Array.from(
event.currentTarget.querySelectorAll<HTMLButtonElement>(
'[role="radio"]'
)
)
const currentIndex = options.indexOf(
document.activeElement as HTMLButtonElement
)
const moveForward =
event.key === "ArrowDown" || event.key === "ArrowRight"
let nextIndex = currentIndex
if (event.key === "Home") nextIndex = 0
else if (event.key === "End") nextIndex = options.length - 1
else if (moveForward) {
nextIndex = (currentIndex + 1) % options.length
} else {
nextIndex =
(currentIndex - 1 + options.length) % options.length
}
options[nextIndex]?.focus()
options[nextIndex]?.click()
}}
>
{themeModes.map((mode) => {
const selected = appearance.themeMode === mode
return (
<button
key={mode}
type="button"
role="radio"
aria-checked={selected}
tabIndex={selected ? 0 : -1}
className="flex h-11 w-full cursor-pointer items-center gap-3 rounded-lg px-3 text-left transition-colors hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-indigo-500 aria-checked:bg-indigo-100 aria-checked:text-indigo-700 dark:hover:bg-slate-800 dark:aria-checked:bg-indigo-950 dark:aria-checked:text-indigo-300"
onClick={() =>
appearance.updateAppearance({ themeMode: mode })
}
>
<ThemeIcon mode={mode} />
<span>{t(messages.themeModes[mode])}</span>
{selected && (
<Icon
name="tabler:check"
className="ml-auto size-4"
/>
)}
</button>
)
})}
</div>
</fieldset>
<ColorField
label={t(messages.accent)}
value={appearance.accentColor}
options={accentColors}
getColors={(accentColor) => ({
light: getPaletteColor(accentColor, 600),
dark: getPaletteColor(accentColor, 400),
})}
onChange={(accentColor) =>
appearance.updateAppearance({ accentColor })
}
/>
<ColorField
label={t(messages.background)}
value={appearance.backgroundShade}
options={backgroundShades}
getColors={(backgroundShade) => ({
light: getPaletteColor(backgroundShade, 50),
dark: getPaletteColor(backgroundShade, 950),
})}
onChange={(backgroundShade) =>
appearance.updateAppearance({ backgroundShade })
}
/>
<fieldset>
<legend className="mb-3">{t(messages.foreground)}</legend>
<div className="flex flex-wrap items-center gap-4">
{foregroundOptions.map((option) => (
<Swatch
key={option.value}
light={getPaletteColor(option.value, 950)}
dark={getPaletteColor(option.value, 50)}
aria-label={t(option.label)}
title={t(option.label)}
selected={appearance.foregroundShade === option.value}
onClick={() =>
appearance.updateAppearance({
foregroundShade: option.value,
})
}
/>
))}
</div>
</fieldset>
</div>
</section>
<section
className="mt-10 sm:mt-12"
aria-labelledby="language-heading"
>
<h2 id="language-heading" className="mb-4 text-xl font-semibold">
{t(messages.language)}
</h2>
<div className="space-y-8 rounded-xl border bg-white/50 p-5 sm:p-8 dark:border-slate-800 dark:bg-slate-900/30">
<fieldset>
<legend className="mb-3">{t(messages.language)}</legend>
<div className="w-full space-y-1 rounded-xl border p-1 sm:w-fit sm:min-w-48 dark:border-slate-700">
<LocaleLink
disabled={autoDetectLocale}
href={getLocalizedHref(window.location.pathname, "zh-Hans")}
label="简体中文"
locale="zh-Hans"
selected={locale === "zh-Hans"}
onClick={() => selectLocale("zh-Hans")}
/>
<LocaleLink
disabled={autoDetectLocale}
href={getLocalizedHref(window.location.pathname, "en-US")}
label="English"
locale="en-US"
selected={locale === "en-US"}
onClick={() => selectLocale("en-US")}
/>
</div>
</fieldset>
<div className="flex flex-col gap-2">
<div>{t(messages.autoDetect)}</div>
<label className="inline-flex w-fit cursor-pointer items-center gap-3">
<input
type="checkbox"
className="size-4 accent-indigo-600"
checked={autoDetectLocale}
onChange={(event) =>
setAutomaticLocale(event.currentTarget.checked)
}
/>
<span>{t(messages.enable)}</span>
</label>
<p className="text-sm text-(--docs-muted-light) dark:text-(--docs-muted-dark)">
{t(messages.autoDetectDescription)}
</p>
</div>
</div>
</section>
</Container>
</main>
</>
)
}
function ColorField<T extends AccentColor | BackgroundShade>({
getColors,
label,
onChange,
options,
value,
}: {
getColors: (value: T) => { dark: string; light: string }
label: string
onChange: (value: T) => void
options: readonly T[]
value: T
}) {
return (
<fieldset>
<legend className="mb-3">{label}</legend>
<div className="flex flex-wrap items-center gap-4">
{options.map((option) => {
const colors = getColors(option)
const optionLabel = capitalize(option)
return (
<Swatch
key={option}
light={colors.light}
dark={colors.dark}
aria-label={optionLabel}
title={optionLabel}
selected={option === value}
onClick={() => onChange(option)}
/>
)
})}
</div>
</fieldset>
)
}
function ThemeIcon({ mode }: { mode: ThemeMode }) {
if (mode === "light") {
return <Icon name="tabler:sun-high" className="size-5" />
}
if (mode === "dark") {
return <Icon name="tabler:moon" className="size-5" />
}
return <Icon name="tabler:device-desktop" className="size-5" />
}
function LocaleLink({
disabled,
href,
label,
locale,
onClick,
selected,
}: {
disabled: boolean
href: string
label: string
locale: DocLocale
onClick: () => void
selected: boolean
}) {
return (
<a
href={href}
hrefLang={locale}
lang={locale}
aria-disabled={disabled}
aria-current={selected ? "page" : undefined}
tabIndex={disabled ? -1 : undefined}
className="flex h-11 items-center gap-2 rounded-lg px-3 transition-colors hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-indigo-500 aria-disabled:cursor-not-allowed aria-disabled:opacity-45 aria-disabled:hover:bg-transparent aria-[current=page]:bg-indigo-100 aria-[current=page]:text-indigo-700 dark:hover:bg-slate-800 dark:aria-disabled:hover:bg-transparent dark:aria-[current=page]:bg-indigo-950 dark:aria-[current=page]:text-indigo-300"
onClick={(event) => {
if (disabled) {
event.preventDefault()
return
}
onClick()
}}
>
<LocaleFlag locale={locale} className="size-5" />
<span>{label}</span>
{selected && <Icon name="tabler:check" className="ml-auto size-4" />}
</a>
)
}
function capitalize(value: string) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
+9
View File
@@ -0,0 +1,9 @@
declare module "*.mdx" {
import type { ComponentType, ElementType } from "react"
const MDXContent: ComponentType<{
components?: Record<string, ElementType>
}>
export const frontmatter: Record<string, unknown>
export default MDXContent
}
+26
View File
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023", "DOM"],
"module": "esnext",
"types": ["vite/client"],
"allowArbitraryExtensions": true,
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
+23
View File
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023"],
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"module": "nodenext",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
+81
View File
@@ -0,0 +1,81 @@
import react, { reactCompilerPreset } from "@vitejs/plugin-react"
import babel from "@rolldown/plugin-babel"
import tailwindcss from "@tailwindcss/vite"
import { icones } from "@icones/vite"
import { readFile } from "node:fs/promises"
import { fileURLToPath } from "node:url"
import { defineConfig, type Plugin } from "vite"
import satteri from "vite-plugin-satteri"
const packagesDirectory = fileURLToPath(new URL("../packages", import.meta.url))
/**
* Vite 默认监听 docs 根目录。现有的外部 MDX 会作为模块被监听,但新建 package
* 或新建 MDX 文件在首次进入模块图之前不会触发 import.meta.glob 重新计算。
*/
function watchPackageDocs(): Plugin {
return {
name: "watch-package-docs",
configureServer(server) {
server.watcher.add(packagesDirectory)
},
}
}
function loadDocSearchSources(): Plugin {
const suffix = "?docs-search-raw"
const virtualPrefix = "\0docs-search-raw:"
return {
name: "load-doc-search-sources",
enforce: "pre",
async resolveId(id, importer) {
if (!id.endsWith(suffix)) return
const sourceId = id.slice(0, -suffix.length)
const resolved = await this.resolve(sourceId, importer, {
skipSelf: true,
})
const filename = resolved?.id ?? sourceId
return `${virtualPrefix}${encodeURIComponent(filename)}.js`
},
async load(id) {
if (!id.startsWith(virtualPrefix)) return
const sourceId = decodeURIComponent(
id.slice(virtualPrefix.length, -".js".length)
)
const filename = sourceId.startsWith("/@fs/")
? sourceId.slice("/@fs".length)
: sourceId
this.addWatchFile(filename)
const source = await readFile(filename, "utf8")
return `export default ${JSON.stringify(source)}`
},
}
}
// https://vite.dev/config/
export default defineConfig({
plugins: [
watchPackageDocs(),
loadDocSearchSources(),
satteri({
features: {
frontmatter: true,
gfm: true,
headingAttributes: true,
},
mdx: {
jsxImportSource: "react",
},
}),
tailwindcss(),
icones({
mode: "symbol",
emitData: false,
}),
react(),
babel({ presets: [reactCompilerPreset()] }),
],
})
+2 -1
View File
@@ -23,6 +23,7 @@
},
"workspaces": [
"apps/*",
"docs",
"packages/*"
]
}
}
@@ -0,0 +1,40 @@
---
title: Appearance
description: Finish the application with persistent theme, color, and compact-layout preferences.
order: 22
toc:
- id: provide-ui-state
title: Provide UI state
- id: apply-preferences
title: Apply preferences
- id: add-locales
title: Add locales
---
## Provide UI state {#provide-ui-state}
Mount `UiStateProvider` near the application root. A controlled provider can persist every state update through its change handler.
```tsx
<UiStateProvider>
<AppearanceController />
<App />
<ThemeToggleButton />
</UiStateProvider>
```
## Apply preferences {#apply-preferences}
Render `AppearanceController` once so the current state becomes document classes and theme variables. `useUiState` reads or updates individual preferences, while `useResolvedTheme` returns the effective light or dark scheme after resolving system mode.
## Add locales {#add-locales}
Add the appearance catalog only when the application uses these controls:
```json
{
"catalogSources": ["@workspace/blocks/appearance/locales/{locale}"]
}
```
The application shell is now complete. Add only the other block-specific catalogs used by the product.
@@ -0,0 +1,34 @@
---
title: Media library
description: Supply a storage adapter and add browsing, upload, folders, and asset selection.
order: 21
toc:
- id: implement-the-adapter
title: Implement the adapter
- id: provide-media
title: Provide media
- id: selection-flows
title: Selection flows
---
## Implement the adapter {#implement-the-adapter}
`MediaAdapter` is the media feature's complete persistence boundary. Implement its query, upload, folder, update, and delete operations with the product's storage service.
## Provide media {#provide-media}
Mount the adapter once around the media surfaces:
```tsx
import { MediaLibrary, MediaProvider } from "@workspace/blocks/media"
;<MediaProvider adapter={mediaAdapter} notify={showNotice}>
<MediaLibrary />
</MediaProvider>
```
The optional `notify` callback translates operational results into the application's toast or notification system.
## Selection flows {#selection-flows}
Use `MediaPickerDialog` for single or multiple asset selection. `readMediaDimensions`, `formatMediaFileSize`, and `defaultMediaReference` cover the common presentation work around uploads and previews.
@@ -0,0 +1,21 @@
---
title: Notifications
description: Connect notification state without coupling the UI to a backend.
order: 20
toc:
- id: notifications-query
title: Notifications query
---
## Notifications query {#notifications-query}
Notifications use TanStack Query as their data boundary:
```tsx
const notifications = useNotifications({
queryKey: ["notifications"],
queryFn: loadNotifications,
})
```
The result includes unread counts, optimistic read-state updates, action execution, refetching, and pending or error status. `AppLayout` can create the sheet directly from the same query options.
@@ -0,0 +1,35 @@
---
title: Chats
description: Add a responsive conversation workspace and connect its events to application services.
order: 30
toc:
- id: render-the-workspace
title: Render the workspace
- id: controlled-state
title: Controlled state
- id: media-and-pagination
title: Media and pagination
---
## Render the workspace {#render-the-workspace}
`ChatWorkspace` renders conversation discovery, the active thread, and the composer:
```tsx
import { ChatWorkspace } from "@workspace/blocks/chats"
;<ChatWorkspace
conversations={conversations}
onSend={({ conversation, message }) => sendMessage(conversation, message)}
/>
```
## Controlled state {#controlled-state}
The active conversation, draft, search query, and Enter-to-send preference can be controlled individually. Leave a value undefined when the workspace should own that state.
The UI emits events but does not select a transport or persistence format. Map API responses into `ChatConversation` and the exported message variants at the application boundary.
## Media and pagination {#media-and-pagination}
Provide `resolveMediaUrl` when stored media references need signed or transformed URLs. Use `onLoadEarlierMessages` with `hasMoreMessages` and `isLoadingMoreMessages` to connect historical pagination.
@@ -0,0 +1,45 @@
---
title: Application shell
description: Assemble the shared header, sidebar, breadcrumbs, chat, and notification surfaces.
order: 11
toc:
- id: render-app-layout
title: Render AppLayout
- id: layout-inputs
title: Layout inputs
- id: optional-preset
title: Optional preset
---
## Render AppLayout {#render-app-layout}
`AppLayout` is the integration point for the rest of this tutorial:
```tsx
import { AppLayout } from "@workspace/blocks/layout"
export function WorkspaceLayout() {
return (
<AppLayout
navigationGroups={navigationGroups}
notifications={{
queryKey: ["notifications"],
queryFn: loadNotifications,
}}
chatThreads={chatThreads}
>
<Outlet />
</AppLayout>
)
}
```
## Layout inputs {#layout-inputs}
The shell composes `AppSidebar`, `AppBreadcrumb`, `AppQueryIndicator`, `UserMenu`, a notification sheet, and chat threads. Pass `headerActions` when the product needs additional global controls.
The route content remains the layout's `children`, so the package does not constrain the router used by the application.
## Optional preset {#optional-preset}
`@workspace/blocks/layouts/vega` provides an optional visual shell. It lives on a separate entry point so the standard layout does not include preset-specific code.
@@ -0,0 +1,40 @@
---
title: Installation
description: Add the package, its styles, and the providers required by the tutorial.
order: 10
toc:
- id: add-the-package
title: Add the package
- id: import-styles
title: Import styles
- id: application-providers
title: Application providers
---
## Add the package {#add-the-package}
Add the workspace dependency to the consuming application:
```json
{
"dependencies": {
"@workspace/blocks": "workspace:*"
}
}
```
Blocks use `@workspace/ui`, React Query, and the workspace internationalization runtime. The workspace package manager resolves those dependencies for local applications.
## Import styles {#import-styles}
Import the block stylesheet once from the application entry:
```ts
import "@workspace/blocks/globals.css"
```
Keep feature imports on their explicit subpaths. For example, importing `@workspace/blocks/media` does not pull chat into the same module graph.
## Application providers {#application-providers}
The full shell expects React Query and internationalization to be available above it. Add those providers before the route tree, then continue to the application-shell chapter.
@@ -0,0 +1,46 @@
---
title: Navigation
description: Define navigation groups once and connect them to desktop and mobile surfaces.
order: 12
toc:
- id: define-groups
title: Define groups
- id: provide-navigation
title: Provide navigation
- id: route-state
title: Route state
---
## Define groups {#define-groups}
Start with typed `NavigationGroup` values. Items may represent direct routes or nested navigation branches.
```ts
import type { NavigationGroup } from "@workspace/blocks/navigation"
export const navigationGroups: NavigationGroup[] = [
{
id: "workspace",
label: "Workspace",
items: [
{ id: "dashboard", label: "Dashboard", to: "/dashboard" },
{ id: "media", label: "Media", to: "/media" },
],
},
]
```
## Provide navigation {#provide-navigation}
`AppLayout` already mounts the provider and the responsive navigation surfaces. For a custom shell, compose them directly:
```tsx
<NavigationProvider groups={navigationGroups}>
<PrimaryNavigation groups={navigationGroups} />
<MobileNavigationSheet groups={navigationGroups} />
</NavigationProvider>
```
## Route state {#route-state}
Use `resolveNavigationRouteState` for the standard route model. Applications with custom routing can supply a `GetNavigationRouteState` implementation while keeping the same navigation UI.
+33
View File
@@ -0,0 +1,33 @@
---
title: Overview
description: Understand how the Blocks package turns UI primitives into complete application features.
order: 1
toc:
- id: what-you-will-build
title: What you will build
- id: design-boundary
title: Design boundary
- id: tutorial-map
title: Tutorial map
---
## What you will build {#what-you-will-build}
This tutorial builds an application shell step by step. You will begin with the shared layout, connect navigation, then add notifications, chat, media, and appearance preferences.
By the end, the application owns its data and business rules while `@workspace/blocks` owns the reusable presentation and interaction patterns.
## Design boundary {#design-boundary}
Blocks sit above `@workspace/ui`. They combine low-level components into features, but they do not choose your API, database, router configuration, or storage provider.
> Keep data access in the host application. Pass it into a block through props, query functions, providers, or adapters.
## Tutorial map {#tutorial-map}
1. Install the package and global styles.
2. Create the application shell.
3. Define navigation and route state.
4. Connect notifications.
5. Add chat and media workflows.
6. Finish with persistent appearance controls.
@@ -0,0 +1,40 @@
---
title: 外观设置
description: 使用可持久化的主题、颜色和紧凑布局偏好完成应用。
order: 22
toc:
- id: provide-ui-state
title: 提供 UI 状态
- id: apply-preferences
title: 应用偏好
- id: add-locales
title: 添加词典
---
## 提供 UI 状态 {#provide-ui-state}
在应用根节点附近挂载 `UiStateProvider`。受控 Provider 可以通过变更处理器持久化每次状态更新。
```tsx
<UiStateProvider>
<AppearanceController />
<App />
<ThemeToggleButton />
</UiStateProvider>
```
## 应用偏好 {#apply-preferences}
渲染一次 `AppearanceController`,使当前状态转换为文档类名和主题变量。`useUiState` 读取或更新单个偏好,`useResolvedTheme` 在解析系统模式后返回最终的亮色或暗色主题。
## 添加词典 {#add-locales}
只有应用使用这些控件时才添加外观词典:
```json
{
"catalogSources": ["@workspace/blocks/appearance/locales/{locale}"]
}
```
至此应用外壳已经完成。其他功能也只需添加产品实际使用的 Block 词典。
@@ -0,0 +1,34 @@
---
title: 媒体库
description: 提供存储 Adapter,并加入浏览、上传、文件夹和资源选择能力。
order: 21
toc:
- id: implement-the-adapter
title: 实现 Adapter
- id: provide-media
title: 提供媒体能力
- id: selection-flows
title: 选择流程
---
## 实现 Adapter {#implement-the-adapter}
`MediaAdapter` 是媒体功能完整的持久化边界。通过产品存储服务实现查询、上传、文件夹、更新和删除操作。
## 提供媒体能力 {#provide-media}
在媒体界面外挂载一次 Adapter:
```tsx
import { MediaLibrary, MediaProvider } from "@workspace/blocks/media"
;<MediaProvider adapter={mediaAdapter} notify={showNotice}>
<MediaLibrary />
</MediaProvider>
```
可选的 `notify` 回调把操作结果转换为应用的 Toast 或通知。
## 选择流程 {#selection-flows}
单选或多选资源使用 `MediaPickerDialog`。`readMediaDimensions`、`formatMediaFileSize` 和 `defaultMediaReference` 覆盖上传与预览相关的常用展示工作。
@@ -0,0 +1,21 @@
---
title: 通知
description: 接入通知状态,同时避免让 UI 与后端耦合。
order: 20
toc:
- id: notifications-query
title: 通知查询
---
## 通知查询 {#notifications-query}
通知以 TanStack Query 作为数据边界:
```tsx
const notifications = useNotifications({
queryKey: ["notifications"],
queryFn: loadNotifications,
})
```
返回结果包含未读数量、乐观已读更新、操作执行、重新请求以及等待和错误状态。`AppLayout` 可以直接通过相同查询选项创建通知 Sheet。
@@ -0,0 +1,35 @@
---
title: 聊天示例
description: 添加响应式会话工作区,并将事件连接到应用服务。
order: 30
toc:
- id: render-the-workspace
title: 渲染工作区
- id: controlled-state
title: 受控状态
- id: media-and-pagination
title: 媒体与分页
---
## 渲染工作区 {#render-the-workspace}
`ChatWorkspace` 渲染会话查找、当前线程和消息编辑器:
```tsx
import { ChatWorkspace } from "@workspace/blocks/chats"
;<ChatWorkspace
conversations={conversations}
onSend={({ conversation, message }) => sendMessage(conversation, message)}
/>
```
## 受控状态 {#controlled-state}
当前会话、草稿、搜索词和回车发送偏好都可以单独受控。当工作区应自行管理某项状态时,不传对应值即可。
UI 只发送事件,不选择传输方式或持久化格式。在应用边界将 API 响应转换为 `ChatConversation` 和导出的消息类型。
## 媒体与分页 {#media-and-pagination}
当已存储媒体需要签名或转换 URL 时,提供 `resolveMediaUrl`。使用 `onLoadEarlierMessages`、`hasMoreMessages` 和 `isLoadingMoreMessages` 接入历史消息分页。
@@ -0,0 +1,45 @@
---
title: 应用外壳
description: 组装共享 Header、Sidebar、面包屑、聊天和通知界面。
order: 11
toc:
- id: render-app-layout
title: 渲染 AppLayout
- id: layout-inputs
title: 布局输入
- id: optional-preset
title: 可选预设
---
## 渲染 AppLayout {#render-app-layout}
`AppLayout` 是本教程其余部分的集成点:
```tsx
import { AppLayout } from "@workspace/blocks/layout"
export function WorkspaceLayout() {
return (
<AppLayout
navigationGroups={navigationGroups}
notifications={{
queryKey: ["notifications"],
queryFn: loadNotifications,
}}
chatThreads={chatThreads}
>
<Outlet />
</AppLayout>
)
}
```
## 布局输入 {#layout-inputs}
应用外壳组合了 `AppSidebar`、`AppBreadcrumb`、`AppQueryIndicator`、`UserMenu`、通知 Sheet 和聊天线程。产品需要更多全局控件时可传入 `headerActions`。
路由内容仍作为布局的 `children`,因此该包不会限制应用使用的路由器。
## 可选预设 {#optional-preset}
`@workspace/blocks/layouts/vega` 提供可选的视觉外壳。它位于独立入口,因此标准布局不会包含预设专属代码。
@@ -0,0 +1,40 @@
---
title: 安装
description: 添加教程所需的包、样式和 Providers。
order: 10
toc:
- id: add-the-package
title: 添加包
- id: import-styles
title: 导入样式
- id: application-providers
title: 应用 Providers
---
## 添加包 {#add-the-package}
在使用方应用中添加工作区依赖:
```json
{
"dependencies": {
"@workspace/blocks": "workspace:*"
}
}
```
Blocks 使用 `@workspace/ui`、React Query 和工作区国际化运行时。工作区包管理器会为本地应用解析这些依赖。
## 导入样式 {#import-styles}
在应用入口导入一次 Block 样式:
```ts
import "@workspace/blocks/globals.css"
```
功能模块应始终从显式子路径导入。例如,导入 `@workspace/blocks/media` 不会把聊天功能加入同一个模块图。
## 应用 Providers {#application-providers}
完整应用外壳要求上层已经提供 React Query 和国际化。在路由树之前加入这些 Providers,然后继续应用外壳章节。
@@ -0,0 +1,46 @@
---
title: 导航
description: 统一定义导航分组,并将其连接到桌面和移动端界面。
order: 12
toc:
- id: define-groups
title: 定义分组
- id: provide-navigation
title: 提供导航
- id: route-state
title: 路由状态
---
## 定义分组 {#define-groups}
从类型安全的 `NavigationGroup` 开始。导航项可以是直接路由,也可以是嵌套导航分支。
```ts
import type { NavigationGroup } from "@workspace/blocks/navigation"
export const navigationGroups: NavigationGroup[] = [
{
id: "workspace",
label: "工作区",
items: [
{ id: "dashboard", label: "仪表盘", to: "/dashboard" },
{ id: "media", label: "媒体", to: "/media" },
],
},
]
```
## 提供导航 {#provide-navigation}
`AppLayout` 已经挂载 Provider 和响应式导航界面。自定义外壳可以直接组合它们:
```tsx
<NavigationProvider groups={navigationGroups}>
<PrimaryNavigation groups={navigationGroups} />
<MobileNavigationSheet groups={navigationGroups} />
</NavigationProvider>
```
## 路由状态 {#route-state}
标准路由模型使用 `resolveNavigationRouteState`。使用自定义路由的应用可以提供 `GetNavigationRouteState` 实现,同时复用相同导航 UI。
+33
View File
@@ -0,0 +1,33 @@
---
title: 概览
description: 了解 Blocks 包如何把 UI 基础组件组合成完整应用功能。
order: 1
toc:
- id: what-you-will-build
title: 将要构建的内容
- id: design-boundary
title: 设计边界
- id: tutorial-map
title: 教程路线
---
## 将要构建的内容 {#what-you-will-build}
本教程会逐步构建一个应用外壳。你将从共享布局开始,接入导航,再添加通知、聊天、媒体和外观偏好。
完成后,应用负责数据和业务规则,`@workspace/blocks` 负责可复用的展示与交互模式。
## 设计边界 {#design-boundary}
Blocks 位于 `@workspace/ui` 之上。它们将底层组件组合成完整功能,但不会替你选择 API、数据库、路由配置或存储服务。
> 将数据访问保留在宿主应用中,通过属性、查询函数、Provider 或 Adapter 传给 Block。
## 教程路线 {#tutorial-map}
1. 安装包和全局样式。
2. 创建应用外壳。
3. 定义导航和路由状态。
4. 接入通知。
5. 添加聊天和媒体工作流。
6. 使用持久化外观控件完成应用。
+6 -8
View File
@@ -11,14 +11,14 @@
"@base-ui/react": "^1.6.0",
"@base-ui/utils": "^0.3.1",
"@floating-ui/utils": "^0.2.12",
"@icones/react": "^0.0.1",
"@lexical/react": "^0.48.0",
"@tanstack/react-hotkeys": "^0.10.0",
"@tanstack/react-query": "^5.101.4",
"@tanstack/react-router": "^1.170.18",
"@workspace/icons": "workspace:*",
"@workspace/i18n": "workspace:*",
"@workspace/ui": "workspace:*",
"cmdk": "^1.1.1",
"lucide-react": "^1.27.0",
"lexical": "^0.48.0",
"react": "^19.2.6",
"react-dom": "^19.2.6"
},
@@ -44,6 +44,7 @@
"./layout": "./src/blocks/layout/index.ts",
"./layout/locales": "./src/blocks/layout/locales/catalogs.ts",
"./layout/locales/*": "./src/blocks/layout/locales/*.ts",
"./layouts/vega": "./src/blocks/layouts/vega/index.tsx",
"./media": "./src/blocks/media/index.ts",
"./media/locales": "./src/blocks/media/locales/catalogs.ts",
"./media/locales/*": "./src/blocks/media/locales/*.ts",
@@ -52,9 +53,6 @@
"./navigation/locales/*": "./src/blocks/navigation/locales/*.ts",
"./notifications": "./src/blocks/notifications/index.ts",
"./notifications/locales": "./src/blocks/notifications/locales/catalogs.ts",
"./notifications/locales/*": "./src/blocks/notifications/locales/*.ts",
"./search": "./src/blocks/search/index.ts",
"./search/locales": "./src/blocks/search/locales/catalogs.ts",
"./search/locales/*": "./src/blocks/search/locales/*.ts"
"./notifications/locales/*": "./src/blocks/notifications/locales/*.ts"
}
}
}
@@ -1,5 +1,6 @@
export { appearanceDialogHandle, AppearanceTrigger } from "./dialog"
export { AppearanceController } from "./controller"
export { ThemeToggleButton } from "./theme-toggle-button"
export {
UiStateProvider,
type UiStateProviderProps,
@@ -3,27 +3,15 @@ import { describe, it } from "vitest"
import { expectCompleteCatalogs } from "../../../i18n/test-catalogs"
import { appearanceMessages, themeColorMessages } from "../messages"
import { appearanceCatalogLocales } from "./catalogs"
import { messages as de } from "./de"
import { messages as en } from "./en"
import { messages as es } from "./es"
import { messages as fr } from "./fr"
import { messages as ja } from "./ja"
import { messages as ko } from "./ko"
import { messages as enUS } from "./en-US"
import { messages as zhHans } from "./zh-Hans"
import { messages as zhHant } from "./zh-Hant"
describe("appearance locale catalogs", () => {
it("ship every appearance message in every built-in locale", () => {
expectCompleteCatalogs({
catalogs: {
de,
en,
es,
fr,
ja,
ko,
"en-US": enUS,
"zh-Hans": zhHans,
"zh-Hant": zhHant,
},
locales: appearanceCatalogLocales,
messageIds: [
@@ -1,60 +0,0 @@
import type { AppearanceMessageCatalog } from "./catalogs"
export const locale = "de"
export const languageTag = "de-DE"
export const messages = {
"blocks.appearance.active": "Aktiv",
"blocks.appearance.baseColor": "Basisfarbe",
"blocks.appearance.color.amber": "Bernstein",
"blocks.appearance.color.blue": "Blau",
"blocks.appearance.color.cyan": "Cyan",
"blocks.appearance.color.emerald": "Smaragd",
"blocks.appearance.color.fuchsia": "Fuchsia",
"blocks.appearance.color.green": "Grün",
"blocks.appearance.color.indigo": "Indigo",
"blocks.appearance.color.lime": "Limette",
"blocks.appearance.color.mauve": "Malve",
"blocks.appearance.color.mist": "Nebel",
"blocks.appearance.color.neutral": "Neutral",
"blocks.appearance.color.olive": "Oliv",
"blocks.appearance.color.orange": "Orange",
"blocks.appearance.color.pink": "Rosa",
"blocks.appearance.color.purple": "Lila",
"blocks.appearance.color.red": "Rot",
"blocks.appearance.color.rose": "Rose",
"blocks.appearance.color.sky": "Himmelblau",
"blocks.appearance.color.stone": "Stein",
"blocks.appearance.color.taupe": "Taupe",
"blocks.appearance.color.teal": "Blaugrün",
"blocks.appearance.color.violet": "Violett",
"blocks.appearance.color.yellow": "Gelb",
"blocks.appearance.color.zinc": "Zink",
"blocks.appearance.command.description":
"Design- und Layout-Einstellungen öffnen",
"blocks.appearance.compact.description":
"Eine kompakte Breite für den Hauptinhalt verwenden",
"blocks.appearance.compact.title": "Kompaktes Layout",
"blocks.appearance.description": "Design- und Layout-Einstellungen",
"blocks.appearance.mode.dark": "Dunkel",
"blocks.appearance.mode.dark.description":
"Die Oberfläche verwendet immer das dunkle Design",
"blocks.appearance.mode.light": "Hell",
"blocks.appearance.mode.light.description":
"Die Oberfläche verwendet immer das helle Design",
"blocks.appearance.mode.system": "System",
"blocks.appearance.mode.system.description":
"Das Design der Oberfläche folgt der Systemeinstellung",
"blocks.appearance.mode.title": "Darstellungsmodus",
"blocks.appearance.primaryColor": "Primärfarbe",
"blocks.appearance.theme.dark": "Dunkles Design",
"blocks.appearance.theme.dark.description":
"Dieses Design wird im dunklen Systemmodus verwendet",
"blocks.appearance.theme.light": "Helles Design",
"blocks.appearance.theme.light.description":
"Dieses Design wird im hellen Systemmodus verwendet",
"blocks.appearance.title": "Darstellung",
"blocks.appearance.toggleTheme": "Design wechseln",
"blocks.appearance.toggleTheme.description":
"Zwischen hellem und dunklem Design wechseln",
} as const satisfies AppearanceMessageCatalog
@@ -3,7 +3,7 @@ import type { MessageDescriptor } from "@workspace/i18n"
import { appearanceMessages, themeColorMessages } from "../messages.ts"
import type { AppearanceMessageCatalog } from "./catalogs"
export const locale = "en"
export const locale = "en-US"
export const languageTag = "en-US"
function createSourceCatalog(
@@ -1,60 +0,0 @@
import type { AppearanceMessageCatalog } from "./catalogs"
export const locale = "es"
export const languageTag = "es-ES"
export const messages = {
"blocks.appearance.active": "Activo",
"blocks.appearance.baseColor": "Color base",
"blocks.appearance.color.amber": "Ámbar",
"blocks.appearance.color.blue": "Azul",
"blocks.appearance.color.cyan": "Cian",
"blocks.appearance.color.emerald": "Esmeralda",
"blocks.appearance.color.fuchsia": "Fucsia",
"blocks.appearance.color.green": "Verde",
"blocks.appearance.color.indigo": "Índigo",
"blocks.appearance.color.lime": "Lima",
"blocks.appearance.color.mauve": "Malva",
"blocks.appearance.color.mist": "Niebla",
"blocks.appearance.color.neutral": "Neutro",
"blocks.appearance.color.olive": "Oliva",
"blocks.appearance.color.orange": "Naranja",
"blocks.appearance.color.pink": "Rosa",
"blocks.appearance.color.purple": "Morado",
"blocks.appearance.color.red": "Rojo",
"blocks.appearance.color.rose": "Rosa intenso",
"blocks.appearance.color.sky": "Celeste",
"blocks.appearance.color.stone": "Piedra",
"blocks.appearance.color.taupe": "Topo",
"blocks.appearance.color.teal": "Verde azulado",
"blocks.appearance.color.violet": "Violeta",
"blocks.appearance.color.yellow": "Amarillo",
"blocks.appearance.color.zinc": "Zinc",
"blocks.appearance.command.description":
"Abrir las preferencias de tema y diseño",
"blocks.appearance.compact.description":
"Usar un ancho compacto para el contenido principal",
"blocks.appearance.compact.title": "Diseño compacto",
"blocks.appearance.description": "Preferencias de tema y diseño",
"blocks.appearance.mode.dark": "Oscuro",
"blocks.appearance.mode.dark.description":
"La interfaz siempre usará el tema oscuro",
"blocks.appearance.mode.light": "Claro",
"blocks.appearance.mode.light.description":
"La interfaz siempre usará el tema claro",
"blocks.appearance.mode.system": "Sistema",
"blocks.appearance.mode.system.description":
"El tema de la interfaz seguirá la apariencia del sistema",
"blocks.appearance.mode.title": "Modo de tema",
"blocks.appearance.primaryColor": "Color principal",
"blocks.appearance.theme.dark": "Tema oscuro",
"blocks.appearance.theme.dark.description":
"Este tema se usa cuando el sistema está en modo oscuro",
"blocks.appearance.theme.light": "Tema claro",
"blocks.appearance.theme.light.description":
"Este tema se usa cuando el sistema está en modo claro",
"blocks.appearance.title": "Apariencia",
"blocks.appearance.toggleTheme": "Cambiar tema",
"blocks.appearance.toggleTheme.description":
"Cambiar entre los temas claro y oscuro",
} as const satisfies AppearanceMessageCatalog

Some files were not shown because too many files have changed in this diff Show More