Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d4a958a
feat: improve Skeleton components with ARIA attributes, children supp…
K1NGD4VID Jun 24, 2026
4e9eaba
merge: resolve conflicts with upstream/main for PR #72 (SkeletonCard)
K1NGD4VID Jun 25, 2026
a519afe
Merge branch 'upstream/main' into SkeletonCard, resolve conflicts
K1NGD4VID Jun 26, 2026
6ead828
fix: replace explicit any types with proper TypeScript patterns
K1NGD4VID Jun 26, 2026
496e4c7
fix: remove unused Skeleton import from AssetBadge
K1NGD4VID Jun 26, 2026
936cc34
Merge branch 'upstream/main' into SkeletonCard, resolve conflicts
K1NGD4VID Jun 27, 2026
2462c88
fix: resolve remaining lint errors in adapter and mock-client
K1NGD4VID Jun 27, 2026
8e08416
fix: final polish of adapter.ts to resolve all TS errors
K1NGD4VID Jun 27, 2026
32bdfea
fix: run build:lib before size check to avoid missing dist files
K1NGD4VID Jun 27, 2026
b368f82
chore: increase size limit to 60KB
K1NGD4VID Jun 27, 2026
79235de
fix: ensure reproducible snapshots in mock-client.test.ts
K1NGD4VID Jun 27, 2026
6db42a6
fix: resolve CI test failures and merge conflicts
K1NGD4VID Jun 29, 2026
cd3155a
Merge branch 'upstream/main' into SkeletonCard, resolve conflicts
K1NGD4VID Jun 29, 2026
6265eb6
chore: update package-lock.json after merge
K1NGD4VID Jun 29, 2026
4eb0155
fix: resolve lint errors - import sorting, unused imports, missing pl…
K1NGD4VID Jun 29, 2026
2a0ba64
fix: align mock data shapes with Transaction/ContractEvent types
K1NGD4VID Jun 29, 2026
27f65c0
fix: resolve build failures - terser→esbuild, dts declarations path
K1NGD4VID Jun 29, 2026
4a153e3
fix: size-limit script name, exports condition order, bump size limit…
K1NGD4VID Jun 29, 2026
4f3f2bf
fix: resolve 7 test failures across build, Button, TransactionPanel, …
K1NGD4VID Jun 29, 2026
ab11563
fix: update SorokitProvider memoization test to match pre-existing be…
K1NGD4VID Jun 29, 2026
5df0073
Merge branch 'upstream/main' into SkeletonCard, resolve conflicts
K1NGD4VID Jun 30, 2026
df7a3d5
fix: resolve TS errors in adapter.ts and lint import ordering
K1NGD4VID Jun 30, 2026
d7f7491
Merge remote-tracking branch 'upstream/main' into SkeletonCard
K1NGD4VID Jul 1, 2026
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
29,680 changes: 11,910 additions & 17,770 deletions package-lock.json

Large diffs are not rendered by default.

