@gluwa/connect-kit 0.2.0-next.1 → 0.2.0-next.3

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 (89) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/assets/creditwallet.png +0 -0
  3. package/assets/fonts/Inter-Medium.ttf +0 -0
  4. package/assets/fonts/Inter-Medium.woff2 +0 -0
  5. package/assets/fonts/Inter-Regular.ttf +0 -0
  6. package/assets/fonts/Inter-Regular.woff2 +0 -0
  7. package/assets/fonts/Inter-SemiBold.ttf +0 -0
  8. package/assets/fonts/Inter-SemiBold.woff2 +0 -0
  9. package/assets/graphic.png +0 -0
  10. package/assets/metamask.png +0 -0
  11. package/assets/wc-logo.svg +3 -0
  12. package/assets/wc-search.png +0 -0
  13. package/dist/chunk-EDNRW47A.js +401 -0
  14. package/dist/credit-connect.js +2 -409
  15. package/dist/index.css +1903 -390
  16. package/dist/index.d.ts +16 -10
  17. package/dist/index.js +1110 -870
  18. package/dist/package.json +7 -6
  19. package/package.json +8 -8
  20. package/src/ConnectKitProvider.tsx +90 -65
  21. package/src/assets.d.ts +5 -0
  22. package/src/components/actionItem/index.tsx +50 -0
  23. package/src/components/actionItem/style.scss +47 -0
  24. package/src/components/copyLink/index.tsx +16 -0
  25. package/src/components/copyLink/style.scss +16 -0
  26. package/src/components/qrArea/index.tsx +70 -0
  27. package/src/components/qrArea/style.scss +130 -0
  28. package/src/components/searchBar/asset/Search.tsx +20 -0
  29. package/src/components/searchBar/asset/XMarkCircle.tsx +24 -0
  30. package/src/components/searchBar/index.tsx +44 -0
  31. package/src/components/searchBar/style.scss +58 -0
  32. package/src/components/snackbar/index.tsx +25 -0
  33. package/src/components/snackbar/style.scss +51 -0
  34. package/src/components/toggle/index.tsx +27 -0
  35. package/src/components/toggle/style.scss +53 -0
  36. package/src/components/walletGrid/index.tsx +64 -0
  37. package/src/components/walletGrid/style.scss +108 -0
  38. package/src/components/walletItem/index.tsx +49 -0
  39. package/src/components/walletItem/style.scss +70 -0
  40. package/src/connector-meta.ts +12 -7
  41. package/src/core/config.ts +20 -1
  42. package/src/creditConnectConnector.ts +17 -25
  43. package/src/hooks/useWCState.ts +22 -5
  44. package/src/hooks/useWagmiConnect.ts +0 -1
  45. package/src/index.ts +2 -2
  46. package/src/layout/allWallets/index.tsx +52 -0
  47. package/src/layout/allWallets/style.scss +62 -0
  48. package/src/layout/connectDialog/asset/backIcon.tsx +24 -0
  49. package/src/layout/connectDialog/asset/checkIcon.tsx +28 -0
  50. package/src/layout/connectDialog/asset/closeIcon.tsx +21 -0
  51. package/src/layout/connectDialog/asset/copyIcon.tsx +28 -0
  52. package/src/layout/connectDialog/asset/linkIcon.tsx +24 -0
  53. package/src/layout/connectDialog/asset/spinner.tsx +21 -0
  54. package/src/layout/connectDialog/idle/index.tsx +32 -0
  55. package/src/layout/connectDialog/idle/style.scss +61 -0
  56. package/src/layout/connectDialog/index.tsx +286 -0
  57. package/src/layout/connectDialog/loading/index.tsx +13 -0
  58. package/src/layout/connectDialog/loading/style.scss +19 -0
  59. package/src/layout/connectDialog/metaMask/index.tsx +61 -0
  60. package/src/layout/connectDialog/metaMask/style.scss +65 -0
  61. package/src/layout/connectDialog/qr/index.tsx +33 -0
  62. package/src/layout/connectDialog/style.scss +95 -0
  63. package/src/layout/connectDialog/timeout/index.tsx +31 -0
  64. package/src/layout/connectDialog/timeout/style.scss +64 -0
  65. package/src/layout/connectDialog/walletConnect/index.tsx +45 -0
  66. package/src/layout/connectDialog/walletConnect/style.scss +48 -0
  67. package/src/layout/connectDialog/walletQR/index.tsx +37 -0
  68. package/src/layout/detailPanel/index.tsx +147 -0
  69. package/src/layout/detailPanel/style.scss +64 -0
  70. package/src/layout/selectorPanel/index.tsx +100 -0
  71. package/src/layout/selectorPanel/style.scss +113 -0
  72. package/src/style/color.scss +417 -0
  73. package/src/style/font.scss +28 -0
  74. package/src/style/index.scss +5 -0
  75. package/src/style/mixin.scss +217 -0
  76. package/src/style/reset.scss +110 -0
  77. package/src/style/variables.scss +13 -0
  78. package/src/types.ts +11 -8
  79. package/src/utils/platform.ts +6 -0
  80. package/tsup.config.ts +32 -2
  81. package/dist/chunk-DDA6FP6U.js +0 -7
  82. package/src/ConnectModal.scss +0 -665
  83. package/src/ConnectModal.tsx +0 -573
  84. package/src/components/QRFrame.tsx +0 -43
  85. package/src/views/CreditWalletView.tsx +0 -58
  86. package/src/views/IdleView.tsx +0 -10
  87. package/src/views/MetaMaskView.tsx +0 -66
  88. package/src/views/SwitchChainView.tsx +0 -72
  89. package/src/views/WalletConnectView.tsx +0 -206
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gluwa/connect-kit",
3
- "version": "0.2.0-next.1",
3
+ "version": "0.2.0-next.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -11,6 +11,7 @@
11
11
  "types": "./src/credit-connect.ts",
