@livo-build/kit 0.1.0 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +44 -4
  2. package/dist/account/Avatar.d.ts +9 -0
  3. package/dist/account/Avatar.js +15 -0
  4. package/dist/account/Identity.d.ts +9 -0
  5. package/dist/account/Identity.js +11 -0
  6. package/dist/account/account.css +3 -0
  7. package/dist/account/gates.d.ts +18 -0
  8. package/dist/account/gates.js +21 -0
  9. package/dist/account/index.d.ts +3 -0
  10. package/dist/account/index.js +3 -0
  11. package/dist/auth/SignInWithEthereum.d.ts +8 -0
  12. package/dist/auth/SignInWithEthereum.js +18 -0
  13. package/dist/auth/index.d.ts +2 -0
  14. package/dist/auth/index.js +2 -0
  15. package/dist/auth/useSiwe.d.ts +18 -0
  16. package/dist/auth/useSiwe.js +72 -0
  17. package/dist/contracts/index.d.ts +1 -0
  18. package/dist/contracts/index.js +1 -0
  19. package/dist/contracts/useContractEvent.d.ts +10 -0
  20. package/dist/contracts/useContractEvent.js +13 -0
  21. package/dist/data/Async.d.ts +17 -0
  22. package/dist/data/Async.js +22 -0
  23. package/dist/data/DataTable.d.ts +26 -0
  24. package/dist/data/DataTable.js +15 -0
  25. package/dist/data/data.css +22 -0
  26. package/dist/data/index.d.ts +5 -0
  27. package/dist/data/index.js +5 -0
  28. package/dist/data/useCollection.d.ts +42 -0
  29. package/dist/data/useCollection.js +67 -0
  30. package/dist/data/useCollection.test.d.ts +1 -0
  31. package/dist/data/useCollection.test.js +27 -0
  32. package/dist/data/useEventSource.d.ts +15 -0
  33. package/dist/data/useEventSource.js +37 -0
  34. package/dist/data/useWebSocket.d.ts +17 -0
  35. package/dist/data/useWebSocket.js +61 -0
  36. package/dist/dataviz/Stat.d.ts +18 -0
  37. package/dist/dataviz/Stat.js +14 -0
  38. package/dist/dataviz/dataviz.css +15 -0
  39. package/dist/dataviz/index.d.ts +2 -0
  40. package/dist/dataviz/index.js +2 -0
  41. package/dist/dataviz/sparkline.d.ts +15 -0
  42. package/dist/dataviz/sparkline.js +31 -0
  43. package/dist/dataviz/sparkline.test.d.ts +1 -0
  44. package/dist/dataviz/sparkline.test.js +14 -0
  45. package/dist/format.d.ts +3 -0
  46. package/dist/format.js +9 -0
  47. package/dist/hooks/index.d.ts +20 -0
  48. package/dist/hooks/index.js +97 -0
  49. package/dist/hyperliquid/PriceTicker.d.ts +11 -0
  50. package/dist/hyperliquid/PriceTicker.js +24 -0
  51. package/dist/hyperliquid/client.d.ts +9 -0
  52. package/dist/hyperliquid/client.js +17 -0
  53. package/dist/hyperliquid/hl.css +10 -0
  54. package/dist/hyperliquid/index.d.ts +3 -0
  55. package/dist/hyperliquid/index.js +3 -0
  56. package/dist/hyperliquid/useHyperliquid.d.ts +76 -0
  57. package/dist/hyperliquid/useHyperliquid.js +52 -0
  58. package/dist/index.d.ts +10 -0
  59. package/dist/index.js +26 -5
  60. package/dist/nft/MintButton.d.ts +20 -0
  61. package/dist/nft/MintButton.js +9 -0
  62. package/dist/nft/NFTCard.d.ts +15 -0
  63. package/dist/nft/NFTCard.js +14 -0
  64. package/dist/nft/NFTMedia.d.ts +11 -0
  65. package/dist/nft/NFTMedia.js +16 -0
  66. package/dist/nft/index.d.ts +5 -0
  67. package/dist/nft/index.js +5 -0
  68. package/dist/nft/nft.css +16 -0
  69. package/dist/nft/resolveUri.d.ts +1 -0
  70. package/dist/nft/resolveUri.js +15 -0
  71. package/dist/nft/resolveUri.test.d.ts +1 -0
  72. package/dist/nft/resolveUri.test.js +13 -0
  73. package/dist/nft/useNFT.d.ts +34 -0
  74. package/dist/nft/useNFT.js +65 -0
  75. package/dist/provider/LivoApp.d.ts +13 -0
  76. package/dist/provider/LivoApp.js +16 -0
  77. package/dist/provider/index.d.ts +1 -0
  78. package/dist/provider/index.js +1 -0
  79. package/dist/telegram/LinkTelegramButton.d.ts +12 -0
  80. package/dist/telegram/LinkTelegramButton.js +19 -0
  81. package/dist/telegram/index.d.ts +5 -0
  82. package/dist/telegram/index.js +5 -0
  83. package/dist/telegram/miniapp.d.ts +15 -0
  84. package/dist/telegram/miniapp.js +63 -0
  85. package/dist/telegram/telegram.css +6 -0
  86. package/dist/telegram/useTelegramLink.d.ts +20 -0
  87. package/dist/telegram/useTelegramLink.js +114 -0
  88. package/dist/telegram/useTelegramMiniApp.d.ts +17 -0
  89. package/dist/telegram/useTelegramMiniApp.js +24 -0
  90. package/dist/telegram/useTelegramTheme.d.ts +2 -0
  91. package/dist/telegram/useTelegramTheme.js +20 -0
  92. package/dist/theme/ThemeProvider.d.ts +17 -0
  93. package/dist/theme/ThemeProvider.js +23 -0
  94. package/dist/theme/index.d.ts +1 -0
  95. package/dist/theme/index.js +1 -0
  96. package/dist/token/ApproveButton.d.ts +12 -0
  97. package/dist/token/ApproveButton.js +9 -0
  98. package/dist/token/index.d.ts +5 -0
  99. package/dist/token/index.js +5 -0
  100. package/dist/token/useAllowance.d.ts +13 -0
  101. package/dist/token/useAllowance.js +22 -0
  102. package/dist/token/useApprove.d.ts +10 -0
  103. package/dist/token/useApprove.js +14 -0
  104. package/dist/token/useToken.d.ts +7 -0
  105. package/dist/token/useToken.js +15 -0
  106. package/dist/token/useTokenGate.d.ts +17 -0
  107. package/dist/token/useTokenGate.js +24 -0
  108. package/dist/tx/TxProgress.d.ts +13 -0
  109. package/dist/tx/TxProgress.js +22 -0
  110. package/dist/tx/index.d.ts +1 -0
  111. package/dist/tx/index.js +1 -0
  112. package/dist/tx/tx.css +9 -0
  113. package/dist/ui/index.d.ts +1 -1
  114. package/dist/ui/index.js +1 -1
  115. package/dist/ui/ui.css +18 -0
  116. package/dist/ui/ui.d.ts +19 -1
  117. package/dist/ui/ui.js +31 -0
  118. package/dist/user/UserMenu.d.ts +10 -0
  119. package/dist/user/UserMenu.js +18 -0
  120. package/dist/user/index.d.ts +2 -0
  121. package/dist/user/index.js +2 -0
  122. package/dist/user/useUser.d.ts +32 -0
  123. package/dist/user/useUser.js +36 -0
  124. package/dist/user/user.css +15 -0
  125. package/dist/web3/AddressInput.d.ts +10 -0
  126. package/dist/web3/AddressInput.js +20 -0
  127. package/dist/web3/ChainSwitcher.d.ts +5 -0
  128. package/dist/web3/ChainSwitcher.js +11 -0
  129. package/dist/web3/TokenAmount.d.ts +16 -0
  130. package/dist/web3/TokenAmount.js +17 -0
  131. package/dist/web3/index.d.ts +3 -0
  132. package/dist/web3/index.js +3 -0
  133. package/dist/web3/web3.css +12 -1
  134. package/package.json +1 -1