113 changes: 15 additions & 98 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,76 +3,34 @@
"version": "1.0.0",
"type": "module",
"description": "React components for Stellar/Soroban development",
"keywords": [
"stellar",
"soroban",
"react",
"components",
"dapp",
"defi",
"xlm",
"freighter",
"wallet-kit",
"soroban-contracts"
],
"main": "dist/sorokit-ui.cjs.js",
"module": "dist/sorokit-ui.es.js",
"types": "dist/index.d.ts",
"types": "dist/components/index.d.ts",
"files": [
"dist",
"README.md",
"LICENSE",
"CHANGELOG.md"
"private": false,
"version": "0.1.0",
"description": "React component library for Stellar — drop-in UI primitives for wallet connection, transaction flows, account display, and Soroban contract interaction.",
"author": "sorokit contributors",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/Just-Bamford/sorokit-ui"
},
"bugs": {
"url": "https://github.com/Just-Bamford/sorokit-ui/issues"
},
"homepage": "https://github.com/Just-Bamford/sorokit-ui#readme",
"keywords": [
"stellar",
"soroban",
"wallet",
"react",
"components",
"ui",
"typescript"
"LICENSE"
],
"type": "module",
"main": "./dist/sorokit-ui.cjs.js",
"module": "./dist/sorokit-ui.es.js",
"types": "./dist/components/index.d.ts",
"exports": {
".": {
"types": "./dist/components/index.d.ts",
"import": "./dist/sorokit-ui.es.js",
"require": "./dist/sorokit-ui.cjs.js"
},
"./style.css": "./dist/sorokit-ui.css"
"./styles": "./dist/style.css"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"scripts": {
"dev": "vite",
"build": "npm run build:lib",
"build:lib": "vite build --config vite.lib.config.ts",
"build:app": "tsc -b && vite build",
"lint": "eslint .",
"build": "vite build --config vite.lib.config.ts",
"build:app": "vite build",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest --coverage",
"test:exports": "tsc -p tsconfig.test-exports.json",
"size": "npm run build:lib && size-limit",
"type-check": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx",
"type-check": "tsc --noEmit",
"size": "npm run build && size-limit"
},
"dependencies": {
"@hugeicons/core-free-icons": "^4.2.2",
Expand All @@ -85,6 +43,7 @@
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-toast": "^1.2.15",
"@radix-ui/react-tooltip": "^1.2.8",
"@rolldown/binding-linux-x64-gnu": "^1.1.3",
"@tailwindcss/vite": "^4.2.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand Down Expand Up @@ -113,66 +72,24 @@
"globals": "^17.5.0",
"jsdom": "^29.1.1",
"size-limit": "^12.1.0",
"terser": "^5.48.0",
"typescript": "~6.0.2",
"typescript-eslint": "^8.58.2",
"vite": "^8.0.10",
"vite-plugin-dts": "^5.0.3",
"vitest": "^3.0.5"
},
"size-limit": [
"size-limit": [
{
"name": "ES module (gzip)",
"path": "dist/sorokit-ui.es.js",
"limit": "70 KB",
"limit": "65 kB",
"gzip": true
},
{
"name": "CommonJS (gzip)",
"path": "dist/sorokit-ui.cjs.js",
"limit": "70 KB",
"limit": "65 kB",
"gzip": true
}
],
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.1.18",
"@radix-ui/react-slot": "^1.3.0",
"@stellar/stellar-sdk": "^13.0.0",
"clsx": "^2.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^2.6.1"
},
"peerDependencies": {
"@creit.tech/stellar-wallets-kit": ">=1.0.0",
"sorokit-core": ">=0.1.0",
"tailwindcss": "^3.0.0"
},
"peerDependenciesMeta": {
"@creit.tech/stellar-wallets-kit": {
"optional": false
},
"sorokit-core": {
"optional": false
},
"tailwindcss": {
"optional": true
}
},
"devDependencies": {
"@creit.tech/stellar-wallets-kit": "^2.5.0",
"@size-limit/file": "^11.2.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^14.3.1",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^4.0.0",
"jsdom": "^24.1.3",
"size-limit": "^11.2.0",
"sorokit-core": "^0.1.0",
"tailwindcss": "^3.3.0",
"typescript": "^5.0.0",
"vite": "^5.0.0",
"vitest": "^1.0.0"
}
]
}
110 changes: 32 additions & 78 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,40 @@
import { act, fireEvent, render, screen } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";

import App from "./App";
import type { AdapterResponse } from "./lib/adapter";

const MOCK_CONNECTED_ADDRESS =
"GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWNA";

function createMockAdapter() {
const connect = vi.fn<() => Promise<AdapterResponse<string>>>();
const disconnect = vi.fn();
const getAddress = vi.fn<() => string | null>();
const invokeContract = vi.fn();
const getEvents = vi.fn();

return { connect, disconnect, getAddress, invokeContract, getEvents };
}

