From 58c3b6929797ef0ae7829f8702e3e90e93f5ac6f Mon Sep 17 00:00:00 2001
From: Pete Watters <2938440+pete-watters@users.noreply.github.com>
Date: Tue, 17 Sep 2024 11:11:20 +0100
Subject: [PATCH] feat: add collectibles widget, ref leather-io/issues#222
---
.../add-wallet/add-wallet-list-item.tsx | 2 -
apps/mobile/src/components/home/home.tsx | 18 +-
.../widgets/accounts/accounts-header.tsx | 2 -
.../accounts/accounts-widget.layout.tsx | 1 -
.../collectibles/collectibles-header.tsx | 30 ++
.../collectibles-serializer.spec.ts | 60 ++++
.../collectibles/collectibles-serializer.ts | 21 ++
.../collectibles-widget.layout.tsx | 30 ++
.../collectibles/collectibles-widget.tsx | 33 ++
.../components/widgets/collectibles/index.ts | 3 +
.../components/balance/token-balance.tsx | 2 -
.../src/components/widgets/tokens/index.ts | 2 +
.../widgets/tokens/tokens-header.tsx | 2 -
.../widgets/tokens/tokens-widget.layout.tsx | 1 -
.../widgets/tokens/tokens.mocks.tsx | 8 -
apps/mobile/src/components/widgets/widget.tsx | 2 -
apps/mobile/src/locales/en/messages.po | 20 +-
.../src/locales/pseudo-locale/messages.po | 20 +-
packages/ui/native.ts | 1 +
packages/ui/package.json | 7 +-
.../approver/stories/approver.web.stories.tsx | 2 +-
.../collectibles/collectibles.shared.tsx | 319 ++++++++++++++++++
.../components/collectibles/index.native.ts | 6 +
.../src/components/collectibles/index.web.ts | 8 +
.../native/collectible-audio.native.tsx | 10 +
.../native/collectible-card-layout.native.tsx | 14 +
.../collectible-card.native.stories.tsx | 64 ++++
.../native/collectible-card.native.tsx | 31 ++
.../native/collectible-html.native.tsx | 15 +
.../native/collectible-image.native.tsx | 15 +
.../native/collectible-text.native.tsx | 42 +++
.../web/collectible-audio.web.tsx | 18 +
.../web/collectible-hover.web.tsx | 58 ++++
.../web/collectible-iframe.web.tsx | 34 ++
.../web/collectible-image.web.tsx | 48 +++
.../web/collectible-item.layout.web.tsx | 119 +++++++
.../web/collectible-other.web.tsx | 23 ++
.../collectible-placeholder.layout.web.tsx | 19 ++
.../web/collectible-text.layout.web.tsx | 31 ++
.../collectibles/web/collectible-text.web.tsx | 15 +
.../collectibles/web/iframe.web.tsx | 34 ++
.../web/image-unavailable.web.tsx | 15 +
.../components/inscription-image.web.tsx | 11 +
.../components/inscription-metadata.web.tsx | 31 ++
.../inscription-preview.layout.web.tsx | 19 ++
.../inscription-text.layout.web.tsx | 31 ++
.../web/inscription-preview-card/index.web.ts | 4 +
.../inscription-preview-card.web.tsx | 39 +++
.../collectibles/web/inscription-text.web.tsx | 31 ++
packages/ui/src/components/spinner/index.ts | 2 +
.../spinner/loading-spinner.web.tsx | 27 ++
.../components/{ => spinner}/spinner.web.tsx | 0
packages/ui/src/exports.web.ts | 4 +-
packages/ui/src/hooks/use-pressable.web.tsx | 67 ++++
packages/ui/src/icons/headset-icon.native.tsx | 17 +
packages/ui/src/icons/icon/icon.native.tsx | 2 +-
pnpm-lock.yaml | 319 ++++++++++++++++--
57 files changed, 1739 insertions(+), 70 deletions(-)
create mode 100644 apps/mobile/src/components/widgets/collectibles/collectibles-header.tsx
create mode 100644 apps/mobile/src/components/widgets/collectibles/collectibles-serializer.spec.ts
create mode 100644 apps/mobile/src/components/widgets/collectibles/collectibles-serializer.ts
create mode 100644 apps/mobile/src/components/widgets/collectibles/collectibles-widget.layout.tsx
create mode 100644 apps/mobile/src/components/widgets/collectibles/collectibles-widget.tsx
create mode 100644 apps/mobile/src/components/widgets/collectibles/index.ts
create mode 100644 apps/mobile/src/components/widgets/tokens/index.ts
create mode 100644 packages/ui/src/components/collectibles/collectibles.shared.tsx
create mode 100644 packages/ui/src/components/collectibles/index.native.ts
create mode 100644 packages/ui/src/components/collectibles/index.web.ts
create mode 100644 packages/ui/src/components/collectibles/native/collectible-audio.native.tsx
create mode 100644 packages/ui/src/components/collectibles/native/collectible-card-layout.native.tsx
create mode 100644 packages/ui/src/components/collectibles/native/collectible-card.native.stories.tsx
create mode 100644 packages/ui/src/components/collectibles/native/collectible-card.native.tsx
create mode 100644 packages/ui/src/components/collectibles/native/collectible-html.native.tsx
create mode 100644 packages/ui/src/components/collectibles/native/collectible-image.native.tsx
create mode 100644 packages/ui/src/components/collectibles/native/collectible-text.native.tsx
create mode 100644 packages/ui/src/components/collectibles/web/collectible-audio.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/collectible-hover.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/collectible-iframe.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/collectible-image.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/collectible-item.layout.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/collectible-other.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/collectible-placeholder.layout.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/collectible-text.layout.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/collectible-text.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/iframe.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/image-unavailable.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-image.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-metadata.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-preview.layout.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-text.layout.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/inscription-preview-card/index.web.ts
create mode 100644 packages/ui/src/components/collectibles/web/inscription-preview-card/inscription-preview-card.web.tsx
create mode 100644 packages/ui/src/components/collectibles/web/inscription-text.web.tsx
create mode 100644 packages/ui/src/components/spinner/index.ts
create mode 100644 packages/ui/src/components/spinner/loading-spinner.web.tsx
rename packages/ui/src/components/{ => spinner}/spinner.web.tsx (100%)
create mode 100644 packages/ui/src/hooks/use-pressable.web.tsx
create mode 100644 packages/ui/src/icons/headset-icon.native.tsx
diff --git a/apps/mobile/src/components/add-wallet/add-wallet-list-item.tsx b/apps/mobile/src/components/add-wallet/add-wallet-list-item.tsx
index f557ee93f..237e171c3 100644
--- a/apps/mobile/src/components/add-wallet/add-wallet-list-item.tsx
+++ b/apps/mobile/src/components/add-wallet/add-wallet-list-item.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import { Box, Text, TouchableOpacity } from '@leather.io/ui/native';
interface AddWalletListItemProps {
diff --git a/apps/mobile/src/components/home/home.tsx b/apps/mobile/src/components/home/home.tsx
index 6a5341d92..c4b29bff2 100644
--- a/apps/mobile/src/components/home/home.tsx
+++ b/apps/mobile/src/components/home/home.tsx
@@ -1,12 +1,22 @@
import { AccountsWidget } from '@/components/widgets/accounts/accounts-widget';
-import { TokensWidget } from '@/components/widgets/tokens/tokens-widget';
-import { getMockTokens, mockTotalBalance } from '@/components/widgets/tokens/tokens.mocks';
+import {
+ CollectiblesWidget,
+ mockCollectibles,
+ serializeCollectibles,
+} from '@/components/widgets/collectibles';
+import { TokensWidget, getMockTokens } from '@/components/widgets/tokens';
import { useAccounts } from '@/store/accounts/accounts.read';
import { useWallets } from '@/store/wallets/wallets.read';
import { useLingui } from '@lingui/react';
import { HomeLayout } from './home.layout';
+const mockTotalBalance = {
+ totalUsdBalance: '$126.74',
+ totalBtcBalance: '0.00215005',
+ totalStxBalance: '0.0024',
+};
+
export function Home() {
useLingui();
const wallets = useWallets();
@@ -16,6 +26,10 @@ export function Home() {
+
);
}
diff --git a/apps/mobile/src/components/widgets/accounts/accounts-header.tsx b/apps/mobile/src/components/widgets/accounts/accounts-header.tsx
index a02c26ffa..8e0c4f1b1 100644
--- a/apps/mobile/src/components/widgets/accounts/accounts-header.tsx
+++ b/apps/mobile/src/components/widgets/accounts/accounts-header.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import { AccountSelectorSheet } from '@/features/account-selector-sheet';
import { t } from '@lingui/macro';
diff --git a/apps/mobile/src/components/widgets/accounts/accounts-widget.layout.tsx b/apps/mobile/src/components/widgets/accounts/accounts-widget.layout.tsx
index 3abf4a56c..57f2c4567 100644
--- a/apps/mobile/src/components/widgets/accounts/accounts-widget.layout.tsx
+++ b/apps/mobile/src/components/widgets/accounts/accounts-widget.layout.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { ScrollView } from 'react-native-gesture-handler';
import { useTheme } from '@shopify/restyle';
diff --git a/apps/mobile/src/components/widgets/collectibles/collectibles-header.tsx b/apps/mobile/src/components/widgets/collectibles/collectibles-header.tsx
new file mode 100644
index 000000000..6d5a01ee6
--- /dev/null
+++ b/apps/mobile/src/components/widgets/collectibles/collectibles-header.tsx
@@ -0,0 +1,30 @@
+import { t } from '@lingui/macro';
+
+import { Box, ChevronRightIcon, Chip, SheetRef, Text } from '@leather.io/ui/native';
+
+import { FiatBalance } from '../components/balance/fiat-balance';
+
+interface CollectiblesHeaderProps {
+ collectibleCount: number;
+ sheetRef: React.RefObject;
+ totalBalance: string;
+}
+
+function CollectiblesHeaderText() {
+ return {t`My collectibles`};
+}
+
+export function CollectiblesHeader({ collectibleCount, totalBalance }: CollectiblesHeaderProps) {
+ const hasCollectibles = collectibleCount > 0;
+ if (!hasCollectibles) return ;
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/widgets/collectibles/collectibles-serializer.spec.ts b/apps/mobile/src/components/widgets/collectibles/collectibles-serializer.spec.ts
new file mode 100644
index 000000000..6204fa1db
--- /dev/null
+++ b/apps/mobile/src/components/widgets/collectibles/collectibles-serializer.spec.ts
@@ -0,0 +1,60 @@
+import { mockCollectibles } from '@leather.io/ui/native';
+
+import { serializeCollectibles } from './collectibles-serializer';
+
+describe('serializeCollectibles', () => {
+ it('should correctly serialize collectibles', () => {
+ const serializedCollectibles = serializeCollectibles(mockCollectibles);
+
+ expect(serializedCollectibles).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ id: expect.any(String),
+ title: expect.any(String),
+ imageUrl: expect.any(String),
+ collection: expect.any(String),
+ type: expect.stringMatching(/^(ordinal|stacks)$/),
+ }),
+ ])
+ );
+ });
+
+ it('should handle empty input', () => {
+ const serializedCollectibles = serializeCollectibles([]);
+ expect(serializedCollectibles).toEqual([]);
+ });
+
+ it('should correctly serialize Ordinals', () => {
+ const ordinals = mockCollectibles.filter(c => 'name' in c && 'mimeType' in c);
+ const serializedOrdinals = serializeCollectibles(ordinals);
+
+ serializedOrdinals.forEach(ordinal => {
+ expect(ordinal).toEqual(
+ expect.objectContaining({
+ type: 'inscription',
+ id: expect.any(String),
+ title: expect.any(String),
+ imageUrl: expect.any(String),
+ collection: expect.any(String),
+ })
+ );
+ });
+ });
+
+ it('should correctly serialize StacksNfts', () => {
+ const stacksNfts = mockCollectibles.filter(c => 'metadata' in c);
+ const serializedStacksNfts = serializeCollectibles(stacksNfts);
+
+ serializedStacksNfts.forEach(nft => {
+ expect(nft).toEqual(
+ expect.objectContaining({
+ type: 'stacks',
+ id: expect.any(String),
+ title: expect.any(String),
+ imageUrl: expect.any(String),
+ collection: expect.any(String),
+ })
+ );
+ });
+ });
+});
diff --git a/apps/mobile/src/components/widgets/collectibles/collectibles-serializer.ts b/apps/mobile/src/components/widgets/collectibles/collectibles-serializer.ts
new file mode 100644
index 000000000..0c6a9ad32
--- /dev/null
+++ b/apps/mobile/src/components/widgets/collectibles/collectibles-serializer.ts
@@ -0,0 +1,21 @@
+import { type Collectible, type CollectibleCardProps } from '@leather.io/ui/native';
+
+export function serializeCollectibles(collectibles: Collectible[]): CollectibleCardProps[] {
+ return collectibles.map(collectible => {
+ const isOrdinal = 'name' in collectible && 'mimeType' in collectible;
+ if (isOrdinal) {
+ return {
+ type: 'inscription',
+ name: collectible.title,
+ src: collectible.src,
+ mimeType: collectible.mimeType,
+ };
+ }
+ return {
+ type: 'stacks',
+ name: collectible.metadata.name,
+ src: collectible.metadata.cached_image,
+ mimeType: null,
+ };
+ });
+}
diff --git a/apps/mobile/src/components/widgets/collectibles/collectibles-widget.layout.tsx b/apps/mobile/src/components/widgets/collectibles/collectibles-widget.layout.tsx
new file mode 100644
index 000000000..cdf50d3a1
--- /dev/null
+++ b/apps/mobile/src/components/widgets/collectibles/collectibles-widget.layout.tsx
@@ -0,0 +1,30 @@
+import { ScrollView } from 'react-native-gesture-handler';
+
+import { useTheme } from '@shopify/restyle';
+
+import { Box, SheetRef, Theme } from '@leather.io/ui/native';
+
+import { Widget } from '../widget';
+
+interface CollectiblesWidgetLayoutProps {
+ balance?: React.ReactNode;
+ children: React.ReactNode;
+ header?: React.ReactNode;
+ sheetRef?: React.RefObject;
+}
+
+export function CollectiblesWidgetLayout({ children, header }: CollectiblesWidgetLayoutProps) {
+ const theme = useTheme();
+ return (
+
+ {header}
+
+ {children}
+
+
+ );
+}
diff --git a/apps/mobile/src/components/widgets/collectibles/collectibles-widget.tsx b/apps/mobile/src/components/widgets/collectibles/collectibles-widget.tsx
new file mode 100644
index 000000000..025102197
--- /dev/null
+++ b/apps/mobile/src/components/widgets/collectibles/collectibles-widget.tsx
@@ -0,0 +1,33 @@
+import React, { useRef } from 'react';
+
+import { CollectibleCard, CollectibleCardProps, SheetRef } from '@leather.io/ui/native';
+
+import { TokenBalance } from '../components/balance/token-balance';
+import { CollectiblesHeader } from './collectibles-header';
+import { CollectiblesWidgetLayout } from './collectibles-widget.layout';
+
+interface CollectiblesWidgetProps {
+ collectibles: CollectibleCardProps[];
+ totalBalance: string;
+}
+
+export function CollectiblesWidget({ collectibles, totalBalance }: CollectiblesWidgetProps) {
+ const sheetRef = useRef(null);
+
+ return (
+
+ }
+ balance={collectibles.length > 0 && }
+ >
+ {collectibles.map((collectible: CollectibleCardProps, index) => (
+
+ ))}
+
+ );
+}
diff --git a/apps/mobile/src/components/widgets/collectibles/index.ts b/apps/mobile/src/components/widgets/collectibles/index.ts
new file mode 100644
index 000000000..9f4a0ef31
--- /dev/null
+++ b/apps/mobile/src/components/widgets/collectibles/index.ts
@@ -0,0 +1,3 @@
+export { mockCollectibles } from '@leather.io/ui/native';
+export { CollectiblesWidget } from './collectibles-widget';
+export { serializeCollectibles } from './collectibles-serializer';
diff --git a/apps/mobile/src/components/widgets/components/balance/token-balance.tsx b/apps/mobile/src/components/widgets/components/balance/token-balance.tsx
index 3066a8d22..197874aff 100644
--- a/apps/mobile/src/components/widgets/components/balance/token-balance.tsx
+++ b/apps/mobile/src/components/widgets/components/balance/token-balance.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import { t } from '@lingui/macro';
import { BulletSeparator, Text } from '@leather.io/ui/native';
diff --git a/apps/mobile/src/components/widgets/tokens/index.ts b/apps/mobile/src/components/widgets/tokens/index.ts
new file mode 100644
index 000000000..8f68cf5ca
--- /dev/null
+++ b/apps/mobile/src/components/widgets/tokens/index.ts
@@ -0,0 +1,2 @@
+export { TokensWidget } from './tokens-widget';
+export { getMockTokens } from './tokens.mocks';
diff --git a/apps/mobile/src/components/widgets/tokens/tokens-header.tsx b/apps/mobile/src/components/widgets/tokens/tokens-header.tsx
index d9c7f273e..acea83d17 100644
--- a/apps/mobile/src/components/widgets/tokens/tokens-header.tsx
+++ b/apps/mobile/src/components/widgets/tokens/tokens-header.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import { t } from '@lingui/macro';
import { Box, ChevronRightIcon, Chip, SheetRef, Text } from '@leather.io/ui/native';
diff --git a/apps/mobile/src/components/widgets/tokens/tokens-widget.layout.tsx b/apps/mobile/src/components/widgets/tokens/tokens-widget.layout.tsx
index d3199790a..098cfdc2a 100644
--- a/apps/mobile/src/components/widgets/tokens/tokens-widget.layout.tsx
+++ b/apps/mobile/src/components/widgets/tokens/tokens-widget.layout.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { ScrollView } from 'react-native-gesture-handler';
import { useTheme } from '@shopify/restyle';
diff --git a/apps/mobile/src/components/widgets/tokens/tokens.mocks.tsx b/apps/mobile/src/components/widgets/tokens/tokens.mocks.tsx
index bc79f8c16..399ac18f7 100644
--- a/apps/mobile/src/components/widgets/tokens/tokens.mocks.tsx
+++ b/apps/mobile/src/components/widgets/tokens/tokens.mocks.tsx
@@ -4,14 +4,6 @@ import BigNumber from 'bignumber.js';
import { Money } from '@leather.io/models';
import { BtcAvatarIcon, StxAvatarIcon } from '@leather.io/ui/native';
-// provided by useTotalBalance hook in extension/src/app/common/hooks/balance/use-total-balance.tsx
-
-export const mockTotalBalance = {
- totalUsdBalance: '$126.74',
- totalBtcBalance: '0.00215005',
- totalStxBalance: '0.0024',
-};
-
export interface Token {
availableBalance: Record;
formattedBalance: { isAbbreviated: boolean; value: string };
diff --git a/apps/mobile/src/components/widgets/widget.tsx b/apps/mobile/src/components/widgets/widget.tsx
index 11291babb..4ba72990c 100644
--- a/apps/mobile/src/components/widgets/widget.tsx
+++ b/apps/mobile/src/components/widgets/widget.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import { Box } from '@leather.io/ui/native';
interface WidgetProps {
diff --git a/apps/mobile/src/locales/en/messages.po b/apps/mobile/src/locales/en/messages.po
index 7377d59fe..dd07fcc2e 100644
--- a/apps/mobile/src/locales/en/messages.po
+++ b/apps/mobile/src/locales/en/messages.po
@@ -1,6 +1,6 @@
msgid ""
msgstr ""
-"POT-Creation-Date: 2024-09-23 11:25-0400\n"
+"POT-Creation-Date: 2024-09-25 06:31+0100\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -194,11 +194,11 @@ msgstr "Awaiting verification"
msgid "BIP39 passphrase"
msgstr "BIP39 passphrase"
-#: src/components/widgets/tokens/tokens.mocks.tsx:50
+#: src/components/widgets/tokens/tokens.mocks.tsx:42
msgid "Bitcoin"
msgstr "Bitcoin"
-#: src/components/widgets/tokens/tokens.mocks.tsx:27
+#: src/components/widgets/tokens/tokens.mocks.tsx:19
msgid "Bitcoin blockchain"
msgstr "Bitcoin blockchain"
@@ -490,7 +490,7 @@ msgid "Lock app"
msgstr "Lock app"
#: src/components/widgets/components/balance/fiat-balance.tsx:45
-#: src/components/widgets/components/balance/token-balance.tsx:21
+#: src/components/widgets/components/balance/token-balance.tsx:19
msgid "locked"
msgstr "locked"
@@ -507,11 +507,15 @@ msgstr "More options"
msgid "Must use physical device for Push Notifications"
msgstr "Must use physical device for Push Notifications"
-#: src/components/widgets/accounts/accounts-header.tsx:14
+#: src/components/widgets/accounts/accounts-header.tsx:12
msgid "My accounts"
msgstr "My accounts"
-#: src/components/widgets/tokens/tokens-header.tsx:18
+#: src/components/widgets/collectibles/collectibles-header.tsx:14
+msgid "My collectibles"
+msgstr "My collectibles"
+
+#: src/components/widgets/tokens/tokens-header.tsx:16
msgid "My tokens"
msgstr "My tokens"
@@ -704,11 +708,11 @@ msgstr "Something is wrong!"
msgid "Something went wrong"
msgstr "Something went wrong"
-#: src/components/widgets/tokens/tokens.mocks.tsx:113
+#: src/components/widgets/tokens/tokens.mocks.tsx:105
msgid "Stacks"
msgstr "Stacks"
-#: src/components/widgets/tokens/tokens.mocks.tsx:59
+#: src/components/widgets/tokens/tokens.mocks.tsx:51
msgid "Stacks blockchain"
msgstr "Stacks blockchain"
diff --git a/apps/mobile/src/locales/pseudo-locale/messages.po b/apps/mobile/src/locales/pseudo-locale/messages.po
index 5729fe75a..0af230288 100644
--- a/apps/mobile/src/locales/pseudo-locale/messages.po
+++ b/apps/mobile/src/locales/pseudo-locale/messages.po
@@ -1,6 +1,6 @@
msgid ""
msgstr ""
-"POT-Creation-Date: 2024-09-23 11:25-0400\n"
+"POT-Creation-Date: 2024-09-25 06:31+0100\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -194,11 +194,11 @@ msgstr ""
msgid "BIP39 passphrase"
msgstr ""
-#: src/components/widgets/tokens/tokens.mocks.tsx:50
+#: src/components/widgets/tokens/tokens.mocks.tsx:42
msgid "Bitcoin"
msgstr ""
-#: src/components/widgets/tokens/tokens.mocks.tsx:27
+#: src/components/widgets/tokens/tokens.mocks.tsx:19
msgid "Bitcoin blockchain"
msgstr ""
@@ -490,7 +490,7 @@ msgid "Lock app"
msgstr ""
#: src/components/widgets/components/balance/fiat-balance.tsx:45
-#: src/components/widgets/components/balance/token-balance.tsx:21
+#: src/components/widgets/components/balance/token-balance.tsx:19
msgid "locked"
msgstr ""
@@ -507,11 +507,15 @@ msgstr ""
msgid "Must use physical device for Push Notifications"
msgstr ""
-#: src/components/widgets/accounts/accounts-header.tsx:14
+#: src/components/widgets/accounts/accounts-header.tsx:12
msgid "My accounts"
msgstr ""
-#: src/components/widgets/tokens/tokens-header.tsx:18
+#: src/components/widgets/collectibles/collectibles-header.tsx:14
+msgid "My collectibles"
+msgstr ""
+
+#: src/components/widgets/tokens/tokens-header.tsx:16
msgid "My tokens"
msgstr ""
@@ -704,11 +708,11 @@ msgstr ""
msgid "Something went wrong"
msgstr ""
-#: src/components/widgets/tokens/tokens.mocks.tsx:113
+#: src/components/widgets/tokens/tokens.mocks.tsx:105
msgid "Stacks"
msgstr ""
-#: src/components/widgets/tokens/tokens.mocks.tsx:59
+#: src/components/widgets/tokens/tokens.mocks.tsx:51
msgid "Stacks blockchain"
msgstr ""
diff --git a/packages/ui/native.ts b/packages/ui/native.ts
index c62bb9039..4f8492e28 100644
--- a/packages/ui/native.ts
+++ b/packages/ui/native.ts
@@ -38,3 +38,4 @@ export {
export { SheetHeader } from './src/components/sheet/components/sheet-header.native';
export { RadioButton } from './src/components/radio-button/radio-button.native';
export { Switch } from './src/components/switch/switch.native';
+export * from './src/components/collectibles/index.native';
diff --git a/packages/ui/package.json b/packages/ui/package.json
index e43620a63..e47d1c8d1 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -43,6 +43,7 @@
"@rnx-kit/metro-config": "1.3.14",
"@rnx-kit/metro-resolver-symlinks": "0.1.35",
"@shopify/restyle": "2.4.2",
+ "dompurify": "3.1.4",
"expo": "51.0.26",
"expo-asset": "10.0.6",
"expo-blur": "13.0.2",
@@ -59,12 +60,15 @@
"react-native": "0.74.1",
"react-native-reanimated": "3.10.1",
"react-native-safe-area-context": "4.10.1",
- "react-native-svg": "15.2.0"
+ "react-native-svg": "15.2.0",
+ "react-native-webview": "13.8.6",
+ "use-events": "1.4.2"
},
"devDependencies": {
"@babel/core": "7.24.6",
"@babel/runtime": "7.25.0",
"@leather.io/eslint-config": "workspace:*",
+ "@leather.io/models": "workspace:*",
"@leather.io/panda-preset": "workspace:*",
"@microsoft/api-extractor": "7.47.6",
"@pandacss/dev": "0.46.1",
@@ -87,6 +91,7 @@
"@storybook/test": "8.3.2",
"@storybook/theming": "8.3.2",
"@svgr/webpack": "8.1.0",
+ "@types/dompurify": "3.0.5",
"@types/react": "18.2.79",
"@types/react-dom": "18.2.25",
"babel-preset-expo": "11.0.6",
diff --git a/packages/ui/src/components/approver/stories/approver.web.stories.tsx b/packages/ui/src/components/approver/stories/approver.web.stories.tsx
index a3848da33..8edb4c3fc 100644
--- a/packages/ui/src/components/approver/stories/approver.web.stories.tsx
+++ b/packages/ui/src/components/approver/stories/approver.web.stories.tsx
@@ -1,7 +1,7 @@
import { Meta, StoryObj } from '@storybook/react';
import { Box, Circle, Flex } from 'leather-styles/jsx';
-import { ZapIcon } from 'src/icons/index.web';
+import { ZapIcon } from '../../../icons/zap-icon.web';
import { Button } from '../../button/button.web';
import { Flag } from '../../flag/flag.web';
import { ItemLayout } from '../../item-layout/item-layout.web';
diff --git a/packages/ui/src/components/collectibles/collectibles.shared.tsx b/packages/ui/src/components/collectibles/collectibles.shared.tsx
new file mode 100644
index 000000000..7376bbf95
--- /dev/null
+++ b/packages/ui/src/components/collectibles/collectibles.shared.tsx
@@ -0,0 +1,319 @@
+/* eslint-disable */
+
+export interface Ordinal {
+ id: string;
+ number: number;
+ output: string;
+ txid: string;
+ offset: string;
+ address: string;
+ preview: string;
+ title: string;
+ genesisBlockHeight: number;
+ genesisBlockHash: string;
+ genesisTimestamp: number;
+ value: string;
+ mimeType: string;
+ name: string;
+ src: string;
+}
+
+const mockOrdinals: Ordinal[] = [
+ {
+ id: 'a494e48bf7120c959239e8c544bc821ca4fb5a46e5fff79938943d434f252949i0',
+ number: 74703951,
+ output: '0',
+ txid: 'a494e48bf7120c959239e8c544bc821ca4fb5a46e5fff79938943d434f252949',
+ offset: '0',
+ address: 'bc1pwz9n62p9dhjpqcpdmfcrewdnz3nk8jcved242vd2lj9fgvtvwnwscvdyre',
+ preview:
+ 'https://ordinals.hiro.so/inscription/a494e48bf7120c959239e8c544bc821ca4fb5a46e5fff79938943d434f252949i0',
+ title: 'Inscription 74703951',
+ genesisBlockHeight: 857719,
+ genesisBlockHash: '00000000000000000002bc6789fc6742da4958d003d3abff740687a863613a46',
+ genesisTimestamp: 1724219117,
+ value: '546',
+ mimeType: 'html',
+ name: 'inscription',
+ src: 'https://ordinals.com/preview/a494e48bf7120c959239e8c544bc821ca4fb5a46e5fff79938943d434f252949i0',
+ },
+ {
+ id: '335209b72c452f52199ae09e8ce586a451ce452c73326f01f958d8aa8417e062i0',
+ number: 73858867,
+ output: '0',
+ txid: '335209b72c452f52199ae09e8ce586a451ce452c73326f01f958d8aa8417e062',
+ offset: '0',
+ address: 'bc1pwz9n62p9dhjpqcpdmfcrewdnz3nk8jcved242vd2lj9fgvtvwnwscvdyre',
+ preview:
+ 'https://ordinals.hiro.so/inscription/335209b72c452f52199ae09e8ce586a451ce452c73326f01f958d8aa8417e062i0',
+ title: 'Inscription 73858867',
+ genesisBlockHeight: 855754,
+ genesisBlockHash: '000000000000000000021972c2000a8d347dbac1a2540112fadf81219b188796',
+ genesisTimestamp: 1723027746,
+ value: '546',
+ mimeType: 'text',
+ name: 'inscription',
+ src: 'https://bis-ord-content.fra1.cdn.digitaloceanspaces.com/ordinals/335209b72c452f52199ae09e8ce586a451ce452c73326f01f958d8aa8417e062i0',
+ },
+ {
+ id: 'cd27e71f955e021dd0840aa0544067fc92c3608009f2191a405f9f4910712b78i0',
+ number: 55549412,
+ output: '0',
+ txid: 'cd27e71f955e021dd0840aa0544067fc92c3608009f2191a405f9f4910712b78',
+ offset: '0',
+ address: 'bc1pwz9n62p9dhjpqcpdmfcrewdnz3nk8jcved242vd2lj9fgvtvwnwscvdyre',
+ preview:
+ 'https://ordinals.hiro.so/inscription/cd27e71f955e021dd0840aa0544067fc92c3608009f2191a405f9f4910712b78i0',
+ title: 'Inscription 55549412',
+ genesisBlockHeight: 825933,
+ genesisBlockHash: '00000000000000000002f95317315f9d00b2299eb3499b0f499a707506ad6735',
+ genesisTimestamp: 1705356588,
+ value: '600',
+ mimeType: 'image',
+ name: 'inscription',
+ src: 'https://bis-ord-content.fra1.cdn.digitaloceanspaces.com/ordinals/cd27e71f955e021dd0840aa0544067fc92c3608009f2191a405f9f4910712b78i0',
+ },
+ {
+ id: 'e59434da4436cbdcdcf6b7b31fb734d43b304e981a2e3b69092bd6ca83108009i1286',
+ number: 64484111,
+ output: '1287',
+ txid: 'e59434da4436cbdcdcf6b7b31fb734d43b304e981a2e3b69092bd6ca83108009',
+ offset: '0',
+ address: 'bc1pwz9n62p9dhjpqcpdmfcrewdnz3nk8jcved242vd2lj9fgvtvwnwscvdyre',
+ preview:
+ 'https://ordinals.hiro.so/inscription/e59434da4436cbdcdcf6b7b31fb734d43b304e981a2e3b69092bd6ca83108009i1286',
+ title: 'Inscription 64484111',
+ genesisBlockHeight: 834795,
+ genesisBlockHash: '00000000000000000000a3f2c9b0459df8eda99abca3c83f0e94a2a224badaba',
+ genesisTimestamp: 1710504509,
+ value: '546',
+ mimeType: 'gltf',
+ name: 'inscription',
+ src: 'https://ordinals.com/preview/e59434da4436cbdcdcf6b7b31fb734d43b304e981a2e3b69092bd6ca83108009i1286',
+ },
+];
+
+export interface StacksNft {
+ token_uri: string;
+ metadata: {
+ sip: number;
+ name: string;
+ description: string;
+ image: string;
+ cached_image: string;
+ cached_thumbnail_image: string;
+ attributes?: Array<{
+ trait_type: string;
+ value: string;
+ display_type?: string;
+ }>;
+ properties?: {
+ collection?: string;
+ collectionId?: string;
+ [key: string]: any;
+ };
+ };
+}
+
+const mockStacksNfts: StacksNft[] = [
+ {
+ token_uri: 'ipfs://ipfs/QmQ63rXC9F7GyLYoYNyqxeiYvbBUvmHmL36PrfYNxpw5sT/90.json',
+ metadata: {
+ sip: 16,
+ name: 'BlockSurvey #90',
+ description: 'Worlds First Software License as an NFT',
+ image: 'ipfs://QmZXkLMrN2ejpzGv1wk4HgcuL6XbyLVieW3Zm9wyAoDk18/90.png',
+ cached_image:
+ 'https://assets.hiro.so/api/mainnet/token-metadata-api/SPNWZ5V2TPWGQGVDR6T7B6RQ4XMGZ4PXTEE0VQ0S.blocksurvey/90.png',
+ cached_thumbnail_image:
+ 'https://assets.hiro.so/api/mainnet/token-metadata-api/SPNWZ5V2TPWGQGVDR6T7B6RQ4XMGZ4PXTEE0VQ0S.blocksurvey/90-thumb.png',
+ attributes: [
+ {
+ trait_type: 'NightBackground',
+ value: 'MidnightMoss',
+ display_type: '',
+ },
+ {
+ trait_type: 'NightLogo',
+ value: 'AtomicTangerine',
+ display_type: '',
+ },
+ {
+ trait_type: 'SignatureWhite',
+ value: 'SignatureWhite',
+ display_type: '',
+ },
+ ],
+ },
+ },
+ {
+ token_uri: 'ipfs://ipfs/QmWRQyaVxUjHGjBUoZqGcNjL37VN99jcFwmoB1wZnpjJEg/',
+ metadata: {
+ sip: 16,
+ name: 'Portals-ALEX-Anniversary-Series',
+ description: 'From proof-of-concept to bringing Bitcoin value to the multi-chain.\n',
+ image: 'ipfs://ipfs/QmUgHdbTy5LYi4wijf9YJgGs89SCAKNbXascvzuFgAsMB9',
+ cached_image:
+ 'https://assets.hiro.so/api/mainnet/token-metadata-api/SP3N7Y3K01Y24G9JC1XXA13RQXXCY721WAVBMMD38.alex-anniversary-series/1452.png',
+ cached_thumbnail_image:
+ 'https://assets.hiro.so/api/mainnet/token-metadata-api/SP3N7Y3K01Y24G9JC1XXA13RQXXCY721WAVBMMD38.alex-anniversary-series/1452-thumb.png',
+ properties: {
+ collection: 'ALEX Anniversary Series',
+ },
+ },
+ },
+ {
+ token_uri: 'ipfs://QmYTX3u58v2Ero2drdtqhL6rPE5qnv51EJZ6WSu3LKqUBN/crashpunks-5559.json',
+ metadata: {
+ sip: 16,
+ name: 'Crash Punk 5559',
+ description: '',
+ image: 'ipfs://Qmb84UcaMr1MUwNbYBnXWHM3kEaDcYrKuPWwyRLVTNKELC/5559.png',
+ cached_image:
+ 'https://assets.hiro.so/api/mainnet/token-metadata-api/SP3QSAJQ4EA8WXEDSRRKMZZ29NH91VZ6C5X88FGZQ.crashpunks-v2/5559.png',
+ cached_thumbnail_image:
+ 'https://assets.hiro.so/api/mainnet/token-metadata-api/SP3QSAJQ4EA8WXEDSRRKMZZ29NH91VZ6C5X88FGZQ.crashpunks-v2/5559-thumb.png',
+ attributes: [
+ {
+ trait_type: 'Background',
+ value: 'Blue',
+ display_type: 'string',
+ },
+ {
+ trait_type: 'Outfit Back',
+ value: 'Stacks Hoodie Back',
+ display_type: 'string',
+ },
+ {
+ trait_type: 'Neck',
+ value: 'Neck Metal',
+ display_type: 'string',
+ },
+ {
+ trait_type: 'Outfit Front',
+ value: 'Stacks Hoodie',
+ display_type: 'string',
+ },
+ {
+ trait_type: 'Head',
+ value: 'Head Tan',
+ display_type: 'string',
+ },
+ {
+ trait_type: 'Piercings',
+ value: 'Piercings',
+ display_type: 'string',
+ },
+ {
+ trait_type: 'Mouth',
+ value: 'Lips Bare',
+ display_type: 'string',
+ },
+ {
+ trait_type: 'Eyes',
+ value: 'RoboEyes Blue',
+ display_type: 'string',
+ },
+ {
+ trait_type: 'Hair',
+ value: 'Bob Silver',
+ display_type: 'string',
+ },
+ ],
+ properties: {
+ collection: 'Crash Punks',
+ collectionId: 'grace.btc/crash_punks',
+ dna: '5c2f54662bb494b5e4ebc195070d9ce624c5a849',
+ total_supply: '9216',
+ external_url:
+ 'https://thisisnumberone.com/nfts/SP3QSAJQ4EA8WXEDSRRKMZZ29NH91VZ6C5X88FGZQ.crashpunks-v2/5559',
+ },
+ },
+ },
+ {
+ token_uri: 'ipfs://ipfs/QmZYoSr94MKdarScJZSsyBYxBgMJchUQqqbtLxxxR86wZN/',
+ metadata: {
+ sip: 16,
+ name: 'WORRY - NFT - MUSIC',
+ description:
+ 'Musical NFT Collection \nWorry is a self-reflective song done by Brythreesixty also known as 3hunnatheartist. Worry is an emotional state of being anxious and troubled over actual or potential problems. The greatest weapon is positivity. Welcome to my Bullish state of Mind. This collection is a gift to the community. Enjoy \n\nhttps://gamma.io/3hunnatheartist.btc\nhttps://gamma.io/brythreesixty\n\nhttps://twitter.com/brythreesixty\nhttps://twitter.com/3hunnatheartist\n\nhttps://discord.gg/hRqeVRFG',
+ image: '',
+ cached_image: '',
+ cached_thumbnail_image: '',
+ },
+ },
+ {
+ token_uri: 'ipfs://QmbMdASbHZb5XHizZJsFPL9hdmuDgekUHH9Ya1DnuSxfHj/1547.json',
+ metadata: {
+ sip: 16,
+ name: 'StacksMFers #1547',
+ description: 'Just a bunch of mfers on stacks',
+ image: 'ipfs://QmUL7yELAmF1wnbqt6yaNLmCVbBa7BSbSNXYKijpku2r45/1547.png',
+ cached_image:
+ 'https://assets.hiro.so/api/mainnet/token-metadata-api/SP2N3BAG4GBF8NHRPH6AY4YYH1SP6NK5TGCY7RDFA.stacks-mfers/1547.png',
+ cached_thumbnail_image:
+ 'https://assets.hiro.so/api/mainnet/token-metadata-api/SP2N3BAG4GBF8NHRPH6AY4YYH1SP6NK5TGCY7RDFA.stacks-mfers/1547-thumb.png',
+ attributes: [
+ {
+ trait_type: 'BG',
+ value: 'Pixels',
+ display_type: '',
+ },
+ {
+ trait_type: 'Type',
+ value: 'Plain',
+ display_type: '',
+ },
+ {
+ trait_type: 'Eyes',
+ value: 'Greenglasses',
+ display_type: '',
+ },
+ {
+ trait_type: 'Mouth',
+ value: 'Smile',
+ display_type: '',
+ },
+ {
+ trait_type: 'Beard',
+ value: 'None',
+ display_type: '',
+ },
+ {
+ trait_type: 'Shirt',
+ value: 'GreenHoodie',
+ display_type: '',
+ },
+ {
+ trait_type: 'Accessory',
+ value: 'None',
+ display_type: '',
+ },
+ {
+ trait_type: 'LongHair',
+ value: 'LongBlue',
+ display_type: '',
+ },
+ {
+ trait_type: 'Hat under',
+ value: 'RedBandana',
+ display_type: '',
+ },
+ {
+ trait_type: 'Headphones',
+ value: 'Red',
+ display_type: '',
+ },
+ {
+ trait_type: 'Smoke',
+ value: 'None',
+ display_type: '',
+ },
+ ],
+ },
+ },
+];
+
+export type Collectible = Ordinal | StacksNft;
+
+export const mockCollectibles: (Ordinal | StacksNft)[] = [...mockOrdinals, ...mockStacksNfts];
diff --git a/packages/ui/src/components/collectibles/index.native.ts b/packages/ui/src/components/collectibles/index.native.ts
new file mode 100644
index 000000000..b7b2d41da
--- /dev/null
+++ b/packages/ui/src/components/collectibles/index.native.ts
@@ -0,0 +1,6 @@
+export * from './native/collectible-html.native';
+export * from './native/collectible-image.native';
+export * from './native/collectible-text.native';
+export * from './native/collectible-card.native';
+export * from './native/collectible-card-layout.native';
+export * from './collectibles.shared';
diff --git a/packages/ui/src/components/collectibles/index.web.ts b/packages/ui/src/components/collectibles/index.web.ts
new file mode 100644
index 000000000..3b6b44330
--- /dev/null
+++ b/packages/ui/src/components/collectibles/index.web.ts
@@ -0,0 +1,8 @@
+export * from './web/collectible-audio.web';
+export * from './web/collectible-image.web';
+export * from './web/collectible-text.web';
+export * from './web/collectible-other.web';
+export * from './web/collectible-iframe.web';
+export * from './web/image-unavailable.web';
+export * from './web/inscription-preview-card/index.web';
+export * from './web/collectible-item.layout.web';
diff --git a/packages/ui/src/components/collectibles/native/collectible-audio.native.tsx b/packages/ui/src/components/collectibles/native/collectible-audio.native.tsx
new file mode 100644
index 000000000..05e1e7f76
--- /dev/null
+++ b/packages/ui/src/components/collectibles/native/collectible-audio.native.tsx
@@ -0,0 +1,10 @@
+import { HeadsetIcon } from '../../../icons/headset-icon.native';
+import { CollectibleCardLayout } from './collectible-card-layout.native';
+
+export function CollectibleAudio() {
+ return (
+
+
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/native/collectible-card-layout.native.tsx b/packages/ui/src/components/collectibles/native/collectible-card-layout.native.tsx
new file mode 100644
index 000000000..7eb255006
--- /dev/null
+++ b/packages/ui/src/components/collectibles/native/collectible-card-layout.native.tsx
@@ -0,0 +1,14 @@
+import { BaseTheme, BoxProps } from '@shopify/restyle';
+
+import { Box, Theme } from '../../../../native';
+import { HasChildren } from '../../../utils/has-children.shared';
+
+type CollectibleCardLayoutProps = BoxProps & HasChildren;
+
+export function CollectibleCardLayout({ children, ...props }: CollectibleCardLayoutProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/native/collectible-card.native.stories.tsx b/packages/ui/src/components/collectibles/native/collectible-card.native.stories.tsx
new file mode 100644
index 000000000..c7b124978
--- /dev/null
+++ b/packages/ui/src/components/collectibles/native/collectible-card.native.stories.tsx
@@ -0,0 +1,64 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { CollectibleCard } from './collectible-card.native';
+
+const meta: Meta = {
+ title: 'Collectibles/CollectibleCard',
+ component: CollectibleCard,
+ tags: ['autodocs'],
+ argTypes: {},
+ parameters: {},
+ decorators: [Story => ],
+};
+
+export default meta;
+
+export const OrdinalHtmlCollectibleCardStory = {
+ args: {
+ mimeType: 'html',
+ name: 'Inscription 74703951',
+ src: 'https://ordinals.com/preview/a494e48bf7120c959239e8c544bc821ca4fb5a46e5fff79938943d434f252949i0',
+ type: 'inscription',
+ },
+ argTypes: {},
+} satisfies StoryObj;
+
+export const OrdinalTextCollectibleCardStory = {
+ args: {
+ mimeType: 'text',
+ name: 'Inscription 73858867',
+ src: 'https://bis-ord-content.fra1.cdn.digitaloceanspaces.com/ordinals/335209b72c452f52199ae09e8ce586a451ce452c73326f01f958d8aa8417e062i0',
+ type: 'inscription',
+ },
+ argTypes: {},
+} satisfies StoryObj;
+
+export const OrdinalImageCollectibleCardStory = {
+ args: {
+ mimeType: 'image',
+ name: 'Inscription 55549412',
+ src: 'https://bis-ord-content.fra1.cdn.digitaloceanspaces.com/ordinals/cd27e71f955e021dd0840aa0544067fc92c3608009f2191a405f9f4910712b78i0',
+ type: 'inscription',
+ },
+ argTypes: {},
+} satisfies StoryObj;
+
+export const OrdinalGltfCollectibleCardStory = {
+ args: {
+ mimeType: 'gltf',
+ name: 'Inscription 64484111',
+ src: 'https://ordinals.com/preview/e59434da4436cbdcdcf6b7b31fb734d43b304e981a2e3b69092bd6ca83108009i1286',
+ type: 'inscription',
+ },
+ argTypes: {},
+} satisfies StoryObj;
+
+export const StxNftCollectibleCardStory = {
+ args: {
+ mimeType: null,
+ name: 'BlockSurvey #90',
+ src: 'https://assets.hiro.so/api/mainnet/token-metadata-api/SPNWZ5V2TPWGQGVDR6T7B6RQ4XMGZ4PXTEE0VQ0S.blocksurvey/90.png',
+ type: 'stacks',
+ },
+ argTypes: {},
+} satisfies StoryObj;
diff --git a/packages/ui/src/components/collectibles/native/collectible-card.native.tsx b/packages/ui/src/components/collectibles/native/collectible-card.native.tsx
new file mode 100644
index 000000000..abae1b73f
--- /dev/null
+++ b/packages/ui/src/components/collectibles/native/collectible-card.native.tsx
@@ -0,0 +1,31 @@
+import { CollectibleAudio } from './collectible-audio.native';
+import { CollectibleHtml } from './collectible-html.native';
+import { CollectibleImage } from './collectible-image.native';
+import { CollectibleText } from './collectible-text.native';
+
+export interface CollectibleCardProps {
+ name: string;
+ type: 'inscription' | 'stacks';
+ src: string;
+ mimeType?: string | null;
+}
+
+export function CollectibleCard({ name, type, src, mimeType }: CollectibleCardProps) {
+ const isOrdinal = type === 'inscription';
+
+ if (isOrdinal) {
+ switch (mimeType) {
+ case 'audio':
+ return ;
+ case 'text':
+ return ;
+ case 'html':
+ case 'gltf':
+ case 'svg':
+ case 'video':
+ return ;
+ }
+ }
+
+ return ;
+}
diff --git a/packages/ui/src/components/collectibles/native/collectible-html.native.tsx b/packages/ui/src/components/collectibles/native/collectible-html.native.tsx
new file mode 100644
index 000000000..ee763c9fa
--- /dev/null
+++ b/packages/ui/src/components/collectibles/native/collectible-html.native.tsx
@@ -0,0 +1,15 @@
+import { WebView } from 'react-native-webview';
+
+import { CollectibleCardLayout } from './collectible-card-layout.native';
+
+interface CollectibleHtmlProps {
+ src: string;
+}
+
+export function CollectibleHtml({ src }: CollectibleHtmlProps) {
+ return (
+
+
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/native/collectible-image.native.tsx b/packages/ui/src/components/collectibles/native/collectible-image.native.tsx
new file mode 100644
index 000000000..c8aec4a5e
--- /dev/null
+++ b/packages/ui/src/components/collectibles/native/collectible-image.native.tsx
@@ -0,0 +1,15 @@
+import { Image } from 'react-native';
+
+import { Box } from '../../../../native';
+
+interface CollectibleImageProps {
+ alt: string;
+ source: string;
+}
+export function CollectibleImage({ alt, source }: CollectibleImageProps) {
+ return (
+
+
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/native/collectible-text.native.tsx b/packages/ui/src/components/collectibles/native/collectible-text.native.tsx
new file mode 100644
index 000000000..45b21940c
--- /dev/null
+++ b/packages/ui/src/components/collectibles/native/collectible-text.native.tsx
@@ -0,0 +1,42 @@
+import { useEffect, useState } from 'react';
+
+// import { sanitize } from 'dompurify';
+import { Text } from '../../../../native';
+import { CollectibleCardLayout } from './collectible-card-layout.native';
+
+interface CollectibleTextProps {
+ src: string;
+}
+
+export function CollectibleText({ src }: CollectibleTextProps) {
+ const [content, setContent] = useState(null);
+
+ useEffect(() => {
+ const fetchContent = async () => {
+ try {
+ const response = await fetch(src);
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ const data = await response.json();
+ setContent(JSON.stringify(data, null, 2));
+ } catch (err) {}
+ };
+
+ void fetchContent();
+ }, [src]);
+
+ return (
+
+
+ {/* FIXME
+ - implement alternative for dompurify in native
+ - I tried using jsdom as a polyfill but then hit an error Can't resolve 'vm' in jsdom
+ - maybe we should write our own sanitizer?
+ */}
+ {/* {content ? sanitize(content) : ''} */}
+ {content}
+
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/collectible-audio.web.tsx b/packages/ui/src/components/collectibles/web/collectible-audio.web.tsx
new file mode 100644
index 000000000..772b2f10e
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/collectible-audio.web.tsx
@@ -0,0 +1,18 @@
+import { ReactNode } from 'react';
+
+import { HeadsetIcon } from '../../../icons/headset-icon.web';
+import { CollectibleItemLayout, CollectibleItemLayoutProps } from './collectible-item.layout.web';
+import { CollectiblePlaceholderLayout } from './collectible-placeholder.layout.web';
+
+interface CollectibleAudioProps extends Omit {
+ icon: ReactNode;
+}
+export function CollectibleAudio({ icon, ...props }: CollectibleAudioProps) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/collectible-hover.web.tsx b/packages/ui/src/components/collectibles/web/collectible-hover.web.tsx
new file mode 100644
index 000000000..c79f64683
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/collectible-hover.web.tsx
@@ -0,0 +1,58 @@
+import { ReactNode } from 'react';
+
+import { Box } from 'leather-styles/jsx';
+
+import { ExternalLinkIcon } from '../../../icons/external-link-icon.web';
+import { IconButton } from '../../icon-button/icon-button.web';
+
+interface CollectibleHoverProps {
+ collectibleTypeIcon?: ReactNode;
+ isHovered: boolean;
+ onClickCallToAction?(): void;
+}
+export function CollectibleHover({
+ collectibleTypeIcon,
+ isHovered,
+ onClickCallToAction,
+}: CollectibleHoverProps) {
+ return (
+
+
+ {collectibleTypeIcon}
+
+ {onClickCallToAction && (
+
+ }
+ onClick={e => {
+ e.stopPropagation();
+ onClickCallToAction();
+ }}
+ />
+
+ )}
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/collectible-iframe.web.tsx b/packages/ui/src/components/collectibles/web/collectible-iframe.web.tsx
new file mode 100644
index 000000000..665b1dc70
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/collectible-iframe.web.tsx
@@ -0,0 +1,34 @@
+import { ReactNode, useState } from 'react';
+
+import { CollectibleItemLayout, CollectibleItemLayoutProps } from './collectible-item.layout.web';
+import { Iframe } from './iframe.web';
+import { ImageUnavailable } from './image-unavailable.web';
+
+interface CollectibleIframeProps extends Omit {
+ icon: ReactNode;
+ src: string;
+}
+export function CollectibleIframe({ icon, src, ...props }: CollectibleIframeProps) {
+ const [isError, setIsError] = useState(false);
+
+ if (isError)
+ return (
+
+
+
+ );
+
+ return (
+
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/collectible-image.web.tsx b/packages/ui/src/components/collectibles/web/collectible-image.web.tsx
new file mode 100644
index 000000000..54e1783b5
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/collectible-image.web.tsx
@@ -0,0 +1,48 @@
+import { ReactNode, useState } from 'react';
+
+import { CollectibleItemLayout, CollectibleItemLayoutProps } from './collectible-item.layout.web';
+import { ImageUnavailable } from './image-unavailable.web';
+
+interface CollectibleImageProps extends Omit {
+ alt?: string;
+ icon: ReactNode;
+ src: string;
+}
+export function CollectibleImage(props: CollectibleImageProps) {
+ const { alt, icon, src, ...rest } = props;
+ const [isError, setIsError] = useState(false);
+ const [isLoading, setIsLoading] = useState(true);
+ const [width, setWidth] = useState(0);
+
+ if (isError)
+ return (
+
+
+
+ );
+
+ return (
+
+ setIsError(true)}
+ loading="lazy"
+ onLoad={event => {
+ const target = event.target as HTMLImageElement;
+ setWidth(target.naturalWidth);
+ setIsLoading(false);
+ }}
+ src={src}
+ style={{
+ width: '100%',
+ height: '100%',
+ aspectRatio: '1 / 1',
+ objectFit: 'cover',
+ // display: 'none' breaks onLoad event firing
+ opacity: isLoading ? '0' : '1',
+ imageRendering: width <= 40 ? 'pixelated' : 'auto',
+ }}
+ />
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/collectible-item.layout.web.tsx b/packages/ui/src/components/collectibles/web/collectible-item.layout.web.tsx
new file mode 100644
index 000000000..e9fc593d1
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/collectible-item.layout.web.tsx
@@ -0,0 +1,119 @@
+import { ReactNode } from 'react';
+
+import { Box, Stack, styled } from 'leather-styles/jsx';
+import { token } from 'leather-styles/tokens';
+import { useHover } from 'use-events';
+
+import { CollectibleHover } from './collectible-hover.web';
+
+export interface CollectibleItemLayoutProps {
+ children: ReactNode;
+ hoverText?: string;
+ onClickCallToAction?(): void;
+ onClickLayout?(): void;
+ onClickSend?(): void;
+ collectibleTypeIcon?: ReactNode;
+ showBorder?: boolean;
+ subtitle: string;
+ title: string;
+ testId?: string;
+}
+export function CollectibleItemLayout({
+ children,
+ onClickCallToAction,
+ onClickSend,
+ onClickLayout,
+ collectibleTypeIcon,
+ showBorder,
+ subtitle,
+ title,
+ testId,
+ ...rest
+}: CollectibleItemLayoutProps) {
+ const [isHovered, bind] = useHover();
+
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ {title}
+
+
+ {subtitle}
+
+
+ {onClickSend ? (
+
+ {
+ e.stopPropagation();
+ onClickSend();
+ }}
+ px="space.03"
+ py="space.02"
+ textStyle="caption.01"
+ type="button"
+ >
+ Send
+
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/collectible-other.web.tsx b/packages/ui/src/components/collectibles/web/collectible-other.web.tsx
new file mode 100644
index 000000000..1256d7198
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/collectible-other.web.tsx
@@ -0,0 +1,23 @@
+import { Box } from 'leather-styles/jsx';
+
+import { CollectibleItemLayout, CollectibleItemLayoutProps } from './collectible-item.layout.web';
+
+interface CollectibleOtherProps extends Omit {
+ children: React.JSX.Element;
+}
+export function CollectibleOther({ children, ...props }: CollectibleOtherProps) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/collectible-placeholder.layout.web.tsx b/packages/ui/src/components/collectibles/web/collectible-placeholder.layout.web.tsx
new file mode 100644
index 000000000..d14651553
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/collectible-placeholder.layout.web.tsx
@@ -0,0 +1,19 @@
+import { Flex } from 'leather-styles/jsx';
+
+import { HasChildren } from '../../../utils/has-children.shared';
+
+export function CollectiblePlaceholderLayout({ children }: HasChildren) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/collectible-text.layout.web.tsx b/packages/ui/src/components/collectibles/web/collectible-text.layout.web.tsx
new file mode 100644
index 000000000..d42348668
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/collectible-text.layout.web.tsx
@@ -0,0 +1,31 @@
+import { sanitize } from 'dompurify';
+import { Box } from 'leather-styles/jsx';
+
+interface CollectibleTextLayoutProps {
+ children: string;
+}
+export function CollectibleTextLayout({ children }: CollectibleTextLayoutProps) {
+ return (
+
+ {sanitize(children)}
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/collectible-text.web.tsx b/packages/ui/src/components/collectibles/web/collectible-text.web.tsx
new file mode 100644
index 000000000..c7077a7c8
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/collectible-text.web.tsx
@@ -0,0 +1,15 @@
+import { CollectibleItemLayout, CollectibleItemLayoutProps } from './collectible-item.layout.web';
+import { CollectibleTextLayout } from './collectible-text.layout.web';
+
+interface CollectibleTextProps extends Omit {
+ icon: React.JSX.Element;
+ content: string;
+}
+export function CollectibleText(props: CollectibleTextProps) {
+ const { content, icon, ...rest } = props;
+ return (
+
+ {content}
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/iframe.web.tsx b/packages/ui/src/components/collectibles/web/iframe.web.tsx
new file mode 100644
index 000000000..17d5d46a3
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/iframe.web.tsx
@@ -0,0 +1,34 @@
+//
+// __ __ _____ _ _ _____ _ _ _____
+// \ \ / /\ | __ \| \ | |_ _| \ | |/ ____|
+// \ \ /\ / / \ | |__) | \| | | | | \| | | __
+// \ \/ \/ / /\ \ | _ /| . ` | | | | . ` | | |_ |
+// \ /\ / ____ \| | \ \| |\ |_| |_| |\ | |__| |
+// \/ \/_/ \_\_| \_\_| \_|_____|_| \_|\_____|
+//
+// The purpose of this iframe is to wrap content from external sources,
+// primarily for use with inscriptions. Iframes are dangerous and we
+// need to be very careful with our use of them.
+//
+// Below, we use the sandbox attribute to limit what they can do, as well as
+// disabling any interaction with pointer events and user selection.
+import { HTMLStyledProps, styled } from 'leather-styles/jsx';
+
+interface IframeProps extends HTMLStyledProps<'iframe'> {
+ onError(): void;
+ src: string;
+}
+export function Iframe({ onError, src, ...props }: IframeProps) {
+ return (
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/image-unavailable.web.tsx b/packages/ui/src/components/collectibles/web/image-unavailable.web.tsx
new file mode 100644
index 000000000..e0070e509
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/image-unavailable.web.tsx
@@ -0,0 +1,15 @@
+import { styled } from 'leather-styles/jsx';
+
+import { Eye1ClosedIcon } from '../../../../native';
+import { CollectiblePlaceholderLayout } from './collectible-placeholder.layout.web';
+
+export function ImageUnavailable() {
+ return (
+
+
+
+ Image currently unavailable
+
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-image.web.tsx b/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-image.web.tsx
new file mode 100644
index 000000000..241170bbe
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-image.web.tsx
@@ -0,0 +1,11 @@
+interface InscriptionImageProps {
+ src: string;
+}
+export function InscriptionImage({ src }: InscriptionImageProps) {
+ return (
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-metadata.web.tsx b/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-metadata.web.tsx
new file mode 100644
index 000000000..2ec881129
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-metadata.web.tsx
@@ -0,0 +1,31 @@
+import { Flex, styled } from 'leather-styles/jsx';
+
+import { Link } from '../../../../link/link.web';
+
+interface InscriptionMetadataProps {
+ action?(): void;
+ actionLabel?: string;
+ icon?: React.JSX.Element;
+ subtitle: string;
+ title: string;
+}
+export function InscriptionMetadata({
+ action,
+ actionLabel,
+ icon,
+ subtitle,
+ title,
+}: InscriptionMetadataProps) {
+ return (
+
+ {icon && icon}
+ {title}
+ {subtitle}
+ {action ? (
+ action()} textStyle="caption.01" variant="text">
+ {actionLabel}
+
+ ) : null}
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-preview.layout.web.tsx b/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-preview.layout.web.tsx
new file mode 100644
index 000000000..266415469
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-preview.layout.web.tsx
@@ -0,0 +1,19 @@
+import { Box, BoxProps } from 'leather-styles/jsx';
+
+import { HasChildren } from '../../../../../utils/has-children.shared';
+
+export function InscriptionPreviewLayout({ children, ...props }: HasChildren & BoxProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-text.layout.web.tsx b/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-text.layout.web.tsx
new file mode 100644
index 000000000..96d5ceb44
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/inscription-preview-card/components/inscription-text.layout.web.tsx
@@ -0,0 +1,31 @@
+import { sanitize } from 'dompurify';
+import { Box } from 'leather-styles/jsx';
+
+interface InscriptionTextLayoutProps {
+ content: string;
+}
+export function InscriptionTextLayout({ content }: InscriptionTextLayoutProps) {
+ return (
+
+ {sanitize(content)}
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/inscription-preview-card/index.web.ts b/packages/ui/src/components/collectibles/web/inscription-preview-card/index.web.ts
new file mode 100644
index 000000000..2c875e58a
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/inscription-preview-card/index.web.ts
@@ -0,0 +1,4 @@
+export { InscriptionPreviewCard } from './inscription-preview-card.web';
+export { InscriptionTextLayout } from './components/inscription-text.layout.web';
+export { InscriptionPreviewLayout } from './components/inscription-preview.layout.web';
+export { InscriptionImage } from './components/inscription-image.web';
diff --git a/packages/ui/src/components/collectibles/web/inscription-preview-card/inscription-preview-card.web.tsx b/packages/ui/src/components/collectibles/web/inscription-preview-card/inscription-preview-card.web.tsx
new file mode 100644
index 000000000..2abcc9ed3
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/inscription-preview-card/inscription-preview-card.web.tsx
@@ -0,0 +1,39 @@
+import { Flag } from '../../../../exports.web';
+import { InscriptionMetadata } from './components/inscription-metadata.web';
+
+interface InscriptionPreviewCardProps {
+ action?(): void;
+ actionLabel?: string;
+ hideBorder?: boolean;
+ icon?: React.JSX.Element;
+ image: React.JSX.Element;
+ subtitle: string;
+ title: string;
+}
+export function InscriptionPreviewCard({
+ action,
+ actionLabel,
+ hideBorder,
+ icon,
+ image,
+ subtitle,
+ title,
+}: InscriptionPreviewCardProps) {
+ return (
+
+
+
+ );
+}
diff --git a/packages/ui/src/components/collectibles/web/inscription-text.web.tsx b/packages/ui/src/components/collectibles/web/inscription-text.web.tsx
new file mode 100644
index 000000000..96d5ceb44
--- /dev/null
+++ b/packages/ui/src/components/collectibles/web/inscription-text.web.tsx
@@ -0,0 +1,31 @@
+import { sanitize } from 'dompurify';
+import { Box } from 'leather-styles/jsx';
+
+interface InscriptionTextLayoutProps {
+ content: string;
+}
+export function InscriptionTextLayout({ content }: InscriptionTextLayoutProps) {
+ return (
+
+ {sanitize(content)}
+
+ );
+}
diff --git a/packages/ui/src/components/spinner/index.ts b/packages/ui/src/components/spinner/index.ts
new file mode 100644
index 000000000..ac50720a8
--- /dev/null
+++ b/packages/ui/src/components/spinner/index.ts
@@ -0,0 +1,2 @@
+export { LoadingSpinner } from './loading-spinner.web';
+export { Spinner } from './spinner.web';
diff --git a/packages/ui/src/components/spinner/loading-spinner.web.tsx b/packages/ui/src/components/spinner/loading-spinner.web.tsx
new file mode 100644
index 000000000..35977c047
--- /dev/null
+++ b/packages/ui/src/components/spinner/loading-spinner.web.tsx
@@ -0,0 +1,27 @@
+import { Flex, FlexProps } from 'leather-styles/jsx';
+
+import { Spinner } from './spinner.web';
+
+export function LoadingSpinner(props: { size?: string } & FlexProps) {
+ return (
+
+
+
+ );
+}
+
+export function FullPageLoadingSpinner(props: FlexProps) {
+ return (
+
+
+
+ );
+}
+
+export function FullPageWithHeaderLoadingSpinner(props: FlexProps) {
+ return (
+
+
+
+ );
+}
diff --git a/packages/ui/src/components/spinner.web.tsx b/packages/ui/src/components/spinner/spinner.web.tsx
similarity index 100%
rename from packages/ui/src/components/spinner.web.tsx
rename to packages/ui/src/components/spinner/spinner.web.tsx
diff --git a/packages/ui/src/exports.web.ts b/packages/ui/src/exports.web.ts
index a0234749f..c81c6c3af 100644
--- a/packages/ui/src/exports.web.ts
+++ b/packages/ui/src/exports.web.ts
@@ -23,10 +23,12 @@ export * from './components/typography/index.web';
export { DynamicColorCircle } from './components/dynamic-color-circle.web';
export { Hr, DashedHr, type HrProps } from './components/hr.web';
export { Logo } from './components/logo.web';
-export { Spinner } from './components/spinner.web';
+export { LoadingSpinner, Spinner } from './components/spinner';
export { shimmerStyles } from './components/skeleton-loader/shimmer.styles.web';
export { SkeletonLoader } from './components/skeleton-loader/skeleton-loader.web';
export { Icon } from './icons/icon/icon.web';
export * from './icons/index.web';
export * from './components/approver/approver.web';
export * from './components/favicon/favicon.web';
+export { usePressable } from './hooks/use-pressable.web';
+export * from './components/collectibles/index.web';
diff --git a/packages/ui/src/hooks/use-pressable.web.tsx b/packages/ui/src/hooks/use-pressable.web.tsx
new file mode 100644
index 000000000..f1872620d
--- /dev/null
+++ b/packages/ui/src/hooks/use-pressable.web.tsx
@@ -0,0 +1,67 @@
+import { Square, SquareProps } from 'leather-styles/jsx';
+import { useFocus, useHover } from 'use-events';
+
+function ItemHover({
+ isFocused,
+ isHovered,
+ ...rest
+}: {
+ isFocused: boolean;
+ isHovered: boolean;
+} & SquareProps) {
+ return (
+
+ );
+}
+
+type HoverBind = ReturnType[1];
+type FocusBind = ReturnType[1];
+
+interface DefaultSpreadProps extends HoverBind, FocusBind {
+ cursor: 'pointer' | 'default';
+ position: 'relative';
+ zIndex: 1;
+}
+
+interface StateReturnProps {
+ isHovered: boolean;
+ isFocused: boolean;
+}
+
+type UsePressableReturn = [React.JSX.Element, DefaultSpreadProps, StateReturnProps];
+
+export function usePressable(isPressable?: boolean): UsePressableReturn {
+ const [isHovered, bind] = useHover();
+ const [isFocused, focusBind] = useFocus();
+
+ const component = ;
+ if (!isPressable)
+ return [
+ <>>,
+ // Not really this type but it's safe to spread
+ {} as unknown as DefaultSpreadProps,
+ { isFocused: false, isHovered: false } as const,
+ ];
+ return [
+ component,
+ {
+ ...bind,
+ ...focusBind,
+ cursor: isPressable ? 'pointer' : 'default',
+ position: 'relative',
+ zIndex: 1,
+ },
+ { isHovered, isFocused },
+ ];
+}
diff --git a/packages/ui/src/icons/headset-icon.native.tsx b/packages/ui/src/icons/headset-icon.native.tsx
new file mode 100644
index 000000000..a4af85f1b
--- /dev/null
+++ b/packages/ui/src/icons/headset-icon.native.tsx
@@ -0,0 +1,17 @@
+import HeadsetSmall from '../assets/icons/headset-16-16.svg';
+import Headset from '../assets/icons/headset-24-24.svg';
+import { Icon, IconProps } from './icon/icon.native';
+
+export function HeadsetIcon({ variant, ...props }: IconProps) {
+ if (variant === 'small')
+ return (
+
+
+
+ );
+ return (
+
+
+
+ );
+}
diff --git a/packages/ui/src/icons/icon/icon.native.tsx b/packages/ui/src/icons/icon/icon.native.tsx
index 2efb0866a..651664f04 100644
--- a/packages/ui/src/icons/icon/icon.native.tsx
+++ b/packages/ui/src/icons/icon/icon.native.tsx
@@ -2,8 +2,8 @@ import React from 'react';
import { SvgProps } from 'react-native-svg';
import { useTheme } from '@shopify/restyle';
-import { Theme } from 'src/theme-native';
+import { Theme } from '../../theme-native';
import { IconVariant } from './icon.shared';
export interface IconProps extends SvgProps {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1ed834157..11af4dc34 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -44,7 +44,7 @@ importers:
version: 2.2.3
'@vitest/coverage-v8':
specifier: 2.0.5
- version: 2.0.5(vitest@2.0.5(@types/node@22.6.1)(jsdom@22.1.0)(lightningcss@1.25.1)(terser@5.33.0))
+ version: 2.0.5(vitest@2.0.5(@types/node@22.6.1)(jsdom@25.0.1)(lightningcss@1.25.1)(terser@5.33.0))
dependency-cruiser:
specifier: 16.3.10
version: 16.3.10
@@ -68,7 +68,7 @@ importers:
version: 5.5.4
vitest:
specifier: 2.0.5
- version: 2.0.5(@types/node@22.6.1)(jsdom@22.1.0)(lightningcss@1.25.1)(terser@5.33.0)
+ version: 2.0.5(@types/node@22.6.1)(jsdom@25.0.1)(lightningcss@1.25.1)(terser@5.33.0)
apps/mobile:
dependencies:
@@ -393,7 +393,7 @@ importers:
version: 5.5.4
vitest:
specifier: 2.0.5
- version: 2.0.5(@types/node@20.14.0)(jsdom@22.1.0)(lightningcss@1.25.1)(terser@5.33.0)
+ version: 2.0.5(@types/node@20.14.0)(jsdom@25.0.1)(lightningcss@1.25.1)(terser@5.33.0)
packages/bitcoin:
dependencies:
@@ -481,7 +481,7 @@ importers:
version: 5.5.4
vitest:
specifier: 2.0.5
- version: 2.0.5(@types/node@22.6.1)(jsdom@22.1.0)(lightningcss@1.25.1)(terser@5.33.0)
+ version: 2.0.5(@types/node@22.6.1)(jsdom@25.0.1)(lightningcss@1.25.1)(terser@5.33.0)
packages/constants:
dependencies:
@@ -534,7 +534,7 @@ importers:
version: 8.1.0(@microsoft/api-extractor@7.47.6(@types/node@22.6.1))(@swc/core@1.7.28)(postcss@8.4.47)(typescript@5.5.4)
vitest:
specifier: 2.0.5
- version: 2.0.5(@types/node@22.6.1)(jsdom@22.1.0)(lightningcss@1.25.1)(terser@5.33.0)
+ version: 2.0.5(@types/node@22.6.1)(jsdom@25.0.1)(lightningcss@1.25.1)(terser@5.33.0)
packages/eslint-config:
dependencies:
@@ -589,7 +589,7 @@ importers:
dependencies:
'@pandacss/dev':
specifier: 0.46.1
- version: 0.46.1(jsdom@22.1.0)(typescript@5.5.4)
+ version: 0.46.1(jsdom@25.0.1)(typescript@5.5.4)
devDependencies:
'@leather.io/tokens':
specifier: workspace:*
@@ -852,6 +852,9 @@ importers:
'@shopify/restyle':
specifier: 2.4.2
version: 2.4.2(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.25.4(@babel/core@7.24.6))(@react-native/assets-registry@0.73.1)(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)
+ dompurify:
+ specifier: 3.1.4
+ version: 3.1.4
expo:
specifier: 51.0.26
version: 51.0.26(myqknr2ojwjf6ytdd6j6fwllau)
@@ -903,6 +906,12 @@ importers:
react-native-svg:
specifier: 15.2.0
version: 15.2.0(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.25.4(@babel/core@7.24.6))(@react-native/assets-registry@0.73.1)(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)
+ react-native-webview:
+ specifier: 13.8.6
+ version: 13.8.6(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.25.4(@babel/core@7.24.6))(@react-native/assets-registry@0.73.1)(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)
+ use-events:
+ specifier: 1.4.2
+ version: 1.4.2(react@18.2.0)
devDependencies:
'@babel/core':
specifier: 7.24.6
@@ -913,6 +922,9 @@ importers:
'@leather.io/eslint-config':
specifier: workspace:*
version: link:../eslint-config
+ '@leather.io/models':
+ specifier: workspace:*
+ version: link:../models
'@leather.io/panda-preset':
specifier: workspace:*
version: link:../panda-preset
@@ -921,7 +933,7 @@ importers:
version: 7.47.6(@types/node@22.6.1)
'@pandacss/dev':
specifier: 0.46.1
- version: 0.46.1(jsdom@22.1.0)(typescript@5.5.4)
+ version: 0.46.1(jsdom@25.0.1)(typescript@5.5.4)
'@react-native-async-storage/async-storage':
specifier: 1.23.1
version: 1.23.1(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.25.4(@babel/core@7.24.6))(@react-native/assets-registry@0.73.1)(@types/react@18.2.79)(react@18.2.0))
@@ -979,6 +991,9 @@ importers:
'@svgr/webpack':
specifier: 8.1.0
version: 8.1.0(typescript@5.5.4)
+ '@types/dompurify':
+ specifier: 3.0.5
+ version: 3.0.5
'@types/react':
specifier: 18.2.79
version: 18.2.79
@@ -1072,7 +1087,7 @@ importers:
version: 5.5.4
vitest:
specifier: 2.0.5
- version: 2.0.5(@types/node@22.6.1)(jsdom@22.1.0)(lightningcss@1.25.1)(terser@5.33.0)
+ version: 2.0.5(@types/node@22.6.1)(jsdom@25.0.1)(lightningcss@1.25.1)(terser@5.33.0)
packages:
@@ -4639,6 +4654,9 @@ packages:
'@types/doctrine@0.0.9':
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
+ '@types/dompurify@3.0.5':
+ resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==}
+
'@types/elliptic@6.4.18':
resolution: {integrity: sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw==}
@@ -4798,6 +4816,9 @@ packages:
'@types/tough-cookie@4.0.5':
resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
+ '@types/trusted-types@2.0.7':
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
@@ -5135,6 +5156,10 @@ packages:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
+ agent-base@7.1.1:
+ resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==}
+ engines: {node: '>= 14'}
+
aggregate-error@3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
@@ -6150,6 +6175,10 @@ packages:
resolution: {integrity: sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==}
engines: {node: '>=14'}
+ cssstyle@4.1.0:
+ resolution: {integrity: sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==}
+ engines: {node: '>=18'}
+
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@@ -6181,6 +6210,10 @@ packages:
resolution: {integrity: sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==}
engines: {node: '>=14'}
+ data-urls@5.0.0:
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
+
data-view-buffer@1.0.1:
resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==}
engines: {node: '>= 0.4'}
@@ -6397,6 +6430,9 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
+ dompurify@3.1.4:
+ resolution: {integrity: sha512-2gnshi6OshmuKil8rMZuQCGiUF3cUxHY3NGDzUAdUx/NPEe5DVnO8BDoAQouvgwnx0R/+a6jUn36Z0FSdq8vww==}
+
domutils@2.8.0:
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
@@ -7536,6 +7572,10 @@ packages:
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
engines: {node: '>=12'}
+ html-encoding-sniffer@4.0.0:
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
+
html-entities@2.5.2:
resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==}
@@ -7574,10 +7614,18 @@ packages:
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
engines: {node: '>= 6'}
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
+ https-proxy-agent@7.0.5:
+ resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==}
+ engines: {node: '>= 14'}
+
human-id@1.0.2:
resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==}
@@ -8253,6 +8301,15 @@ packages:
canvas:
optional: true
+ jsdom@25.0.1:
+ resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ canvas: ^2.11.2
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
jsesc@0.5.0:
resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==}
hasBin: true
@@ -10305,6 +10362,9 @@ packages:
reselect@5.1.1:
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
+ resize-observer-polyfill@1.5.1:
+ resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
+
resolve-cwd@3.0.0:
resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
engines: {node: '>=8'}
@@ -10390,6 +10450,9 @@ packages:
rrweb-cssom@0.6.0:
resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
+ rrweb-cssom@0.7.1:
+ resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==}
+
run-async@2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
@@ -11047,6 +11110,13 @@ packages:
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
engines: {node: '>=14.0.0'}
+ tldts-core@6.1.47:
+ resolution: {integrity: sha512-6SWyFMnlst1fEt7GQVAAu16EGgFK0cLouH/2Mk6Ftlwhv3Ol40L0dlpGMcnnNiiOMyD2EV/aF3S+U2nKvvLvrA==}
+
+ tldts@6.1.47:
+ resolution: {integrity: sha512-R/K2tZ5MiY+mVrnSkNJkwqYT2vUv1lcT6wJvd2emGaMJ7PHUGRY4e3tUsdFCXgqxi2QgbHjL3yJgXCo40v9Hxw==}
+ hasBin: true
+
tmp@0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
engines: {node: '>=0.6.0'}
@@ -11076,6 +11146,10 @@ packages:
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
engines: {node: '>=6'}
+ tough-cookie@5.0.0:
+ resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==}
+ engines: {node: '>=16'}
+
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -11086,6 +11160,10 @@ packages:
resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==}
engines: {node: '>=14'}
+ tr46@5.0.0:
+ resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
+ engines: {node: '>=18'}
+
trace-event-lib@1.4.1:
resolution: {integrity: sha512-TOgFolKG8JFY+9d5EohGWMvwvteRafcyfPWWNIqcuD1W/FUvxWcy2MSCZ/beYHM63oYPHYHCd3tkbgCctHVP7w==}
engines: {node: '>=12.0.0'}
@@ -11494,6 +11572,11 @@ packages:
'@types/react':
optional: true
+ use-events@1.4.2:
+ resolution: {integrity: sha512-CVgNgSl5dnJaHKirbWab6TtdxSnb+e5rfi4WybLFUTXweRyYO+kkBtECauHlUiZLghGTsCyRaSgOeWSETvgtmw==}
+ peerDependencies:
+ react: '>=16.8.1'
+
use-latest-callback@0.2.1:
resolution: {integrity: sha512-QWlq8Is8BGWBf883QOEQP5HWYX/kMI+JTbJ5rdtvJLmXTIh9XoHIO3PQcmQl8BU44VKxow1kbQUHa6mQSMALDQ==}
peerDependencies:
@@ -11645,6 +11728,10 @@ packages:
resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==}
engines: {node: '>=14'}
+ w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+
walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
@@ -11717,6 +11804,10 @@ packages:
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
engines: {node: '>=12'}
+ whatwg-encoding@3.1.1:
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+
whatwg-fetch@3.6.20:
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
@@ -11724,6 +11815,10 @@ packages:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
engines: {node: '>=12'}
+ whatwg-mimetype@4.0.0:
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
+
whatwg-url-without-unicode@8.0.0-3:
resolution: {integrity: sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==}
engines: {node: '>=10'}
@@ -11732,6 +11827,10 @@ packages:
resolution: {integrity: sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==}
engines: {node: '>=14'}
+ whatwg-url@14.0.0:
+ resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==}
+ engines: {node: '>=18'}
+
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
@@ -11852,6 +11951,10 @@ packages:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
+ xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
xml2js@0.6.0:
resolution: {integrity: sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==}
engines: {node: '>=4.0.0'}
@@ -14200,7 +14303,7 @@ snapshots:
'@gorhom/portal@1.0.14(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.25.4(@babel/core@7.24.6))(@react-native/assets-registry@0.73.1)(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)':
dependencies:
- nanoid: 3.3.7
+ nanoid: 3.3.4
react: 18.2.0
react-native: 0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.25.4(@babel/core@7.24.6))(@react-native/assets-registry@0.73.1)(@types/react@18.2.79)(react@18.2.0)
@@ -14680,13 +14783,13 @@ snapshots:
postcss-selector-parser: 6.1.1
ts-pattern: 5.0.8
- '@pandacss/dev@0.46.1(jsdom@22.1.0)(typescript@5.5.4)':
+ '@pandacss/dev@0.46.1(jsdom@25.0.1)(typescript@5.5.4)':
dependencies:
'@clack/prompts': 0.7.0
'@pandacss/config': 0.46.1
'@pandacss/logger': 0.46.1
- '@pandacss/node': 0.46.1(jsdom@22.1.0)(typescript@5.5.4)
- '@pandacss/postcss': 0.46.1(jsdom@22.1.0)(typescript@5.5.4)
+ '@pandacss/node': 0.46.1(jsdom@25.0.1)(typescript@5.5.4)
+ '@pandacss/postcss': 0.46.1(jsdom@25.0.1)(typescript@5.5.4)
'@pandacss/preset-panda': 0.46.1
'@pandacss/shared': 0.46.1
'@pandacss/token-dictionary': 0.46.1
@@ -14696,10 +14799,10 @@ snapshots:
- jsdom
- typescript
- '@pandacss/extractor@0.46.1(jsdom@22.1.0)(typescript@5.5.4)':
+ '@pandacss/extractor@0.46.1(jsdom@25.0.1)(typescript@5.5.4)':
dependencies:
'@pandacss/shared': 0.46.1
- ts-evaluator: 1.2.0(jsdom@22.1.0)(typescript@5.5.4)
+ ts-evaluator: 1.2.0(jsdom@25.0.1)(typescript@5.5.4)
ts-morph: 21.0.1
transitivePeerDependencies:
- jsdom
@@ -14726,14 +14829,14 @@ snapshots:
'@pandacss/types': 0.46.1
kleur: 4.1.5
- '@pandacss/node@0.46.1(jsdom@22.1.0)(typescript@5.5.4)':
+ '@pandacss/node@0.46.1(jsdom@25.0.1)(typescript@5.5.4)':
dependencies:
'@pandacss/config': 0.46.1
'@pandacss/core': 0.46.1
- '@pandacss/extractor': 0.46.1(jsdom@22.1.0)(typescript@5.5.4)
+ '@pandacss/extractor': 0.46.1(jsdom@25.0.1)(typescript@5.5.4)
'@pandacss/generator': 0.46.1
'@pandacss/logger': 0.46.1
- '@pandacss/parser': 0.46.1(jsdom@22.1.0)(typescript@5.5.4)
+ '@pandacss/parser': 0.46.1(jsdom@25.0.1)(typescript@5.5.4)
'@pandacss/shared': 0.46.1
'@pandacss/token-dictionary': 0.46.1
'@pandacss/types': 0.46.1
@@ -14761,11 +14864,11 @@ snapshots:
- jsdom
- typescript
- '@pandacss/parser@0.46.1(jsdom@22.1.0)(typescript@5.5.4)':
+ '@pandacss/parser@0.46.1(jsdom@25.0.1)(typescript@5.5.4)':
dependencies:
'@pandacss/config': 0.46.1
'@pandacss/core': 0.46.1
- '@pandacss/extractor': 0.46.1(jsdom@22.1.0)(typescript@5.5.4)
+ '@pandacss/extractor': 0.46.1(jsdom@25.0.1)(typescript@5.5.4)
'@pandacss/logger': 0.46.1
'@pandacss/shared': 0.46.1
'@pandacss/types': 0.46.1
@@ -14777,9 +14880,9 @@ snapshots:
- jsdom
- typescript
- '@pandacss/postcss@0.46.1(jsdom@22.1.0)(typescript@5.5.4)':
+ '@pandacss/postcss@0.46.1(jsdom@25.0.1)(typescript@5.5.4)':
dependencies:
- '@pandacss/node': 0.46.1(jsdom@22.1.0)(typescript@5.5.4)
+ '@pandacss/node': 0.46.1(jsdom@25.0.1)(typescript@5.5.4)
postcss: 8.4.47
transitivePeerDependencies:
- jsdom
@@ -17440,6 +17543,10 @@ snapshots:
'@types/doctrine@0.0.9': {}
+ '@types/dompurify@3.0.5':
+ dependencies:
+ '@types/trusted-types': 2.0.7
+
'@types/elliptic@6.4.18':
dependencies:
'@types/bn.js': 4.11.6
@@ -17611,6 +17718,8 @@ snapshots:
'@types/tough-cookie@4.0.5': {}
+ '@types/trusted-types@2.0.7': {}
+
'@types/unist@3.0.3': {}
'@types/use-sync-external-store@0.0.3': {}
@@ -17850,7 +17959,7 @@ snapshots:
graphql: 15.8.0
wonka: 4.0.15
- '@vitest/coverage-v8@2.0.5(vitest@2.0.5(@types/node@22.6.1)(jsdom@22.1.0)(lightningcss@1.25.1)(terser@5.33.0))':
+ '@vitest/coverage-v8@2.0.5(vitest@2.0.5(@types/node@22.6.1)(jsdom@25.0.1)(lightningcss@1.25.1)(terser@5.33.0))':
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 0.2.3
@@ -17864,7 +17973,7 @@ snapshots:
std-env: 3.7.0
test-exclude: 7.0.1
tinyrainbow: 1.2.0
- vitest: 2.0.5(@types/node@22.6.1)(jsdom@22.1.0)(lightningcss@1.25.1)(terser@5.33.0)
+ vitest: 2.0.5(@types/node@22.6.1)(jsdom@25.0.1)(lightningcss@1.25.1)(terser@5.33.0)
transitivePeerDependencies:
- supports-color
@@ -18082,6 +18191,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ agent-base@7.1.1:
+ dependencies:
+ debug: 4.3.7
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
aggregate-error@3.1.0:
dependencies:
clean-stack: 2.2.0
@@ -19324,6 +19440,11 @@ snapshots:
dependencies:
rrweb-cssom: 0.6.0
+ cssstyle@4.1.0:
+ dependencies:
+ rrweb-cssom: 0.7.1
+ optional: true
+
csstype@3.1.3: {}
csv-generate@3.4.3: {}
@@ -19351,6 +19472,12 @@ snapshots:
whatwg-mimetype: 3.0.0
whatwg-url: 12.0.1
+ data-urls@5.0.0:
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.0.0
+ optional: true
+
data-view-buffer@1.0.1:
dependencies:
call-bind: 1.0.7
@@ -19606,6 +19733,8 @@ snapshots:
dependencies:
domelementtype: 2.3.0
+ dompurify@3.1.4: {}
+
domutils@2.8.0:
dependencies:
dom-serializer: 1.4.1
@@ -21113,6 +21242,11 @@ snapshots:
dependencies:
whatwg-encoding: 2.0.0
+ html-encoding-sniffer@4.0.0:
+ dependencies:
+ whatwg-encoding: 3.1.1
+ optional: true
+
html-entities@2.5.2: {}
html-escaper@2.0.2: {}
@@ -21162,6 +21296,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.1
+ debug: 4.3.7
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
@@ -21169,6 +21311,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ https-proxy-agent@7.0.5:
+ dependencies:
+ agent-base: 7.1.1
+ debug: 4.3.7
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
human-id@1.0.2: {}
human-signals@2.1.0: {}
@@ -22002,6 +22152,35 @@ snapshots:
- supports-color
- utf-8-validate
+ jsdom@25.0.1:
+ dependencies:
+ cssstyle: 4.1.0
+ data-urls: 5.0.0
+ decimal.js: 10.4.3
+ form-data: 4.0.0
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.5
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.12
+ parse5: 7.1.2
+ rrweb-cssom: 0.7.1
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 5.0.0
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.0.0
+ ws: 8.18.0
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ optional: true
+
jsesc@0.5.0: {}
jsesc@2.5.2: {}
@@ -23669,7 +23848,7 @@ snapshots:
postcss@8.4.47:
dependencies:
- nanoid: 3.3.4
+ nanoid: 3.3.7
picocolors: 1.1.0
source-map-js: 1.2.1
@@ -24339,6 +24518,8 @@ snapshots:
reselect@5.1.1: {}
+ resize-observer-polyfill@1.5.1: {}
+
resolve-cwd@3.0.0:
dependencies:
resolve-from: 5.0.0
@@ -24434,6 +24615,9 @@ snapshots:
rrweb-cssom@0.6.0: {}
+ rrweb-cssom@0.7.1:
+ optional: true
+
run-async@2.4.1: {}
run-parallel@1.2.0:
@@ -25141,6 +25325,14 @@ snapshots:
tinyspy@3.0.2: {}
+ tldts-core@6.1.47:
+ optional: true
+
+ tldts@6.1.47:
+ dependencies:
+ tldts-core: 6.1.47
+ optional: true
+
tmp@0.0.33:
dependencies:
os-tmpdir: 1.0.2
@@ -25166,6 +25358,11 @@ snapshots:
universalify: 0.2.0
url-parse: 1.5.10
+ tough-cookie@5.0.0:
+ dependencies:
+ tldts: 6.1.47
+ optional: true
+
tr46@0.0.3: {}
tr46@1.0.1:
@@ -25176,6 +25373,11 @@ snapshots:
dependencies:
punycode: 2.3.1
+ tr46@5.0.0:
+ dependencies:
+ punycode: 2.3.1
+ optional: true
+
trace-event-lib@1.4.1:
dependencies:
browser-process-hrtime: 1.0.0
@@ -25211,14 +25413,14 @@ snapshots:
ts-dedent@2.2.0: {}
- ts-evaluator@1.2.0(jsdom@22.1.0)(typescript@5.5.4):
+ ts-evaluator@1.2.0(jsdom@25.0.1)(typescript@5.5.4):
dependencies:
ansi-colors: 4.1.3
crosspath: 2.0.0
object-path: 0.11.8
typescript: 5.5.4
optionalDependencies:
- jsdom: 22.1.0
+ jsdom: 25.0.1
ts-interface-checker@0.1.13: {}
@@ -25542,6 +25744,11 @@ snapshots:
optionalDependencies:
'@types/react': 18.2.79
+ use-events@1.4.2(react@18.2.0):
+ dependencies:
+ react: 18.2.0
+ resize-observer-polyfill: 1.5.1
+
use-latest-callback@0.2.1(react@18.2.0):
dependencies:
react: 18.2.0
@@ -25671,7 +25878,7 @@ snapshots:
lightningcss: 1.25.1
terser: 5.33.0
- vitest@2.0.5(@types/node@20.14.0)(jsdom@22.1.0)(lightningcss@1.25.1)(terser@5.33.0):
+ vitest@2.0.5(@types/node@20.14.0)(jsdom@25.0.1)(lightningcss@1.25.1)(terser@5.33.0):
dependencies:
'@ampproject/remapping': 2.3.0
'@vitest/expect': 2.0.5
@@ -25694,7 +25901,7 @@ snapshots:
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 20.14.0
- jsdom: 22.1.0
+ jsdom: 25.0.1
transitivePeerDependencies:
- less
- lightningcss
@@ -25739,12 +25946,51 @@ snapshots:
- supports-color
- terser
+ vitest@2.0.5(@types/node@22.6.1)(jsdom@25.0.1)(lightningcss@1.25.1)(terser@5.33.0):
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@vitest/expect': 2.0.5
+ '@vitest/pretty-format': 2.1.1
+ '@vitest/runner': 2.0.5
+ '@vitest/snapshot': 2.0.5
+ '@vitest/spy': 2.0.5
+ '@vitest/utils': 2.0.5
+ chai: 5.1.1
+ debug: 4.3.7
+ execa: 8.0.1
+ magic-string: 0.30.11
+ pathe: 1.1.2
+ std-env: 3.7.0
+ tinybench: 2.9.0
+ tinypool: 1.0.1
+ tinyrainbow: 1.2.0
+ vite: 5.4.7(@types/node@22.6.1)(lightningcss@1.25.1)(terser@5.33.0)
+ vite-node: 2.0.5(@types/node@22.6.1)(lightningcss@1.25.1)(terser@5.33.0)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 22.6.1
+ jsdom: 25.0.1
+ transitivePeerDependencies:
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
vlq@1.0.1: {}
w3c-xmlserializer@4.0.0:
dependencies:
xml-name-validator: 4.0.0
+ w3c-xmlserializer@5.0.0:
+ dependencies:
+ xml-name-validator: 5.0.0
+ optional: true
+
walker@1.0.8:
dependencies:
makeerror: 1.0.12
@@ -25832,10 +26078,18 @@ snapshots:
dependencies:
iconv-lite: 0.6.3
+ whatwg-encoding@3.1.1:
+ dependencies:
+ iconv-lite: 0.6.3
+ optional: true
+
whatwg-fetch@3.6.20: {}
whatwg-mimetype@3.0.0: {}
+ whatwg-mimetype@4.0.0:
+ optional: true
+
whatwg-url-without-unicode@8.0.0-3:
dependencies:
buffer: 5.7.1
@@ -25847,6 +26101,12 @@ snapshots:
tr46: 4.1.1
webidl-conversions: 7.0.0
+ whatwg-url@14.0.0:
+ dependencies:
+ tr46: 5.0.0
+ webidl-conversions: 7.0.0
+ optional: true
+
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
@@ -25972,6 +26232,9 @@ snapshots:
xml-name-validator@4.0.0: {}
+ xml-name-validator@5.0.0:
+ optional: true
+
xml2js@0.6.0:
dependencies:
sax: 1.4.1