@@ -0,0 +1,24 @@
1
+ import { useEffect } from "react";
2
+ function getWebApp() {
3
+ if (typeof window === "undefined")
4
+ return undefined;
5
+ return window.Telegram?.WebApp;
6
+ }
7
+ // Read the Telegram Mini App context (window.Telegram.WebApp), if the page is open
8
+ // inside Telegram. Requires the Telegram WebApp SDK script
9
+ // (https://telegram.org/js/telegram-web-app.js) in your <head>.
10
+ export function useTelegramMiniApp() {
11
+ const wa = getWebApp();
12
+ useEffect(() => {
13
+ wa?.ready();
14
+ }, [wa]);
15
+ const u = wa?.initDataUnsafe?.user;
16
+ return {
17
+ available: Boolean(wa?.initData),
18
+ initData: wa?.initData ?? "",
19
+ user: u ? { id: u.id, username: u.username, firstName: u.first_name, photoUrl: u.photo_url } : undefined,
20
+ colorScheme: wa?.colorScheme,
21
+ ready: () => wa?.ready(),
22
+ expand: () => wa?.expand(),
23
+ };
24
+ }
@@ -0,0 +1,2 @@
1
+ import { type KitTheme } from "../theme";
2
+ export declare function useTelegramTheme(): KitTheme | undefined;
@@ -0,0 +1,20 @@
1
+ import {} from "../theme";
2
+ // A KitTheme derived from the host Telegram Mini App's theme, or undefined outside
3
+ // Telegram. Pass it to <LivoApp theme={…}> / <KitThemeProvider> so the kit matches
4
+ // the user's Telegram colours automatically.
5
+ export function useTelegramTheme() {
6
+ if (typeof window === "undefined")
7
+ return undefined;
8
+ const wa = window.Telegram?.WebApp;
9
+ const tp = wa?.themeParams;
10
+ if (!tp)
11
+ return undefined;
12
+ return {
13
+ accent: tp.button_color,
14
+ accentFg: tp.button_text_color,
15
+ bg: tp.bg_color,
16
+ fg: tp.text_color,
17
+ muted: tp.hint_color,
18
+ border: tp.secondary_bg_color,
19
+ };
20
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactNode, type CSSProperties } from "react";
2
+ export interface KitTheme {
3
+ accent?: string;
4
+ accentFg?: string;
5
+ radius?: number | string;
6
+ bg?: string;
7
+ fg?: string;
8
+ muted?: string;
9
+ border?: string;
10
+ }
11
+ export interface KitThemeProviderProps {
12
+ theme: KitTheme;
13
+ children: ReactNode;
14
+ className?: string;
15
+ style?: CSSProperties;
16
+ }
17
+ export declare function KitThemeProvider({ theme, children, className, style }: KitThemeProviderProps): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import {} from "react";
3
+ const VARS = {
4
+ accent: ["--wm-accent", "--kit-accent", "--kit-t-accent"],
5
+ accentFg: ["--wm-accent-fg", "--kit-accent-fg"],
6
+ radius: ["--wm-radius", "--kit-radius"],
7
+ bg: ["--wm-bg", "--kit-bg", "--kit-t-bg"],
8
+ fg: ["--wm-fg", "--kit-fg", "--kit-t-fg"],
9
+ muted: ["--wm-muted", "--kit-muted", "--kit-t-muted"],
10
+ border: ["--wm-border", "--kit-border", "--kit-t-border"],
11
+ };
12
+ export function KitThemeProvider({ theme, children, className, style }) {
13
+ const vars = {};
14
+ Object.keys(theme).forEach((k) => {
15
+ const v = theme[k];
16
+ if (v == null)
17
+ return;
18
+ const value = typeof v === "number" ? `${v}px` : String(v);
19
+ for (const cssVar of VARS[k])
20
+ vars[cssVar] = value;
21
+ });
22
+ return (_jsx("div", { className: className, style: { ...vars, ...style }, children: children }));
23
+ }
@@ -0,0 +1 @@
1
+ export { KitThemeProvider, type KitTheme, type KitThemeProviderProps } from "./ThemeProvider";
@@ -0,0 +1 @@
1
+ export { KitThemeProvider } from "./ThemeProvider";
@@ -0,0 +1,12 @@
1
+ import { type ReactNode } from "react";
2
+ export interface ApproveButtonProps {
3
+ token: `0x${string}`;
4
+ spender: `0x${string}`;
5
+ /** Amount to approve (base units). Omit for unlimited. */
6
+ amount?: bigint;
7
+ children?: ReactNode;
8
+ className?: string;
9
+ toast?: boolean;
10
+ onSuccess?: (hash: `0x${string}`) => void;
11
+ }
12
+ export declare function ApproveButton({ token, spender, amount, children, className, toast, onSuccess }: ApproveButtonProps): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import {} from "react";
3
+ import { erc20Abi, maxUint256 } from "viem";
4
+ import { TxButton } from "../tx";
5
+ // A TxButton wired for ERC-20 approve — pending/confirming states + toast + explorer
6
+ // link, same as any other write.
7
+ export function ApproveButton({ token, spender, amount, children, className, toast, onSuccess }) {
8
+ return (_jsx(TxButton, { address: token, abi: erc20Abi, functionName: "approve", args: [spender, amount ?? maxUint256], className: className, toast: toast, onSuccess: onSuccess, children: children ?? "Approve" }));
9
+ }
@@ -0,0 +1,5 @@
1
+ export { useToken, type TokenInfo } from "./useToken";
2
+ export { useAllowance, type AllowanceResult, type UseAllowanceParams } from "./useAllowance";
3
+ export { useApprove, type UseApproveParams } from "./useApprove";
4
+ export { ApproveButton, type ApproveButtonProps } from "./ApproveButton";
5
+ export { useTokenGate, type UseTokenGateParams, type TokenGateResult } from "./useTokenGate";
@@ -0,0 +1,5 @@
1
+ export { useToken } from "./useToken";
2
+ export { useAllowance } from "./useAllowance";
3
+ export { useApprove } from "./useApprove";
4
+ export { ApproveButton } from "./ApproveButton";
5
+ export { useTokenGate } from "./useTokenGate";
@@ -0,0 +1,13 @@
1
+ export interface AllowanceResult {
2
+ data: bigint | undefined;
3
+ /** Whether the allowance covers `amount` (false until loaded, or if `amount` omitted). */
4
+ enough: (amount: bigint) => boolean;
5
+ isLoading: boolean;
6
+ refetch: () => void;
7
+ }
8
+ export interface UseAllowanceParams {
9
+ token?: `0x${string}`;
10
+ owner?: `0x${string}`;
11
+ spender?: `0x${string}`;
12
+ }
13
+ export declare function useAllowance({ token, owner, spender }: UseAllowanceParams): AllowanceResult;
@@ -0,0 +1,22 @@
1
+ import { useReadContract } from "wagmi";
2
+ import { erc20Abi } from "viem";
3
+ // The ERC-20 allowance owner has granted spender. Pair with useApprove — the
4
+ // classic approve→spend gate.
5
+ export function useAllowance({ token, owner, spender }) {
6
+ const r = useReadContract({
7
+ address: token,
8
+ abi: erc20Abi,
9
+ functionName: "allowance",
10
+ args: owner && spender ? [owner, spender] : undefined,
11
+ query: { enabled: Boolean(token && owner && spender) },
12
+ });
13
+ const data = r.data;
14
+ return {
15
+ data,
16
+ enough: (amount) => data !== undefined && data >= amount,
17
+ isLoading: r.isLoading,
18
+ refetch: () => {
19
+ void r.refetch();
20
+ },
21
+ };
22
+ }
@@ -0,0 +1,10 @@
1
+ import { type UseTxResult } from "../tx/useTx";
2
+ export interface UseApproveParams {
3
+ token: `0x${string}`;
4
+ spender: `0x${string}`;
5
+ /** Amount to approve (base units). Omit for an unlimited (maxUint256) approval. */
6
+ amount?: bigint;
7
+ onSuccess?: () => void;
8
+ onError?: (error: Error) => void;
9
+ }
10
+ export declare function useApprove({ token, spender, amount, onSuccess, onError }: UseApproveParams): UseTxResult;
@@ -0,0 +1,14 @@
1
+ import { erc20Abi, maxUint256 } from "viem";
2
+ import { useTx } from "../tx/useTx";
3
+ // Approve an ERC-20 spender — the whole approve tx lifecycle via useTx. Read the
4
+ // current allowance with useAllowance to decide whether you even need this.
5
+ export function useApprove({ token, spender, amount, onSuccess, onError }) {
6
+ return useTx({
7
+ address: token,
8
+ abi: erc20Abi,
9
+ functionName: "approve",
10
+ args: [spender, amount ?? maxUint256],
11
+ onSuccess: onSuccess ? () => onSuccess() : undefined,
12
+ onError,
13
+ });
14
+ }
@@ -0,0 +1,7 @@
1
+ export interface TokenInfo {
2
+ name?: string;
3
+ symbol?: string;
4
+ decimals?: number;
5
+ isLoading: boolean;
6
+ }
7
+ export declare function useToken(token?: `0x${string}`): TokenInfo;
@@ -0,0 +1,15 @@
1
+ import { useReadContract } from "wagmi";
2
+ import { erc20Abi } from "viem";
3
+ // ERC-20 metadata (name / symbol / decimals) for a token address.
4
+ export function useToken(token) {
5
+ const enabled = Boolean(token);
6
+ const name = useReadContract({ address: token, abi: erc20Abi, functionName: "name", query: { enabled } });
7
+ const symbol = useReadContract({ address: token, abi: erc20Abi, functionName: "symbol", query: { enabled } });
8
+ const decimals = useReadContract({ address: token, abi: erc20Abi, functionName: "decimals", query: { enabled } });
9
+ return {
10
+ name: name.data,
11
+ symbol: symbol.data,
12
+ decimals: decimals.data,
13
+ isLoading: name.isLoading || symbol.isLoading || decimals.isLoading,
14
+ };
15
+ }
@@ -0,0 +1,17 @@
1
+ export interface UseTokenGateParams {
2
+ /** ERC-20 / ERC-721 token address that gates access. */
3
+ token: `0x${string}`;
4
+ /** Minimum balance required (base units for ERC-20; count for ERC-721). */
5
+ threshold: bigint;
6
+ /** Account to check (defaults to the connected wallet). */
7
+ address?: `0x${string}`;
8
+ }
9
+ export interface TokenGateResult {
10
+ /** Whether the wallet currently meets the gate. */
11
+ eligible: boolean;
12
+ balance: bigint | undefined;
13
+ /** How much more is needed to qualify (0n when eligible). */
14
+ shortBy: bigint;
15
+ isLoading: boolean;
16
+ }
17
+ export declare function useTokenGate({ token, threshold, address }: UseTokenGateParams): TokenGateResult;
@@ -0,0 +1,24 @@
1
+ import { useReadContract, useConnection } from "wagmi";
2
+ import { erc20Abi } from "viem";
3
+ // Does a wallet hold enough of a token to pass a gate? For showing eligibility on
4
+ // the link/join page (the bot/keeper enforces it server-side with meetsGate). Works
5
+ // for ERC-20 (balance) and ERC-721 (count) via balanceOf.
6
+ export function useTokenGate({ token, threshold, address }) {
7
+ const { address: connected } = useConnection();
8
+ const owner = address ?? connected;
9
+ const r = useReadContract({
10
+ address: token,
11
+ abi: erc20Abi,
12
+ functionName: "balanceOf",
13
+ args: owner ? [owner] : undefined,
14
+ query: { enabled: Boolean(token && owner) },
15
+ });
16
+ const balance = r.data;
17
+ const eligible = balance !== undefined && balance >= threshold;
18
+ return {
19
+ eligible,
20
+ balance,
21
+ shortBy: balance !== undefined && balance < threshold ? threshold - balance : 0n,
22
+ isLoading: r.isLoading,
23
+ };
24
+ }
@@ -0,0 +1,13 @@
1
+ import "./tx.css";
2
+ import { type TxStatus } from "./useTx";
3
+ export interface TxProgressProps {
4
+ status: TxStatus;
5
+ hash?: `0x${string}`;
6
+ error?: Error | null;
7
+ /** Block explorer base, e.g. "https://sepolia.etherscan.io". Links the hash when set. */
8
+ explorer?: string;
9
+ className?: string;
10
+ /** Hide when idle (default true). */
11
+ hideIdle?: boolean;
12
+ }
13
+ export declare function TxProgress({ status, hash, error, explorer, className, hideIdle }: TxProgressProps): import("react").JSX.Element | null;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "./tx.css";
3
+ import {} from "./useTx";
4
+ import { shortHash } from "../format";
5
+ import { cx } from "../util";
6
+ const LABELS = {
7
+ idle: "",
8
+ pending: "Confirm in your wallet…",
9
+ confirming: "Transaction submitted, confirming…",
10
+ success: "Confirmed",
11
+ error: "Failed",
12
+ };
13
+ // A neutral inline status line for a transaction lifecycle (pairs with useTx). Style
14
+ // it via the .kit-txp* classes — see STYLING.md.
15
+ export function TxProgress({ status, hash, error, explorer, className, hideIdle = true }) {
16
+ if (status === "idle" && hideIdle)
17
+ return null;
18
+ const label = status === "error" ? error?.message ?? LABELS.error : LABELS[status];
19
+ const busy = status === "pending" || status === "confirming";
20
+ return (_jsxs("div", { className: cx("kit-txp", `kit-txp-${status}`, className), role: "status", "aria-live": "polite", children: [busy && _jsx("span", { className: "kit-txp-spinner", "aria-hidden": "true" }), _jsx("span", { className: "kit-txp-label", children: label }), hash &&
21
+ (explorer ? (_jsx("a", { className: "kit-txp-hash", href: `${explorer}/tx/${hash}`, target: "_blank", rel: "noreferrer", children: shortHash(hash) })) : (_jsx("span", { className: "kit-txp-hash", children: shortHash(hash) })))] }));
22
+ }
@@ -1,3 +1,4 @@
1
1
  export { useTx, type UseTxOptions, type UseTxResult, type TxStatus } from "./useTx";