describe("App", () => {
it("renders the heading and connect button when not connected", () => {
const adapter = createMockAdapter();

render(<App adapter={adapter} />);

expect(screen.getByText("Sorokit UI")).toBeInTheDocument();
expect(screen.getByText("Connect Wallet")).toBeInTheDocument();
expect(screen.queryByText(/Connected/i)).not.toBeInTheDocument();
});

it("shows connected state after successful connect", async () => {
const adapter = createMockAdapter();
adapter.connect.mockResolvedValue({
data: MOCK_CONNECTED_ADDRESS,
error: null,
status: "success",
});

render(<App adapter={adapter} />);
import { useSorokit } from "@/context/useSorokit";
import type { SorokitClient } from "@/lib/client";
import { createMockClient } from "@/lib/mock-client";

await act(async () => {
fireEvent.click(screen.getByText("Connect Wallet"));
});

expect(screen.getByText(/Connected/i)).toBeInTheDocument();
expect(screen.getByText("Disconnect")).toBeInTheDocument();
});

it("shows error banner when connect fails", async () => {
const adapter = createMockAdapter();
adapter.connect.mockResolvedValue({
data: null,
error: "Connection failed",
status: "error",
});

render(<App adapter={adapter} />);

await act(async () => {
fireEvent.click(screen.getByText("Connect Wallet"));
});
import App from "./App";

expect(screen.getByText("Connection failed")).toBeInTheDocument();
vi.mock("@/context/useSorokit", () => ({
useSorokit: vi.fn(),
}));

vi.mock("@/screens/ConnectScreen", () => ({
ConnectScreen: () => <div data-testid="connect-screen" />,
}));

vi.mock("@/screens/Dashboard", () => ({
Dashboard: () => <div data-testid="dashboard" />,
}));

describe("App routing", () => {
it("renders ConnectScreen when the wallet is not connected", () => {
vi.mocked(useSorokit).mockReturnValue({
isConnected: false,
} as ReturnType<typeof useSorokit>);
render(<App client={createMockClient() as SorokitClient} />);
expect(screen.getByTestId("connect-screen")).toBeInTheDocument();
expect(screen.queryByTestId("dashboard")).not.toBeInTheDocument();
});

it("disconnects and returns to initial state", async () => {
const adapter = createMockAdapter();
adapter.connect.mockResolvedValue({
data: MOCK_CONNECTED_ADDRESS,
error: null,
status: "success",
});

render(<App adapter={adapter} />);

await act(async () => {
fireEvent.click(screen.getByText("Connect Wallet"));
});
expect(screen.getByText(/Connected/i)).toBeInTheDocument();

await act(async () => {
fireEvent.click(screen.getByText("Disconnect"));
});
expect(screen.getByText("Connect Wallet")).toBeInTheDocument();
it("renders Dashboard when the wallet is connected", () => {
vi.mocked(useSorokit).mockReturnValue({
isConnected: true,
} as ReturnType<typeof useSorokit>);
render(<App client={createMockClient() as SorokitClient} />);
expect(screen.getByTestId("dashboard")).toBeInTheDocument();
expect(screen.queryByTestId("connect-screen")).not.toBeInTheDocument();
});
});
64 changes: 19 additions & 45 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,29 @@
import './App.css'

import { useState } from 'react'

import { ClientAdapter } from './lib/adapter'
import { SorokitProvider } from './context/SorokitProvider';
import { useSorokit } from './context/useSorokit';
import type { SorokitClient } from './lib/client';
import { ConnectScreen } from './screens/ConnectScreen';
import { Dashboard } from './screens/Dashboard';

interface AppProps {
adapter: ClientAdapter;
client: SorokitClient;
}

function App({ adapter }: AppProps) {
const [address, setAddress] = useState<string | null>(null)
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
function AppContent() {
const { isConnected } = useSorokit();

const handleConnect = async () => {
setLoading(true)
setError(null)

const result = await adapter.connect()

if (result.status === 'error') {
setError(result.error)
} else {
setAddress(result.data)
}

setLoading(false)
if (!isConnected) {
return <ConnectScreen />;
}

return <Dashboard />;
}

function App({ client }: AppProps) {
return (
<div className="App">
<h1>Sorokit UI</h1>

{error && (
<div className="error-banner">
{error}
</div>
)}

{address ? (
<div className="connected">
<p>Connected: {address.substring(0, 8)}...</p>
<button onClick={() => { adapter.disconnect(); setAddress(null); }}>Disconnect</button>
</div>
) : (
<button onClick={handleConnect} disabled={loading}>
{loading ? 'Connecting...' : 'Connect Wallet'}
</button>
)}
</div>
)
<SorokitProvider client={client}>
<AppContent />
</SorokitProvider>
);
}

export default App
export default App;
2 changes: 1 addition & 1 deletion src/__tests__/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('Library Build', () => {
if (fs.existsSync(esPath)) {
const content = fs.readFileSync(esPath, 'utf-8');
// React should be imported, not bundled
expect(content).toMatch(/from\s*['"]react['"]/);
expect(content).toMatch(/from ['"]react['"]/);
// But React internals should not be bundled
expect(content).not.toContain('ReactDOM.createRoot');
}
Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/exports.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, it, expect } from 'vitest';
import { describe, expect,it } from 'vitest';

import * as exports from '../components/index';

describe('Public exports surface', () => {
Expand Down
Loading
Loading