Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ export type ScrollAreaOrientation = "vertical" | "horizontal" | "both";
export type ScrollbarVisibility = "auto" | "always" | "hidden";

export interface ScrollAreaProps extends HTMLAttributes<HTMLDivElement> {
"data-bf-component"?: string;
"data-bf-part"?: string;
orientation?: ScrollAreaOrientation;
scrollbarVisibility?: ScrollbarVisibility;
}

export const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(
function ScrollArea({
className,
"data-bf-component": component = "scroll-area",
"data-bf-part": part = "viewport",
orientation = "vertical",
scrollbarVisibility = "auto",
...props
Expand All @@ -21,9 +25,9 @@ export const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(
<div
{...props}
className={classNames(styles.root, className)}
data-bf-component="scroll-area"
data-bf-component={component}
data-bf-orientation={orientation}
data-bf-part="viewport"
data-bf-part={part}
data-bf-scrollbar-visibility={scrollbarVisibility}
ref={ref}
/>
Expand Down
1 change: 1 addition & 0 deletions design-system/packages/ui/tests/registry.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.") ||
Expand Down
13 changes: 13 additions & 0 deletions design-system/packages/ui/tests/scroll-area.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<svg
aria-hidden="true"
focusable="false"
height={size}
viewBox="0 0 24 24"
width={size}
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"
/>
</svg>
);
}

if (app === 'feishu') {
return (
<svg
aria-hidden="true"
focusable="false"
height={size}
viewBox="0 0 24 24"
width={size}
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="m12.924 12.803.057-.054q.056-.052.11-.11l.076-.076.23-.227 1.335-1.319.334-.331q.096-.094.195-.183.184-.165.376-.322a8 8 0 0 1 .896-.621 7 7 0 0 1 1.095-.533 5 5 0 0 1 .47-.164 13.2 13.2 0 0 0-2.499-5.013 1.2 1.2 0 0 0-.94-.448h-9.65c-.174 0-.247.224-.108.325a28.2 28.2 0 0 1 8 9.098q.012-.009.023-.022"
/>
<path
fill="currentColor"
d="M9.097 21.299a13.26 13.26 0 0 0 11.821-7.247q-.173.331-.379.625-.075.105-.151.205a5.3 5.3 0 0 1-.732.773q-.105.089-.214.173-.13.1-.262.186a5 5 0 0 1-.354.218q-.188.104-.381.192-.17.076-.344.14a5.319 5.319 0 0 1-1.883.312 6 6 0 0 1-.524-.032 6.53 6.53 0 0 1-.729-.126q-.09-.023-.18-.044a23 23 0 0 1-.495-.14q-.123-.035-.246-.072c-.123-.038-.246-.072-.366-.11l-.3-.095-.284-.094-.192-.067c-.08-.025-.155-.053-.234-.082a4 4 0 0 1-.167-.06q-.165-.06-.328-.12-.094-.036-.19-.072l-.252-.098c-.088-.035-.18-.07-.268-.107l-.173-.07c-.073-.028-.142-.06-.215-.088l-.164-.07q-.086-.037-.17-.075l-.149-.066-.135-.06-.14-.063-.32-.149q-.094-.044-.187-.088a6 6 0 0 1-.199-.098 27.762 27.762 0 0 1-8.067-5.969.18.18 0 0 0-.312.123l.006 9.21c0 .4.2.779.534 1a13.18 13.18 0 0 0 7.325 2.205"
/>
<path
fill="currentColor"
d="M23.732 9.295a7.55 7.55 0 0 0-3.35-.776 7.5 7.5 0 0 0-2.284.35q-.081.026-.158.05a8.318 8.318 0 0 0-.855.35q-.284.137-.552.297a7 7 0 0 0-.533.347q-.184.133-.363.275c-.13.104-.252.211-.375.321q-.099.091-.196.184l-.335.328-1.337 1.321-.23.228-.076.075q-.058.056-.11.11l-.057.054-.085.08c-.032.028-.064.06-.095.088a13.3 13.3 0 0 1-2.748 1.946q.09.043.18.082l.142.066q.066.031.139.063l.135.06.149.067.17.075.164.07c.073.031.142.06.215.088q.086.037.173.07.132.053.268.107.126.048.253.098l.189.072q.163.062.328.12c.057.019.11.041.167.06q.117.04.234.082l.192.066.284.095.3.095q.183.056.366.11l.246.072q.247.072.495.14.09.023.18.043.17.042.34.07.195.032.389.057.264.029.523.031.238.003.47-.013.223-.015.439-.05a5 5 0 0 0 .975-.25 5.427 5.427 0 0 0 .726-.334q.091-.05.182-.107.086-.053.17-.11a8 8 0 0 0 .263-.186q.11-.085.214-.174a5.3 5.3 0 0 0 .732-.773q.075-.1.151-.205.204-.294.376-.621l.13-.26 1.166-2.324-.003.006a7.4 7.4 0 0 1 1.527-2.186"
/>
</svg>
);
}

return (
<svg
aria-hidden="true"
focusable="false"
height={size}
viewBox="0 0 24 24"
width={size}
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z"
/>
</svg>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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('<ChatAppBrandIcon app={botTab} size={28} />');
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('<Send size={28} />');
expect(dialogSource).not.toContain('<MessageSquareText size={28} />');
expect(dialogSource).not.toContain('<MessagesSquare size={28} />');
});

it('keeps BitFun Page out of the account and device lifecycle', () => {
expect(accountPanelSource).not.toContain('pagesEntry');
expect(accountPanelSource).not.toContain("openScene('pages')");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -59,6 +59,7 @@ import {
stopAfterPendingStart,
updateIfOperationCurrent,
} from './remoteConnectOperationCleanup';
import { ChatAppBrandIcon } from './ChatAppBrandIcon';
import './RemoteConnectDialog.scss';

// ── Types ────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -889,16 +890,10 @@ export const RemoteConnectDialog: React.FC<RemoteConnectDialogProps> = ({
: botTab === 'feishu'
? t('remoteConnect.feishu')
: t('remoteConnect.weixin');
const icon = botTab === 'telegram'
? <Send size={28} />
: botTab === 'feishu'
? <MessageSquareText size={28} />
: <MessagesSquare size={28} />;

return (
<div className="bitfun-remote-connect__bot-identity">
<span className="bitfun-remote-connect__bot-identity-icon" aria-hidden="true">
{icon}
<ChatAppBrandIcon app={botTab} size={28} />
</span>
<h3 className="bitfun-remote-connect__bot-identity-title">{label}</h3>
<p className="bitfun-remote-connect__bot-identity-description">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ vi.mock('@bitfun/ui', () => ({
Button: ({ children, disabled, onClick }: React.ButtonHTMLAttributes<HTMLButtonElement>) => (
<button type="button" disabled={disabled} onClick={onClick}>{children}</button>
),
Icon: ({ name }: { name: string }) => <span aria-hidden="true" data-icon={name} />,
ConfirmDialog: ({ confirmText, isOpen, message, onConfirm, title }: {
confirmText?: string;
isOpen: boolean;
Expand Down
Loading