2
2
  export { TxButton, type TxButtonProps } from "./TxButton";
3
3
  export { decodeRevert } from "./revert";
4
+ export { TxProgress, type TxProgressProps } from "./TxProgress";
package/dist/tx/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { useTx } from "./useTx";
2
2
  export { TxButton } from "./TxButton";
3
3
  export { decodeRevert } from "./revert";
4
+ export { TxProgress } from "./TxProgress";
package/dist/tx/tx.css CHANGED
@@ -11,3 +11,12 @@
11
11
  .kit-btn:hover:not(:disabled){opacity:.9}
12
12
  .kit-btn:active:not(:disabled){transform:translateY(1px)}
13
13
  @media (prefers-color-scheme:dark){.kit-btn{background:var(--kit-accent,#f5f5f7);color:var(--kit-accent-fg,#111827)}}
14
+
15
+ /* Transaction status line (TxProgress). Neutral; restyle via these classes. */
16
+ .kit-txp{display:inline-flex;align-items:center;gap:8px;font:inherit;font-size:13px;color:var(--kit-muted,#6b7280)}
17
+ .kit-txp-success{color:var(--kit-success,#15803d)}
18
+ .kit-txp-error{color:var(--kit-danger,#b91c1c)}
19
+ .kit-txp-hash{color:var(--kit-accent,#2563eb);text-decoration:none;font-variant-numeric:tabular-nums}
20
+ .kit-txp-hash:hover{text-decoration:underline}
21
+ .kit-txp-spinner{width:12px;height:12px;border-radius:50%;border:2px solid currentColor;border-top-color:transparent;animation:kit-txp-spin .7s linear infinite}
22
+ @keyframes kit-txp-spin{to{transform:rotate(360deg)}}
@@ -1 +1 @@
1
- export { Dialog, Card, Skeleton, Spinner, EmptyState, CopyButton, type DialogProps, type SkeletonProps, type EmptyStateProps, type CopyButtonProps, } from "./ui";
1
+ export { Dialog, Card, Skeleton, Spinner, EmptyState, CopyButton, Button, Badge, Countdown, type DialogProps, type SkeletonProps, type EmptyStateProps, type CopyButtonProps, type ButtonProps, type CountdownProps, } from "./ui";
package/dist/ui/index.js CHANGED
@@ -1 +1 @@
1
- export { Dialog, Card, Skeleton, Spinner, EmptyState, CopyButton, } from "./ui";
1
+ export { Dialog, Card, Skeleton, Spinner, EmptyState, CopyButton, Button, Badge, Countdown, } from "./ui";
package/dist/ui/ui.css CHANGED
@@ -13,6 +13,17 @@
13
13
  .kit-empty-desc{font-size:14px;margin:0;max-width:34ch}
14
14
  .kit-spinner{display:inline-block;box-sizing:border-box;border:2px solid var(--kit-border,#e5e7eb);border-top-color:currentColor;border-radius:50%;animation:kit-spin .7s linear infinite}
15
15
  .kit-copy{display:inline-flex;align-items:center;gap:6px;border:0;background:transparent;color:inherit;font:inherit;cursor:pointer;padding:0}
16
+ .kit-button{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:9px 15px;border-radius:10px;font:inherit;font-weight:600;font-size:14px;cursor:pointer;transition:opacity .12s,transform .08s,background .12s;border:1px solid transparent}
17
+ .kit-button:disabled{opacity:.6;cursor:default}
18
+ .kit-button:active:not(:disabled){transform:translateY(1px)}
19
+ .kit-button-primary{background:var(--kit-accent,#111827);color:var(--kit-accent-fg,#fff)}
20
+ .kit-button-primary:hover:not(:disabled){opacity:.9}
21
+ .kit-button-secondary{background:var(--kit-row,#f3f4f6);color:var(--kit-fg,#111827)}
22
+ .kit-button-secondary:hover:not(:disabled){background:var(--kit-row-hover,#e9eaee)}
23
+ .kit-button-ghost{background:transparent;color:inherit;border-color:var(--kit-border,#e5e7eb)}
24
+ .kit-button-ghost:hover:not(:disabled){background:var(--kit-row,#f3f4f6)}
25
+ .kit-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 9px;border-radius:999px;font:inherit;font-size:12px;font-weight:600;background:var(--kit-row,#f3f4f6);color:var(--kit-muted,#6b7280)}
26
+ @media (prefers-color-scheme:dark){.kit-button-secondary,.kit-badge{--kit-row:#27272c;--kit-row-hover:#313137;--kit-fg:#f5f5f7}}
16
27
  @keyframes kit-fade{from{opacity:0}to{opacity:1}}
17
28
  @keyframes kit-pop{from{opacity:0;transform:translateY(6px) scale(.98)}to{opacity:1;transform:none}}
18
29
  @keyframes kit-spin{to{transform:rotate(360deg)}}
@@ -21,3 +32,10 @@
21
32
  @media (max-width:480px){.kit-dialog-root{align-items:flex-end}.kit-dialog{width:100%;max-width:100%;border-radius:16px 16px 0 0;animation:kit-up .22s cubic-bezier(.2,.9,.25,1)}}
22
33
  @keyframes kit-up{from{transform:translateY(100%)}to{transform:none}}
23
34
  @media (prefers-reduced-motion:reduce){.kit-dialog,.kit-dialog-overlay,.kit-skeleton,.kit-spinner{animation:none}}
35
+
36
+ /* Countdown — neutral inline D:H:M:S. Restyle via these classes. */
37
+ .kit-countdown{display:inline-flex;gap:10px;font:inherit;font-variant-numeric:tabular-nums}
38
+ .kit-countdown-unit{display:inline-flex;flex-direction:column;align-items:center;line-height:1.1}
39
+ .kit-countdown-num{font-weight:700;font-size:20px;color:var(--kit-fg,#111827)}
40
+ .kit-countdown-label{font-size:11px;color:var(--kit-muted,#6b7280);text-transform:uppercase;letter-spacing:.04em}
41
+ @media (prefers-color-scheme:dark){.kit-countdown-num{color:var(--kit-fg,#f5f5f7)}}
package/dist/ui/ui.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import "./ui.css";
2
- import { type ReactNode, type CSSProperties, type HTMLAttributes } from "react";
2
+ import { type ReactNode, type CSSProperties, type HTMLAttributes, type ButtonHTMLAttributes } from "react";
3
+ import { type CountdownState } from "../hooks";
4
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
5
+ variant?: "primary" | "secondary" | "ghost";
6
+ }
7
+ export declare function Button({ variant, className, ...rest }: ButtonProps): import("react").JSX.Element;
8
+ export declare function Badge({ className, ...rest }: HTMLAttributes<HTMLSpanElement>): import("react").JSX.Element;
3
9
  export interface DialogProps {
4
10
  open: boolean;
5
11
  onClose: () => void;
@@ -38,3 +44,15 @@ export interface CopyButtonProps {
38
44
  className?: string;
39
45
  }
40
46
  export declare function CopyButton({ value, children, copiedLabel, className }: CopyButtonProps): import("react").JSX.Element;
47
+ export interface CountdownProps {
48
+ /** Target time — Date, unix seconds, or ms. */
49
+ to: Date | number;
50
+ /** Hide leading zero days/hours. Default false (always D:H:M:S). */
51
+ trim?: boolean;
52
+ /** Rendered when the countdown reaches zero. */
53
+ whenDone?: ReactNode;
54
+ className?: string;
55
+ /** Custom render given the live countdown parts. */
56
+ children?: (c: CountdownState) => ReactNode;
57
+ }
58
+ export declare function Countdown({ to, trim, whenDone, className, children }: CountdownProps): import("react").JSX.Element;
package/dist/ui/ui.js CHANGED
@@ -2,6 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import "./ui.css";
3
3
  import { useEffect, useRef, useState, } from "react";
4
4
  import { cx } from "../util";
5
+ import { useCountdown } from "../hooks";
6
+ export function Button({ variant = "secondary", className, ...rest }) {
7
+ return _jsx("button", { className: cx("kit-button", "kit-button-" + variant, className), ...rest });
8
+ }
9
+ // ---- Badge ---------------------------------------------------------------------
10
+ export function Badge({ className, ...rest }) {
11
+ return _jsx("span", { className: cx("kit-badge", className), ...rest });
12
+ }
5
13
  export function Dialog({ open, onClose, title, children, className }) {
6
14
  const ref = useRef(null);
7
15
  useEffect(() => {
@@ -54,3 +62,26 @@ export function CopyButton({ value, children, copiedLabel = "Copied", className
54
62
  }
55
63
  }, children: copied ? copiedLabel : (children ?? "Copy") }));
56
64
  }
65
+ const pad = (n) => String(n).padStart(2, "0");
66
+ // Live D:H:M:S countdown to a target time (e.g. a mint/sale window). Neutral —
67
+ // style via .kit-countdown*, or pass `children` for a custom layout.
68
+ export function Countdown({ to, trim = false, whenDone, className, children }) {
69
+ const c = useCountdown(to);
70
+ if (children)
71
+ return _jsx("span", { className: cx("kit-countdown", className), children: children(c) });
72
+ if (c.done && whenDone !== undefined)
73
+ return _jsx("span", { className: cx("kit-countdown", className), children: whenDone });
74
+ const parts = [
75
+ [c.days, "d"],
76
+ [c.hours, "h"],
77
+ [c.minutes, "m"],
78
+ [c.seconds, "s"],
79
+ ];
80
+ let started = !trim;
81
+ return (_jsx("span", { className: cx("kit-countdown", className), "data-done": c.done || undefined, role: "timer", children: parts.map(([v, label], i) => {
82
+ if (!started && v === 0 && i < 3)
83
+ return null; // trim leading zero units (never the seconds)
84
+ started = true;
85
+ return (_jsxs("span", { className: "kit-countdown-unit", children: [_jsx("span", { className: "kit-countdown-num", children: pad(v) }), _jsx("span", { className: "kit-countdown-label", children: label })] }, label));
86
+ }) }));
87
+ }
@@ -0,0 +1,10 @@
1
+ import "./user.css";
2
+ import { type UseUserOptions } from "./useUser";
3
+ export interface UserMenuProps extends UseUserOptions {
4
+ /** Show the Telegram link control in the menu. Default true. */
5
+ telegram?: boolean;
6
+ /** Show the Sign-In With Ethereum control. Default true. */
7
+ auth?: boolean;
8
+ className?: string;
9
+ }
10
+ export declare function UserMenu({ telegram, auth, className, ...opts }: UserMenuProps): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import "./user.css";
3
+ import { useState } from "react";
4
+ import { useUser } from "./useUser";
5
+ import { Identity } from "../account/Identity";
6
+ import { ConnectWallet } from "../wallet";
7
+ import { LinkTelegramButton } from "../telegram/LinkTelegramButton";
8
+ import { cx } from "../util";
9
+ // A compact account control: a wallet-connect button when disconnected, otherwise
10
+ // an identity pill that opens a menu with SIWE sign-in/out + Telegram linking +
11
+ // disconnect. Neutral — style via .kit-user*. The one-stop user-management widget.
12
+ export function UserMenu({ telegram = true, auth = true, className, ...opts }) {
13
+ const user = useUser(opts);
14
+ const [open, setOpen] = useState(false);
15
+ if (!user.isConnected || !user.address)
16
+ return _jsx(ConnectWallet, { className: className });
17
+ return (_jsxs("div", { className: cx("kit-user", className), "data-open": open || undefined, children: [_jsx("button", { className: "kit-user-trigger", onClick: () => setOpen((o) => !o), children: _jsx(Identity, { address: user.address, size: 24 }) }), open && (_jsxs(_Fragment, { children: [_jsx("div", { className: "kit-user-scrim", onClick: () => setOpen(false) }), _jsxs("div", { className: "kit-user-panel", role: "menu", children: [auth && (_jsxs("div", { className: "kit-user-row", children: [_jsx("span", { className: "kit-user-label", children: "Session" }), user.isSignedIn ? (_jsx("span", { className: "kit-user-action", "data-state": "on", children: "Signed in \u2713" })) : (_jsx("button", { className: "kit-user-action", onClick: () => void user.signIn(), children: "Sign in" }))] })), telegram && (_jsxs("div", { className: "kit-user-row", children: [_jsx("span", { className: "kit-user-label", children: "Telegram" }), _jsx(LinkTelegramButton, { endpoint: opts.telegramEndpoint })] })), _jsx("button", { className: "kit-user-disconnect", onClick: () => void user.signOut(), children: "Disconnect" })] })] }))] }));
18
+ }
@@ -0,0 +1,2 @@
1
+ export { useUser, type KitUser, type UseUserOptions } from "./useUser";
2
+ export { UserMenu, type UserMenuProps } from "./UserMenu";
@@ -0,0 +1,2 @@
1
+ export { useUser } from "./useUser";
2
+ export { UserMenu } from "./UserMenu";
@@ -0,0 +1,32 @@
1
+ import { type SiweUser } from "../auth/useSiwe";
2
+ import { type LinkedTelegram } from "../telegram/useTelegramLink";
3
+ export interface UseUserOptions {
4
+ /** SIWE auth API base (default "/api/auth"). */
5
+ authEndpoint?: string;
6
+ /** Telegram link API base (default "/api/telegram"). */
7
+ telegramEndpoint?: string;
8
+ /** Skip the SIWE session check (wallet-only identity). Default false. */
9
+ noAuth?: boolean;
10
+ /** Skip the Telegram link check. Default false. */
11
+ noTelegram?: boolean;
12
+ }
13
+ export interface KitUser {
14
+ /** Connected wallet address (undefined if no wallet). */
15
+ address?: `0x${string}`;
16
+ isConnected: boolean;
17
+ /** Server-verified SIWE session user (undefined until signed in). */
18
+ session?: SiweUser;
19
+ isSignedIn: boolean;
20
+ /** Linked Telegram identity (undefined if not linked). */
21
+ telegram?: LinkedTelegram;
22
+ isTelegramLinked: boolean;
23
+ /** True while any underlying identity check is still loading. */
24
+ isLoading: boolean;
25
+ /** Run the SIWE sign-in flow. */
26
+ signIn: () => Promise<void>;
27
+ /** Sign out of the session AND disconnect the wallet. */
28
+ signOut: () => Promise<void>;
29
+ /** Re-check session + telegram link. */
30
+ refresh: () => void;
31
+ }
32
+ export declare function useUser(opts?: UseUserOptions): KitUser;
@@ -0,0 +1,36 @@
1
+ import { useConnection, useDisconnect } from "wagmi";
2
+ import { useSiwe } from "../auth/useSiwe";
3
+ import { useTelegramLink } from "../telegram/useTelegramLink";
4
+ // One hook for "who is the current user", unifying the three identity surfaces a
5
+ // Livo app has: the connected wallet, the SIWE session (your /api/auth), and the
6
+ // linked Telegram account (your /api/telegram). The backbone of user management for
7
+ // Telegram-connected apps.
8
+ export function useUser(opts = {}) {
9
+ const { address, isConnected } = useConnection();
10
+ const { disconnect } = useDisconnect();
11
+ const siwe = useSiwe({ endpoint: opts.authEndpoint });
12
+ const tg = useTelegramLink({ endpoint: opts.telegramEndpoint });
13
+ const useAuth = !opts.noAuth;
14
+ const useTg = !opts.noTelegram;
15
+ return {
16
+ address,
17
+ isConnected: Boolean(isConnected),
18
+ session: useAuth ? siwe.user : undefined,
19
+ isSignedIn: useAuth ? siwe.status === "signed-in" : Boolean(isConnected),
20
+ telegram: useTg ? tg.telegram : undefined,
21
+ isTelegramLinked: useTg ? tg.status === "linked" : false,
22
+ isLoading: (useAuth && siwe.status === "loading") || (useTg && tg.status === "loading"),
23
+ signIn: siwe.signIn,
24
+ signOut: async () => {
25
+ if (useAuth)
26
+ await siwe.signOut();
27
+ disconnect();
28
+ },
29
+ refresh: () => {
30
+ if (useAuth)
31
+ siwe.refresh();
32
+ if (useTg)
33
+ tg.refresh();
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,15 @@
1
+ /* UserMenu — neutral account control + dropdown. Restyle via .kit-user*. */
2
+ .kit-user{position:relative;display:inline-block;font:inherit}
3
+ .kit-user-trigger{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--kit-border,#e5e7eb);background:transparent;color:inherit;border-radius:999px;padding:4px 12px 4px 4px;font:inherit;cursor:pointer}
4
+ .kit-user-trigger:hover{border-color:var(--kit-muted,#9ca3af)}
5
+ .kit-user-scrim{position:fixed;inset:0;z-index:40}
6
+ .kit-user-panel{position:absolute;right:0;top:calc(100% + 8px);z-index:41;min-width:240px;display:flex;flex-direction:column;gap:4px;padding:10px;border:1px solid var(--kit-border,#e5e7eb);border-radius:14px;background:var(--kit-bg,#fff);box-shadow:0 12px 32px rgba(0,0,0,.12)}
7
+ .kit-user-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:6px 6px}
8
+ .kit-user-label{color:var(--kit-muted,#6b7280);font-size:13px}
9
+ .kit-user-action{font:inherit;font-size:13px;font-weight:600;color:var(--kit-accent,#111827);background:transparent;border:0;cursor:pointer;padding:0}
10
+ .kit-user-action[data-state="on"]{color:var(--kit-up,#15803d);cursor:default}
11
+ .kit-user-disconnect{margin-top:4px;border:0;border-top:1px solid var(--kit-border,#eceef1);background:transparent;color:var(--kit-down,#b91c1c);font:inherit;font-size:13px;text-align:left;padding:10px 6px 4px;cursor:pointer}
12
+ @media (prefers-color-scheme:dark){
13
+ .kit-user-panel{--kit-border:#2c2e36;--kit-bg:#15171c}
14
+ .kit-user-trigger{--kit-border:#2c2e36}
15
+ }
@@ -0,0 +1,10 @@
1
+ import "./web3.css";
2
+ export interface AddressInputProps {
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ /** Fires with the resolved checksummed address (or null when invalid/unresolved). */
6
+ onResolved?: (address: `0x${string}` | null) => void;
7
+ placeholder?: string;
8
+ className?: string;
9
+ }
10
+ export declare function AddressInput({ value, onChange, onResolved, placeholder, className }: AddressInputProps): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "./web3.css";
3
+ import { useEffect } from "react";
4
+ import { useEnsAddress } from "wagmi";
5
+ import { isAddress } from "viem";
6
+ import { shortAddress } from "../format";
7
+ import { cx } from "../util";
8
+ // An address input that accepts a hex address OR an ENS name (`name.eth`), resolves
9
+ // it, and reports validity via [data-valid] + onResolved. Neutral; style .kit-addr-input.
10
+ export function AddressInput({ value, onChange, onResolved, placeholder = "0x… or name.eth", className }) {
11
+ const isEns = /\.eth$/i.test(value.trim());
12
+ const { data: resolved } = useEnsAddress({ name: isEns ? value.trim() : undefined, query: { enabled: isEns } });
13
+ const address = isAddress(value.trim())
14
+ ? value.trim()
15
+ : (resolved ?? null);
16
+ useEffect(() => {
17
+ onResolved?.(address);
18
+ }, [address, onResolved]);
19
+ return (_jsxs("div", { className: cx("kit-addr-input", className), "data-valid": address ? "" : undefined, children: [_jsx("input", { value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, spellCheck: false, autoComplete: "off" }), isEns && resolved ? _jsx("span", { className: "kit-addr-input-resolved", children: shortAddress(resolved) }) : null] }));
20
+ }
@@ -0,0 +1,5 @@
1
+ import "./web3.css";
2
+ export interface ChainSwitcherProps {
3
+ className?: string;
4
+ }
5
+ export declare function ChainSwitcher({ className }: ChainSwitcherProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import "./web3.css";
3
+ import { useChainId, useSwitchChain } from "wagmi";
4
+ import { cx } from "../util";
5
+ // A network picker — lists the chains in your wagmi config and switches on select.
6
+ // Neutral; style .kit-chain.
7
+ export function ChainSwitcher({ className }) {
8
+ const chainId = useChainId();
9
+ const { chains, switchChain, isPending } = useSwitchChain();
10
+ return (_jsx("select", { className: cx("kit-chain", className), value: chainId, disabled: isPending, onChange: (e) => switchChain({ chainId: Number(e.target.value) }), "aria-label": "Network", children: chains.map((c) => (_jsx("option", { value: c.id, children: c.name }, c.id))) }));
11
+ }