12
12
  "default": "./src/credit-connect.ts"
13
13
  },
14
+ "./index.css": "./dist/index.css",
14
15
  "./package.json": "./package.json"
15
16
  },
16
17
  "publishConfig": {
@@ -45,7 +46,11 @@
45
46
  "dependencies": {
46
47
  "qrcode.react": "^4.2.0",
47
48
  "wagmi": "^2.15.6",
48
- "@wagmi/core": "^2.16.7"
49
+ "@wagmi/core": "^2.16.7",
50
+ "@gluwa/credit-connect-sdk": "workspace:*",
51
+ "@gluwa/credit-connect-storage-local": "workspace:*",
52
+ "@gluwa/credit-connect-e2ee-tweetnacl": "workspace:*",
53
+ "@gluwa/credit-connect-hub-node-ws": "workspace:*"
49
54
  },
50
55
  "devDependencies": {
51
56
  "@gluwa/credit-connect-sdk": "workspace:*",
@@ -62,15 +67,11 @@
62
67
  "wagmi": "^2.15.6"
63
68
  },
64
69
  "peerDependencies": {
65
- "@gluwa/credit-connect-sdk": "workspace:*",
66
70
  "@tanstack/react-query": ">=5.0.0",
67
71
  "react": "^18.0.0 || ^19.0.0",
68
72
  "viem": ">=2.0.0"
69
73
  },
70
74
  "peerDependenciesMeta": {
71
- "@gluwa/credit-connect-sdk": {
72
- "optional": true
73
- },
74
75
  "@tanstack/react-query": {
75
76
  "optional": true
76
77
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gluwa/connect-kit",
3
- "version": "0.2.0-next.1",
3
+ "version": "0.2.0-next.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -25,7 +25,11 @@
25
25
  "dependencies": {
26
26
  "qrcode.react": "^4.2.0",
27
27
  "wagmi": "^2.15.6",
28
- "@wagmi/core": "^2.16.7"
28
+ "@wagmi/core": "^2.16.7",
29
+ "@gluwa/credit-connect-sdk": "0.2.0-next.3",
30
+ "@gluwa/credit-connect-storage-local": "0.2.0-next.3",
31
+ "@gluwa/credit-connect-e2ee-tweetnacl": "0.2.0-next.3",
32
+ "@gluwa/credit-connect-hub-node-ws": "0.1.0-next.3"
29
33
  },
30
34
  "devDependencies": {
31
35
  "@tanstack/react-query": "^5.62.0",
@@ -39,18 +43,14 @@
39
43
  "typescript-eslint": "^8.18.2",
40
44
  "viem": "^2.31.7",
41
45
  "wagmi": "^2.15.6",
42
- "@gluwa/credit-connect-sdk": "0.2.0-next.1"
46
+ "@gluwa/credit-connect-sdk": "0.2.0-next.3"
43
47
  },
44
48
  "peerDependencies": {
45
49
  "@tanstack/react-query": ">=5.0.0",
46
50
  "react": "^18.0.0 || ^19.0.0",
47
- "viem": ">=2.0.0",
48
- "@gluwa/credit-connect-sdk": "0.2.0-next.1"
51
+ "viem": ">=2.0.0"
49
52
  },
50
53
  "peerDependenciesMeta": {
51
- "@gluwa/credit-connect-sdk": {
52
- "optional": true
53
- },
54
54
  "@tanstack/react-query": {
55
55
  "optional": true
56
56
  }
@@ -9,15 +9,20 @@ import {
9
9
  type FC,
10
10
  type ReactNode,
11
11
  } from 'react';
12
- import { WagmiProvider, useAccount, useDisconnect, useSwitchChain } from 'wagmi';
12
+ import { WagmiProvider, useAccount, useConfig, useDisconnect, useSwitchChain } from 'wagmi';
13
+ import { getAccount } from '@wagmi/core';
13
14
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
14
- import { ConnectModal } from './ConnectModal';
15
+ // import { ConnectModal } from './ConnectModal';
16
+ import { ConnectDialog } from './layout/connectDialog';
15
17
  import { getConfig, getKitConfig, initConfig } from './core/config';
18
+ import { resolveConnectorId } from './connector-meta';
16
19
  import type {
17
20
  ConnectErrorContext,
18
21
  ConnectKitConfig,
22
+ ConnectKitTheme,
19
23
  ConnectResult,
20
24
  ConnectorId,
25
+ ConnectorKey,
21
26
  Connectors,
22
27
  } from './types';
23
28
 
@@ -28,16 +33,21 @@ export interface ConnectKitProviderProps {
28
33
  kitConfig?: ConnectKitConfig;
29
34
  connectors: Connectors;
30
35
  requiredChainId?: number;
36
+ theme: ConnectKitTheme;
31
37
  onConnect?: (result: ConnectResult) => void;
32
38
  onLog?: (message: string, error?: Error) => void;
33
39
  renderConnectError?: (ctx: ConnectErrorContext) => ReactNode;
34
40
  }
35
-
36
41
  export interface ConnectKitContextValue {
37
42
  isOpen: boolean;
38
43
  open: () => void;
39
44
  close: () => void;
40
45
  isConnected: boolean;
46
+ needsChainSwitch: boolean;
47
+ requiredChainId: number | undefined;
48
+ currentChainId: number | undefined;
49
+ isSwitchingChain: boolean;
50
+ switchToRequiredChain: () => Promise<void>;
41
51
  address: `0x${string}` | undefined;
42
52
  connectorId: ConnectorId | null;
43
53
  disconnect: () => Promise<void>;
@@ -45,14 +55,16 @@ export interface ConnectKitContextValue {
45
55
 
46
56
  const ConnectKitContext = createContext<ConnectKitContextValue | null>(null);
47
57
 
48
- const resolveConnectorId = (wagmiConnectorId: string | undefined): ConnectorId | null => {
49
- if (!wagmiConnectorId) return null;
50
- if (wagmiConnectorId === 'credit-connect') return 'CREDIT_CONNECT';
51
- if (wagmiConnectorId === 'walletConnect') return 'WALLET_CONNECT';
52
- const lower = wagmiConnectorId.toLowerCase();
53
- if (lower.includes('metamask') || wagmiConnectorId === 'injected') return 'METAMASK';
54
- if (lower.includes('walletconnect')) return 'WALLET_CONNECT';
55
- return null;
58
+ // 외부 API Connectors (ConnectorKey 배열) 내부 ConnectorId[] 매핑.
59
+ // 입력 순서 그대로 selector 표시 순서.
60
+ const CONNECTOR_KEY_TO_ID: Record<ConnectorKey, ConnectorId> = {
61
+ creditWallet: 'CREDIT_CONNECT',
62
+ metamask: 'METAMASK',
63
+ walletConnect: 'WALLET_CONNECT',
64
+ };
65
+ const resolveConnectorsFromConfig = (connectorsConfig?: Connectors): ConnectorId[] => {
66
+ if (!connectorsConfig) return [];
67
+ return connectorsConfig.map((key) => CONNECTOR_KEY_TO_ID[key]);
56
68
  };
57
69
 
58
70
  export const ConnectKitProvider: FC<ConnectKitProviderProps> = (props) => {
@@ -81,14 +93,17 @@ const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
81
93
  connectors,
82
94
  kitConfig,
83
95
  requiredChainId,
96
+ theme,
84
97
  onConnect,
85
98
  onLog,
86
- renderConnectError,
87
99
  }) => {
100
+ // NOTE: renderConnectError 는 ConnectDialog 가 아직 error UI customization 을 받지 않으므로
101
+ // destructure 에서 제외. 추후 ConnectDialog 의 timeout/error content 에 wire 필요 시 복원.
88
102
  const [isOpen, setIsOpen] = useState(false);
89
103
  const [pendingResult, setPendingResult] = useState<ConnectResult | null>(null);
90
104
 
91
105
  const account = useAccount();
106
+ const wagmiConfig = useConfig();
92
107
  const { disconnectAsync } = useDisconnect();
93
108
  const { switchChainAsync } = useSwitchChain();
94
109
 
@@ -97,14 +112,6 @@ const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
97
112
  onConnectRef.current = onConnect;
98
113
  }, [onConnect]);
99
114
 
100
- const chainMismatch =
101
- requiredChainId != null &&
102
- account.status === 'connected' &&
103
- account.chainId != null &&
104
- account.chainId !== requiredChainId;
105
-
106
- const [autoSwitchFailedKey, setAutoSwitchFailedKey] = useState<string | null>(null);
107
- const autoSwitchInFlightRef = useRef(false);
108
115
  const isMountedRef = useRef(true);
109
116
  useEffect(() => {
110
117
  isMountedRef.current = true;
@@ -113,39 +120,15 @@ const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
113
120
  };
114
121
  }, []);
115
122
 
116
- const mismatchKey =
117
- chainMismatch && account.chainId != null && requiredChainId != null
118
- ? `${account.chainId}->${requiredChainId}`
119
- : null;
123
+ const needsChainSwitch =
124
+ requiredChainId != null &&
125
+ account.status === 'connected' &&
126
+ account.chainId != null &&
127
+ account.chainId !== requiredChainId;
120
128
 
121
- useEffect(() => {
122
- if (!chainMismatch || requiredChainId == null) return;
123
- if (autoSwitchInFlightRef.current) return;
124
- if (mismatchKey != null && mismatchKey === autoSwitchFailedKey) return;
125
-
126
- autoSwitchInFlightRef.current = true;
127
- switchChainAsync({ chainId: requiredChainId })
128
- .catch((err: unknown) => {
129
- if (!isMountedRef.current) return;
130
- const normalized = err instanceof Error ? err : new Error(String(err));
131
- onLog?.(`[connect-kit] silent switch chain failed: ${normalized.message}`, normalized);
132
- if (mismatchKey != null) {
133
- setAutoSwitchFailedKey(mismatchKey);
134
- }
135
- })
136
- .finally(() => {
137
- autoSwitchInFlightRef.current = false;
138
- });
139
- }, [chainMismatch, mismatchKey, requiredChainId, autoSwitchFailedKey, switchChainAsync, onLog]);
129
+ const [isSwitchingChain, setIsSwitchingChain] = useState(false);
140
130
 
141
- useEffect(() => {
142
- if (!chainMismatch && autoSwitchFailedKey != null) {
143
- setAutoSwitchFailedKey(null);
144
- }
145
- }, [chainMismatch, autoSwitchFailedKey]);
146
-
147
- const autoSwitchFailedForCurrent = mismatchKey != null && mismatchKey === autoSwitchFailedKey;
148
- const effectiveOpen = isOpen || (chainMismatch && autoSwitchFailedForCurrent);
131
+ const effectiveOpen = isOpen;
149
132
 
150
133
  const open = useCallback((): void => {
151
134
  setIsOpen(true);
@@ -153,29 +136,62 @@ const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
153
136
 
154
137
  const close = useCallback((): void => {
155
138
  setIsOpen(false);
156
- }, []);
139
+ const status = getAccount(wagmiConfig).status;
140
+ if (status === 'connecting' || status === 'reconnecting') {
141
+ disconnectAsync().catch(() => undefined);
142
+ }
143
+ }, [wagmiConfig, disconnectAsync]);
157
144
 
158
145
  const disconnect = useCallback(async (): Promise<void> => {
159
146
  await disconnectAsync();
160
147
  }, [disconnectAsync]);
161
148
 
162
- const handleModalConnect = useCallback((result: ConnectResult): void => {
149
+ const switchToRequiredChain = useCallback(async (): Promise<void> => {
150
+ if (requiredChainId == null) return;
151
+ setIsSwitchingChain(true);
152
+ try {
153
+ await switchChainAsync({ chainId: requiredChainId });
154
+ } finally {
155
+ if (isMountedRef.current) setIsSwitchingChain(false);
156
+ }
157
+ }, [requiredChainId, switchChainAsync]);
158
+
159
+ // ConnectDialog 의 wagmi connect 성공 → pendingResult set → 아래 useEffect 가
160
+ // account.status === 'connected' 확인 후 외부 onConnect 호출 + modal close.
161
+ // wagmi state 가 비동기로 'connected' 로 전환되는 race 흡수용.
162
+ const handleDialogConnect = useCallback((result: ConnectResult): void => {
163
163
  setPendingResult(result);
164
164
  }, []);
165
165
 
166
- useEffect(() => {
166
+ const handleDialogError = useCallback(
167
+ (error: Error, connectorId: ConnectorId): void => {
168
+ onLog?.(`[connect-kit] connect failed: ${connectorId} — ${error.message}`, error);
169
+ },
170
+ [onLog],
171
+ );
172
+
173
+ // 연결 성공 시 modal 자동 close — wagmi state 만 기준으로.
174
+ // 이전 구현은 pendingResult 가 set 된 경우만 close 했는데, `setPendingResult` 는
175
+ // ConnectDialog 의 onConnect (`connectAsync.then`) 안에서만 호출됨. MM deeplink 같은
176
+ // 시나리오 (앱 이동 → 승인 → 복귀) 에서 wagmi 가 자체적으로 'connected' 로 전이하지만
177
+ // connectAsync 의 promise 는 resolve 안 될 수 있음 → pendingResult set 안 됨 → modal 안 닫힘.
178
+ // 모든 connector (WC/CC/MM) 의 connected 가 일관된 close 트리거가 되도록 단순화.
179
+ useEffect((): void => {
180
+ if (!effectiveOpen) return;
181
+ if (account.status !== 'connected') return;
182
+ setIsOpen(false);
183
+ }, [effectiveOpen, account.status]);
184
+
185
+ // 외부 onConnect 콜백은 best-effort — pendingResult 가 set 된 경우만 호출.
186
+ useEffect((): void => {
167
187
  if (!pendingResult) return;
168
188
  if (account.status !== 'connected') {
169
189
  setPendingResult(null);
170
190
  return;
171
191
  }
172
- if (requiredChainId != null && account.chainId !== requiredChainId) {
173
- return;
174
- }
175
192
  onConnectRef.current?.(pendingResult);
176
193
  setPendingResult(null);
177
- setIsOpen(false);
178
- }, [pendingResult, account.status, account.chainId, requiredChainId]);
194
+ }, [pendingResult, account.status]);
179
195
 
180
196
  const value = useMemo<ConnectKitContextValue>(
181
197
  () => ({
@@ -183,6 +199,11 @@ const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
183
199
  open,
184
200
  close,
185
201
  isConnected: account.status === 'connected',
202
+ needsChainSwitch,
203
+ requiredChainId,
204
+ currentChainId: account.chainId,
205
+ isSwitchingChain,
206
+ switchToRequiredChain,
186
207
  address: account.address,
187
208
  connectorId: resolveConnectorId(account.connector?.id),
188
209
  disconnect,
@@ -192,6 +213,11 @@ const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
192
213
  open,
193
214
  close,
194
215
  account.status,
216
+ needsChainSwitch,
217
+ requiredChainId,
218
+ account.chainId,
219
+ isSwitchingChain,
220
+ switchToRequiredChain,
195
221
  account.address,
196
222
  account.connector?.id,
197
223
  disconnect,
@@ -201,15 +227,14 @@ const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
201
227
  return (
202
228
  <ConnectKitContext.Provider value={value}>
203
229
  {children}
204
- <ConnectModal
230
+ <ConnectDialog
205
231
  open={effectiveOpen}
206
- connectors={connectors}
232
+ theme={theme}
233
+ connectors={resolveConnectorsFromConfig(connectors)}
207
234
  wcProjectId={kitConfig.wcProjectId}
208
- requiredChainId={requiredChainId}
209
- renderConnectError={renderConnectError}
210
- onConnect={handleModalConnect}
211
235
  onClose={close}
212
- onLog={onLog}
236
+ onConnect={handleDialogConnect}
237
+ onError={handleDialogError}
213
238
  />
214
239
  </ConnectKitContext.Provider>
215
240
  );
package/src/assets.d.ts CHANGED
@@ -2,3 +2,8 @@ declare module '*.png' {
2
2
  const src: string;
3
3
  export default src;
4
4
  }
5
+
6
+ declare module '*.svg' {
7
+ const src: string;
8
+ export default src;
9
+ }
@@ -0,0 +1,50 @@
1
+ import { type ReactNode, type AnchorHTMLAttributes, type ButtonHTMLAttributes } from 'react';
2
+ import './style.scss';
3
+
4
+ interface ActionItemContent {
5
+ label?: string;
6
+ description?: string;
7
+ action?: string;
8
+ children?: ReactNode;
9
+ }
10
+
11
+ type ActionItemAsAnchor = { as: 'a' } & AnchorHTMLAttributes<HTMLAnchorElement> & ActionItemContent;
12
+ type ActionItemAsButton = { as: 'button' } & ButtonHTMLAttributes<HTMLButtonElement> &
13
+ ActionItemContent;
14
+
15
+ type ActionItemProps = ActionItemAsAnchor | ActionItemAsButton;
16
+
17
+ const ActionItemBody = ({ label, description, action, children }: ActionItemContent): ReactNode => {
18
+ if (children) return <>{children}</>;
19
+ return (
20
+ <>
21
+ <span className="ck-action-item-content">
22
+ <span className="ck-action-item-label">{label}</span>
23
+ {description && <span className="ck-action-item-desc">{description}</span>}
24
+ </span>
25
+ {action && <span className="ck-action-item-action">{action}</span>}
26
+ </>
27
+ );
28
+ };
29
+
30
+ export const ActionItem = (props: ActionItemProps) => {
31
+ if (props.as === 'a') {
32
+ const { as: _, label, description, action, children, className, ...rest } = props;
33
+ return (
34
+ <a className={`ck-action-item${className ? ` ${className}` : ''}`} {...rest}>
35
+ <ActionItemBody label={label} description={description} action={action}>
36
+ {children}
37
+ </ActionItemBody>
38
+ </a>
39
+ );
40
+ }
41
+
42
+ const { as: _, label, description, action, children, className, ...rest } = props;
43
+ return (
44
+ <button className={`ck-action-item${className ? ` ${className}` : ''}`} {...rest}>
45
+ <ActionItemBody label={label} description={description} action={action}>
46
+ {children}
47
+ </ActionItemBody>
48
+ </button>
49
+ );
50
+ };
@@ -0,0 +1,47 @@
1
+ @use '../../style/mixin.scss' as *;
2
+
3
+ .ck-root .ck-action-item {
4
+ display: flex;
5
+ align-items: center;
6
+ gap: 16px;
7
+ width: 100%;
8
+ padding: 16px 20px;
9
+ border-radius: 12px;
10
+ background: var(--scale-gray-100);
11
+ cursor: pointer;
12
+ margin-top: auto;
13
+ flex-shrink: 0;
14
+ text-decoration: none;
15
+ }
16
+
17
+ .ck-action-item-content {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 2px;
21
+ flex: 1;
22
+ min-width: 0;
23
+ }
24
+
25
+ .ck-action-item-label {
26
+ @include textToken('subtitle2-bold');
27
+ color: var(--scale-gray-900);
28
+ }
29
+
30
+ .ck-action-item-desc {
31
+ @include textToken('label5-regular');
32
+ color: var(--scale-gray-600);
33
+ }
34
+
35
+ .ck-action-item-action {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ padding: 0 20px;
40
+ height: 44px;
41
+ border-radius: 6px;
42
+ background: var(--scale-blue2-alpha-100);
43
+ @include textToken('label5-bold');
44
+ color: var(--semantic-brand-trugi-primary);
45
+ white-space: nowrap;
46
+ flex-shrink: 0;
47
+ }
@@ -0,0 +1,16 @@
1
+ import { type FC } from 'react';
2
+ import './style.scss';
3
+ import { CopyIcon } from '../../layout/connectDialog/asset/copyIcon';
4
+
5
+ interface CopyLinkProps {
6
+ onCopy?: () => void;
7
+ }
8
+
9
+ export const CopyLink: FC<CopyLinkProps> = ({ onCopy }) => {
10
+ return (
11
+ <button type="button" className="ck-copy-link" onClick={onCopy}>
12
+ <CopyIcon className="ck-copy-link-icon" />
13
+ Copy link
14
+ </button>
15
+ );
16
+ };
@@ -0,0 +1,16 @@
1
+ @use '../../style/mixin.scss' as *;
2
+
3
+ .ck-root .ck-copy-link {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ align-self: center;
7
+ gap: 6px;
8
+ @include textToken('body3-l1-regular');
9
+ color: var(--scale-gray-500);
10
+ cursor: pointer;
11
+ }
12
+
13
+ .ck-copy-link-icon {
14
+ flex-shrink: 0;
15
+ color: var(--scale-gray-500);
16
+ }
@@ -0,0 +1,70 @@
1
+ import { type FC, type CSSProperties } from 'react';
2
+ import { QRCodeSVG } from 'qrcode.react';
3
+ import { CopyLink } from '../copyLink';
4
+ import './style.scss';
5
+
6
+ export type QrAreaStatus = 'loading' | 'ready' | 'timeout';
7
+
8
+ interface QrAreaProps {
9
+ uri?: string | null;
10
+ status: QrAreaStatus;
11
+ logoUrl?: string;
12
+ hasBg?: boolean;
13
+ label?: string;
14
+ onCopy?: () => void;
15
+ }
16
+
17
+ const BG_COLOR = '#ffffff';
18
+ const FG_COLOR = '#000000';
19
+ const FG_GRADIENT = 'url(#ck-qr-gradient)';
20
+
21
+ export const QrArea: FC<QrAreaProps> = ({ uri, status, logoUrl, hasBg = false, label, onCopy }) => {
22
+ const bgColor = hasBg ? BG_COLOR : 'transparent';
23
+ const fgColor = hasBg ? FG_COLOR : FG_GRADIENT;
24
+
25
+ return (
26
+ <div className="ck-qr-area">
27
+ <div className="ck-qr-group">
28
+ <figure className={`ck-qr ck-qr-${status}`} aria-label="QR Code">
29
+ {status === 'loading' && (
30
+ <div className="ck-qr-skeleton" aria-busy="true" aria-label="Loading QR code" />
31
+ )}
32
+ {status === 'ready' && uri && (
33
+ <div
34
+ className="ck-qr-frame"
35
+ style={{ 'background': bgColor, '--ck-qr-bg': bgColor } as CSSProperties}
36
+ >
37
+ {!hasBg && (
38
+ <svg width="0" height="0" style={{ position: 'absolute' }} aria-hidden="true">
39
+ <defs>
40
+ <linearGradient id="ck-qr-gradient" x1="67%" y1="11%" x2="45%" y2="99%">
41
+ <stop offset="0%" stopColor="#7B91FF" />
42
+ <stop offset="100%" stopColor="#61BCFF" />
43
+ </linearGradient>
44
+ </defs>
45
+ </svg>
46
+ )}
47
+ <QRCodeSVG
48
+ value={uri}
49
+ size={288}
50
+ bgColor={bgColor}
51
+ fgColor={fgColor}
52
+ level="M"
53
+ marginSize={0}
54
+ imageSettings={
55
+ logoUrl ? { src: logoUrl, width: 68, height: 68, excavate: true } : undefined
56
+ }
57
+ />
58
+ </div>
59
+ )}
60
+ </figure>
61
+ {(label || onCopy) && (
62
+ <div className="ck-qr-text">
63
+ {label && <p className="ck-qr-label">{label}</p>}
64
+ {onCopy && <CopyLink onCopy={onCopy} />}
65
+ </div>
66
+ )}
67
+ </div>
68
+ </div>
69
+ );
70
+ };