Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/demo-dapp-with-react-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-router-dom": "^7.15.1",
"solid-js": "1.9.7",
"react-simple-code-editor": "^0.14.1",
"tailwind-merge": "3.6.0",
"tailwindcss": "4.3.0",
Expand All @@ -49,6 +50,7 @@
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"eruda": "^3.4.3",
"vite-plugin-solid": "^2.11.8",
"typescript": "^5.9.2",
"vite": "^8.0.14"
},
Expand Down
49 changes: 43 additions & 6 deletions apps/demo-dapp-with-react-ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { TonConnectUIProvider } from '@tonconnect/ui-react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useMemo } from 'react';

import { AppRouter, ThemeProvider } from './core/components/index';
import { tonConnectManifestUrl, tonConnectUiPreferences } from './core/configs/app-kit';
import { getProviderAnalyticsSettingsFromLocation } from './features/dev-settings/utils/settings-url';
import { createPreviewConnector } from './features/widget-builder/utils/preview-mocks';
import {
getProviderAnalyticsSettingsFromLocation,
parseSettingsFromSearchParams,
toTonConnectOptions
} from './features/dev-settings/utils/settings-url';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -17,14 +23,45 @@ const queryClient = new QueryClient({
});

export const App = () => {
const isWidgetPreview =
typeof window !== 'undefined' && window.location.pathname.endsWith('/widget-preview');
const widgetPreviewOptions =
typeof window !== 'undefined' && isWidgetPreview
? toTonConnectOptions(
parseSettingsFromSearchParams(new URLSearchParams(window.location.search))
)
: undefined;
const previewConnector = useMemo(
() => (isWidgetPreview ? createPreviewConnector(tonConnectManifestUrl) : null),
[isWidgetPreview]
);

const tonConnectProviderProps = isWidgetPreview
? {
connector: previewConnector!,
restoreConnection: false as const,
uiPreferences: widgetPreviewOptions?.uiPreferences ?? tonConnectUiPreferences,
language: widgetPreviewOptions?.language,
actionsConfiguration: {
...widgetPreviewOptions?.actionsConfiguration,
returnStrategy: 'none' as const,
skipRedirectToWallet: 'always' as const
},
walletsRequiredFeatures: widgetPreviewOptions?.walletsRequiredFeatures,
walletsPreferredFeatures: widgetPreviewOptions?.walletsPreferredFeatures,
enableAndroidBackHandler: widgetPreviewOptions?.enableAndroidBackHandler,
analytics: getProviderAnalyticsSettingsFromLocation()
}
: {
manifestUrl: tonConnectManifestUrl,
uiPreferences: tonConnectUiPreferences,
analytics: getProviderAnalyticsSettingsFromLocation()
};

return (
<ThemeProvider defaultTheme="dark" storageKey="demo-dapp-theme">
<QueryClientProvider client={queryClient}>
<TonConnectUIProvider
manifestUrl={tonConnectManifestUrl}
uiPreferences={tonConnectUiPreferences}
analytics={getProviderAnalyticsSettingsFromLocation()}
>
<TonConnectUIProvider {...tonConnectProviderProps}>
<AppRouter />
</TonConnectUIProvider>
</QueryClientProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import {
TonProofPage,
SignMessagePage,
TransferUsdtPage,
TxFormPage
TxFormPage,
WidgetBuilderPage,
WidgetModalPreviewPage,
WidgetSandboxPage
} from '../../../../pages/index';

export const AppRouter = () => {
Expand All @@ -41,6 +44,9 @@ export const AppRouter = () => {
<Route path="/find-tx" element={<FindTxPage />} />
<Route path="/merkle" element={<MerklePage />} />
<Route path="/create-jetton" element={<CreateJettonPage />} />
<Route path="/widget-builder" element={<WidgetBuilderPage />} />
<Route path="/widget-preview" element={<WidgetModalPreviewPage />} />
<Route path="/widget-sandbox" element={<WidgetSandboxPage />} />
<Route path="/settings" element={<SettingsPage />} />
<Route path="/iframe" element={<IframePage />} />
<Route path="/iframe/iframe" element={<IframeIframePage />} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TonConnectButton, useTonWallet } from '@tonconnect/ui-react';
import { BookOpen, ExternalLink, Settings } from 'lucide-react';
import { BookOpen, ChevronLeft, ChevronRight, ExternalLink, Settings } from 'lucide-react';
import { Link, NavLink } from 'react-router-dom';
import type { ComponentType, FC, ReactNode } from 'react';

Expand All @@ -21,11 +21,13 @@ import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarTooltip,
SidebarProvider,
SidebarSeparator,
SidebarTrigger,
useSidebar
} from '../../shared/sidebar/index';
import { Button } from '../../ui/button';
import { GitHubIcon } from '../../ui/icons';
import { AppLogo } from '../app-logo';
import { ThemeSwitcher } from '../theme-switcher/index';
Expand All @@ -40,7 +42,10 @@ interface LayoutProps {
sourceHref?: string;
subtitle?: ReactNode;
wide?: boolean;
hideHeader?: boolean;
hideFooter?: boolean;
contentClassName?: string;
contentInnerClassName?: string;
/** Stable test id applied to the `<main>` content element. */
'data-testid'?: string;
}
Expand All @@ -59,9 +64,10 @@ const EXTERNAL_LINKS: readonly {
];

const AppSidebar: FC = () => {
const { setOpenMobile, isMobile } = useSidebar();
const { setOpenMobile, isMobile, open, toggleSidebar } = useSidebar();
const withSearch = usePreserveSearch();
const wallet = useTonWallet();
const collapsed = !isMobile && !open;

const closeOnMobile = () => {
if (isMobile) setOpenMobile(false);
Expand All @@ -73,27 +79,50 @@ const AppSidebar: FC = () => {
<Link
to={withSearch('/tx-form')}
onClick={closeOnMobile}
className="flex items-center gap-2 px-2 py-1.5"
className={cn(
'flex h-10 items-center gap-2 px-2 py-1.5 transition-[width,padding] duration-200 ease-out',
collapsed && 'mx-auto !w-12 justify-center !gap-0 !px-0'
)}
title="TON Connect Demo"
>
<AppLogo className="size-7" />
<span className="text-base font-bold text-foreground">TON Connect Demo</span>
<span
className={cn(
'min-w-0 overflow-hidden whitespace-nowrap text-base font-bold text-foreground transition-[max-width,opacity] duration-200 ease-out',
collapsed ? 'max-w-0 opacity-0' : 'max-w-56 opacity-100'
)}
>
TON Connect Demo
</span>
</Link>
</SidebarHeader>

<SidebarContent>
{wallet && (
<>
<SidebarGroup>
<div
className={cn(
'overflow-hidden transition-[max-height,opacity] duration-200 ease-out',
collapsed ? 'max-h-0 opacity-0' : 'max-h-40 opacity-100'
)}
>
<SidebarGroup className={cn(collapsed && 'pointer-events-none')}>
<BalanceCard />
</SidebarGroup>

<SidebarSeparator />
</>
</div>
)}

{DEMO_NAV_GROUPS.map(group => (
<SidebarGroup key={group.label}>
<SidebarGroupLabel>{group.label}</SidebarGroupLabel>
<SidebarGroupLabel
className={cn(
'transition-opacity duration-200 ease-out',
collapsed && 'opacity-0'
)}
>
{group.label}
</SidebarGroupLabel>
<SidebarMenu>
{group.links.map(link => (
<SidebarNavItem
Expand All @@ -113,27 +142,87 @@ const AppSidebar: FC = () => {
<SidebarFooter>
<SidebarMenu>
{EXTERNAL_LINKS.map(({ href, label, icon: Icon }) => (
<SidebarMenuItem key={href}>
<SidebarMenuItem key={href} className="group">
<SidebarMenuButton asChild>
<a href={href} target="_blank" rel="noreferrer">
<a
href={href}
target="_blank"
rel="noreferrer"
aria-label={label}
title={label}
className={cn(
collapsed && 'mx-auto !w-12 justify-center !gap-0 !px-0'
)}
>
<Icon />
<span>{label}</span>
<ExternalLink className="ml-auto text-tertiary-foreground" />
<span
className={cn(
'min-w-0 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-200 ease-out',
collapsed ? 'max-w-0 opacity-0' : 'max-w-32 opacity-100'
)}
>
{label}
</span>
{!collapsed ? (
<ExternalLink className="ml-auto text-tertiary-foreground transition-opacity duration-200 ease-out" />
) : null}
</a>
</SidebarMenuButton>
{collapsed ? <SidebarTooltip>{label}</SidebarTooltip> : null}
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuItem className="group">
<NavLink to={withSearch('/settings')} end onClick={closeOnMobile}>
{({ isActive }) => (
<SidebarMenuButton isActive={isActive}>
<SidebarMenuButton
isActive={isActive}
aria-label="Ton Connect settings"
title="Ton Connect settings"
className={cn(
collapsed && 'mx-auto !w-12 justify-center !gap-0 !px-0'
)}
>
<Settings />
<span>Ton Connect settings</span>
<span
className={cn(
'min-w-0 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-200 ease-out',
collapsed ? 'max-w-0 opacity-0' : 'max-w-48 opacity-100'
)}
>
Ton Connect settings
</span>
</SidebarMenuButton>
)}
</NavLink>
{collapsed ? <SidebarTooltip>Ton Connect settings</SidebarTooltip> : null}
</SidebarMenuItem>
</SidebarMenu>
{!isMobile ? (
<div className="flex flex-col gap-1">
<SidebarSeparator />
<div className="group relative">
<Button
type="button"
variant="ghost"
size="icon"
onClick={toggleSidebar}
className={cn(
'h-7 justify-center transition-[width] duration-200 ease-out',
collapsed ? 'mx-auto !h-7 !w-12 !gap-0' : 'w-full'
)}
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
{collapsed ? (
<ChevronRight className="size-4" />
) : (
<ChevronLeft className="size-4" />
)}
</Button>
{collapsed ? <SidebarTooltip>Expand sidebar</SidebarTooltip> : null}
</div>
</div>
) : null}
</SidebarFooter>
</Sidebar>
);
Expand All @@ -146,51 +235,65 @@ export const Layout: FC<LayoutProps> = ({
sourceHref,
subtitle,
wide = false,
hideHeader = false,
hideFooter = false,
contentClassName,
contentInnerClassName,
'data-testid': testId
}) => {
return (
<SidebarProvider>
<AppSidebar />

<SidebarInset>
<header
data-app-chrome-header
className="sticky top-0 z-10 flex h-14 items-center gap-3 border-b border-tertiary bg-background/80 px-4 backdrop-blur"
>
<AppLogo className="size-8 md:hidden" />
{title != null && (
<div className="hidden min-w-0 md:flex md:items-center">
<PageHeading title={title} docHref={docHref} sourceHref={sourceHref} />
</div>
)}
{!hideHeader ? (
<header
data-app-chrome-header
className="sticky top-0 z-10 flex h-14 items-center gap-3 border-b border-tertiary bg-background/80 px-4 backdrop-blur"
>
<AppLogo className="size-8 md:hidden" />
{title != null && (
<div className="hidden min-w-0 md:flex md:items-center">
<PageHeading
title={title}
docHref={docHref}
sourceHref={sourceHref}
/>
</div>
)}

<div className="ml-auto">
<TonConnectButton />
</div>
<ThemeSwitcher />
<div className="md:hidden">
<SidebarTrigger />
</div>
</header>
<div className="ml-auto">
<TonConnectButton />
</div>
<ThemeSwitcher />
<div className="md:hidden">
<SidebarTrigger />
</div>
</header>
) : null}

<main
className={cn('mx-auto w-full flex-1 p-4', wide ? 'max-w-7xl' : 'max-w-4xl')}
className={cn(
'mx-auto flex min-h-0 w-full flex-1 flex-col p-4',
wide ? 'max-w-7xl' : 'max-w-4xl',
contentClassName
)}
data-testid={testId}
>
<div
className={cn(
'mx-auto flex w-full flex-col gap-6 py-2 md:py-6',
wide ? 'max-w-none' : 'max-w-2xl'
wide ? 'max-w-none' : 'max-w-2xl',
contentInnerClassName
)}
>
{title != null && (
<div className="flex w-full items-center justify-start md:hidden">
<div className="flex w-full items-baseline justify-start md:hidden">
<PageHeading
title={title}
docHref={docHref}
sourceHref={sourceHref}
className="flex w-full min-w-0 items-center gap-1.5"
className="flex w-full min-w-0 items-baseline gap-1.5"
/>
</div>
)}
Expand Down
Loading
Loading