refactor(onboarding): integrate mode selection into intro step

- Merge mode selection UI directly into intro step after typing animation
- Update navigation flow to skip standalone MODE_SELECT step
- Improve component previews with accurate V2 UI structure
- Fix transition logic for fluent vs legacy mode paths
- Add documentation for onboarding flow improvements
This commit is contained in:
Boninall 2025-10-03 21:24:18 +08:00
parent 6307b018ca
commit 055ba03772
9 changed files with 515 additions and 94 deletions

View file

@ -0,0 +1,276 @@
# Onboarding Intro Step 修复文档
## 问题描述
在引导流程Onboarding的 Intro 步骤中第四条消息intro-line-4在显示后立即被清除用户无法阅读完整内容。
**问题消息内容:**
> "In the current version, Task Genius provides a brand new visual and interactive experience: Fluent; while also providing the option to return to the previous interface. Which one do you prefer?"
## 根本原因
### 错误的实现逻辑
在重构后的代码中commit 8f5daebd`IntroStep.ts` 的实现逻辑是:
```typescript
// 错误的实现
new TypingAnimation(typingContainer, messages, () => {
// 打字完成后立即切换步骤
footerEl.style.display = "";
controller.setStep(OnboardingStep.MODE_SELECT); // ❌ 这里切换步骤
});
```
`controller.setStep(OnboardingStep.MODE_SELECT)` 被调用时:
1. `ModeSelectionStep.render()` 被触发
2. 该方法首先调用 `contentEl.empty()` 清空内容
3. `intro-line-4` 消息被立即清除
4. 用户几乎看不到消息内容
### 原始的正确逻辑
在旧版本中commit 491e2a6`IntroTyping` 组件的实现是:
```typescript
// 正确的实现
this.introTyping.render(this.onboardingContentEl, () => {
// 打字完成后,在同一容器中添加模式选择
const modeContainer = this.onboardingContentEl.createDiv({
cls: "intro-mode-selection-container"
});
this.modeSelection.render(modeContainer, this.state.uiMode, (mode) => {
this.state.uiMode = mode;
// 用户选择后才显示 footer
this.footerEl.style.display = '';
this.updateButtonStates();
});
});
```
**关键区别:**
- ✅ 不切换步骤,保持在 INTRO 步骤
- ✅ 在同一个 `contentEl` 中追加模式选择 UI
- ✅ `intro-line-4` 消息保留在页面上
- ✅ 用户可以完整阅读消息后再选择模式
## 解决方案
### 修改的文件
1. **`src/components/features/onboarding/steps/IntroStep.ts`**
- 修改 `TypingAnimation``onComplete` 回调
- 不再切换到 MODE_SELECT 步骤
- 而是在同一容器中渲染模式选择 UI
2. **`src/components/features/onboarding/steps/ModeSelectionStep.ts`**
- 添加新方法 `renderInline()`
- 该方法不清空容器,直接在给定容器中渲染
- 接受自定义的 `onSelect` 回调
### 具体实现
#### IntroStep.ts 修改
```typescript
// Start typing animation
new TypingAnimation(typingContainer, messages, () => {
// After typing completes, show mode selection in same container
const modeContainer = introWrapper.createDiv({
cls: "intro-mode-selection-container"
});
// Render mode selection inline (without clearing intro-line-4)
ModeSelectionStep.renderInline(
modeContainer,
controller,
(mode: UIMode) => {
// User selected a mode, show footer with Next button
controller.setUIMode(mode);
footerEl.style.display = "";
}
);
});
```
#### ModeSelectionStep.ts 新增方法
```typescript
/**
* Render mode selection inline (for intro step)
* This version doesn't clear the container and calls a custom callback
*/
static renderInline(
containerEl: HTMLElement,
controller: OnboardingController,
onSelect: (mode: UIMode) => void
) {
// Get current state
const currentMode = controller.getState().uiMode;
// Create cards configuration
const cardConfigs: SelectableCardConfig<UIMode>[] = [
// ... 卡片配置
];
// Render selectable cards
const card = new SelectableCard<UIMode>(
containerEl,
cardConfigs,
{
containerClass: "selectable-cards-container",
cardClass: "selectable-card",
showPreview: true,
},
(mode) => {
onSelect(mode); // 调用自定义回调
}
);
// Set initial selection
if (currentMode) {
card.setSelected(currentMode);
}
// Add info alert
Alert.create(
containerEl,
t("You can change this option later in settings"),
{
variant: "info",
className: "mode-selection-tip",
}
);
}
```
## 用户体验流程
修复后的用户体验流程:
1. **打字动画阶段**
- 显示 "Hi,"
- 显示 "Thank you for using Task Genius"
- 显示长文本,然后淡出前三条消息
- 显示 `intro-line-4` 消息(关键消息)
2. **模式选择阶段**
- `intro-line-4` 消息**保留在页面上**
- 在消息下方显示模式选择卡片Fluent vs Legacy
- 用户可以完整阅读消息内容
- 用户选择模式后,显示 Next 按钮
3. **下一步**
- 用户点击 Next 按钮
- 才清除内容并进入下一个步骤
## CSS 样式支持
相关的 CSS 样式已经存在于 `src/styles/onboarding.css`
```css
/* Mode selection container that appears after intro typing */
.intro-mode-selection-container {
animation: fadeInFromBottom 0.6s ease-out;
animation-fill-mode: both;
width: 100%;
margin-top: var(--size-4-10);
}
.intro-line-4 {
font-size: clamp(1rem, 2vw, 1.4rem);
color: var(--text-muted);
line-height: 1.8;
margin-bottom: var(--size-4-5);
}
```
## 测试验证
构建成功,无错误:
```bash
npm run build
# ✅ Build successful
# dist\main.js 3.7mb
# dist\main.css 345.1kb
```
## 导航逻辑修复
### 问题
修复 intro 消息显示后,发现了新的导航问题:
当用户在 INTRO 步骤中选择模式后点击 Next会经过以下流程
1. INTRO → MODE_SELECT因为 handleNext 中 INTRO 的下一步是 MODE_SELECT
2. MODE_SELECT 被渲染(但用户已经选择过了)
3. 用户再次点击 Next
4. MODE_SELECT → FLUENT_COMPONENTS**跳过了 FLUENT_PLACEMENT**
### 解决方案
修改 `OnboardingController.ts` 中的 `handleNext()` 逻辑:
```typescript
case OnboardingStep.INTRO:
// Mode selection is now inline in INTRO step
// So we skip MODE_SELECT and go directly based on selected mode
if (this.state.uiMode === 'fluent') {
nextStep = OnboardingStep.FLUENT_PLACEMENT; // ✅ 正确跳转
} else {
// Legacy mode: check for existing changes
if (this.state.userHasChanges) {
nextStep = OnboardingStep.SETTINGS_CHECK;
} else {
nextStep = OnboardingStep.USER_LEVEL_SELECT;
}
}
break;
case OnboardingStep.MODE_SELECT:
// This step is now integrated into INTRO, but keep for backward compatibility
if (this.state.uiMode === 'fluent') {
nextStep = OnboardingStep.FLUENT_PLACEMENT; // ✅ 修复:不再跳过
} else {
// Legacy mode: check for existing changes
if (this.state.userHasChanges) {
nextStep = OnboardingStep.SETTINGS_CHECK;
} else {
nextStep = OnboardingStep.USER_LEVEL_SELECT;
}
}
break;
```
### 修复后的流程
**Fluent 模式:**
1. INTRO包含模式选择
2. FLUENT_PLACEMENT选择 Sideleaves 或 Inline
3. FLUENT_COMPONENTS组件预览
4. SETTINGS_CHECK 或 USER_LEVEL_SELECT根据是否有更改
**Legacy 模式:**
1. INTRO包含模式选择
2. SETTINGS_CHECK如果有更改或 USER_LEVEL_SELECT如果没有更改
## 总结
通过恢复原始的实现逻辑并修复导航逻辑,现在:
**消息显示:**
- ✅ `intro-line-4` 消息完整显示在页面上
- ✅ 在模式选择卡片上方保留
- ✅ 用户有充足时间阅读
- ✅ 只有在点击 Next 后才被清除
**导航流程:**
- ✅ INTRO 步骤包含模式选择(内联)
- ✅ 选择 Fluent 后正确进入 FLUENT_PLACEMENT
- ✅ 选择 Legacy 后根据配置进入相应步骤
- ✅ 不会跳过任何必要的步骤
这符合原始设计意图,提供了更好的用户体验。

