diff --git a/design-system/apps/design-lab/vite/component-detail-contract.test.mjs b/design-system/apps/design-lab/vite/component-detail-contract.test.mjs index b94d625f2c..c1f70514e1 100644 --- a/design-system/apps/design-lab/vite/component-detail-contract.test.mjs +++ b/design-system/apps/design-lab/vite/component-detail-contract.test.mjs @@ -39,7 +39,7 @@ test("preview matrices define horizontal columns for every registered state coun ); assert.match( source, - /\.component-preview-matrix\[data-state-count="6"\]\s*\{[^}]*grid-template-columns:\s*96px\s+repeat\(6, minmax\(144px, 1fr\)\)/s, + /\.component-preview-matrix\[data-state-count="6"\]\s*\{[^}]*grid-template-columns:\s*96px\s+repeat\(6, minmax\(260px, 1fr\)\)/s, ); }); diff --git a/design-system/packages/ui/src/components/ScrollArea/ScrollArea.tsx b/design-system/packages/ui/src/components/ScrollArea/ScrollArea.tsx index 646771634e..ba559fad2b 100644 --- a/design-system/packages/ui/src/components/ScrollArea/ScrollArea.tsx +++ b/design-system/packages/ui/src/components/ScrollArea/ScrollArea.tsx @@ -6,6 +6,8 @@ export type ScrollAreaOrientation = "vertical" | "horizontal" | "both"; export type ScrollbarVisibility = "auto" | "always" | "hidden"; export interface ScrollAreaProps extends HTMLAttributes { + "data-bf-component"?: string; + "data-bf-part"?: string; orientation?: ScrollAreaOrientation; scrollbarVisibility?: ScrollbarVisibility; } @@ -13,6 +15,8 @@ export interface ScrollAreaProps extends HTMLAttributes { export const ScrollArea = forwardRef( function ScrollArea({ className, + "data-bf-component": component = "scroll-area", + "data-bf-part": part = "viewport", orientation = "vertical", scrollbarVisibility = "auto", ...props @@ -21,9 +25,9 @@ export const ScrollArea = forwardRef(
diff --git a/design-system/packages/ui/tests/registry.test.mjs b/design-system/packages/ui/tests/registry.test.mjs index 386ab7cf55..f2994e9303 100644 --- a/design-system/packages/ui/tests/registry.test.mjs +++ b/design-system/packages/ui/tests/registry.test.mjs @@ -86,6 +86,7 @@ test("every registered component declares states and owned tokens", () => { token.startsWith("control.") || token.startsWith("font.") || token.startsWith("layout.") || + token.startsWith("lineHeight.") || token.startsWith("overlay.") || token.startsWith("radius.") || token.startsWith("scrollbar.") || diff --git a/design-system/packages/ui/tests/scroll-area.test.mjs b/design-system/packages/ui/tests/scroll-area.test.mjs index a1b1d8719a..41ed9f472c 100644 --- a/design-system/packages/ui/tests/scroll-area.test.mjs +++ b/design-system/packages/ui/tests/scroll-area.test.mjs @@ -30,6 +30,19 @@ test("ScrollArea exposes orientation and scrollbar visibility contracts", () => assert.match(markup, /data-bf-scrollbar-visibility="always"/); }); +test("ScrollArea preserves feature-owned appearance contracts", () => { + const markup = renderToStaticMarkup( + createElement( + ScrollArea, + { "data-bf-component": "model-settings", "data-bf-part": "root" }, + "Content", + ), + ); + + assert.match(markup, /data-bf-component="model-settings"/); + assert.match(markup, /data-bf-part="root"/); +}); + test("ScrollArea styling uses public scrollbar tokens and preserves native scrolling", async () => { const styles = await readFile( new URL("../src/components/ScrollArea/ScrollArea.module.css", import.meta.url), diff --git a/src/web-ui/src/app/components/RemoteConnectDialog/ChatAppBrandIcon.tsx b/src/web-ui/src/app/components/RemoteConnectDialog/ChatAppBrandIcon.tsx new file mode 100644 index 0000000000..9b16630628 --- /dev/null +++ b/src/web-ui/src/app/components/RemoteConnectDialog/ChatAppBrandIcon.tsx @@ -0,0 +1,73 @@ +interface ChatAppBrandIconProps { + app: 'telegram' | 'feishu' | 'weixin'; + size?: number; +} + +/** + * Monochrome contours of the actual chat-app marks. The SVGs intentionally + * inherit color from the surrounding identity badge so they work in every + * theme without replacing the recognizable brand silhouettes. + * References: telegram.org/tour/screenshots, feishu.cn, and the CC0 + * simple-icons WeChat/Telegram vectors. + */ +export const ChatAppBrandIcon = ({ app, size = 24 }: ChatAppBrandIconProps) => { + if (app === 'telegram') { + return ( + + ); + } + + if (app === 'feishu') { + return ( + + ); + } + + return ( + + ); +}; diff --git a/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.contract.test.ts b/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.contract.test.ts index 4325df25ea..0269f563bd 100644 --- a/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.contract.test.ts +++ b/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.contract.test.ts @@ -9,6 +9,10 @@ const dialogStyleSource = readFileSync( new URL('./RemoteConnectDialog.scss', import.meta.url), 'utf8', ); +const chatAppBrandIconSource = readFileSync( + new URL('./ChatAppBrandIcon.tsx', import.meta.url), + 'utf8', +); const accountPanelSource = readFileSync( new URL('./AccountPanel.tsx', import.meta.url), 'utf8', @@ -92,6 +96,17 @@ describe('Remote Connect safety contracts', () => { expect(methods).toContain("id: 'weixin'"); }); + it('uses the real monochrome app marks for every chat provider', () => { + expect(dialogSource).toContain(''); + expect(chatAppBrandIconSource).toContain("app === 'telegram'"); + expect(chatAppBrandIconSource).toContain("app === 'feishu'"); + expect(chatAppBrandIconSource.match(/viewBox="0 0 24 24"/g)).toHaveLength(3); + expect(chatAppBrandIconSource.match(/fill="currentColor"/g)).toHaveLength(5); + expect(dialogSource).not.toContain(''); + expect(dialogSource).not.toContain(''); + expect(dialogSource).not.toContain(''); + }); + it('keeps BitFun Page out of the account and device lifecycle', () => { expect(accountPanelSource).not.toContain('pagesEntry'); expect(accountPanelSource).not.toContain("openScene('pages')"); diff --git a/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.tsx b/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.tsx index 14aac22524..2ce45a3204 100644 --- a/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.tsx +++ b/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.tsx @@ -27,7 +27,7 @@ import { } from '@bitfun/ui'; import React, { useState, useEffect, useCallback, useRef } from 'react'; import { QRCodeSVG } from 'qrcode.react'; -import { MessageSquareText, MessagesSquare, Monitor, MonitorSmartphone, Radar, Send, Smartphone } from 'lucide-react'; +import { Monitor, MonitorSmartphone, Radar, Smartphone } from 'lucide-react'; import { useI18n } from '@/infrastructure/i18n'; import { getLocaleFallbackChain, type LocaleId } from '@/infrastructure/i18n/presets'; import { Select } from '@/component-library'; @@ -59,6 +59,7 @@ import { stopAfterPendingStart, updateIfOperationCurrent, } from './remoteConnectOperationCleanup'; +import { ChatAppBrandIcon } from './ChatAppBrandIcon'; import './RemoteConnectDialog.scss'; // ── Types ──────────────────────────────────────────────────────────── @@ -889,16 +890,10 @@ export const RemoteConnectDialog: React.FC = ({ : botTab === 'feishu' ? t('remoteConnect.feishu') : t('remoteConnect.weixin'); - const icon = botTab === 'telegram' - ? - : botTab === 'feishu' - ? - : ; - return (

{label}

diff --git a/src/web-ui/src/infrastructure/config/components/HooksConfig.test.tsx b/src/web-ui/src/infrastructure/config/components/HooksConfig.test.tsx index 6dc7eb9515..2f75cf948c 100644 --- a/src/web-ui/src/infrastructure/config/components/HooksConfig.test.tsx +++ b/src/web-ui/src/infrastructure/config/components/HooksConfig.test.tsx @@ -30,6 +30,7 @@ vi.mock('@bitfun/ui', () => ({ Button: ({ children, disabled, onClick }: React.ButtonHTMLAttributes) => ( ), + Icon: ({ name }: { name: string }) =>