From 80c78995c3eb28df43dd77b7096bf4d3f38ce08b Mon Sep 17 00:00:00 2001 From: joaner Date: Thu, 4 Jun 2026 12:25:38 +0800 Subject: [PATCH 1/4] fix(css): scope Tailwind preflight to #rosview-root for embed hosts Disable global preflight and inject an equivalent reset under #rosview-root so importing @ioai/rosview/style.css no longer resets host navbar/button styles. --- src/index.css | 72 ++++++++++++++++++++++++++++++++++++++++++++++ tailwind.config.js | 3 ++ 2 files changed, 75 insertions(+) diff --git a/src/index.css b/src/index.css index 6680c31..ba59c06 100644 --- a/src/index.css +++ b/src/index.css @@ -2,6 +2,78 @@ @tailwind components; @tailwind utilities; +/* ── Scoped preflight ───────────────────────────────────────────── + Replaces Tailwind's global CSS reset with one limited to + #rosview-root so it doesn't bleed into the host application. + ──────────────────────────────────────────────────────────────── */ +@layer base { + #rosview-root *, #rosview-root ::before, #rosview-root ::after { + box-sizing: border-box; + border-width: 0; + border-style: solid; + border-color: hsl(var(--border)); + } + #rosview-root ::before, #rosview-root ::after { --tw-content: ''; } + #rosview-root { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji"; + font-feature-settings: normal; + font-variation-settings: normal; + -webkit-tap-highlight-color: transparent; + } + #rosview-root h1, #rosview-root h2, #rosview-root h3, + #rosview-root h4, #rosview-root h5, #rosview-root h6 { + font-size: inherit; + font-weight: inherit; + } + #rosview-root a { color: inherit; text-decoration: inherit; } + #rosview-root b, #rosview-root strong { font-weight: bolder; } + #rosview-root code, #rosview-root kbd, #rosview-root samp, #rosview-root pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + font-size: 1em; + } + #rosview-root table { text-indent: 0; border-color: inherit; border-collapse: collapse; } + #rosview-root button, #rosview-root input, #rosview-root optgroup, + #rosview-root select, #rosview-root textarea { + font-family: inherit; + font-size: 100%; + font-weight: inherit; + line-height: inherit; + color: inherit; + margin: 0; + padding: 0; + } + #rosview-root button, #rosview-root select { text-transform: none; } + #rosview-root button, + #rosview-root [type='button'], + #rosview-root [type='reset'], + #rosview-root [type='submit'] { + -webkit-appearance: button; + background-color: transparent; + background-image: none; + } + #rosview-root :-moz-focusring { outline: auto; } + #rosview-root :-moz-ui-invalid { box-shadow: none; } + #rosview-root progress { vertical-align: baseline; } + #rosview-root ::-webkit-inner-spin-button, + #rosview-root ::-webkit-outer-spin-button { height: auto; } + #rosview-root [type='search'] { -webkit-appearance: textbox; outline-offset: -2px; } + #rosview-root ::-webkit-search-decoration { -webkit-appearance: none; } + #rosview-root summary { display: list-item; } + #rosview-root img, #rosview-root svg, #rosview-root video, + #rosview-root canvas, #rosview-root audio, + #rosview-root iframe, #rosview-root embed, #rosview-root object { + display: block; + vertical-align: middle; + } + #rosview-root img, #rosview-root video { max-width: 100%; height: auto; } + #rosview-root [hidden]:where(:not([hidden='until-found'])) { display: none !important; } +} + @layer base { /* Scope tokens to embed root (align with LeRobot / shadcn scoped apps) */ #rosview-root { diff --git a/tailwind.config.js b/tailwind.config.js index d108d38..d7473c5 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -89,5 +89,8 @@ export default { }, }, }, + corePlugins: { + preflight: false, + }, plugins: [], } From e3109735329312dc5ada88d54ce4da93b38f728e Mon Sep 17 00:00:00 2001 From: joaner Date: Thu, 4 Jun 2026 12:26:31 +0800 Subject: [PATCH 2/4] feat(embed): add navbar brand props and document embed styling Expose showNavbarBrand and navbarBrandLabel on RosViewer so hosts can hide or replace the default ROS View label, and document CSS scoping plus branding in the English and Chinese embedding guides. --- docs/EMBEDDING.md | 25 +++++++++++++++++++++ docs/EMBEDDING.zh.md | 25 +++++++++++++++++++++ src/app/AppShell.tsx | 6 +++++ src/features/viewer/RosViewerImpl.tsx | 10 +++++++++ src/features/workspace/navbar/Navbar.tsx | 28 ++++++++++++++++-------- 5 files changed, 85 insertions(+), 9 deletions(-) diff --git a/docs/EMBEDDING.md b/docs/EMBEDDING.md index 2c6a453..9bc9fe1 100644 --- a/docs/EMBEDDING.md +++ b/docs/EMBEDDING.md @@ -31,6 +31,8 @@ The component requires its bundled CSS. Import it once at your application root: import '@ioai/rosview/style.css'; ``` +Styles are scoped to `#rosview-root` inside the bundle, so Tailwind preflight does not reset global elements (navbar, buttons, etc.) in your host application. + --- ## Step 2 — Basic usage @@ -133,6 +135,29 @@ const rows = parseRemoteDatasetListJson(await res.json()); --- +## Advanced: Navbar branding + +The navbar shows **ROS View** on the left by default. When embedding inside a larger app, you can hide it or replace it with your product name: + +```tsx +// Hide the brand button (File / Layout menus remain) + + +// Replace with host branding + +``` + +Related props: + +| Prop | Description | +|------|-------------| +| `showNavbarBrand` | Left brand button visibility (`true` by default). | +| `navbarBrandLabel` | Custom brand text; defaults to the localized product name. | +| `navbarSourceName` | Center label for the active dataset (separate from brand). | +| `showNavbar` | Hides the entire navbar when `false` (via `chrome` or explicit prop). | + +--- + ## Advanced: Controlled theme & language Disable internal localStorage persistence and fully control state from your application: diff --git a/docs/EMBEDDING.zh.md b/docs/EMBEDDING.zh.md index 2f49bab..84b5295 100644 --- a/docs/EMBEDDING.zh.md +++ b/docs/EMBEDDING.zh.md @@ -31,6 +31,8 @@ npm install @ioai/rosview import '@ioai/rosview/style.css'; ``` +样式在构建产物中限定在 `#rosview-root` 内,Tailwind preflight 不会重置宿主应用的全局元素(导航栏、按钮等)。 + --- ## 步骤 2 — 基本用法 @@ -133,6 +135,29 @@ const rows = parseRemoteDatasetListJson(await res.json()); --- +## 进阶:Navbar 品牌文案 + +Navbar 左侧默认显示 **ROS View**。嵌入到更大页面时,可以隐藏或替换为你的产品名: + +```tsx +// 隐藏品牌按钮(File / Layout 菜单仍保留) + + +// 替换为宿主品牌 + +``` + +相关 props: + +| Prop | 说明 | +|------|------| +| `showNavbarBrand` | 左侧品牌按钮是否显示(默认 `true`)。 | +| `navbarBrandLabel` | 自定义品牌文案;未设置时使用本地化产品名。 | +| `navbarSourceName` | 中间区域的数据源名称(与品牌文案无关)。 | +| `showNavbar` | 设为 `false` 时隐藏整条 Navbar(通过 `chrome` 或显式 prop)。 | + +--- + ## 进阶:受控主题与语言 关闭组件内部的 localStorage 持久化,由宿主应用完全控制状态: diff --git a/src/app/AppShell.tsx b/src/app/AppShell.tsx index 1130b38..3065814 100644 --- a/src/app/AppShell.tsx +++ b/src/app/AppShell.tsx @@ -27,6 +27,8 @@ interface AppShellProps { onLanguageChange: (lang: 'en' | 'zh' | 'ja') => void; showLanguageSwitcher?: boolean; showThemeSwitcher?: boolean; + showNavbarBrand?: boolean; + navbarBrandLabel?: string; onBrandClick?: () => void; preferAutoLayout?: boolean; preferencePersistence: PreferencePersistence; @@ -77,6 +79,8 @@ export const AppShell: React.FC = ({ onLanguageChange, showLanguageSwitcher = true, showThemeSwitcher = true, + showNavbarBrand = true, + navbarBrandLabel, onBrandClick, preferAutoLayout = false, preferencePersistence, @@ -150,6 +154,8 @@ export const AppShell: React.FC = ({ onLanguageChange={onLanguageChange} showLanguageSwitcher={showLanguageSwitcher} showThemeSwitcher={showThemeSwitcher} + showNavbarBrand={showNavbarBrand} + brandLabel={navbarBrandLabel} onBrandClick={onBrandClick} onOpenFilePick={hideOpenFileMenus ? undefined : onOpenFilePick} onOpenDirectory={hideOpenFileMenus ? undefined : onOpenDirectory} diff --git a/src/features/viewer/RosViewerImpl.tsx b/src/features/viewer/RosViewerImpl.tsx index febd99f..f075b67 100644 --- a/src/features/viewer/RosViewerImpl.tsx +++ b/src/features/viewer/RosViewerImpl.tsx @@ -250,6 +250,10 @@ export interface RosViewerProps { extensions?: RosViewExtension[]; /** Optional center label override shown in navbar source area. */ navbarSourceName?: string; + /** Whether to show the left navbar brand button. @default true */ + showNavbarBrand?: boolean; + /** Custom label for the left navbar brand button (defaults to product name). */ + navbarBrandLabel?: string; /** Whether to show navbar language switcher. @default true */ showLanguageSwitcher?: boolean; /** Whether to show navbar theme switcher. @default true */ @@ -1145,6 +1149,8 @@ export const RosViewer: React.FC = (props) => { onLanguageChange={handleLanguageChange} showLanguageSwitcher={props.showLanguageSwitcher ?? true} showThemeSwitcher={props.showThemeSwitcher ?? true} + showNavbarBrand={props.showNavbarBrand ?? true} + navbarBrandLabel={props.navbarBrandLabel} onBrandClick={handleGoHome} preferAutoLayout={props.preferAutoLayout ?? false} preferencePersistence={persistence} @@ -1206,6 +1212,8 @@ export const RosViewer: React.FC = (props) => { onLanguageChange={handleLanguageChange} showLanguageSwitcher={props.showLanguageSwitcher ?? true} showThemeSwitcher={props.showThemeSwitcher ?? true} + showNavbarBrand={props.showNavbarBrand ?? true} + brandLabel={props.navbarBrandLabel} onBrandClick={handleGoHome} onOpenFilePick={() => { clearOpenFeedback(); @@ -1309,6 +1317,8 @@ export const RosViewer: React.FC = (props) => { onLanguageChange={handleLanguageChange} showLanguageSwitcher={props.showLanguageSwitcher ?? true} showThemeSwitcher={props.showThemeSwitcher ?? true} + showNavbarBrand={props.showNavbarBrand ?? true} + brandLabel={props.navbarBrandLabel} onBrandClick={handleGoHome} onOpenFilePick={() => { clearOpenFeedback(); diff --git a/src/features/workspace/navbar/Navbar.tsx b/src/features/workspace/navbar/Navbar.tsx index 1ff87c6..d491612 100644 --- a/src/features/workspace/navbar/Navbar.tsx +++ b/src/features/workspace/navbar/Navbar.tsx @@ -83,6 +83,10 @@ interface NavbarProps { onLanguageChange?: (lang: 'en' | 'zh' | 'ja') => void; showLanguageSwitcher?: boolean; showThemeSwitcher?: boolean; + /** When false, hide the left brand button. @default true */ + showNavbarBrand?: boolean; + /** Override default product name in the left brand button. */ + brandLabel?: string; onBrandClick?: () => void; onOpenFilePick?: () => void; onOpenDirectory?: () => void; @@ -103,6 +107,8 @@ export const Navbar: React.FC = ({ onLanguageChange, showLanguageSwitcher = true, showThemeSwitcher = true, + showNavbarBrand = true, + brandLabel, onBrandClick, onOpenFilePick, onOpenDirectory, @@ -164,20 +170,24 @@ export const Navbar: React.FC = ({ onOpenFilePick || onOpenDirectory || onOpenTarPick || onOpenRemotePrompt || onOpenSampleDialog; const showCenter = Boolean(sourceLoading || (sourceName && sourceName.trim().length > 0)); const centerLabel = sourceLoading ? formatMessage({ id: 'navbar.sourceLoading' }) : (sourceName ?? ''); + const brandText = brandLabel ?? formatMessage({ id: 'common.productName' }); + const brandAccessibleName = brandLabel ?? formatMessage({ id: 'navbar.goHome' }); return (