View file

@ -132,13 +132,24 @@ export class OnboardingController {
// Determine next step based on current step and state
switch (currentStep) {
case OnboardingStep.INTRO:
nextStep = OnboardingStep.MODE_SELECT;
// Mode selection is now inline in INTRO step
// So we skip MODE_SELECT and go directly based on selected mode
if (this.state.uiMode === 'fluent') {
nextStep = OnboardingStep.FLUENT_PLACEMENT;
} else {
// Legacy mode: check for existing changes
if (this.state.userHasChanges) {
nextStep = OnboardingStep.SETTINGS_CHECK;
} else {
nextStep = OnboardingStep.USER_LEVEL_SELECT;
}
}
break;
case OnboardingStep.MODE_SELECT:
// This step is now integrated into INTRO, but keep for backward compatibility
if (this.state.uiMode === 'fluent') {
// Go directly to Fluent components when Fluent mode is selected
nextStep = OnboardingStep.FLUENT_COMPONENTS;
nextStep = OnboardingStep.FLUENT_PLACEMENT;
} else {
// Legacy mode: check for existing changes
if (this.state.userHasChanges) {

View file

@ -218,7 +218,7 @@ export class OnboardingView extends ItemView {
console.log("handleNext - User has changes:", state.userHasChanges);
// Special handling for INTRO step - show config check transition
if (step === OnboardingStep.INTRO) {
if (step === OnboardingStep.INTRO && state.uiMode !== "fluent") {
// If user has changes, show checking animation before settings check
if (state.userHasChanges) {
console.log("Showing config check transition from INTRO");

View file

@ -15,81 +15,119 @@ export class ComponentPreviewFactory {
// Header with workspace selector
const header = sidebar.createDiv({ cls: "v2-sidebar-header" });
const workspaceSelector = header.createDiv({ cls: "workspace-selector" });
const workspaceButton = workspaceSelector.createDiv({ cls: "workspace-button" });
const workspaceIcon = workspaceButton.createSpan({ cls: "workspace-icon" });
setIcon(workspaceIcon, "layout-dashboard");
workspaceButton.createSpan({ text: t("Personal"), cls: "workspace-name" });
const chevron = workspaceButton.createSpan({ cls: "workspace-chevron" });
setIcon(chevron, "chevron-down");
// Workspace selector with correct structure
const workspaceSelectorEl = header.createDiv();
const workspaceSelector = workspaceSelectorEl.createDiv({ cls: "workspace-selector" });
const workspaceButton = workspaceSelector.createDiv({ cls: "workspace-selector-button" });
const workspaceInfo = workspaceButton.createDiv({ cls: "workspace-info" });
const workspaceIcon = workspaceInfo.createDiv({ cls: "workspace-icon" });
workspaceIcon.style.backgroundColor = "#3498db";
setIcon(workspaceIcon, "layers");
const workspaceDetails = workspaceInfo.createDiv({ cls: "workspace-details" });
const nameContainer = workspaceDetails.createDiv({ cls: "workspace-name-container" });
nameContainer.createSpan({ text: t("Personal"), cls: "workspace-name" });
workspaceDetails.createDiv({ text: t("Workspace"), cls: "workspace-label" });
const dropdownIcon = workspaceButton.createDiv({ cls: "workspace-dropdown-icon" });
setIcon(dropdownIcon, "chevron-down");
// New task button
const newTaskBtn = header.createDiv({ cls: "new-task-button" });
setIcon(newTaskBtn, "plus");
newTaskBtn.createSpan({ text: t("New Task") });
const newTaskBtn = header.createEl("button", { cls: "v2-new-task-btn", text: t("New Task") });
setIcon(newTaskBtn.createDiv({ cls: "v2-new-task-icon" }), "plus");
// Main navigation area
const content = sidebar.createDiv({ cls: "v2-sidebar-content" });
// Primary navigation section
const primarySection = content.createDiv({ cls: "v2-sidebar-section" });
const primaryList = primarySection.createDiv({ cls: "v2-navigation-list" });
// Primary navigation
const primaryNav = sidebar.createDiv({ cls: "v2-sidebar-section" });
const primaryItems = [
{ id: "inbox", label: t("Inbox"), icon: "inbox", count: 5 },
{ id: "today", label: t("Today"), icon: "calendar-days", count: 3 },
{ id: "upcoming", label: t("Upcoming"), icon: "calendar", count: 8 },
{ id: "flagged", label: t("Flagged"), icon: "flag", count: 2 },
{ id: "inbox", label: t("Inbox"), icon: "inbox", badge: 5 },
{ id: "today", label: t("Today"), icon: "calendar-days", badge: 3 },
{ id: "upcoming", label: t("Upcoming"), icon: "calendar", badge: 8 },
{ id: "flagged", label: t("Flagged"), icon: "flag", badge: 2 },
];
primaryItems.forEach((item, index) => {
const navItem = primaryNav.createDiv({ cls: "v2-nav-item" });
const navItem = primaryList.createDiv({
cls: "v2-navigation-item",
attr: { "data-view-id": item.id }
});
if (index === 0) navItem.addClass("is-active");
const icon = navItem.createSpan({ cls: "v2-nav-icon" });
const icon = navItem.createDiv({ cls: "v2-navigation-icon" });
setIcon(icon, item.icon);
navItem.createSpan({ text: item.label, cls: "v2-nav-label" });
if (item.count > 0) {
navItem.createSpan({ text: item.count.toString(), cls: "v2-nav-count" });
navItem.createSpan({ text: item.label, cls: "v2-navigation-label" });
if (item.badge && item.badge > 0) {
navItem.createDiv({ text: item.badge.toString(), cls: "v2-navigation-badge" });
}
});
// Projects section
const projectsSection = sidebar.createDiv({ cls: "v2-sidebar-section" });
const projectsSection = content.createDiv({ cls: "v2-sidebar-section" });
const projectsHeader = projectsSection.createDiv({ cls: "v2-section-header" });
projectsHeader.createSpan({ text: t("Projects") });
const projectsHeaderActions = projectsHeader.createDiv({ cls: "v2-section-actions" });
const addIcon = projectsHeaderActions.createSpan({ cls: "clickable-icon" });
setIcon(addIcon, "plus");
const buttonContainer = projectsHeader.createDiv({ cls: "v2-project-header-buttons" });
const treeToggleBtn = buttonContainer.createDiv({
cls: "v2-tree-toggle-btn",
attr: { "aria-label": t("Toggle tree/list view") }
});
setIcon(treeToggleBtn, "list");
const sortProjectBtn = buttonContainer.createDiv({
cls: "v2-sort-project-btn",
attr: { "aria-label": t("Sort projects") }
});
setIcon(sortProjectBtn, "arrow-up-down");
// Project list
const projectListEl = projectsSection.createDiv();
const projectList = projectListEl.createDiv({ cls: "v2-project-list" });
const scrollArea = projectList.createDiv({ cls: "v2-project-scroll" });
// Mock projects
const projects = [
{ name: t("Work"), color: "#3b82f6", count: 12 },
{ name: t("Personal"), color: "#10b981", count: 5 },
{ name: t("Learning"), color: "#f59e0b", count: 3 },
{ id: "work", name: t("Work"), color: "#3b82f6", count: 12 },
{ id: "personal", name: t("Personal"), color: "#10b981", count: 5 },
{ id: "learning", name: t("Learning"), color: "#f59e0b", count: 3 },
];
projects.forEach(project => {
const projectItem = projectsSection.createDiv({ cls: "project-item" });
const colorDot = projectItem.createSpan({ cls: "project-color" });
const projectItem = scrollArea.createDiv({
cls: "v2-project-item",
attr: { "data-project-id": project.id, "data-level": "0" }
});
const colorDot = projectItem.createDiv({ cls: "v2-project-color" });
colorDot.style.backgroundColor = project.color;
projectItem.createSpan({ text: project.name, cls: "project-name" });
projectItem.createSpan({ text: project.count.toString(), cls: "project-count" });
projectItem.createSpan({ text: project.name, cls: "v2-project-name" });
projectItem.createSpan({ text: project.count.toString(), cls: "v2-project-count" });
});
// Other views section
const otherSection = sidebar.createDiv({ cls: "v2-sidebar-section" });
const otherSection = content.createDiv({ cls: "v2-sidebar-section" });
const otherHeader = otherSection.createDiv({ cls: "v2-section-header" });
otherHeader.createSpan({ text: t("Other Views") });
const otherList = otherSection.createDiv({ cls: "v2-navigation-list" });
const otherItems = [
{ label: t("Calendar"), icon: "calendar" },
{ label: t("Gantt"), icon: "git-branch" },
{ label: t("Tags"), icon: "tag" },
{ id: "calendar", label: t("Calendar"), icon: "calendar" },
{ id: "gantt", label: t("Gantt"), icon: "git-branch" },
{ id: "tags", label: t("Tags"), icon: "tag" },
];
otherItems.forEach(item => {
const navItem = otherSection.createDiv({ cls: "v2-nav-item" });
const icon = navItem.createSpan({ cls: "v2-nav-icon" });
const navItem = otherList.createDiv({
cls: "v2-navigation-item",
attr: { "data-view-id": item.id }
});
const icon = navItem.createDiv({ cls: "v2-navigation-icon" });
setIcon(icon, item.icon);
navItem.createSpan({ text: item.label, cls: "v2-nav-label" });
navItem.createSpan({ text: item.label, cls: "v2-navigation-label" });
});
}
@ -104,12 +142,15 @@ export class ComponentPreviewFactory {
// Left section - Search
const leftSection = topNav.createDiv({ cls: "v2-nav-left" });
const searchContainer = leftSection.createDiv({ cls: "v2-search-container" });
const searchInput = searchContainer.createEl("input", {
// Match SearchComponent structure
const searchInputContainer = searchContainer.createDiv({ cls: "search-input-container" });
const searchInput = searchInputContainer.createEl("input", {
type: "text",
placeholder: t("Search tasks, projects ..."),
cls: "search-input",
attr: { disabled: "true" },
});
searchInput.addClass("search-input");
// Center section - View mode tabs
const centerSection = topNav.createDiv({ cls: "v2-nav-center" });
@ -117,31 +158,33 @@ export class ComponentPreviewFactory {
const modes = [
{ id: "list", label: t("List"), icon: "list" },
{ id: "kanban", label: t("Kanban"), icon: "columns" },
{ id: "tree", label: t("Tree"), icon: "network" },
{ id: "kanban", label: t("Kanban"), icon: "layout-grid" },
{ id: "tree", label: t("Tree"), icon: "git-branch" },
{ id: "calendar", label: t("Calendar"), icon: "calendar" },
];
modes.forEach((mode, index) => {
const tab = viewTabs.createDiv({ cls: "v2-view-tab" });
const tab = viewTabs.createEl("button", {
cls: ["v2-view-tab", "clickable-icon"],
attr: { "data-mode": mode.id }
});
if (index === 0) tab.addClass("is-active");
const icon = tab.createSpan({ cls: "v2-tab-icon" });
const icon = tab.createDiv({ cls: "v2-view-tab-icon" });
setIcon(icon, mode.icon);
tab.createSpan({ text: mode.label, cls: "v2-tab-label" });
tab.createSpan({ text: mode.label });
});
// Right section - Actions
// Right section - Notifications and Settings
const rightSection = topNav.createDiv({ cls: "v2-nav-right" });
const filterBtn = rightSection.createDiv({ cls: "v2-nav-action" });
setIcon(filterBtn, "filter");
filterBtn.createSpan({ text: t("Filter") });
// Notification button
const notificationBtn = rightSection.createDiv({ cls: "v2-nav-icon-button" });
setIcon(notificationBtn, "bell");
const badge = notificationBtn.createDiv({ cls: "v2-notification-badge", text: "3" });
const sortBtn = rightSection.createDiv({ cls: "v2-nav-action" });
setIcon(sortBtn, "arrow-up-down");
const settingsBtn = rightSection.createDiv({ cls: "v2-nav-action" });
// Settings button
const settingsBtn = rightSection.createDiv({ cls: "v2-nav-icon-button" });
setIcon(settingsBtn, "settings");
}
@ -151,16 +194,31 @@ export class ComponentPreviewFactory {
static createContentAreaPreview(container: HTMLElement): void {
container.addClass("tg-v2-container", "component-preview-content");
const content = container.createDiv({ cls: "v2-content-area component-preview" });
const content = container.createDiv({ cls: "task-content component-preview" });
// Content header
const header = content.createDiv({ cls: "v2-content-header" });
header.createEl("h2", { text: t("Inbox") });
const headerStats = header.createDiv({ cls: "v2-content-stats" });
headerStats.createSpan({ text: `5 ${t("tasks")}` });
const header = content.createDiv({ cls: "content-header" });
// View title
const titleEl = header.createDiv({ cls: "content-title", text: t("Inbox") });
// Task count
const countEl = header.createDiv({ cls: "task-count", text: `5 ${t("tasks")}` });
// Filter controls
const filterEl = header.createDiv({ cls: "content-filter" });
const filterInput = filterEl.createEl("input", {
cls: "filter-input",
attr: { type: "text", placeholder: t("Filter tasks..."), disabled: "true" }
});
// View toggle button
const viewToggleBtn = header.createDiv({ cls: "view-toggle-btn" });
setIcon(viewToggleBtn, "list");
viewToggleBtn.setAttribute("aria-label", t("Toggle list/tree view"));
// Task list
const taskList = content.createDiv({ cls: "v2-task-list" });
const taskList = content.createDiv({ cls: "task-list" });
const mockTasks = [
{ title: t("Review project proposal"), priority: "high", project: "Work" },
@ -171,7 +229,7 @@ export class ComponentPreviewFactory {
];
mockTasks.forEach(task => {
const taskItem = taskList.createDiv({ cls: "v2-task-item" });
const taskItem = taskList.createDiv({ cls: "task-list-item" });
const checkbox = taskItem.createDiv({ cls: "task-checkbox" });
setIcon(checkbox, "circle");

View file

@ -1,7 +1,8 @@
import { t } from "@/translations/helper";
import { OnboardingController, OnboardingStep } from "../OnboardingController";
import { OnboardingController } from "../OnboardingController";
import { TypingAnimation } from "./intro/TypingAnimation";
import { TransitionMessage } from "./intro/TransitionMessage";
import { ModeSelectionStep, UIMode } from "./ModeSelectionStep";
/**
* Intro Step - Welcome message with typing animation + mode selection
@ -36,11 +37,6 @@ export class IntroStep {
cls: "intro-typing",
});
// Create transition message container (initially hidden)
const transitionContainer = introWrapper.createDiv({
cls: "intro-transition-container",
});
// Define welcome messages with timing from original implementation
const messages = [
{
@ -76,10 +72,22 @@ export class IntroStep {
];
// Start typing animation
const typing = new TypingAnimation(typingContainer, messages, () => {
// Reveal footer and transition to proper MODE_SELECT step instead of rendering inline
footerEl.style.display = "";
controller.setStep(OnboardingStep.MODE_SELECT);
new TypingAnimation(typingContainer, messages, () => {
// After typing completes, show mode selection in same container
const modeContainer = introWrapper.createDiv({
cls: "intro-mode-selection-container"
});
// Render mode selection inline (without clearing intro-line-4)
ModeSelectionStep.renderInline(
modeContainer,
controller,
(mode: UIMode) => {
// User selected a mode, show footer with Next button
controller.setUIMode(mode);
footerEl.style.display = "";
}
);
});
}

View file

@ -76,6 +76,70 @@ export class ModeSelectionStep {
);
}
/**
* Render mode selection inline (for intro step)
* This version doesn't clear the container and calls a custom callback
*/
static renderInline(
containerEl: HTMLElement,
controller: OnboardingController,
onSelect: (mode: UIMode) => void
) {
// Get current state
const currentMode = controller.getState().uiMode;
// Create cards configuration
const cardConfigs: SelectableCardConfig<UIMode>[] = [
{
id: "fluent",
title: t("Fluent"),
subtitle: t("Modern & Sleek"),
description: t(
"New visual design with elegant animations and modern interactions"
),
preview: this.createFluentPreview(),
},
{
id: "legacy",
title: t("Legacy"),
subtitle: t("Classic & Familiar"),
description: t(
"Keep the familiar interface and interaction style you know"
),
preview: this.createLegacyPreview(),
},
];
// Render selectable cards
const card = new SelectableCard<UIMode>(
containerEl,
cardConfigs,
{
containerClass: "selectable-cards-container",
cardClass: "selectable-card",
showPreview: true,
},
(mode) => {
onSelect(mode);
}
);
// Set initial selection
if (currentMode) {
card.setSelected(currentMode);
}
// Add info alert
Alert.create(
containerEl,
t("You can change this option later in settings"),
{
variant: "info",
className: "mode-selection-tip",
}
);
}
/**
* Create Fluent mode preview
*/

View file

@ -1,5 +1,9 @@
/* Onboarding Component Previews Styles */
.onboarding-view:has(.component-showcase) .onboarding-content {
overflow-y: hidden;
}
/* Component showcase container */
.component-showcase {
display: flex;
@ -70,7 +74,6 @@
/* Scale down previews to fit */
.component-preview-sidebar {
max-width: 300px;
transform: scale(0.85);
}
.component-preview-topnav {
@ -127,7 +130,6 @@
display: grid;
grid-template-columns: 1fr;
gap: var(--size-4-8);
margin-top: var(--size-4-6);
}
/* Responsive adjustments */
@ -219,3 +221,7 @@
color: var(--text-muted);
font-size: var(--font-ui-small);
}
.component-showcase-preview.tg-v2-container.component-preview-sidebar .v2-sidebar {
width: 100%;
}

View file

@ -53,6 +53,11 @@
text-align: center;
}
/* Hide onboarding-header if no header content */
.onboarding-view .onboarding-header:empty {
display: none;
}
.onboarding-view .onboarding-subtitle {
color: var(--text-muted);
font-size: 0.95em;
@ -1105,13 +1110,6 @@
/* Responsive Design */
/* ============================== */
/* Tablet adjustments */
@media (max-width: 1024px) {
.intro-mode-selection-container {
max-width: 700px;
}
}
@media (max-width: 768px) {
.onboarding-view {
--dialog-width: 95vw;
@ -1576,7 +1574,6 @@
animation: fadeInFromBottom 0.6s ease-out;
animation-fill-mode: both;
width: 100%;
margin-top: var(--size-4-10);
}
@keyframes fadeInFromBottom {

File diff suppressed because one or more lines are too long